Java Dynamic Management Kit 5.1 Tutorial

2.2 Implementing a Dynamic MBean

A dynamic MBean consists of a class that implements the DynamicMBean interface coherently. By this, we mean a class that exposes a management interface whose description matches the attributes and operations that are accessible through the generic getters, setters and invokers.

A dynamic MBean class can declare any number of public or private methods and variables. None of these are visible to management applications. Only the methods implementing the DynamicMBean interface are exposed for management. A dynamic MBean can also rely on other classes that might be a part of the manageable resource.

2.2.1 Dynamic Programming Issues

An MBean is a manageable resource that exposes a specific management interface. The name dynamic MBean refers to the fact that the interface is revealed at runtime, instead of through the introspection of static class names. The term dynamic is not meant to imply that the MBean can dynamically change its management interface.

2.2.2 getMBeanInfo Method

Because the MBean description should never change, it is usually created one time only at instantiation, and the getMBeanInfo method simply returns its reference at every call. The MBean constructor should therefore build the MBeanInfo object from the MBean metadata classes such that it accurately describes the management interface. And since most dynamic MBeans will always be instantiated with the same management interface, building the MBeanInfo object is fairly straightforward.

Example 2–2 shows how the SimpleDynamic MBean defines its management interface, as built at instantiation and returned by its getMBeanInfo method.


Example 2–2 Implementation of the getMBeanInfo Method

    private void buildDynamicMBeanInfo() {

        dAttributes[0] =
            new MBeanAttributeInfo("State",
                                   "java.lang.String",
                                   "State string.",
                                   true,
                                   true,
                                   false);
        dAttributes[1] =
            new MBeanAttributeInfo("NbChanges",
                                   "java.lang.Integer",
                                   "Number of times the " +
                                   "State string has been changed.",
                                   true,
                                   false,
                                   false);

        Constructor[] constructors = this.getClass().getConstructors();
        dConstructors[0] =
            new MBeanConstructorInfo("Constructs a " +
                                     "SimpleDynamic object",
                                     constructors[0]);

        MBeanParameterInfo[] params = null;
        dOperations[0] =
            new MBeanOperationInfo("reset",
                                   "reset State and NbChanges " +
                                   "attributes to their initial values",
                                   params , 
                                   "void", 
                                   MBeanOperationInfo.ACTION);

        dNotifications[0] =
            new MBeanNotificationInfo(
            new String[] { AttributeChangeNotification.ATTRIBUTE_CHANGE },
            AttributeChangeNotification.class.getName(),
            "This notification is emitted when the reset() method 
            is called.");

        dMBeanInfo = new MBeanInfo(dClassName,
                                   dDescription,
                                   dAttributes,
                                   dConstructors,
                                   dOperations,
                                   dNotifications);
    }
// PRIVATE VARIABLES

    private MBeanAttributeInfo[] dAttributes =
        new MBeanAttributeInfo[2];
    private MBeanConstructorInfo[] dConstructors =
        new MBeanConstructorInfo[1];
    private MBeanNotificationInfo[] dNotifications =
        new MBeanNotificationInfo[1];
    private MBeanOperationInfo[] dOperations =
        new MBeanOperationInfo[1];
    private MBeanInfo dMBeanInfo = null;
}

2.2.3 Generic Attribute Getters and Setters

Generic getters and setters take a parameter that indicates the name of the attribute to read or write. There are two issues to keep in mind when implementing these methods:

The getAttribute method is the simplest, since only the attribute name must be verified.


Example 2–3 Implementation of the getAttribute Method

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

    // Check attribute_name to avoid NullPointerException later on
    if (attribute_name == null) {
        throw new RuntimeOperationsException(
            new IllegalArgumentException("Attribute name cannot be null"), 
            "Cannot invoke a getter of " + dClassName +
                " with null attribute name");
    }

    // Call the corresponding getter for a recognized attribute_name
    if (attribute_name.equals("State")) {
        return getState();
    } 
    if (attribute_name.equals("NbChanges")) {
        return getNbChanges();
    }

    // If attribute_name has not been recognized
    throw(new AttributeNotFoundException(
        "Cannot find " + attribute_name + " attribute in " + dClassName));
}

// internal methods for getting attributes
public String getState() {
    return state;
}

public Integer getNbChanges() {
    return new Integer(nbChanges);
}

// internal variables representing attributes
private String      state = "initial state";
private int         nbChanges = 0;

The setAttribute method is more complicated, since you must also ensure that the given type can be assigned to the attribute and handle the special case for a null value.


Example 2–4 Implementation of the setAttribute Method

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

    // Check attribute to avoid NullPointerException later on
    if (attribute == null) {
        throw new RuntimeOperationsException(
            new IllegalArgumentException("Attribute cannot be null"),
            "Cannot invoke a setter of " + dClassName +
                " with null attribute");
    }
    String name = attribute.getName();
    Object value = attribute.getValue();

    if (name.equals("State")) {
        // if null value, try and see if the setter returns any exception
        if (value == null) {
            try {
                setState( null );
            } catch (Exception e) {
                throw(new InvalidAttributeValueException(
                    "Cannot set attribute "+ name +" to null")); 
            }
        }
        // if non null value, make sure it is assignable to the attribute
        else if (String.class.isAssignableFrom(value.getClass())) {
                    setState((String) value);
        } else {
                    throw new InvalidAttributeValueException(
                        "Cannot set attribute "+ name +
                            " to a " + value.getClass().getName() +
                            " object, String expected");
                }
            } 
    // recognize an attempt to set a read-only attribute
    else if (name.equals("NbChanges")) {
        throw new AttributeNotFoundException(
            "Cannot set attribute "+ name +
                " because it is read-only");
    }

    // unrecognized attribute name
    else {
        throw new AttributeNotFoundException(
            "Attribute " + name + " not found in " +
                this.getClass().getName());
    }
}

