Chapter 4. Dynamic MBeans

Dynamic MBeans, as the name implies, provide a more dynamic definition of the management interface compared to Standard MBeans. Instead of relying on a statically typed Java interface, the Dynamic MBeans implement a generic interface that provides methods for the agent to discover the management interface. This allows the management interface to be changed without a recompilation of the class files and enables the management attributes and operations to be created dynamically at runtime.

Due to their dynamic nature, Dynamic MBeans are quite useful for situations in which you anticipate that the management interface will change often. In addition, the Dynamic MBeans enable you to define the management interface declaratively, for example in an XML file, and create the management interface at runtime. If the exposed management interface is determined depending on an external information source, you might want to postpone the creation of the management interface until such information is available.

One possible case for using Dynamic MBeans is enabling the Java management on existing resources or classes that do not follow the Standard MBean naming conventions. Dynamic MBeans lend themselves to act as adapters exposing a consistent management view to the agent level and delegating the agent's requests to correct methods or even to entirely different components not directly seen by the agent.

Another aspect of the Dynamic MBeans is that they expose the metadata that describes the management interface to the developer. With Standard MBeans, the metadata is generated by the agent that used introspection to discover the management interface. With Dynamic MBeans, the developer is responsible for providing this metadata.

In this chapter, you will first look at all the details of how to implement the methods of the DynamicMBean interface. Then you will go through the metadata structures and see what type of information the developer needs to provide with each Dynamic MBean. In the end, you'll build the equivalent of the standard User MBean as a Dynamic MBean and then see how the JMX management architecture allows you to use the different implementations interchangeably.

DynamicMBean Interface

To be recognized as a Dynamic MBean by the agent, the MBean must implement the DynamicMBean interface. The agent will use the methods defined in this interface to interact with the MBean to determine the management attributes and operations of the MBean, to query the attribute values, and to invoke the MBean operations. The DynamicMBean interface contains six methods (Listing 4.1) that you, as a developer, are responsible for implementing. The methods described in the interface are generic and detyped, so some extra discipline is in order when implementing Dynamic MBeans. The generic definition of the Dynamic MBean interface provides increased flexibility but should be handled with care.

The DynamicMBean interface is shown in Listing 4.1 (the exceptions declared in the interface have been left out for the sake of brevity).

Example 4.1. DynamicMBean interface

package javax.management;

public interface DynamicMBean {

   public MBeanInfo getMBeanInfo();

   public Object getAttribute(String attribute);

   public AttributeList getAttributes(String[] attributes);

   public void setAttribute(Attribute attribute);

   public AttributeList setAttributes(AttributeList attributes);

   public Object invoke(String actionName,
                        Object[] params,

String[] signature);
}

Notice the declaration of the invoke() method. All the type information is passed as strings, forming the invocation's signature. The params and signature arrays must be of the same length and a parameters type is retrieved from the same index location in the corresponding signature array.

The getMBeanInfo() method returns an object that fully describes the management interface of the Dynamic MBean. This includes the MBean's attributes, operations, constructors, and notifications. Each of the metadata elements has its own class in the JMX API, which you will examine in detail later in this chapter, in the section “MBean Metadata Classes.”

GetAttribute and GetAttributes

The getAttribute() and getAttributes() methods are used by the agent to retrieve the values of the management attributes from the Dynamic MBean. When you create a Dynamic MBean, it is up to you to map the named attributes to their corresponding values in the MBean implementation and then return these values to the agent on request. The full declaration of the getAttribute() method is as follows:

public Object getAttribute(String attribute) throws
   AttributeNotFoundException, MBeanException, ReflectionException;

The returned object must contain the value of the attribute. The runtime type of the returned object must also match the Java type defined in the Dynamic MBean's metadata classes. You can use the AttributeNotFoundException class to indicate to the agent that the named attribute was not found or its value could not be retrieved. The MBeanException class can be used for wrapping any checked exceptions that may be thrown by the getAttribute() implementation, such as the java.io.IOException. The ReflectionException class can be used to propagate exceptions resulting from the use of the java.lang.reflect package back to the agent. You will learn more about the JMX agent exception mechanism and each declared exception in Chapter 6, “MBean Server.”

Listing 4.2 is an example implementation of the getAttribute() method that processes the attributes of the User MBean you built as an example for Standard MBeans. This code snippet shows how you would deal with readable attributes for dynamic User MBean.

Example 4.2. DynamicUser.java

...

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

    if (attribute == null || attribute.equals(""))
        throw new IllegalArgumentException(
            "null or empty attribute name"
        );

    // map the named attributes to fields
    if (attribute.equals("Name"))
        return name;

    if (attribute.equals("Address"))
        return address;

    if (attribute.equals("PhoneNumbers"))
        return numbers;

    if (attribute.equals("ID"))
        return new Long(id);

    throw new AttributeNotFoundException(
        "Attribute " + attribute + " not found."
    );
}

...

In Listing 4.2, you first check the attribute parameter for a null reference and empty strings. An attempt to retrieve an attribute with a null or empty name will yield an IllegalArgumentException being thrown from the implementation. The agent will catch this and other runtime exceptions and will wrap them in a subclass of javax.management.JMRuntimeException.

If the given attribute parameter is valid, a simple comparison for the parameter string is made next. Remember that the MBean attribute names are case sensitive, so don't confuse the MBean attribute names with the field names in the implementation class. In a more robust implementation than that shown here, it would be a good idea to define the attribute names as constants to eliminate the possibility of simple typing errors.

