How Do Dynamic MBeans Work?

Like standard MBeans, dynamic MBeans must be created and registered with the MBean server. When the MBean server is asked to register a dynamic MBean, however, no introspection is performed. Recall that it is the strict application of the standard MBean design patterns (discussed in Chapter 2)—enforced through introspection—that tells the MBean server what management interface is exposed on an MBean. So, how does a dynamic MBean expose its management interface?

Instead of using a Java interface with the name “MBean” on it, dynamic MBeans use metadata classes to expose their management interfaces. They make that metadata available through an interface called DynamicMBean , which must be implemented by all dynamic MBeans. This interface is shown in Example 3-1.

Example 3-1. The DynamicMBean interface

package javax.management;
  
public interface DynamicMBean {
  
  public Object getAttribute(String attribute) 
    throws AttributeNotFoundException, MBeanException, ReflectionException; 
  
  public void setAttribute(Attribute attribute) 
    throws AttributeNotFoundException, InvalidAttributeValueException,
           MBeanException, ReflectionException; 
  
  public AttributeList getAttributes(String[] attributes);
  
  public AttributeList setAttributes(AttributeList attributes);
  
  public Object invoke(String actionName, Object params[], String signature[])
    throws MBeanException, ReflectionException;    
  
  public MBeanInfo getMBeanInfo(  );
    
}

Essentially, the DynamicMBean interface provides a way for a management interface to do four things:

  • Dynamically discover the management interface exposed by the MBean (getMBeanInfo( )).

  • Retrieve the value of one or more attributes on the management interface (getAttribute( ) and getAttributes( ), respectively).

  • Set the value of one or more attributes on the management interface (setAttribute( ) and setAttributes( ), respectively).

  • Invoke an operation on the management interface (invoke( )).

Instances of the appropriate metadata classes are created by the MBean (usually in the constructor) and added to the MBeanInfo instance that is returned by getMBeanInfo( ). Similarly, the management application, after invoking getMBeanInfo( ), uses the metadata classes to discover that interface. In the next section, we will take a look at how a dynamic MBean uses the metadata classes to expose its management interface.

Describing the Management Interface

Dynamic MBeans tell the MBean server that they are dynamic MBeans by exposing the DynamicMBean interface, but it is the use of the dynamic MBean metadata classes that ties it all together. In this section, we will look at these classes and how a dynamic MBean uses them to describe and expose its management interface to the world.

There are six significant metadata classes:

MBeanInfo

The top-level container of metadata; each MBean requires only one instance of this class to completely describe its management interface.

MBeanAttributeInfo

Each instance of this class provides information about a single attribute.

MBeanParameterInfo

Each instance of this class provides information about a single parameter.

MBeanConstructorInfo

Each instance of this class provides information about a single constructor and may contain one or more MBeanParamaterInfo instances.

MBeanOperationInfo

Each instance of this class provides information about a single operation and may contain one or more MBeanParameterInfo instances.

MBeanNotificationInfo

Each instance of this class contains information about a group of notifications emitted by this MBean.

Each of these classes (except MBeanInfo) is a subclass of MBeanFeatureInfo. Figure 3-1 shows a UML diagram that describes the multiplicity (or cardinality) between MBeanInfo and the other metadata classes.

UML diagram showing the multiplicity between MBeanInfo and the other metadata classes

Figure 3-1. UML diagram showing the multiplicity between MBeanInfo and the other metadata classes

As you can see from Figure 3-1, there is a whole lot of aggregation going on! The aggregation mechanism used by all of the container classes is an array. Each of these classes is discussed in detail below.

MBeanAttributeInfo

This metadata class is used to describe a single attribute that is exposed on an MBean. To describe a single MBean attribute, six essential properties must be set:

name

The name of the attribute as it appears on the management interface

type

The string representation of the Class object that corresponds to the attribute’s data type

description

The description of the attribute as it should appear to a management application

isReadable

A boolean property that indicates whether or not the attribute’s value can be retrieved and viewed by a management application

isWritable

A boolean property that indicates whether or not the attribute’s value can be set by a management application

isIs

A boolean property that indicates whether or not the attribute is a boolean and if its getter begins with is instead of get

An MBean creates an instance of this class for each attribute that it wants to expose, using one of two constructors of MBeanAttributeInfo to set its essential properties. The first constructor is defined as:

public class MBeanAttributeInfo extends MBeanFeatureInfo implements 
  java.io.Serializable, Cloneable  { 
// . . .
  public MBeanAttributeInfo(String name,
                                              String type,
                                              String description,
                                              boolean isReadable,
                                              boolean isWritable,
                                              boolean isIs) {
  // . . .
  }
// . . .
}

The name parameter is the name of the attribute as it is exposed on the management interface and is a String. The name must match exactly what is expected by the methods on the DynamicMBean interface implementation to retrieve and set the attribute’s value.

The type parameter is the fully qualified name of the class that is the type of the attribute’s value. If type is an Integer, for example, this parameter will have the value "java.lang.Integer". If type is one of the Java fundamental types (e.g., byte, char, long), you may use either the literal string for that fundamental type or the name attribute of the TYPE member variable of the corresponding fundamental type java.lang wrapper class. The following example shows how to use this constructor for the QueueSize attribute of Queue (whose type is int) by using the literal string explicitly for type int:

// . . .
  public Queue(int queueSize) {
    super(  );
    MBeanAttributeInfo att = new MBeanAttributeInfo(
      "QueueSize",          // name
      "int",                // type
      "Size of the Queue.", // description
      true,                 // isReadable
      true,                 // isWritable
      false                 // isIs
      );
    // . . .
  }
// . . .

Here is an example that shows how to use the JDK wrapper class for the fundamental type int to create an MBeanAttributeInfo object for the same QueueSize attribute of Queue:

// . . .
  public Queue(int queueSize) {
    super(  );
    MBeanAttributeInfo att = new MBeanAttributeInfo(
      "QueueSize",            // name
      Integer.TYPE.getName(  ), // type
      "Size of the Queue.",   // description
      true,                   // isReadable
      true,                   // isWritable
      false                   // isIs
      );
    // . . .
  }
// . . .

For each fundamental type, a JDK wrapper class has been provided as part of the java.lang package. Each wrapper class has a public static member variable called TYPE, which is a java.lang.Class object that has been initialized with a literal string that serves as the string representation of the fundamental type. Table 3-1 shows the corresponding type parameter values for each fundamental type, along with their JDK 1.3 string representations.

Table 3-1. type parameter values for attributes whose values are of a fundamental type

Attribute type

type parameter value

JDK 1.3 literal value

boolean

Boolean.TYPE.getName(  )

“boolean”

byte

Byte.TYPE.getName(  )

“byte”

char

Character.TYPE.getName(  )

“char”

short

Short.TYPE.getName(  )

“short”

int

Integer.TYPE.getName(  )

“int”

long

Long.TYPE.getName(  )

“long”

float