// internal method for setting attribute
public void setState(String s) {
    state = s;
    nbChanges++;
}

Notice that if a change in your management solution requires you to change your management interface, it will be harder to do with a dynamic MBean. In a standard MBean, each attribute and operation is a separate method, so unchanged attributes are unaffected. In a dynamic MBean, you must modify the generic methods that encode all attributes.

2.2.4 Bulk Getters and Setters

The DynamicMBean interface includes bulk getter and setter methods for reading or writing more than one attribute at once. These methods rely on the classes shown in Table 2–2.

Table 2–2 Bulk Getter and Setter Classes

Class Name 

Description 

Attribute

A simple object that contains the name string and value object of any attribute 

AttributeList

A dynamically extendable list of Attribute objects (extends java.util.ArrayList)

The AttributeList class extends the java.util.ArrayList class.

The bulk getter and setter methods usually rely on the generic getter and setter, respectively. This makes them independent of the management interface, which can simplify certain modifications. In this case, their implementation consists mostly of error checking on the list of attributes. However, all bulk getters and setters must be implemented so that an error on any one attribute does not interrupt or invalidate the bulk operation on the other attributes.

If an attribute cannot be read, then its name-value pair is not included in the list of results. If an attribute cannot be written, it will not be copied to the returned list of successful set operations. As a result, if there are any errors, the lists returned by bulk operators will not have the same length as the array or list passed to them. In any case, the bulk operators do not guarantee that their returned lists have the same ordering of attributes as the input array or list.

The SimpleDynamic MBean shows one way of implementing the bulk getter and setter methods.


Example 2–5 Implementation of the Bulk Getter and Setter Methods

public AttributeList getAttributes(String[] attributeNames) {

    // Check attributeNames to avoid NullPointerException later on
    if (attributeNames == null) {
        throw new RuntimeOperationsException(
            new IllegalArgumentException(
                "attributeNames[] cannot be null"),
            "Cannot invoke a getter of " + dClassName);
    }
    AttributeList resultList = new AttributeList();

    // if attributeNames is empty, return an empty result list
    if (attributeNames.length == 0)
            return resultList;
        
    // build the result attribute list
    for (int i=0 ; i<attributeNames.length ; i++){
        try {        
            Object value = getAttribute((String) attributeNames[i]);     
            resultList.add(new Attribute(attributeNames[i],value));
        } catch (Exception e) {
            // print debug info but continue processing list
            e.printStackTrace();
        }
    }
    return(resultList);
}

public AttributeList setAttributes(AttributeList attributes) {

    // Check attributes to avoid NullPointerException later on
    if (attributes == null) {
        throw new RuntimeOperationsException(
            new IllegalArgumentException(
                "AttributeList attributes cannot be null"),
            "Cannot invoke a setter of " + dClassName);
    }
    AttributeList resultList = new AttributeList();

    // if attributeNames is empty, nothing more to do
    if (attributes.isEmpty())
        return resultList;

    // try to set each attribute and add to result list if successful
    for (Iterator i = attributes.iterator(); i.hasNext();) {
        Attribute attr = (Attribute) i.next();
        try {
            setAttribute(attr);
            String name = attr.getName();
            Object value = getAttribute(name); 
            resultList.add(new Attribute(name,value));
        } catch(Exception e) {
            // print debug info but keep processing list
            e.printStackTrace();
        }
    }
    return(resultList);
}

2.2.5 Generic Operation Invoker

A dynamic MBean must implement the invoke method so that operations in the management interface can be called. This method requires the same considerations as the generic getter and setter:

The implementation in the SimpleDynamic MBean is relatively simple due to the one operation with no parameters.


Example 2–6 Implementation of the invoke Method

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

    // Check operationName to avoid NullPointerException later on
    if (operationName == null) {
        throw new RuntimeOperationsException(
            new IllegalArgumentException(
                "Operation name cannot be null"),
            "Cannot invoke a null operation in " + dClassName);
    }

    // Call the corresponding operation for a recognized name
    if (operationName.equals("reset")){
        // this code is specific to the internal "reset" method:
        reset();     // no parameters to check
        return null; // and no return value
    } else { 
        // unrecognized operation name:
        throw new ReflectionException(
            new NoSuchMethodException(operationName), 
            "Cannot find the operation " + operationName +
                " in " + dClassName);
    }
}

// internal variable
private int         nbResets = 0;

// internal method for implementing the reset operation
public void reset() {
       AttributeChangeNotification acn =
            new AttributeChangeNotification(this,
                                            0,
                                            0,
                                            "NbChanges reset",
                                            "NbChanges",
                                            "Integer",
                                            new Integer(nbChanges),
                                            new Integer(0));
        state = "initial state";
        nbChanges = 0;
        nbResets++;
        sendNotification(acn); 
}

As it is written, the SimpleDynamic MBean correctly provides a description of its management interface and implements its attributes and operations. However, this example demonstrates the need for a strict coherence between what is exposed by the getMBeanInfo method and what can be accessed through the generic getters, setters, and invoker.

A dynamic MBean whose getMBeanInfo method describes an attribute or operation that cannot be accessed is not compliant with the JMX specification and is technically not a manageable resource. Similarly, a class could make attributes or operations accessible without describing them in the returned MBeanInfo object. Since MBeans should raise an exception when an undefined attribute or operation is accessed, this would, again, technically not be a compliant resource.