Again, notice that the management attribute names can be independent of the underlying implementation names. You can use names in the management interface that are more self-explanatory and consistent than the names used in the resource implementation. With Dynamic MBeans it is up to you to map the attribute name to the object field names.

The getAttributes() method returns the values of several MBean attributes. The return type is an AttributeList class defined in the JMX API, which is a subclass of the ArrayList class in the Java Collections framework. The returned list must contain a set of Attribute objects that hold the name and the value of the requested attribute.

The getAttributes() method does not signal errors using the Java exception mechanism. Instead, any attributes whose value retrieval caused an error condition should be left out of the return list.

SetAttribute and SetAttributes

The setAttribute() and setAttributes() methods are the corresponding setter methods to the getter methods in the Dynamic MBean interface. Again, setAttribute() is used for setting a single MBean attribute value, and it is up to the MBean implementation to map the given attribute name in the implementation and set the attribute value. The setAttribute() method uses the Attribute class as a wrapper for passing the attribute name and value. The full declaration of the setAttribute() method defines several exceptions that can be used for signaling error conditions.

public void setAttribute(Attribute attribute) throws
         AttributeNotFoundException, InvalidAttributeValueException,
         MBeanException, ReflectionException;

The AttributeNotFoundException class can be used to indicate if the given attribute does not exist in the MBean. InvalidAttributeValueException should be used to indicate the value given in the Attribute object is not acceptable for the given attribute or the value is of the wrong type. The use of MBeanException class is similar to that seen with the getAttribute() method. If the attribute-set operation causes a checked exception to be thrown in the MBean implementation, it can be caught, wrapped into an MBeanException object, and rethrown to be managed by the agent. Similarly, the ReflectionException class can be used to wrap exceptions resulting from using the operations of java.lang.reflect package in setting the MBean's state.

Listing 4.3 is an example implementation of the setAttribute() method. It implements the attributes for the example User MBean you built as a Standard MBean example and shows how similar functionality is achieved with Dynamic MBeans. Each attribute name is matched individually and cast to String or to an array of String objects. In case there is a type mismatch between the provided attribute type and the expected type, the resulting ClassCastException is caught and rethrown as an InvalidAttributeValueException.

Example 4.3. DynamicUser.java

...

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

   if (attribute == null) throw new
       AttributeNotFoundException("null attribute");

   // map the attributes to fields
   try {
      if (attribute.getName().equals("Name"))
         this.name = (String)attribute.getValue();

      else if (attribute.getName().equals("Address"))
         this.address = (String)attribute.getValue();

      else if (attribute.getName().equals("PhoneNumbers"))
         this.numbers = (String[])attribute.getValue();

      else if (attribute.getName().equals("Password"))
         this.passwd = (String)attribute.getValue();

      else
         throw new AttributeNotFoundException(
            "attribute "+attribute.getName()+" not found."
         );
   }
   catch (ClassCastException e) {

      // In case of the type mismatch, throw an
      // InvalidAttributeValueException
      throw new InvalidAttributeValueException(
          "Invalid attribute type " +
          attribute.getValue().getClass().getName()
      );
   }
}

...

The setAttributes() method takes an AttributeList as a parameter and allows for the setting of several MBean attributes at once. Similar to getAttributes(), this method does not indicate error conditions using the exception mechanism but returns a list of attributes whose values were successfully set. In case there were errors in setting some of the attributes, they will not be added to the returned result set. You can quickly determine if all attributes were successfully set by comparing the sizes of the input list and return list.

The invoke() method is called by the agent when a request to invoke a management operation has been made. The parameters passed with the call include the operation's name, an array of objects containing the values for the operation parameters, and an array of strings declaring the types of the parameter values. Again, the MBean implementation is responsible for parsing all of this information and mapping the invoke call to the appropriate methods in the implementation. The checked exceptions thrown by the implementation can be passed back to the agent by wrapping them with the MBeanException class. The return type from the invoke operation must match the type declared in the management operation metadata for this Dynamic MBean.

Listing 4.4 shows a code snippet that handles the management operation invocation of a Dynamic MBean. Notice that each of the management operations are mapped from their names to method calls in the implementation class.

Example 4.4. DynamicUser.java

...