Float.TYPE.getName(  )

“float”

double

Double.TYPE.getName(  )

“double”

void

Void.TYPE.getName(  )

“void”

Notice in Table 3-1 that I don’t use a String literal as the type of fundamental types. Rather, I use the appropriate TYPE member variable of the fundamental type wrapper class. Each fundamental type has a JDK wrapper class and a corresponding class-level member variable called TYPE that is a Class object whose name (accessed by the getName( ) method of Class) is a String containing the name of the fundamental type’s Class. The literal values in Table 3-1 are not likely to change, but using the TYPE variable of the appropriate fundamental type’s wrapper class insulates you from changes to this literal in future JDK versions.

If the attribute type is an array, the type string has a different format: left bracket, capital L (i.e., “[L”), followed by the fully qualified name of the attribute class, followed by a semicolon. For example, if the class is an array of String objects, the type string of the array is "[Ljava.lang.String;".

For an array of fundamental types, the format is different still. For example, if the attribute type is an array of char, the type string would be "[C". Notice that there is no L and no semicolon at the end of the type string. Also note that the class name is not “char,” as it would be if the type were simply a char. The class names for arrays of all fundamental types follow this pattern, where a single uppercase character follows the left bracket. Table 3-2 lists the literal strings that you must pass for each type of array.

Table 3-2. Class name strings for arrays of fundamental types

Attribute type

Class name literal string

boolean[]

"[Z"

byte[]

"[B"

char[]

"[C"

short[]

"[S"

int[]

"[I"

long[]

"[J"

float[]

"[F"

double[]

"[D"

Creating the MBeanAttributeInfo for a one-dimensional character array (char[]) that uses this constructor looks like this:

// . . .
    MBeanAttributeInfo att = new MBeanAttributeInfo(
      "StringArray",      // name
      "[C",               // type
      "2D String array.", // description
      true,               // isReadable
      true,               // isWritable
      false               // isIs
      );
// . . .

Creating the MBeanAttributeInfo for a one-dimensional double-precision floating point array (double[]) that uses this constructor looks like this:

// . . .
    MBeanAttributeInfo att = new MBeanAttributeInfo(
      "DoubleArray",      // name
      "[D",               // type
      "2D String array.", // description
      true,               // isReadable
      true,               // isWritable
      false               // isIs
      );
// . . .

What about multi-dimensional arrays? Simply add another left bracket to the beginning of the literal string representing the array.

A two-dimensional String array (String[][]) looks like this:

// . . .
    MBeanAttributeInfo att = new MBeanAttributeInfo(
      "TwoDStringArray",      // name
      "[[Ljava.lang.String;", // type
      "2D String array.",     // description
      true,                   // isReadable
      true,                   // isWritable
      false                   // isIs
      );
// . . .

A three-dimensional long integer array (long[][][]) looks like this:

// . . .
    MBeanAttributeInfo att = new MBeanAttributeInfo(
      "ThreeDLongArray",  // name
      "[[[J",             // type
      "2D String array.", // description
      true,               // isReadable
      true,               // isWritable
      false               // isIs
      );
// . . .

Tip

The final authority on the syntax of array definitions is the Javadoc for java.lang.Class. You should consult the documentation if you run into any problems.

For attributes with user-defined types, the type parameter is the fully qualified class name. For example, suppose that we have an attribute called ConsumerQueue from the application’s Controller class. This attribute’s type is Queue. The code to create an MBeanAttributeInfo object for this attribute is:

// . . .
    MBeanAttributeInfo att = new MBeanAttributeInfo(
      "ConsumerQueue",        // name
      "sample.dynamic.Queue", // type
      "2D String array.",     // description
      true,                   // isReadable
      true,                   // isWritable
      false                   // isIs
      );
// . . .

Warning

When using the HTMLAdaptorServer that ships with the JMX 1.0 RI (in jmxtools.jar), user-defined types are not supported for attributes. This may change without notice, however, because jmxtools.jar is not technically part of the RI.

The description parameter is simply a human-readable description of the attribute and can be anything you want. However, if you are going to use the HTMLAdaptorServer from the JMX 1.0 RI, please note that you cannot use single or double quotes in the description. This seems to mess up the Adaptor, and you will not be able to retrieve the description from your browser.

The isReadable parameter indicates whether or not the attribute’s value can be retrieved through the management interface. Set this parameter to true if the value can be read through the management interface, and false otherwise.

The isWritable parameter indicates whether or not the attribute’s value can be set through the management interface. Pass true if it can be, and false otherwise.

If the attribute is a boolean value and its getter starts with “is”, pass isIs as true. Otherwise, pass false.

There is a second constructor to MBeanAttributeInfo that you may find helpful:

public class MBeanAttributeInfo extends MBeanFeatureInfo implements 
  java.io.Serializable, Cloneable  { 
// . . .
  public MBeanAttributeInfo(String name, 
                                              String description, 
                                              java.lang.reflect.Method getter, 
                                              java.lang.reflect.Method setter)
                      throws IntrospectionException {
  // . . .
  }
// . . .
}

This constructor is provided as a convenience if you prefer to use the Java reflection API to obtain references to the Method objects for the getter and setter and then pass those references to the constructor. The constructor then figures out whether the attribute is readable, writable, and/or boolean, and what its type is. Consequently, you don’t have to worry about memorizing the information in Tables 3-1 and 3-2.

Suppose that for a particular MBean, we are creating the metadata classes to represent the attributes inside the constructor of the MBean. It is simple, then, to get a reference to the Method objects for the getter and setter:

// . . .
  public Queue(int queueSize) {
    super(  );
    Class[] setterSignature = new Class[1];
    setterSignature[0] = Integer.TYPE;
  
    try {
      Method getter = this.getClass(  ).getMethod("getQueueSize", null);
      Method setter = this.getClass(  ).getMethod("setQueueSize", setterSignature);
      MBeanAttributeInfo att = new MBeanAttributeInfo(
        "QueueSize",
        "Size of the Queue.",
        getter,
        setter
      );
    } catch (NoSuchFieldException e) {
      // oops, shouldn't really get this unless we mistyped the name
      e.printStackTrace(  );
    } catch (SecurityException e) {
      // you don't have access to the method. . .
      e.printStackTrace(  );
    } catch (IntrospectionException e) {
      // something has gone horribly wrong. . .
      e.printStackTrace(  );
    }
    // . . .
  }
// . . .

If the attribute is read-only, pass null as the setter parameter. Conversely, if the attribute is write-only, pass null as the getter parameter. This approach works well if the attribute’s name is likely to be more static than its type. If the name never changes, the code above will not need to change, because the MBeanAttributeInfo constructor uses Java’s reflection API to determine the attribute’s type.

MBeanParameterInfo

Three essential properties of a parameter to an MBean constructor or operation must be set in order to describe that parameter:

name

The name of the parameter as it appears to a management application

type

The string representation of the Class object that corresponds to the parameter’s data type

description

The description of the parameter as it should appear to a management application

Each parameter to a constructor or operation must be described using the MBeanParameterInfo class. Each instance of this class serves to completely describe a single parameter to a single constructor or operation. Like MBeanAttributeInfo objects, the way to create instances of MBeanParameterInfo is to use its custom constructors. In fact, there is only one constructor for MBeanParameterInfo:

public class MBeanParameterInfo extends MBeanFeatureInfo
  implements java.io.Serializable, Cloneable  {
// . . .
  public MBeanParameterInfo(String name,
                                              String type,
                                              String description) {
                    // . . .
  }
// . . .
}

The name parameter is the name of the parameter as it appears to the management application. It is good style for this name to match the name in the source code, but the RI doesn’t seem to care what you use for this parameter, including an empty string (“”) or a null reference. Note, however, that if you pass a null reference for this parameter, a NullPointerException will be thrown by the HTMLAdaptorServer when you try to access any MBean that uses this MBeanParameterInfo instance. The MBean server, on the other hand, will happily register the MBean without any glitches. This behavior indicates that Version 1.0 of the HTMLAdaptorServer doesn’t much care for a null reference for the name parameter.

The type parameter for MBeanParameterInfo is exactly the same as the type parameter for MBeanAttributeInfo. All of the information about the type parameter in the previous section applies here as well.

The description parameter is simply a human-readable description of the attribute and can be anything you want. However, if you are going to use the HTMLAdaptorServer from the JMX 1.0 RI, note that you cannot use single or double quotes in the description. This seems to mess up the Adaptor, and you will not be able to retrieve the description from your browser.

It’s time for an example from the application. The Controller class has a management operation called createNewWorker( ) that is used to start another worker thread. In order to do this, however, the worker’s role (a String) and the work factor (the amount of work the worker is to perform for each work unit) must be specified. An instance of MBeanParameterInfo will be created for each of these parameters:

// . . .
MBeanParameterInfo param1 = new MBeanParameterInfo(
  "role",                                         // name
  "java.lang.String",                             // type
  "The role this new worker thread will take on." // description
);
  
MBeanParameterInfo param2 = new MBeanParameterInfo(
  "workFactor",
  Integer.TYPE.getName(  ),
  "The weighted work factor for this new worker thread."
);
// . . .

As I stated earlier, you are not required to supply a name parameter to the constructor of MBeanParameterInfo. In fact, you don’t have to supply a description either. Thus, you don’t have to create different instances of MBeanParameterInfo for parameters that can be described in exactly the same way. In other words, if parameters have at a minimum the same type, they can share the same instance of MBeanParameterInfo. Be careful when doing this, however, because parameters can have the same type but mean different things to a management application. Also, it may confuse the person trying to manage your MBeans if he is relying on either the name or description of an operation or constructor’s parameters to meaningfully manage the resources within your application.

MBeanConstructorInfo

Three essential properties must be set in order to describe an MBean constructor:

name

The name of the constructor as it appears to a management application

description

The description of the constructor as it should appear to a management application

signature

An array of MBeanParameterInfo objects that describe the constructor’s signature

The MBeanConstructorInfo metadata class is used to describe an MBean’s public constructors. One instance of this class completely describes a single constructor. In other words, if an MBean has three public constructors, three instances of this class are required to completely describe the MBean’s constructors. Like the other metadata classes, MBeanConstructorInfo provides two custom constructors that are used to set various properties of this object. The first constructor is defined as:

public class MBeanConstructorInfo extends MBeanFeatureInfo 
  implements java.io.Serializable, Cloneable { 
// . . .
  public MBeanConstructorInfo(String description, 
                                                java.lang.reflect.Constructor constructor) {
  }
// . . .
}

This constructor uses Java’s reflection API to figure out what the constructor’s parameters are and creates MBeanParameterInfo objects for them accordingly. However, when you use this method, you will not be able to specify a name for the constructor or a description for the constructor’s parameters.

This constructor is very easy to use if you want to expose all of the constructors for an MBean. For example, the following code will expose all of the public constructors of the class of which this is an instance:

// . . .
  Constructor[] constructors = this.getClass().getConstructors(  );
  MBeanConstructorInfo[] constructorInfo = new 
      MBeanConstructorInfo[constructors.length];
  for (int aa = 0; aa < constructors.length; aa++) {
    constructorInfo[aa] = new MBeanConstructorInfo(
                        "Constructs a Basic MBean.", // description
                        constructors[aa]             // java.lang.reflect.Constructor
                      );
  }
// . . .

The getConstructors( ) method of the Class object associated with this returns an array of java.lang.reflect.Constructor objects. The size of the array is a value equal to the number of public constructors the class contains, and this value is used to create an equally sized array of MBeanConstructor objects. Then each Constructor object is used to create a corresponding MBeanConstructor instance.

Alternately, you can expose a specific constructor by creating an MBeanConstructorInfo object for it. Example 3-2 shows how to do this.

Example 3-2. Exposing a specific constructor

public class Queue extends Basic implements DynamicMBean {
// . . .
  public Queue(int QueueSize) {
    // . . .
    Class[] signature = {Integer.TYPE};
    Constructor constructor = null;
    MBeanConstructorInfo[] constructorInfo = new MBeanConstructorInfo[1];
    try {
      constructor = this.getClass(  ).getConstructor(signature);
      constructorInfo[0] = new MBeanConstructorInfo(
        "Queue custom constructor",  // description
        constructor                  // java.lang.reflect.Constructor
      );
    } catch (Exception e) {
      e.printStackTrace(  );
      return;
    }
    // . . .
  }
// . . .
}

In this example, we explicitly exposed a single constructor whose signature consists of a single int parameter. The getConstructor( ) method of Class takes a Class array that contains the Class objects that match the signature of the constructor we want to retrieve. If the constructor is not found, a NoSuchMethodException is thrown. If the constructor is found, it’s a simple matter of creating a new MBeanConstructorInfo object, passing the Constructor object we retrieved earlier.

Suppose that we have another constructor that takes an Integer parameter, instead of a fundamental int. How do we get a Class object for an Integer? There is a static method of Class called forName( ) that takes a String (actually, there are two versions of this method, but we’ll use the simpler of the two), which is the fully qualified name of the class for which we want a Class object. This method does throw an exception if the class can’t be found, so we have to surround our code with try/catch blocks. Using this scenario, Example 3-2 becomes:

public class Queue extends Basic implements DynamicMBean {
// . . .
  public Queue(int QueueSize) {
    // . . .
    Class[] signature = new Class[1];
    Constructor constructor = null;
    MBeanConstructorInfo[] constructorInfo = new MBeanConstructorInfo[1];
    try {
      signature[0] = Class.forName("java.lang.Integer");
      constructor = this.getClass(  ).getConstructor(signature);
      constructorInfo[0] = new MBeanConstructorInfo(
        "Queue custom constructor",  // description
        constructor                  // java.lang.reflect.Constructor
      );
    } catch (Exception e) {
      e.printStackTrace(  );
      return;
    }
    // . . .
  }
// . . .
}

The second constructor of MBeanConstructorInfo requires a little more effort on our part, but it allows us to provide a name and description for each parameter of the constructor we expose. This can be helpful to the operator of a management application, as this information will be exposed if this constructor is used. The constructor is defined as:

public class MBeanConstructorInfo extends MBeanFeatureInfo 
  implements java.io.Serializable, Cloneable  { 
// . . .
  public MBeanConstructorInfo(String name,
                                                String description,
                                                MBeanParameterInfo[] signature) {
  }
// . . .
}

The extra work required on our part is that we must create an array of MBeanParameterInfo objects that correspond to the signature of the constructor. This is really not a big deal, though; we saw how to create MBeanParameterInfo objects in the previous section. Suppose that we want to expose the constructor from Example 3-2, which takes a single int parameter. In that case, we create a single MBeanParameterInfo object and pass it to the constructor of MBeanConstructorInfo:

public class Queue extends Basic implements DynamicMBean {
// . . .
  public Queue(int QueueSize) {
    // . . .
    MBeanConstructorInfo[] constructorInfo = new MBeanConstructorInfo[1];
    MBeanParameterInfo[] parms = new MBeanParameterInfo[1];
    parms[0] = new MBeanParameterInfo(
                        "queueSize",
                        Integer.TYPE.getName(  ),
                        "Max number of items the Queue may contain at any time."
                      );
    constructorInfo[0] = new MBeanConstructorInfo(
      "Queue",
      "Queue custom constructor",
      parms
    );
    // . . .
  }
// . . .
}

Notice how we explicitly create an instance of MBeanParameterInfo to describe the int parameter to our constructor. This object is then placed into an array consisting of a single MBeanParameterInfo element, which is passed to the second constructor of MBeanConstructorInfo.

If you are exposing the default constructor, you have two options. The first option is to simply pass null as the third parameter:

// . . .
  MBeanConstructorInfo[] constructorInfo = new MBeanConstructorInfo[1];
  constructorInfo[0] = new MBeanConstructorInfo(
    "Queue",
    "Default Constructor",
    null
  );
// . . .

A second, arguably more readable, way to expose the default constructor is to pass an empty array of MBeanParameterInfo objects:

// . . .
  MBeanConstructorInfo[] constructorInfo = new MBeanConstructorInfo[1];
  constructorInfo[0] = new MBeanConstructorInfo(
    "Queue",
    "Default Constructor",
    new MBeanParameterInfo[0]
  );
// . . .

MBeanOperationInfo

Five essential properties of an MBean operation must be set when describing the operation:

name

The name of the constructor as it appears to a management application

description

The description of the constructor as it should appear to a management application

signature

An array of MBeanParameterInfo objects that describe the constructor’s signature

type

The data type of the value returned by the operation

impact

An indicator of the type of impact to the state of the MBean following an invocation of the operation

MBeanOperationInfo is used to describe the operations that are exposed on an MBean’s management interface. One instance of this class is required to completely describe a single operation. In other words, if an MBean exposes four operations on its management interface, four instances of this class are required in order to describe the operations on the management interface of the MBean. Like the other metadata classes, MBeanOperationInfo uses its constructors to set its five essential properties. The first constructor is defined as:

public class MBeanOperationInfo extends MBeanFeatureInfo 
  implements java.io.Serializable, Cloneable  {
// . . .
  public MBeanOperationInfo(String description, 
                  java.lang.reflect.Method method) {
  // . . .
  }
// . . .
}

This constructor uses Java’s reflection API to figure out what the constructor’s parameters (and their types) are and creates MBeanParameterInfo objects for them accordingly. In addition, the return type of the operation is discovered through reflection. The impact property (see above) cannot be discovered, however, because there is no way to determine the impact of invoking the MBean operation through the reflection API; the impact of invoking the operation on the MBean’s state is unknown, so this constructor sets impact to MBeanParameterInfo.UNKNOWN.

Warning

When you use this method, you will not be able to specify a name or a description for the constructor’s parameters.

You must obtain a reference to the java.lang.reflect.Method object for the operation you wish to expose:

public class Controller extends Basic implements DynamicMBean {
// . . .
  public Controller(  ) {
    // . . .
    MBeanOperationInfo[] operationInfo = new MBeanOperationInfo[1];
    Method operation = null;
    Class[] parms = new Class[2];
    try {
        parms[0] = Class.forName("java.lang.String");
        parms[1] = Integer.TYPE;
        operation = this.getClass(  ).getMethod("createWorker",parms);
        operationInfo[0] = new MBeanOperationInfo(
                            "createWorker",
                            operation
                          );
    } catch (Exception e) {
        e.printStackTrace(  );
        return;
    }
    // . . .
  }
// . . .
  // operation to be exposed on the management interface
  public void createWorker(String workerType, int workFactor) {
  // . . .
  }
}

In this example, we are exposing an operation called createWorker( ) that takes two parameters: a String and an int. To expose an operation using the reflection-based constructor of MBeanParameterInfo, we must accomplish the following:

  • Obtain Class objects that represent the proper Class objects for the types of signatures the MBean operation has. In this example, the first parameter is a String and the second is an int. We can obtain a String Class object by using the static forName( ) method of Class. For the fundamental type int, we simply use the TYPE member, which is a Class object that represents an int:

    // . . .
      public Controller(  ) {
        // . . .
        Method operation = null;
        Class[] parms = new Class[2];
        try {
            parms[0] = Class.forName("java.lang.String");
                                    parms[1] = Integer.TYPE;
            operation = this.getClass(  ).getMethod("createWorker",parms);
            // . . .
        // . . .
      }
    // . . .
  • Obtain a reference to the Method object that corresponds to the operation we want to expose. To do this, we must use the Class object of the MBean object itself. Once we obtain the Class object, we can use its getMethod( ) method to retrieve the MBean operation’s Method object. Because getMethod( ) may throw an exception if the method is not found, we must use try/catch blocks:

    // . . .
      public Controller(  ) {
        // . . .
        try {
            parms[0] = Class.forName("java.lang.String");
            parms[1] = Integer.TYPE;
            operation = this.getClass(  ).getMethod("createWorker",parms);
            operationInfo[0] = new MBeanOperationInfo(
              "createWorker",
              operation
            );
        } catch (Exception e) {
            e.printStackTrace(  );
            return;
        }
        // . . .
      }
  • Create the MBeanOperationInfo object:

    // . . .
      public Controller(  ) {
        // . . .
        try {
            parms[0] = Class.forName("java.lang.String");
            parms[1] = Integer.TYPE;
            operation = this.getClass(  ).getMethod("createWorker",parms);
            operationInfo[0] = new MBeanOperationInfo(
                                      "createWorker",
                                      operation
                                    );
        } catch (Exception e) {
            e.printStackTrace(  );
            return;
        }
        // . . .
      }
    // . . .

The second constructor of MBeanOperationInfo allows you to explicitly specify all of the essential properties of an MBean operation and is defined as:

public class MBeanOperationInfo extends MBeanFeatureInfo 
  implements java.io.Serializable, Cloneable  {
// . . .
  public MBeanOperationInfo(String name, 
                                              String description,
                                              MBeanParameterInfo[] signature, 
                                              String type,
                                              int impact) {
    // . . .
  }
// . . .
}

This constructor is arguably easier to use than its reflection-based counterpart, because it’s so easy to create MBeanParameterInfo objects:

public class Controller extends Basic implements DynamicMBean {
// . . .
  public Controller(  ) {
    // . . .
    MBeanOperationInfo[] operationInfo = new MBeanOperationInfo[1];
    MBeanParameterInfo[] parms = new MBeanParameterInfo[2];
    parms[0] = new MBeanParameterInfo(
      "role",
      "java.lang.String",
      "The role this new worker thread will take on."
    );
    parms[1] = new MBeanParameterInfo(
      "workFactor",
      Integer.TYPE.getName(  ),
      "The work factor for this new worker thread."
    );
    operationInfo[0] = new MBeanOperationInfo(
                        "createWorker",
                        "Creates a new worker thread.",
                        parms,
                        Void.TYPE.getName(  ),
                        MBeanOperationInfo.ACTION
                      );
    // . . .
  }
// . . .
  // operation to be exposed on the management interface
  public void createWorker(String workerType, int workFactor) {
  // . . .
  }
}

In this example, we create an instance of MBeanParameterInfo for each of the parameters to the operation we want to expose and place the instances into an array. We then pass this array to the second constructor of MBeanOperationInfo.

Notice the impact property, which is the fifth parameter to the constructor (we used MBeanOperationInfo.ACTION). Four values for impact are defined on MBeanOperation:

INFO

The state of the MBean will remain unchanged as a result of invoking this operation, because the operation will only return information.

ACTION

The state of the MBean will be changed in some way as a result of invoking this operation. This could be as simple as an internal value changing (i.e., something not on the management interface as an attribute) or as complex as the externally visible state of the MBean changing.

ACTION_INFO

This operation will return some information about the MBean, and the state of the MBean will change as a result of invoking this operation. This is a combination of INFO and ACTION.

UNKNOWN

This value indicates that the impact of invoking the method is not known. When you use the reflection-based constructor of MBeanOperationInfo, this is the value to which impact is set.

MBeanNotificationInfo

There are three essential properties of an MBean notification:

name

The name of the notification as it appears to a management application

description

The description of the notification as it should appear to a management application

notifsType (notification types)

The types of notifications that will be emitted by this MBean

MBeanNotificationInfo is the metadata class used by an MBean to indicate to the MBean server what notification types the MBean emits (we will discuss the JMX notification model in greater detail in Chapter 6). One instance of this class is necessary to completely describe a single group of notifications, but what exactly is a “group of notifications?” A group of notifications is made up of one or more notifications of the same general type. It is up to you to define what notifications belong together, depending upon the different types of notifications that are emitted by the application resource the MBean represents.

Like the other metadata classes, the essential properties for MBeanNotificationInfo are set by calling its single constructor, which is defined as:

public class MBeanNotificationInfo extends MBeanFeatureInfo 
  implements Cloneable, java.io.Serializable  {
// . . .
  public MBeanNotificationInfo(String[] notifsType,
                                                 String name,
                                                 String description) {
    // . . .
  }
// . . .
}

We are already familiar with the name and description parameters, but what about notifsType? This constructor parameter is a String array that contains a group of notifications that the MBean will emit. A notification is a String of the form:

                  vendor[.application][.component][.eventGroup].event

where vendor is the name of your company, application is the name of the application (optional), component is the name of the component (usually the name of the MBean, also optional), eventGroup is the name of the group to which the event belongs (optional), and event is the name of the event notification. The minimal notification String should contain vendor and event, but I encourage you to be as detailed as possible when naming the notifications your MBeans will emit.

For example, suppose we define a group of notifications for the Queue class from the sample application. This group of notifications will be for potential stall conditions, where for some reason the queue appears to be “asleep.” Say we define one notification for the condition when the queue is continuously full for more than a preset amount of time. This may indicate that the application has stalled, perhaps by a consumer thread crashing. Then let’s define another notification for the condition when the queue is continuously empty for longer than a preset period of time. This may indicate that a supplier thread has crashed. These notifications will be defined as "sample.Queue.stalled.Full" and "sample.Queue.stalled.Empty".

In this case, sample is the vendor, and we have omitted the optional application from the notification names. Both of these notifications indicate a potential stall condition in the queue. Now let’s create an instance of MBeanNotificationInfo:

public class Queue extends Basic implements DynamicMBean {
// . . .
  public static final String NOTIF_STALLED_FULL = "sample.Queue.stalled.full";
  public static final String NOTIF_STALLED_EMPTY = "sample.Queue.stalled.empty";
// . . .
  public Queue(int QueueSize) {
    // . . .
    String[] notificationTypes = new String[2];
    notificationTypes[0] = NOTIF_STALLED_FULL;
    notificationTypes[1] = NOTIF_STALLED_EMPTY;
  
    MBeanNotificationInfo[] notificationInfo = new MBeanNotificationInfo[1];
  
    notificationInfo[0] = new MBeanNotificationInfo(
                        notificationTypes,
                        "StalledQueueNotifications",
                        "Potential stall notifications emitted by the Queue."
                      );    
    // . . .
  }
// . . .
}

We declared the notification Strings as constants on the class because these literal strings will be needed by the part of the Queue that actually emits the notifications and can be referenced everywhere by their variable names. As you can see from this example, creating an MBeanNotificationInfo object for a group of notifications is quite straightforward.

The convention I suggest here regarding the use of a single instance of MBeanNotificationInfo to group similar notifications is purely my own. It’s not mentioned in the JMX specification; it just seems to me to be a good idea. Strictly speaking, you don’t have to use this idiom. For example, we could have created an instance of MBeanNotificationInfo for each notification the MBean will emit:

public class Queue extends Basic implements DynamicMBean {
// . . .
  public static final String NOTIF_STALLED_FULL = "sample.Queue.stalled.full";
  public static final String NOTIF_STALLED_EMPTY = "sample.Queue.stalled.empty";
// . . .
  public Queue(int QueueSize) {
    // . . .
    String[] notificationTypes = new String[1];
    notificationTypes[0] = NOTIF_STALLED_FULL;
  
    MBeanNotificationInfo[] notificationInfo = new MBeanNotificationInfo[2];
  
    notificationInfo[0] = new MBeanNotificationInfo(
                  
      notificationTypes,
                  
      "StalledFullQueueNotification",
                  
      "Potential stall notifications emitted when the Queue is full."
                  
    );

    notificationTypes = new String[1];
    notificationTypes[0] = NOTIF_STALLED_EMPTY;
notificationInfo[1] = new MBeanNotificationInfo(
                  
      notificationTypes,
                  
      "StalledEmptyQueueNotification",
                  
      "Potential stall notifications emitted when the Queue is empty."
                  
    );    
    // . . .
  }
// . . .
}

This is a perfectly reasonable approach to use for a small number of notifications. However, if your MBean emits a lot of notifications, you may want to consider grouping them and taking the approach shown earlier.

MBeanInfo

There are six essential properties of an MBean:

className

The name of the MBean class

description

A description of the MBean

attributes

Metadata about the attributes the MBean exposes

constructors

Metadata about the MBean’s constructors

operations

Metadata about the MBean’s exposed operations

notifications

Metadata about the notifications emitted by the MBean

A single instance of MBeanInfo is sufficient to completely describe the management interface for an MBean, because the getters provided by MBeanInfo allow a management application to drill down into, and retrieve, all of the metadata for an MBean. Think of this class as the “magic cookie” that the distributed services level of the JMX architecture uses to expose a dynamic MBean’s management interface to a management application.

When getMBeanInfo( ) is invoked on a dynamic MBean, information in the form of metadata is returned to the caller. The metadata is contained in an instance of a class called MBeanInfo, which is at the top of the dynamic MBean metadata hierarchy. It is through this metadata that the interface is both exposed by the MBean and discovered by the management application. This class should be created last, because all of the attributes, parameters, constructors, operations, and notifications must be completely described before an instance of MBeanInfo can be created.

Like all of the other metadata classes, the MBeanInfo constructor provides a way to set the essential properties. This means, of course, that you must create the metadata class instances first so that they are available at the time you create the MBeanInfo instance. Once instances of the appropriate MBeanAttributeInfo, MBeanConstructorInfo, MBeanOperationInfo, and MBeanNotificationInfo have been created, they can be added to MBeanInfo.

The constructor of the MBeanInfo class looks like this:

public class MBeanInfo implements Cloneable, java.io.Serializable {
// . . .
  public MBeanInfo(String className,
                  
                   String description,
                  
                   MBeanAttributeInfo[] attributes,
                  
                   MBeanConstructorInfo[] constructors,
                  
                   MBeanOperationInfo[] operations,
                  
                   MBeanNotificationInfo[] notifications) {
  // . . .
  }
// . . .
}

Now it’s time for the metadata classes describing the attributes, constructors, operations, and notifications to be added to MBeanInfo. The way to do this is through MBeanInfo’s constructor. You may have noticed in the earlier examples that we created an array of the metadata objects and then added the objects to the array. Once we create the array of metadata objects, we can simply pass the arrays to the MBeanInfo constructor.

Example 3-3 shows how to create the MBeanInfo class, using the other metadata classes. This example ties together all of the previous discussion about metadata classes.

Example 3-3. Creating an instance of MBeanInfo

public class Queue extends Basic implements DynamicMBean {
// . . .
  private MBeanInfo _MBeanInfo;
// . . .
  public static final String NOTIF_STALLED_FULL = "sample.Queue.stalled.full";
  public static final String NOTIF_STALLED_EMPTY = "sample.Queue.stalled.empty";
// . . .
  public Queue(int queueSize) {
    // . . .
    // Attributes
    attributeInfo[0] = new MBeanAttributeInfo(
      "QueueSize", Integer.TYPE.getName(  ),
      "Maximum number of items the queue may contain at one time.",
      true, true, false);
    attributeInfo[1] = new MBeanAttributeInfo(
      "NumberOfConsumers", Integer.TYPE.getName(  ),
      "The number of consumers pulling from this Queue.",
      true, false, false);
    attributeInfo[2] = new MBeanAttributeInfo(
      "NumberOfSuppliers", Integer.TYPE.getName(  ),
      "The number of suppliers supplying to this Queue.",
      true, false, false);
    attributeInfo[3] = new MBeanAttributeInfo(
      "QueueFull", Boolean.TYPE.getName(  ),
      "Indicates whether or not the Queue is full.",
      true, false, true);
    attributeInfo[4] = new MBeanAttributeInfo(
      "QueueEmpty", Boolean.TYPE.getName(  ),
      "Indicates whether or not the Queue is empty.",
      true, false, true);
    attributeInfo[5] = new MBeanAttributeInfo(
      "Suspended", Boolean.TYPE.getName(  ),
      "Indicates whether or not the Queue is currently suspended.",
      true, false, true);
    attributeInfo[6] = new MBeanAttributeInfo(
      "EndOfInput", Boolean.TYPE.getName(  ),
      "Indicates if end-of-input has been signalled by all suppliers.",
      true, false, true);
    attributeInfo[7] = new MBeanAttributeInfo(
      "NumberOfItemsProcessed", Long.TYPE.getName(  ),
      "The number of items that have been removed from the Queue.",
      true, false, false);
    attributeInfo[8] = new MBeanAttributeInfo(
      "AddWaitTime", Long.TYPE.getName(  ),
      "No. Milliseconds spent waiting to add because Queue was full.",
      true, false, false);
    attributeInfo[9] = new MBeanAttributeInfo(
      "RemoveWaitTime", Long.TYPE.getName(  ),
      "No. milliseconds spent waiting to remove because Queue was empty.",
      true, false, false);
    // Constructors
    Class[] signature = {Integer.TYPE};
    Constructor constructor = null;
    MBeanConstructorInfo[] constructorInfo = new MBeanConstructorInfo[1];
    try {
      constructor = this.getClass(  ).getConstructor(signature);
      constructorInfo[0] = new MBeanConstructorInfo(
        "Custom constructor", constructor);
    } catch (Exception e) {
      e.printStackTrace(  );
    }
    // Operations
    MBeanOperationInfo[] operationInfo = new MBeanOperationInfo[3];
    MBeanParameterInfo[] parms = new MBeanParameterInfo[0];
    operationInfo[0] = new MBeanOperationInfo(
      "suspend", "Suspends processing of the Queue.",
      parms, Void.TYPE.getName(  ), MBeanOperationInfo.ACTION);
    operationInfo[1] = new MBeanOperationInfo(
      "resume", "Resumes processing of the Queue.",
      parms, Void.TYPE.getName(  ), MBeanOperationInfo.ACTION);
    operationInfo[2] = new MBeanOperationInfo(
      "reset", "Resets the state of this MBean.",
      parms, Void.TYPE.getName(  ), MBeanOperationInfo.ACTION);
    // Notifications
    MBeanNotificationInfo[] notificationInfo = new MBeanNotificationInfo[1];
    String[] notificationTypes = new String[2];
    notificationTypes[0] = NOTIF_STALLED_FULL;
    notificationTypes[1] = NOTIF_STALLED_EMPTY;
    notificationInfo[0] = new MBeanNotificationInfo(
      notificationTypes,
      "StalledQueueNotifications",
      "Potential stall notifications emitted by the Queue."
    );    
    // MBeanInfo
    _MBeanInfo = new MBeanInfo(
                           "Queue",
                           "Queue MBean",
                           attributeInfo,
                           constructorInfo,
                           operationInfo,
                           notificationInfo
                         );
  }
// . . .
}

In this example, 10 attributes, 1 constructor, 3 operations, and 2 notifications describe the Queue as a managed resource. Notice the private variable _MBeanInfo, which is used to hold an instance of the MBeanInfo object that contains the metadata for the Queue MBean. This variable is returned by the getMBeanInfo( ) method(part of the DynamicMBean interface) that is invoked by a management application to discover the management interface of an MBean.

In this section, we looked at how to create all of the metadata classes necessary to fully describe the management interface of a dynamic MBean. In the next section, we will take a closer look at how the information in the MBeanInfo object must match up to the logic in the implementation of the DynamicMBean interface.

Implementing the DynamicMBean Interface

Example 3-1 showed the definition of the DynamicMBean interface. In this section, we will first look at some other support classes from the javax.management package that are important to properly implementing dynamic MBeans. Then we will look at how to write code to implement the methods on the dynamic MBean interface.

Attribute

This class is used to encapsulate a single attribute value. There are two important properties of this class, name and value, which represent the attribute’s name and value, respectively. The following code shows the significant features of this class:

package javax.management;
// . . .
public class Attribute implements Serializable { 
// . . .
  public Attribute(String name, Object value) { 
    // . . .
  } 
// . . .
  public String getName(  ) { 
    // . . .
  }     
  public Object getValue(  ) { 
    // . . .
  } 
// . . .
}

There are three significant features to this class:

Constructor

When creating the class, pass in the name of the attribute and an object reference for the attribute’s value. If the attribute’s type is one of the Java fundamental types, wrap that fundamental type in the appropriate JDK wrapper class (e.g., if the type is a char, wrap it in an Character object).

Getter for the attribute name

Returns the attribute’s name.

Getter for the attribute’s value

Returns the attribute’s value.

AttributeList

Instances of this class are used to hold a List of Attribute instances. The following code shows the significant features of AttributeList:

package javax.management;
import java.util.ArrayList;
// . . .
public class AttributeList extends ArrayList { 
                    public AttributeList(  ) { 
  } 
// . . .
}

AttributeList inherits from ArrayList, which means that all of the methods you are used to using for an ArrayList—such as get( ), set( ), and iterator( )—are available on AttributeList (see the JDK Javadoc for more information).

To create an instance of AttributeList, use:

AttributeList attributeList = new AttributeList(  );

To set the initial capacity of the internal ArrayList to 10, use:

AttributeList attributeList = new AttributeList(10);

To add an Attribute to the AttributeList, use:

Attribute attribute = new Attribute("QueueSize", new Integer(10));
AttributeList attributeList = new AttributeList(  );
attributeList.add(attribute);

AttributeList is an ArrayList, so there are two ways of getting at the contents of any AttributeList instance. The first way is to use a java.util.Iterator object:

// assume attributeList is an instance of AttributeList
Iterator iter = attributeList.iterator(  );
while (iter.hasNext(  )) {
  Attribute attribute = (Attribute)iter.next(  );
  // do something with attribute. . .
}

The second way is to access the contents using an index. The code snippet below shows how to walk through the contents of the AttributeList from beginning to end. However, you can also access any member of the AttributeList directly by specifying its index (as long as you don’t specify an invalid index, in which case an IndexOutOfBoundsException will be thrown).

// assume attributeList is an instance of AttributeList
for (int idx = 0; idx < attributeList.size(  ); idx++) {
  Attribute attribute = (Attribute)attributeList.get(idx);
  // do something with attribute. . .
}

Now, let’s take the example of the Queue class from the sample application. We have already looked at Queue’s management interface and how we created the necessary metadata classes to expose it. Here we will look at how we implement DynamicMBean on the Queue class so that the management interface functionally corresponds to how it is exposed.

The getMBeanInfo( ) method of the DynamicMBean interface is what ties the metadata to the implementation of the DynamicMBean interface. This is a very simple method that simply returns an MBeanInfo instance that contains the definition of the management interface of the MBean. A management application uses getMBeanInfo( ) to discover what the management interface looks like. But then what? Well, now that the management interface is known, the management application uses the DynamicMBean interface to set and get attribute values and invoke methods on the MBean.

Let’s take a closer look at the methods of the DynamicMBean interface and how to implement them.

getAttribute( )

This method is used to retrieve a single attribute value and is defined as:

public Object getAttribute(String attribute)
  throws AttributeNotFoundException,
         MBeanException,
         ReflectionException {
// . . .
}

Recall from Section 3.2.1 that every attribute must have a corresponding MBeanAttributeInfo instance to describe it. One of the required parameters to both of the constructors of MBeanAttributeInfo is name, which is the name of the attribute. This name is passed to getAttribute( ) as a String. If this parameter does not match one of the attribute names from the metadata, you must throw an AttributeNotFoundException. The following example shows one such implementation. When looking at this example, you may want to refer back to Example 3-3, which shows the metadata created for the attributes of the Queue class, so you can see how the attribute names there must match up with the code in getAttribute( ).

public class Queue extends Basic implements DynamicMBean {
// . . .
  public Object getAttribute(String attributeName) 
    throws AttributeNotFoundException,
           MBeanException,
           ReflectionException {
    Object ret = null;
  
    if (attributeName.equals("QueueSize")) {
      ret = new Integer(getQueueSize(  ));
    }
    else if (attributeName.equals("NumberOfSuppliers")) {
      ret = new Integer(getNumberOfSuppliers(  ));
    }
    else if (attributeName.equals("NumberOfConsumers")) {
      ret = new Integer(getNumberOfConsumers(  ));
    }
    else if (attributeName.equals("QueueFull")) {
      ret = new Boolean(isQueueFull(  ));
    }
    else if (attributeName.equals("QueueEmpty")) {
      ret = new Boolean(isQueueEmpty(  ));
    }
    else if (attributeName.equals("Suspended")) {
      ret = new Boolean(isSuspended(  ));
    }
    else if (attributeName.equals("EndOfInput")) {
      ret = new Boolean(isEndOfInput(  ));
    }
    else if (attributeName.equals("NumberOfItemsProcessed")) {
      ret = new Long(getNumberOfItemsProcessed(  ));
    }
    else if (attributeName.equals("AddWaitTime")) {
      ret = new Long(getAddWaitTime(  ));
    }
    else if (attributeName.equals("RemoveWaitTime")) {
      ret = new Long(getRemoveWaitTime(  ));
    }
    else throw new AttributeNotFoundException(
      "Queue.getAttribute(  ): ERROR: " +
      "Cannot find " + attributeName + " attribute.");
    }
    return ret;
  }
// . . .
}

The code itself is very simple: it’s just a large if/else if/else construct in which we attempt to match the attribute name that is passed as a parameter. If the attribute name matches, we simply call the appropriate getter, wrap any fundamental types in their appropriate JDK wrappers, and return the value to the caller. If the attribute value cannot be found, we throw an AttributeNotFoundException with some information about the requested attribute.

What if your management interface has no attributes? In that case, the body of this method should throw an AttributeNotFoundException, because there are no attributes. For example, if Queue has no attributes, this method will look like this:

public Object getAttribute(String attribute)
  throws AttributeNotFoundException,
         MBeanException,
         ReflectionException {
  throw AttributeNotFoundException("Attribute'" + attribute +
    "' not found.");
}

setAttribute( )

This method is used to set a single attribute value and is defined as:

public void setAttribute(Attribute attribute) 
  throws AttributeNotFoundException,
         InvalidAttributeValueException,
         MBeanException,
         ReflectionException {
// . . .
}

What is passed to this class is an Attribute instance, a JMX class that wraps an attribute’s name and value as a pair. Attribute has two getters: one for the name and one for the value. The name must match one of the attribute names that has been exposed on the management interface of your MBean, or an AttributeNotFoundException will be thrown.

public class Queue extends Basic implements DynamicMBean {
// . . .
  public void setAttribute(Attribute attribute) 
    throws AttributeNotFoundException,
           InvalidAttributeValueException,
           MBeanException,
           ReflectionException {
    String name = attribute.getName(  );
    Object value = attribute.getValue(  );
    if (name.equals("QueueSize")) {
      if (value instanceof Integer)
        setQueueSize(((Integer)value).intValue(  ));
      else {
        throw new InvalidAttributeValueException(
          "QueueSize must be an Integer."
          );
      }
    }
    else throw new AttributeNotFoundException(
      "Queue.setAttribute(  ): ERROR: " +
      "Cannot find attribute '" + name + "'.");
    }
  }
// . . .
}

Queue has only one writable attribute, QueueSize, so that’s the only one we have to handle. But notice that we were careful to make sure the object passed inside the Attribute instance is of the correct type—if it isn’t, we throw an InvalidAttributeValueException . This is standard practice when setting attribute values inside setAttribute( ) and prevents all sorts of nasty runtime exceptions from the JVM. Because it is possible that an ill-mannered management application may ignore the information in the metadata and pass an attribute value of the wrong type, we should always be careful and code for this contingency.

getAttributes( )

This method is used to retrieve the values of a group of attributes and is defined as:

public AttributeList getAttributes(String[] attributeNames) {
  // . . .
}

The attributeNames parameter is a String array that contains the names of the attributes whose values are to be retrieved. It is very straightforward to implement this method by delegating to the getAttribute( ) method for each attribute in the array:

public class Queue extends Basic implements DynamicMBean {
// . . .
  public AttributeList getAttributes(String[] attributeNames) {
  
    AttributeList resultList = new AttributeList(  );
    for (int aa = 0; aa < attributeNames.length; aa++){
      try {
        Object value = getAttribute(attributeNames[aa]);
                          resultList.add(new Attribute(attributeNames[aa], value));
      } catch (Exception e) {
        // handle the exception. . .
      }
    }
    return(resultList);
  }
// . . .
}