public Object invoke(String actionName, Object[] params,
                     String[] signature) throws
                     MBeanException, ReflectionException {

   if (actionName == null || actionName.equals(""))
     throw new IllegalArgumentException("null operation");

   // map management operations
   if (actionName.equals("printInfo")
     return printInfo();

   else if (actionName.equals("addPhoneNumber")) {
     addPhoneNumber((String)params[0]);
     return null;
   }

   else if (actionName.equals("removePhoneNumber")) {
      removePhoneNumber(((Integer)params[0]).intValue());
      return null;
   }

   else throw new IllegalArgumentException(
             "unknown operation " + actionName);
}

...

You have now seen how to implement the basic mechanisms for readable and writable management attributes with Dynamic MBeans and how management operation invocations are implemented. Next, you'll look into the metadata structures of the Dynamic MBeans.

When creating Dynamic MBeans, the metadata needs to be explicitly declared by the programmer. In the case of the Standard MBeans, the metadata was automatically built by the agent via Java language introspection. Now you will see what it is that the agent actually created from the Standard MBean's interface.

MBean Metadata Classes

The Dynamic MBeans expose their management interface via metadata classes defined in the JMX API. The metadata is retrieved by the agent using the getMBeanInfo() method of the DynamicMBean interface. The returned object, an instance of the MBeanInfo class, is an aggregate of several metadata classes. The metadata classes describe all elements of the MBean's management interface—attributes, operations, constructors, and notifications. In addition, the parameters for operations and constructors are represented by their own metadata class. The corresponding metadata classes are

  • MBeanAttributeInfo

  • MBeanOperationInfo

  • MBeanConstructorInfo

  • MBeanNotificationInfo

  • MBeanParameterInfo

Figure 4.1 shows the structure of the metadata classes in the javax.management package.

JMX metadata classes.

Figure 4.1. JMX metadata classes.

As you can see from the figure, the MBeanInfo acts as a collection class for all metadata elements that inherit common features from the MBeanFeatureInfo class. The MBeanOperationInfo and MBeanConstructorInfo classes also use the MBeanParameterInfo to construct the metadata that describes the signature of these methods.

The MBeanInfo object is available to the management applications via the getMBeanInfo() method of the MBeanServer interface. Through the agent, another MBean or a management application can discover the management interface of an MBean and its metadata description.

The metadata can also be extended to supply additional information to what is described here. You will see an example of how to do this when the Model MBeans are discussed and see how they attach additional information to the metadata that the agent or the management application can utilize.

MBeanInfo

The MBeanInfo class fully describes the management interface of an MBean. Using the methods of this class, you can retrieve metadata for the attributes, operations, constructors, and notifications of the MBean.

Table 4.1 lists the methods of the MBeanInfo class with short descriptions of their use.

Table 4.1. List of the Methods of the MBeanInfo Class

Method Name Description
getClassName() Returns the classname of the MBean this MBeanInfo object describes.
getDescription() Returns a description of the MBean this MBeanInfo object describes. The returned string should be in human readable form. Management applications can use this information in their user interfaces.
getAttributes() Returns an array of MBeanAttributeInfo objects. The MBeanAttributeInfo objects describe the management attributes including the attribute name, the type of the attribute and the access mode (read-only, write-only, read-write). MBeans with no attributes return an empty array.
getOperations() Returns an array of MBeanOperationInfo objects. The MBeanOperationInfo objects describe the management operations, including the operation name, its return type, and its parameter types (signature). MBeans with no operations return an empty array.
getConstructors() Returns an array of MBeanConstructorInfo objects. The MBeanConstructorInfo objects describe the public constructors of an MBean.
getNotifications() Returns an array of MBeanNotificationInfo objects describing the notifications emitted by the MBean.

The metadata classes are available to all clients through the MBeanServer interface. The metadata can also be extended to provide a richer description of the management interface to the agent and the management applications. You will see an example of this in Chapter 5, “Model MBeans.”

MBeanFeatureInfo

The MBeanFeatureInfo class is a superclass for all the metadata classes in the javax.management package, except the MBeanInfo class. This class provides an access to two fields, the name and the description of an MBean metadata object. The subclasses use the name to return the name of the MBean attribute, operation, parameter, constructor, or notification. The description is used to return a human readable string describing the purpose and effects of the MBean metadata element. This is often a string that gives a short description of the management operation or describes the semantics of an attribute, parameter, or a notification. The description string can be used by the management tools to describe the managed resource and its properties to the user.

Notice that when creating Standard MBeans, it is not possible to set the descriptions for the management interface elements. The agent created the metadata structures automatically by introspecting the Standard MBean interface and had no access to any descriptive information. Unfortunately, there isn't a mechanism defined in the JMX specification that would allow the Standard MBeans to relay this type of information to the agent. You need to use Dynamic MBeans or their extensions to be able to provide additional metadata, such as the attribute and operation descriptions.

MBeanAttributeInfo

The MBeanAttributeInfo class is used to describe the attributes of an MBean. The class contains the information about the attribute's runtime type, name, description, and its access type. The name is the programmatic name of the management attribute. The description should be a human readable description of the attribute and its intended use.

The runtime type string must be a fully qualified name of the class, meaning that the package declaration must be included in the classname, such as java.lang.String. Also note that this rule applies to the Java array types as well as the primitive types. For example, the fully qualified classname of an array of String objects is "[Ljava.lang.String;", and a two-dimensional array of String objects is "[[Ljava.lang.String;". For arrays of primitive types, it gets even hairier. For example, the fully qualified classname of an array of long types is "[J".

Table 4.2 shows examples of some fully qualified class names.

Table 4.2. Some Java Types and Their Fully Qualified Class Names

Type Fully Qualified Name Type Fully Qualified Name
byte “byte” byte[] “[B”
char “char” char[] “[C”
double “double” double[] “[D”
float “float” float[] “[F”
int “int” int[] “[I”
long “long” long[] “[J”
short “short” short[] “[S”
boolean “boolean” boolean[] “[Z”
Object “java.lang.Object” Object[] “[Ljava.lang.Object;”
void “void”

More details on the string representation of the classnames can be found in the API documentation of the java.lang.Class.getName() method.

Because trying to remember the fully qualified names can be difficult and error-prone, especially in the case of arrays, there's a workaround you can use when creating the metadata classes. The Java language specification defines an expression called a class literal. You can use the class literal with all types in the Java language, including primitive types and type void, to access the Class object. This is useful, because the Class object implements the getName() method, which returns a fully-qualified name of the class. For example, when creating the MBeanAttributeInfo object for the array attribute PhoneNumbers in the User MBean, you could write the code as follows:

...

 MBeanAttributeInfo numbers = new MBeanAttributeInfo(
   "PhoneNumbers",
   String[].class.getName(), // fully qualified class name
   "User's phone numbers.",
   true, true, false
 );

...

So, instead of using the cumbersome textual representation of "[Ljava.lang.String;" for an array of strings, you can let the Java class libraries do the work for you by accessing the array class and then calling its getName() method. The same approach works for a multidimensional array of primitive types, for example, long[][][] or even for type void. You can test it with the following snippet of Java code:

System.out.println(void.class.getName());
System.out.println(long[][][].class.getName());

MBeanParameterInfo

The MBeanParameterInfo describes the parameters for MBean constructors and operations. As with the other metadata classes, the MBeanParameterInfo inherits the name and description properties from its superclass MBeanFeatureInfo. The name property, along with the description string, is often displayed to the user with generic management tools and should contain the required information for the user to be able to either create a new instance of the MBean or invoke one of its operations. In addition, the MBeanParameterInfo class contains the type of the parameter, again a fully qualified classname of the type. This string is accessible through the getType() method of the MBeanParameterInfo class.

MBeanOperationInfo

The MBeanOperationInfo class describes the management operations of an MBean. The object contains information about the operation's name and the description, both inherited from the MBeanFeatureInfo class, the type of the method's return value, the method's signature, and the impact of the operation on the MBean.

The signature consists of an array of MBeanParameterInfo objects that distinguishes overloaded methods from one another. The return type, as usual, must be a fully qualified classname of the returned object instance. The impact value can be used by the MBean developer to give the management application some idea how the invocation of the operation is going to affect the MBean's state.

The JMX API defines four values that can be used to describe the method's impact. The constants shown in Table 4.3 are declared in the MBeanOperationInfo class.

Table 4.3. The Impact of the MBeanOperationInfo Class

Impact Description
INFO The management operation is read-only, not affecting the state of the MBean.
ACTION The management operation does affect the state of the MBean. This usually means changing the values of the MBean object's fields, changing the persistent state of the MBean or changing its configuration.
ACTION_INFO The management operation implements both read-only types of functionality and also changes the state of the MBean in some way.
UNKNOWN The effect of the operation is unknown. This is reserved for the operations of the Standard MBeans.

The impact field can be used to implement a coarse-grained scheme of determining which operations should be exposed to the user in the client. It is the responsibility of the Dynamic MBean developer to assign the correct impact values in the MBeanOperationInfo metadata objects. The class provides the accessor method getImpact() for querying the impact of the operation.

Listing 4.5 shows the metadata implementation of one of the User MBean operations, addPhoneNumber(), as it would be implemented with a Dynamic MBean. Notice that the signature of the management operation is built using an array of MBeanParameterInfo objects, each of which will include the name of the parameter, its runtime type, and a description of its purpose. The impact value is set to MBeanOperationInfo.ACTION as the addPhoneNumber() changes the state of the DynamicUser object.

Example 4.5. DynamicUser.java

...

 // addPhoneNumber operation metadata
 MBeanOperationInfo addPhoneNumber =
   new MBeanOperationInfo(
     "addPhoneNumber",               // operation name
     "Add a new phone number.",      // description

         // parameters
         new MBeanParameterInfo[] {
           new MBeanParameterInfo(
             "number",               // parameter name
             String.class.getName(), // parameter type
             "The number to add."    // parameter descr.
           )
         } ,

     void.class.getName(),           // return type void
     MBeanOperationInfo.ACTION       // impact: ACTION
 );

...

MBeanConstructorInfo

The MBeanConstructorInfo class describes the constructors of an MBean that are exposed to management. The MBeanConstructorInfo contains a name for the constructor, a description string for the management applications to display to the user, and an array of metadata objects describing the parameters of the constructor. The parameters make up the signature of the constructor that is used to differentiate a set of constructors from each other. You build up the parameters by using the MBeanParameterInfo objects and can query the array by calling the getSignature() method of the MBeanConstructorInfo class.

For example, to define a default, no-args constructor for the User MBean, create the metadata object in Listing 4.6.

Example 4.6. DynamicUser.java

...

 MBeanConstructorInfo defaultConstructor =
    new MBeanConstructorInfo(
          "Default Constructor",           // name
          "Creates a new user instance.",  // description
          null                             // parameters
    );
...

MBeanNotificationInfo

Notifications are part of the MBean's management interface, so the MBeanNotificationInfo class is required to describe the notifications broadcast by the MBean. The management applications can use the MBeanNotificationInfo object to discover the details of the notifications emitted by the MBean.

The use of the name field in the MBeanNotificationInfo class differs slightly from the other metadata classes in the JMX API. In the case of the notifications, the meaning of the field is restricted to contain the fully qualified classname of the actual notification object that is being broadcast. The description can be freely formed to describe the purpose of the notification to the management applications.

In addition to the name and description, the MBeanNotificationInfo class describes all the notification types that are sent using the class declared in the name field. The notification types are accessible to the management applications via the getNotifTypes() method of the MBeanNotificationInfo class.

You may remember from Chapter 3, “Standard MBeans,” where notifications were discussed, that the MBeanNotificationInfo is used with both the Standard MBean and Dynamic MBean implementations.

In the BroadcastingUser MBean, you created the following MBeanNotificationInfo instances (see Listing 4.7).

Example 4.7. Notification Metadata

new MBeanNotificationInfo[] {

  // notification for the remove operation
  new MBeanNotificationInfo(
    new String[]
      {  "example.user.remove" } ,  // notif. types
    Notification.class.getName(),   // notif. class
    "User Notifications."           // description
  ),

  // attribute change notification type
  new MBeanNotificationInfo(
    new String[] {
      AttributeChangeNotification.ATTRIBUTE_CHANGE
    } ,
    AttributeChangeNotification.class.getName(),
    "User attribute change notification."
  )
} ;

This code snippet creates the metadata for two different notification class types, Notification and AttributeChangeNotification, each of which is used for sending one notification type.

Attribute and AttributeList

The Attribute class is a convenience class in the JMX API for representing the management attributes of an MBean. The class associates the attribute name with its value and provides the accessor methods for reading these values. It is used in the JMX interfaces, such as the DynamicMBean interface in getAttribute() and setAttribute() methods.

The AttributeList class is a subclass of the ArrayList class in the Java Collection framework. It has been extended to accept Attribute object instances in its insertion methods to ensure type safety in the collection. The AttributeList is used in the DynamicMBean interface setAttributes() and getAttributes() methods.

Inheritance Patterns

Similar to Standard MBeans, Dynamic MBeans can inherit their management interface definition from the superclass. This follows from the normal rules of Java inheritance. If the resource class does not directly implement either the Standard MBean interface or the DynamicMBean interface, the inheritance tree is searched for the first ancestor implementing either one of the interfaces. This interface is then used to expose the management attributes and operations of an MBean. Remember, the same class is not allowed to implement both the Standard MBean and the DynamicMBean interfaces. Such resources are not compatible with the JMX specification.

DynamicUser Example

The next example completes the code snippets you have seen in this chapter so far with a full Dynamic MBean implementation of the User resource. You will build an identical managed resource to the User MBean to demonstrate the differences between instrumenting a resource using either the Standard MBean mechanism or the Dynamic MBean mechanism.

You will find that the Dynamic MBean requires quite a bit of extra code to implement the same management interface as was used with the Standard MBean. This time, you cannot rely on the agent to discover the management interface of the MBean automatically via introspection. You must provide the management interface metadata to the agent explicitly via the getMBeanInfo() method. With non-trivial management interfaces, this can become quite a burden to the programmer. Later, in Part II, “JMX in the J2EE Platform,” you will see how to alleviate the problem by introducing an MBean implementation that can read its management interface definition from an XML file.

In addition, what is different with the Dynamic MBean from the Standard MBean implementation is that you no longer have a statically typed Java interface that describes the management attributes and operations of an MBean. Instead, the developer is left with the responsibility of correctly mapping the management attribute and operation names to their corresponding fields and methods in the resource class. The developer must explicitly handle the type casts between the invocations and the actual runtime types. Again, this places the burden on the programmer to ensure the correct implementation.

However, for these same reasons, Dynamic MBeans do allow for increased flexibility in how they implement the management attributes and operations. You can define the management interface declaratively by implementing a Dynamic MBean that reads an XML file describing the management interface. You could also implement a Dynamic MBean that retrieves its management interface from a database. This is possible because the management interface of Dynamic MBeans can be determined at runtime instead of compile time, which is a requirement for Standard MBeans. In addition, with Dynamic MBeans you gain access to the MBean's metadata elements that you can extend for the application purposes. You will see more examples of this in Chapter 5.

The code example in Listing 4.8 shows you a full Dynamic MBean implementation, in detail, of a resource that has a matching management interface to the User MBean from Chapter 3. The implementation starts with the code of the getAttribute() and getAttributes() methods. After the getter method implementations is the corresponding implementation of the setAttribute() and setAttributes() methods. These methods map the management attribute names to the actual fields in the resource implementation.

After the management attribute getter and setter method implementations, you have an implementation of the invoke() method. The principal is the same as the management attribute accessor methods; the management operation names are mapped to the actual methods in the resource implementation.

Last, you have the implementation of the getMBeanInfo() method. The implementation returns a collection of metadata objects back to the agent that describe each element of the resource's management interface.

The DynamicUser implementation is shown in Listing 4.8.

Example 4.8. DynamicUser.java

package book.jmx.examples;

import javax.management.*;
import java.util.Iterator;

public class DynamicUser
          extends NotificationBroadcasterSupport
          implements DynamicMBean {


  // mgmt attribute name constants
  final static String ID      = "ID";
  final static String NAME    = "Name";
  final static String ADDRESS = "Address";
  final static String NUMBERS = "PhoneNumbers";
  final static String PASSWD  = "Password";


  // mgmt operation name constants
  final static String PRINT_INFO    = "printInfo";
  final static String ADD_NUMBER    = "addPhoneNumber";
  final static String REMOVE_NUMBER = "removePhoneNumber";


  // fields for attribute values
  private long id           = System.currentTimeMillis();
  private String name       = "";
  private String address    = "";
  private String passwd     = null;
  private String[] numbers  = new String[3];


  // getAttribute implementation

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

    if (attribute == null || attribute.equals(""))
      throw new IllegalArgumentException(
          "null or empty attribute name"
      );

    // map the named attributes to fields
    if (attribute.equals(NAME))
      return name;

    if (attribute.equals(ADDRESS))
      return address;

    if (attribute.equals(NUMBERS))
      return numbers;

    if (attribute.equals(ID))
      return new Long(id);

    throw new AttributeNotFoundException(
      "Attribute " + attribute + " not found."
    );
  }


  // getAttributes implementation

  public AttributeList getAttributes(String[] attributes) {
    if (attributes == null)
      throw new IllegalArgumentException("null array");

    AttributeList list = new AttributeList();

    for (int i = 0; i < attributes.length; ++i) {
      try {
        list.add(new Attribute(
            attributes[i], getAttribute(attributes[i])
        ));
      }
      catch (JMException ignored) {
        // if the attribute could not be retrieved, skip it
      }
    }

    return list;
  }


  // setAttribute implementation

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

    if (attribute == null) throw new
      AttributeNotFoundException("null attribute");

    // map attributes to fields
    try {
      if (attribute.getName().equals(NAME))
        this.name = (String)attribute.getValue();

      else if (attribute.getName().equals(ADDRESS))
        this.address = (String)attribute.getValue();

      else if (attribute.getName().equals(NUMBERS))
        this.numbers = (String[])attribute.getValue();

      else if (attribute.getName().equals(PASSWD))
        this.passwd = (String)attribute.getValue();

      else
        throw new AttributeNotFoundException(
          "attribute "+attribute.getName()+" not found."
        );
    }
    catch (ClassCastException e) {
      throw new InvalidAttributeValueException(
        "Invalid attribute type " +
        attribute.getValue().getClass().getName()
      );
    }
  }


  // setAttributes implementation

  public AttributeList setAttributes(AttributeList list) {

    if (list == null)
      throw new IllegalArgumentException("null list");

    AttributeList results = new AttributeList();
    Iterator it           = list.iterator();

    while (it.hasNext()) {
      try {
        Attribute attr = (Attribute)it.next();
        setAttribute(attr);
        results.add(attr);
      }
      catch (JMException ignored) {
        // if unable to set the attribute, skip it
      }
    }

    return results;
  }


  // invoke implementation

  public Object invoke(String actionName,
                       Object[] params,
                       String[] signature)
                throws MBeanException,
                       ReflectionException {

    if (actionName == null || actionName.equals(""))
      throw new IllegalArgumentException("no operation");

    // map operation names to methods
    if (actionName.equals(PRINT_INFO))
      return printInfo();

    else if (actionName.equals(ADD_NUMBER)) {
      addPhoneNumber((String)params[0]);
      return null;
    }

    else if (actionName.equals(REMOVE_NUMBER)) {
      removePhoneNumber(((Integer)params[0]).intValue());
      return null;
    }

    else
      throw new UnsupportedOperationException (
        "unknown operation " + actionName
      );
  }


  // getMBeanInfo implementation

  public MBeanInfo getMBeanInfo() {

    // Is attribute readable and/or writable
    final boolean READABLE  = true;
    final boolean WRITABLE  = true;

    // Is attribute getter in boolean isAttribute()
    // form?
    final boolean IS_GETTER = true;

    // MBean class and description
    String className    = getClass().getName();
    String description  =
      "User resource with dynamic management interface";

    // metadata for 'ID' attribute.
    MBeanAttributeInfo id   = new MBeanAttributeInfo(
      ID,                                   // name
      long.class.getName(),                 // type
      "Unique identifier of user.",         // description
      READABLE, !WRITABLE, !IS_GETTER       // access
    );

    // metadata for 'Name' attribute.
    MBeanAttributeInfo name = new MBeanAttributeInfo(
      NAME,                                 // name
      String.class.getName(),               // type
      "User's name.",                       // description
      READABLE, WRITABLE, !IS_GETTER        // access
    );

    // metadata for 'Address' attribute.
    MBeanAttributeInfo address = new MBeanAttributeInfo(
      ADDRESS,                              // name
      String.class.getName(),               // type
      "User's address.",                    // description
      READABLE, WRITABLE, !IS_GETTER        // access
    );

    // metadata for 'PhoneNumbers' attribute.
    MBeanAttributeInfo numbers = new MBeanAttributeInfo(
      NUMBERS,                              // name
      String[].class.getName(),             // type
      "User's phone numbers.",              // description
      READABLE, WRITABLE, !IS_GETTER        // access
    );

    // metadata for 'Password' attribute.
    MBeanAttributeInfo passwd  = new MBeanAttributeInfo(
      PASSWD,                               // name
      String.class.getName(),               // type
      "User's password.",                   // description
      !READABLE, WRITABLE, !IS_GETTER       // access
    );


    // metadata for 'printInfo' operation
    MBeanOperationInfo printInfo = new MBeanOperationInfo(
      PRINT_INFO,                           // name
      "String representation of the user.", // description
      null,                                 // signature
      String.class.getName(),               // return type
      MBeanOperationInfo.INFO               // impact
    );

    // metadata for 'addPhoneNumber' operation.
    MBeanOperationInfo addPhoneNumber =
      new MBeanOperationInfo(
        ADD_NUMBER,                         // name
        "Adds phone number for the user.",  // description

        new MBeanParameterInfo[] {          // signature
          new MBeanParameterInfo(
            "number",
            String.class.getName(),
            "The number to add."
          )
        } ,

        void.class.getName(),               // return type
        MBeanOperationInfo.ACTION           // impact
    );

    // metadata for 'removePhoneNumber' operation.
    MBeanOperationInfo removePhoneNumber =
      new MBeanOperationInfo(
        REMOVE_NUMBER,                      // name
        "Removes phone number from user.",  // description

        new MBeanParameterInfo[] {          // signature
          new MBeanParameterInfo(
            "index",
            int.class.getName(),
            "The index number."
          )
        } ,

        void.class.getName(),               // return type
        MBeanOperationInfo.ACTION           // impact
    );

    // mbean constructors
    MBeanConstructorInfo defaultConstructor =
      new MBeanConstructorInfo(
        "Default Constructor",
        "Creates a new user instance.", null
      );

    // attribute, constructor and operation lists
    MBeanAttributeInfo[] attributes =
      new MBeanAttributeInfo[] {
        id, name, address, numbers, passwd
      } ;

    MBeanConstructorInfo[] constructors =
      new MBeanConstructorInfo[] {
        defaultConstructor
      } ;

    MBeanOperationInfo[] operations =
      new MBeanOperationInfo[] {
        printInfo, addPhoneNumber, removePhoneNumber
      } ;

    // return the MBeanInfo
    return new MBeanInfo(
      className, description, attributes,
      constructors, operations, null
    );
  }

  // management operation implementations
  public String printInfo() {
    return
      "User: " + name +" n"+
      "Address: " + address +" n"+
      "Phone #: " + numbers[0] +" n"+
      "Phone #: " + numbers[1] +" n"+
      "Phone #: " + numbers[2] +" n";
  }

  public void addPhoneNumber(String number) {
    for (int i = 0; i < numbers.length; ++i)
      if (numbers[i] == null) {
        numbers[i] = number;
        break;
      }
  }

  public void removePhoneNumber(int index) {
    if (index < 0 || index >= numbers.length)
      return;

    numbers[index] = null;
  }

}

Because the management operations and attributes of the DynamicUser MBean are identical to the UserMBean, you can use the same management application to access both managed resources. You can test this by taking the UserClient class you built in Chapter 3 and run it against an MBean server instance that has a DynamicUser instance registered to it. The DynamicUser instance should be bound to the same object name reference "example:name=user" that was used for the UserMBean.

You will need to modify the UserClient class to register DynamicUser class instead of the User. The change to UserClient class is shown in the Listing 4.9.

Example 4.9. UserClient.java

...

try {
    // register the MBean
    ObjectName name = new ObjectName("example:name=user");
    server.registerMBean(new DynamicUser(), name);

    // Invoke the printInfo operation on an
    // uninitialized MBean instance.
    Object result = server.invoke(
                       name,          // MBean name
                       "printInfo",   // operation name
                       null,          // no parameters
                       null           // void signature
                    );

...

When you compile and run the modified UserClient that uses the DynamicUser component, you should see the exact same results on the console that you saw with the UserMBean.

To compile the DynamicUser.java and the modified UserClient.java execute the command:

C: Examples> javac -d . -classpath jmx-1_0_1-ri_bin jmx lib jmxri.jar;
UserClient.java jmx-1_0_1-ri_bin jmx lib jmxtools.jar DynamicUser.java UserClient.java

To run the application, enter the command:

C: Examples> java -classpath .;jmx-1_0_1-ri_bin jmx lib jmxri.jar book.jmx.examples
UserClient.java.UserClient

You should see the following output:

Non-initialized User object:

User:
Address:
Phone #: null
Phone #: null
Phone #: null
Initialized User object:
User: John
Address: Strawberry Street
Phone #: 555-1232
Phone #: null
Phone #: null

The difference in the agent between the standard and Dynamic MBean is how the management interface metadata is retrieved. For the Standard MBeans, it was collected by the agent using introspection, whereas for the Dynamic MBeans, the agent invokes the getMBeanInfo() method to retrieve the metadata. The agent abstracts the MBean type, standard or dynamic, from the management application by requiring all invocations to propagate through its invoke(), setAttribute(), and getAttribute() methods. What is exposed to the management application is the MBeanServer interface that provides the methods to access and discover the management interface.

The metadata description available to the client is either built by the agent or provided by the managed resource explicitly. This, added to the fact that the client is never given a direct Java reference to the managed resource, but instead uses the object name reference, leads to a decoupled invocation mechanism. There is no MBean specific, statically typed interface that the management application needs to rely on to access the managed resource. The next section will give you a simplified example on how to switch component implementations between Standard and Dynamic MBean on a live MBean server.

Hot Deploying Resources

You have built two separate implementations of a resource that have an identical management interface. This means that, from the point of view of the management application, these implementations are interchangeable. You have also learned that the JMX architecture decouples the management applications from the managed resources, and seen how the operation invocations are detyped in the MBean server invoke() method.

These features of the JMX-based management architecture allow you to implement a managed resource that you can change or update at runtime. This is often called hot deployment, a feature you can find in many server-based software applications. It means that a new component can be brought into the server or an existing one can be updated without having to restart the whole server. With JMX this is easy to do. Because the management applications do not have any direct Java references to the managed resources you can switch the component implementations without the client necessarily noticing anything.

To demonstrate this, you will build a Recycler application that will allow you to switch the implementation of an MBean on-the-fly. You will use the DynamicUser MBean built in Listings 4.8 and the BroadcastingUser MBean that you built in Chapter 3. The Recycler component will bind one of the two implementations to the MBean server under the object name "example:name=user".

The Recycler component itself is an MBean, and exposes a recycle() management operation to the client. You can use the recycle() operation to switch the implementation between a Standard MBean and Dynamic MBean version. The management interface of the RecyclerMBean is as follows:

package book.jmx.examples;

import javax.management.*;

public interface RecyclerMBean {

  public String recycle();

}

The recycle() operation will switch the implementation bound to object name "example:name=user" in the MBean server. The RecyclerMBean will also register and start an HTTP adaptor, so that you can manipulate the components easily via your Web browser. The code for the Recycler implementation is shown in Listing 4.10.

Example 4.10. Recycler.java

package book.jmx.examples;

import javax.management.*;
import java.util.List;

public class Recycler implements RecyclerMBean {

  private String component   = "Standard User";
  private MBeanServer server = null;

  public String recycle() {

    // Find an agent from this JVM. Null argument will
    // return a list of all MBeanServer instances.
    List list = MBeanServerFactory.findMBeanServer(null);
    server = (MBeanServer)list.iterator().next();

    try {

      // switch implementations between a standard and
      // dynamic mbeans
      if (component.equals("Standard User")) {
        server.unregisterMBean(
          new ObjectName("example:name=User")
        );

        server.registerMBean(
          new DynamicUser(),
          new ObjectName("example:name=User")
        );

        component = "Dynamic User";
      }

      else {
        server.unregisterMBean(
          new ObjectName("example:name=User")
        );

        server.registerMBean(
          new BroadcastingUser(),
          new ObjectName("example:name=User")
        );
        component = "Standard User";
      }
    }
    catch (JMException e) {
      e.printStackTrace();
    }

    return "Implementation changed to " + component;
  }

  public static void main(String[] args) {

    MBeanServer server =
        MBeanServerFactory.createMBeanServer();

    ObjectName name    = null;

    try {
      name =  new ObjectName("example:name=recycler");
      server.registerMBean(new Recycler(), name);

      name = new ObjectName("example:name=User");
      server.registerMBean(new BroadcastingUser(), name);

      name = new ObjectName("adaptor:protocol=HTTP");
      com.tivoli.jmx.http_pa.Listener adaptor =
            new com.tivoli.jmx.http_pa.Listener();

      adaptor.startListener();
      server.registerMBean(adaptor, name);
    }
    catch (java.io.IOException e) {
        e.printStackTrace();
    }
    catch (JMException e) {
        e.printStackTrace();
    }
  }
}

To compile and run the Recycler example, execute these commands (using Tivoli JMX):

C: Examples> javac -d . -classpath .;tmx4j base lib jmxx.jar;tmx4j base lib jmxc
Recycler.java.jar;tmx4j base lib log.jar; tmx4j ext lib jmxext.jar Recycler.java RecyclerMBean.java

C: Examples> java -classpath .;tmx4j base lib jmxx.jar; tmx4j base lib jmxc.jar;
Recycler.java tmx4j base lib log.jar;tmx4j ext lib jmxext.jar book.jmx.examples.Recycler

When you successfully run the application, the following line will be printed on the console:

The HTTP Adaptor started on port number 6969

Point your browser to http://localhost:6969 and enter *:* in the Find an MBean query page to get a list of registered MBeans. Select the Recycler MBean by clicking link “example:name=recycler”. Execute the recycle() operation to switch the User implementation between the Dynamic MBean and Standard MBean.

Notice that the BroadcastingUser exposes management notifications as part of its management interface. You can see the difference on the HTTP adaptor Web pages in Figures 4.2,4.3 and 4.4 where the notifications are listed. Another difference worth noticing is that the DynamicUser MBean shows the descriptions for its management operations. These were added as part of the MBeanOperationInfo metadata classes. The ability to access and extend the metadata attached to each element of the management interface is an interesting possibility that you will utilize in the later chapters.

The results of the recycle operation.

Figure 4.2. The results of the recycle operation.

Management view of the BroadcastingUser MBean.

Figure 4.3. Management view of the BroadcastingUser MBean.

Management view of the DynamicUser MBean.

Figure 4.4. Management view of the DynamicUser MBean.

There are some features that are lacking in the Recycler example. For example, the change of the implementation will not store the state of the MBean. If you have edited the attribute values, they will be lost. To avoid this, you need to persist the MBean's state to the disk, to a database, or some other persistent storage. In Chapter 5, you will look at the Model MBeans and how they implement the MBean persistence.

The other restriction with the Recycler example is that both components need to be available for the JVM classloader at start-up time. In practice, when you need to hot deploy components, whether you are updating an existing one or adding new ones, you will need to dynamically add the new classes to the class loader. The JMX provides this possibility with a standard agent service called M-Let service. This allows you to add new components dynamically and load them from across the network. You will see the M-Let service in Chapter 7, “Standard Agent Services,” where all the standard agent services are covered.

Summary

As you have seen with the examples, Dynamic MBeans allow the manageable resources to define their management interfaces at runtime, instead of using the statically typed Java interfaces that you needed to implement with Standard MBeans. The agent does not rely on introspection to find the management interface but, instead, directly calls the getMBeanInfo() method of the DynamicMBean interface to discover the management interface and to invoke operations and to get and set attributes. The Dynamic MBean implementation is required to build metadata that explicitly declares all the elements of the management interface.

Because the Dynamic MBeans do not impose a set of naming rules on the resource classes, they are convenient in exposing already existing resources to management. The attribute and operation invocations can easily be mapped to methods using any kind of naming convention.

An important point to remember about Dynamic MBeans is that they allow the late binding of the management interface instead of the early static binding of a Java interface. Another aspect differentiating the Dynamic MBeans from the Standard MBeans is the fact that the developer has an access to the metadata of the MBean. This is not possible with Standard MBeans.

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

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