After delegating to getAttribute( ), a new Attribute object is instantiated and initialized with the name of the attribute and its value. This is the AttributeList object that will be returned to the caller.

setAttributes( )

This method is used to set the values of a group of attributes and is defined as:

public AttributeList setAttributes(AttributeList attributes) {
  // . . .
}

The attributes parameter is an AttributeList object. As we discussed earlier, the AttributeList object contains a List of Attribute objects, each of which corresponds to an attribute to be set. The following example shows how to access each Attribute object from the AttributeList and set its value by delegating to setAttribute( ):

public class Queue extends Basic implements DynamicMBean {
// . . .
  public AttributeList setAttributes(AttributeList attributes) {
    AttributeList attributeList = new AttributeList(  );
    for (int aa = 0; aa < attributes.size(  ); aa++) {
      try {
        Attribute attribute = (Attribute)attributes.get(aa);
                          setAttribute(attribute);
        String name = attribute.getName(  );
        Object value = getAttribute(name);
        attributeList.add(new Attribute(name, value);
      } catch (Exception e) {
        e.printStackTrace(  );
      }
    }
    return attributeList;
  }
// . . .
}

Notice the emphasized lines. We retrieve the Attribute object from the AttributeList and delegate the actual setting of the value to setAttribute( ). The JMX specification states that the attributes whose values were successfully set should be returned in an AttributeList. It is conceivable that this list could differ from the AttributeList that was passed into setAttributes( ), so we must handle any exceptions thrown from setAttribute( ) gracefully and continue to process the remaining Attribute objects.

Because the attribute that has been set may not be readable, we delegate the retrieval of its new value to getAttribute( ). If the attribute is not readable, getAttribute( ) throws an exception that prevents the returned AttributeList object from containing the Attribute object representing that attribute. If the attribute is readable, getAttribute( ) simply returns the value and everything proceeds.

invoke( )

This method is used to invoke an operation on an MBean’s management interface and is defined as:

public Object invoke(String operationName,
                     Object params[],
                     String signature[])
  throws MBeanException,
         ReflectionException {
  // . . .
}

operationName is the name of the management operation to invoke and must match exactly one of the operations for which there is a corresponding MBeanOperationInfo instance. params is an array of objects that contains the actual parameter values that must be passed to the operation. signature is an array of Class objects that represents the signature of the operation.

We first check to see if the operationName parameter matches one of the operations exposed on the MBean’s management interface. If no match is found, a ReflectionException should be thrown. Otherwise, this method should check to make sure the signature matches the expected signature of the operation. If it does not, a ReflectionException should be thrown. Next, the params array should be checked to be sure that the objects passed are actually of the correct type. If they are not, a ReflectionException should be thrown. If they are of the correct type, the invocation should be made and the parameters passed. The following example shows how to implement this algorithm for invoke( ):

public class Queue extends Basic implements DynamicMBean {
// . . .
  public Object invoke(String operationName,
                       Object params[],
                       String signature[])
    throws MBeanException,
           ReflectionException {
    Object ret = null;
    if (operationName.equals("reset")) {
      reset(  );
    }
    else if (operationName.equals("suspend")) {
      suspend(  );
    }
    else if (operationName.equals("resume")) {
      resume(  );
    }
    else { 
      throw new ReflectionException(
              new NoSuchMethodException(operationName), 
               "Cannot find the operation '" +
               operationName + " in " + dClassName);
    }
  }
// . . .
}

The logic inside this method is relatively simple. We just look at the operationName parameter, and if it matches one of the operations for which we created an MBeanOperationInfo instance, we invoke the appropriate method. Otherwise, we throw a NoSuchMethodException wrapped inside a ReflectionException.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset