Chapter 8. XMBean: Model MBean Implementation

In this chapter, you will build an implementation of a Model MBean. You will create an implementation that can handle the basic attribute caching, a simple file-based persistence, and can read and create its management interface from an XML file.

When using Dynamic and Model MBeans the metadata creation of the management interface is left up to the MBean developer. Because this can create quite a bit of extra work, the implementation you build will allow you to create generic builder implementations that can generate the metadata for you. The Model MBean implementation in this chapter will use an XML file to define the management interface but the implementation is easily extended to support other means for building the MBean interface.

The basic attribute method mapping and persistence implementation will allow you to have a consistent Model MBean implementation across several JMX implementations. Remember that the JMX specification does not require either functionality in the mandatory RequiredModelMBean class, so for MBeans that require them it is safer to use your own Model MBean implementation.

ModelMBean Interface Implementation

The ModelMBean interface itself extends three other interfaces defined in the JMX API—the DynamicMBean interface, PersistentMBean interface, and the ModelMBeanNotificationBroadcaster interface (see Figure 8.1)

ModelMBean class diagram.

Figure 8.1. ModelMBean class diagram.

The DynamicMBean interface declares the methods for getting and setting the MBean's attributes and invoking its operations. The PersistentMBean interface adds the store() and load() callbacks to indicate when the MBean should persist or load its state. The ModelMBeanNotificationBroadcaster interface adds methods for adding and removing notification listeners and sending notifications to interested listeners.

The ModelMBean interface itself declares two additional methods—setManagedResource() and setModelMBeanInfo(). In total, you need to implement 19 methods to create a Model MBean implementation (both sendAttributeChangeNotification() and sendNotification() methods require two overloaded versions to be implemented):

  • addAttributeChangeNotificationListener

  • addNotificationListener

  • getAttribute

  • getAttributes

  • getMBeanInfo

  • getNotificationInfo

  • invoke

  • load

  • sendAttributeChangeNotification (×2)

  • sendNotification (×2)

  • setAttribute

  • setAttributes

  • setManagedResource

  • setModelMBeanInfo

  • store

  • removeAttributeChangeNotificationListener

  • removeNotificationListener

As you can see, it is quite a bit of work to do. However, remember that the Model MBeans are used as generic, configurable MBean templates to the resource classes, so you only need to build this implementation once and then you will be able to reuse it in most MBean server environments.

The setManagedResource() implementation allows you to specify the type of the reference it passes to the Model MBean implementation. The type is a logical name representing what kind of a resource the Model MBean is representing. For example, it can be a string representing an Inter-ORB Reference (IOR), RMI Reference, an EJBHandle, JNDI name, or a regular Java object reference. Different Model MBean implementations can accept different reference types. If the Model MBean implementation does not recognize the type string supplied by the resource, it will throw an InvalidTargetObjectTypeException from the setManagedResource() method.

You will implement a Model MBean that only accepts a common Java object reference type as its resource. However, the implementation is left open for extension by verifying the resource type string in a protected isSupportedResourceType() method that the subclasses can extend to accept additional reference types.

The Model MBean implementation class you will implement is called XMBean. It implements the ModelMBean interface, the MBeanRegistration interface (covered in Chapter 6, “MBean Server”), and an XMBeanConstants interface.

The XMBeanConstants interface declares a set of useful constant variables used in the implementation, shown in Listing 8.1. You can later add new constant variables to this interface if you decide to extend the XMBean implementation.

The XMBean class declaration and fields are shown in Listing 8.2. The reference to the MBean server is stored along with the reference to the resource object instance the Model MBean is representing, the resource's type (ObjectReference), management interface metadata, and the MBean's object name. In addition, there are integer fields for storing the sequence numbers for MBean notifications, and an instance of a NotificationBroadcasterSupport class that you will use to delegate all the notification related invocations to. The attribute and operation map fields will contain a map of the management operations and attributes indexed by their names. The values of the map will contain references to the helper classes, XMBeanAttribute, and XMBeanOperation (see Figure 8.2), that handle the logic for setting and getting the attribute values and invoking operations.

Class structure of the XMBean implementation.

Figure 8.2. Class structure of the XMBean implementation.

Example 8.1. XMBeanConstants.java

package book.jmx.examples;

public interface XMBeanConstants {

    // resource types
    final static String OBJECT_REF = "ObjectReference";

    // notification types
    final static String GENERIC_MODELMBEAN_NOTIFICATION =
        "jmx.modelmbean.generic";

    // MBean descriptor field names
    final static String VALUE = "value";
    final static String GET_METHOD = "getMethod";
    final static String SET_METHOD = "setMethod";
    final static String PERSIST_POLICY = "persistPolicy";
    final static String PERSIST_PERIOD = "persistPeriod";
    final static String PERSIST_NAME = "persistName";
    final static String PERSIST_LOCATION = "persistLocation";
    final static String CURRENCY_TIME_LIMIT = "currencyTimeLimit";
    final static String LAST_UPDATED_TIME_STAMP = "lastUpdatedTimeStamp";
    final static String EXPORT = "export";

    final static String XML_DEFINITION_URL = "xml.definition.url";

    // persistence policies
    final static String ON_UPDATE = "OnUpdate";
    final static String NO_MORE_OFTEN_THAN = "NoMoreOftenThan";
    final static String NEVER = "Never";
    final static String ON_TIMER = "OnTimer";

}

Example 8.2. XMBean.java (Class Declaration and Object Fields)

package book.jmx.examples;

import java.net.*;
import java.io.*;
import java.util.*;
import java.lang.reflect.*;
import javax.management.*;
import javax.management.modelmbean.*;
import javax.management.loading.*;


public class XMBean implements ModelMBean, MBeanRegistration,
    XMBeanConstants {

  protected MBeanServer server = null;

  // managed resource, resource type, model mbean metadata
  // and object name reference
  protected Object resource           = null;
  protected String resourceType       = null;
  protected ModelMBeanInfo metadata   = null;
  protected ObjectName name           = null;

  // sequence numbers for notifications
  protected long notifierSequence      = 0;
  protected long attrNotifierSequence  = 0;

  // support for generic notification listeners
  private NotificationBroadcasterSupport notifier =
      new NotificationBroadcasterSupport();

  // maps mbean attribute and operation names to
  // corresponding XMBeanAttribute and XMBeanOperation objects
  protected Map attributeMap          = new HashMap();
  protected Map operationMap          = new HashMap();

  // Continue...

Next are the setModelMBeanInfo() and setManagedResource() method implementations. The setModelMBeanInfo() implementation stores the supplied management interface metadata to the metadata field of the class. The setManagedResource() implementation checks the supported resource types via the isSupportedResourceType() call before storing the managed resource reference and the type string (see Listing 8.3).

Example 8.3. XMBean.java (ModelMBean Interface Implementation)

// XMBean continued...

// ModelMbean interface

public void setModelMBeanInfo(ModelMBeanInfo metadata)
    throws MBeanException, RuntimeOperationsException {

  if (metadata == null) {
    throw new IllegalArgumentException(
        "The Model MBeanInfo cannot be null."
    );
  }

  this.metadata = metadata;
}

public void setManagedResource(Object ref, String resourceType)
    throws MBeanException,
           InstanceNotFoundException,
           InvalidTargetObjectTypeException {

  if (ref == null) {
    throw new IllegalArgumentException(
        "Resource reference cannot be null."
    );
  }

  // check that is a supported resource type
  if (!isSupportedResourceType(resourceType)) {
    throw new InvalidTargetObjectTypeException(
        "Unsupported resource type: " + resourceType
    );
  }

  this.resource = ref;
  this.resourceType = resourceType;
}
protected boolean isSupportedResourceType(
    String resourceType) {

  if (resourceType == null)             return false;
  if (resourceType.equals(OBJECT_REF))  return true;

  return false;
}

Now, let's look at how to build the map of management attributes and operations from the metadata.

MBeanRegistration Interface

Recall that you declared the MBeanRegistration interface to be implemented by the XMBean class. The XMBean implementation uses the MBeanRegistration interface to retrieve a reference to the MBean server, to store its object name, and to build a map of both management attributes and operations based on the metadata configured for the Model MBean.

You will use the attribute and operation maps to associate special helper classes for each attribute and operation. The helper classes will implement the logic that handles, for example, the attribute value caching and persistence callbacks to ModelMBean.store(). Both XMBeanAttribute and XMBeanOperation classes can also be extended to implement more sophisticated logic that should be executed with each invocation: security checks, logging, transaction demarcation and so on. Each map will have an attribute name or operation name and signature as its key and an XMBeanAttribute or XMBeanOperation instance as its value.

The implementation of MBeanRegistration interface is shown in Listing 8.4. The preRegister() method of the interface is the only one with an implementation in it. The operation and attribute maps are created with the private methods createAttributeMap() and createOperationMap(). The XMBeanAttribute and XMBeanOperation classes are discussed in detail in the next section.

Example 8.4. XMBean.java (MBeanRegistration Implementation)

// XMBean continued...
// MBeanRegistration interface

public ObjectName preRegister(MBeanServer server,
                              ObjectName name)
                              throws Exception {
  // store the server reference and name
  this.server = server;
  this.name   = name;

  // create attribute and operation maps
  attributeMap = createAttributeMap(metadata.getAttributes());
  operationMap = createOperationMap(metadata.getOperations());

  return name;
}

public void postRegister(Boolean registrationSuccessful) { }
public void preDeregister() throws Exception { }
public void postDeregister() { }

private Map createAttributeMap(MBeanAttributeInfo[] attributes)
    throws MBeanException  {

  Map attrMap = new HashMap();

  for (int i = 0; i < attributes.length; ++i) {
    String name = attributes[i].getName();

    ModelMBeanAttributeInfo info =
        (ModelMBeanAttributeInfo)attributes[i];

    attrMap.put(name, new XMBeanAttribute(this, info));
  }

  return attrMap;
}

private Map createOperationMap(MBeanOperationInfo[] operations)
    throws MBeanException {

  Map operMap = new HashMap();

  for (int i = 0; i < operations.length; ++i) {
    String name = operations[i].getName();
    MBeanParameterInfo[] params = operations[i].getSignature();

    // create signature
    for (int j = 0; j < params.length; ++j)
      name += params[j].getType();
    ModelMBeanOperationInfo info =
        (ModelMBeanOperationInfo)operations[i];

    operMap.put(name, new XMBeanOperation(this, info));
  }

  return operMap;
}

DynamicMBean Interface

Model MBeans are extensions of the Dynamic MBeans, so they too implement the DynamicMBean interface. As you learned in Chapter 4, “Dynamic MBeans,” the DynamicMBean interface consists of the following six methods:

  • getMBeanInfo

  • getAttribute

  • getAttributes

  • invoke

  • setAttribute

  • setAttributes

Also, recall from Chapter 5, “Model MBeans,” that the Model MBeans can be configured with the descriptor objects to include behavioral properties, such as persistence policy and setter and getter method mapping.

invoke

The implementation of the invoke() operation is quite straightforward. You look up the corresponding XMBeanOperation object based on the operation name and signature and execute invoke() on the XMBeanOperation instance. The XMBeanOperation implementation will use reflection on the managed resource object to invoke a matching method. The implementation of the XMBean invoke() method is shown in Listing 8.5. The XMBeanOperation class is shown in Listing 8.6. Notice that the invoke() implementation in Listing 8.6 does nothing but delegate the invocation to the corresponding method in the resource class. This is where you could add your security implementation and other additional logic you wish to execute before the actual operation is invoked.

If you wish to extend the implementation to invoke other types of resources, for example an EJB reference, you can also create a new implementation of the XMBeanOperation that handles the required EJB lookups and leaves the security and transaction demarcation to the EJB container. In such case however, it is probably a good idea to abstract the XMBeanOperation as a separate interface and create specific ObjectOperation and EJBOperation implementations. The XMBean implementation can then choose the right implementation to be added to the operation map based on the resource type.

Example 8.5. XMBean.java (Invoke Implementation)

// XMBean continued...


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

  String method = actionName;

  // build signature
  if (signature != null) {
    for (int i = 0; i < signature.length; ++i)
      method += signature[i];
  }

  XMBeanOperation operation =
      (XMBeanOperation)operationMap.get(method);

  if (operation == null) {
    throw new ReflectionException(
       new IllegalArgumentException("unknown operation")
    );
  }

  return operation.invoke(params);
}

Example 8.6. XMBeanOperation.java

package book.jmx.examples;

import java.lang.reflect.*;
import javax.management.*;
import javax.management.modelmbean.*;
import javax.management.loading.*;

public class XMBeanOperation implements XMBeanConstants {

  // management operation name
  protected String operationName         = null;

  // reference to the resource
  protected Object managedResource       = null;

  // corresponding method on the resource class
  protected Method operationMethod       = null;

  // signature of the resource method
  protected Class[] signature            = null;

  public XMBeanOperation(XMBean mbean,
      ModelMBeanOperationInfo operationInfo) throws MBeanException {

    try {
      this.operationName   = operationInfo.getName();
      this.managedResource = mbean.resource;
      this.signature       = createSignature(operationInfo);
      this.operationMethod = managedResource.getClass().
          getMethod(operationName, signature);
    }
    catch (NoSuchMethodException e) {
      throw new MBeanException(e,
          "Resource method " + operationName + " not found."
      );
    }
  }

  // creates class signature from MBean parameter metadata
  private Class[] createSignature(ModelMBeanOperationInfo info)
      throws MBeanException {

    Class[] paramTypes        = null;
    MBeanParameterInfo[] sign = info.getSignature();

    if (sign != null) {
      paramTypes = new Class[sign.length];

      for (int i = 0; i < paramTypes.length; ++i) {
        try {
          String type = sign[i].getType();
          paramTypes[i] = DefaultLoaderRepository.loadClass(type);
        }
        catch (ClassNotFoundException e) {
          throw new MBeanException(e,
              "Error loading parameter class " + sign[i].getType()
          );
        }
      }
    }

    return paramTypes;
  }

  // straight forward resource invocation implementation
  public Object invoke(Object[] args) throws ReflectionException {

    if (operationMethod == null) {
      throw new ReflectionException(new Exception(
          "Method " + operationName + " not found.")
       );
    }

    try {
      return operationMethod.invoke(managedResource, args);
    }
    catch (Exception e) {
      throw new ReflectionException(e);
    }
  }

}

getAttribute and getAttributes

The getAttribute() implementation is built in a similar fashion to invoke(). You delegate the implementation to the corresponding XMBeanAttribute instance that you look up from the attributeMap (see Listing 8.7).

The getValue() (Listing 8.8) method in the XMBeanAttribute implements the logic to retrieve an attribute value. It implements attribute caching based on the currencyTimeLimit and getMethod fields in the attribute's descriptor.

In the getValue() method implementation, a check is first made to ensure that the attribute is allowed to be read. If that is true, the required descriptor fields—value, getMethod, lastUpdatedTimeStamp, and currencyTimeLimit—are retrieved. If getMethod mapping exists, it is invoked if the value in the descriptor is considered stale as determined by a time stamp check. If the value in the descriptor is still valid, it is returned directly.

Example 8.7. XMBean.java (getAttribute and getAttributes Implementation)

// XMBean continued...

// DynamicMBean interface

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

  XMBeanAttribute attr =
      (XMBeanAttribute)attributeMap.get(attribute);

  if (attr == null)
    throw new AttributeNotFoundException();

  return attr.getValue();
}


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;
}

Example 8.8. XMBeanAttribute (getValue Implementation)

public Object getValue() throws MBeanException,
    ReflectionException, AttributeNotFoundException {

  ModelMBeanInfo mbeanInfo = (ModelMBeanInfo)mbean.getMBeanInfo();
  ModelMBeanAttributeInfo attrInfo = mbeanInfo.getAttribute(name);

  // see if we're allowed to read this value
  if (!attrInfo.isReadable())
    throw new AttributeNotFoundException("Attribute is not readable");


  Descriptor desc    = attrInfo.getDescriptor();
  Descriptor mmbDesc = mbeanInfo.getMBeanDescriptor();

  // retrieve the relevant values from the attribute's descriptor
  long lastUpdate    = 0;
  long currTimeLimit = -1;
  Object field       = null;
  Object result      = desc.getFieldValue(VALUE);
  Object getMethod   = desc.getFieldValue(GET_METHOD);

  // last update timestamp to check cache validity
  if((field = desc.getFieldValue(LAST_UPDATED_TIME_STAMP))!=null)
    lastUpdate  = Long.parseLong(field.toString());

  // get currencyTimeLimit from MBean descriptor first, overwrite
  // with attribute's descriptor field, if available
  if((field = mmbDesc.getFieldValue(CURRENCY_TIME_LIMIT))!=null)
    currTimeLimit = Long.parseLong(field.toString()) * 1000;
  if((field = desc.getFieldValue(CURRENCY_TIME_LIMIT))!=null)
    currTimeLimit = Long.parseLong(field.toString()) * 1000;

  // if getMethod is specified and cache is stale, invoke it
  if (getMethod != null) {
    long time = System.currentTimeMillis();

    if (time > lastUpdate + currTimeLimit) {

      result = mbean.invoke((String)getMethod, null, null);

      // update descriptor
      desc.setField(VALUE, result);
      desc.setField(LAST_UPDATED_TIME_STAMP, "" + time);
      mbeanInfo.setDescriptor(desc, "attribute");
    }
  }

  return result;
}

setAttribute and setAttributes

The setAttribute() method of the XMBean class also does a look up for the corresponding XMBeanAttributeInfo object and invokes its setValue() method. The setValue() implements the required callbacks to the MBean invoke() method if the setMethod field has been set in the attributes descriptor and to the MBean store() if the persistPolicy field is found in the attribute descriptor.

The implementation for the setAttribute() and setAttributes() methods in the XMBean class is shown in Listing 8.9.

Example 8.9. MBean.java (setAttribute and setAttributes Implementation)

// XMBean continued...

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

  String attrName  = attribute.getName();
  Object attrValue = attribute.getValue();

  XMBeanAttribute attr =
      (XMBeanAttribute)attributeMap.get(attrName);

  if (attr == null)
    throw new AttributeNotFoundException();

  try {
    attr.setValue(attrValue);
  }
  catch (InstanceNotFoundException e) {

    // may be thrown by PersistentMBean.store()

    throw new MBeanException(e);
  }
}

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;
}

In the setValue() implementation (Listing 8.10), first a check is made via the isWritable() method to see if the attribute is declared as writable. If the setMethod field has been set in the attribute descriptor, the corresponding management operation is invoked with the new attribute value as an argument. After the setter operation invocation, the value field and the lastUpdatedTimeStamp are updated. After the attribute descriptors have been updated, the attribute change notification is sent. Last, the persistence policy descriptors are checked to make the proper callbacks to the Model MBean store() method. If the persistence policy has been set to OnUpdate, the store() method is invoked with every setValue() call. If the NoMoreOftenThan policy has been set, the time difference between the previous update and the current one is calculated and checked against the persistPeriod descriptor field. If the difference is greater than the minimum time allowed between store() calls, the store() method of the ModelMBean interface is invoked.

This implementation does not contain the “OnTimer” persistence policy. You can easily add it by checking the policy descriptor in the preRegister() method of the MBeanRegistration interface and register the XMBean instance with a Timer service as a listener of periodic notifications. In the handleNotification method of the listener implementation, invoke the callback to the Model MBean's store() method.

The XMBeanAttribute class is shown completely in the Listing 8.10 with both the getValue() and setValue() implementations.

Example 8.10. XMBeanAttribute.java

package book.jmx.examples;

import javax.management.*;
import javax.management.loading.*;
import javax.management.modelmbean.*;
import java.lang.reflect.*;


public class XMBeanAttribute implements XMBeanConstants {

  // mbean reference for invoke and persistence callbacks
  protected XMBean mbean           = null;

  // reference to the resource instance
  protected Object managedResource = null;

  // name of the management attribute
  protected String name            = null;

  public XMBeanAttribute(XMBean mbean,
      ModelMBeanAttributeInfo attrInfo) throws MBeanException {

    this.mbean           = mbean;
    this.managedResource = mbean.resource;
    this.name            = attrInfo.getName();
  }


  public Object getValue() throws MBeanException,
      ReflectionException, AttributeNotFoundException {

    ModelMBeanInfo mbeanInfo = (ModelMBeanInfo)mbean.getMBeanInfo();
    ModelMBeanAttributeInfo attrInfo = mbeanInfo.getAttribute(name);

    // see if we're allowed to read this value
    if (!attrInfo.isReadable())
      throw new AttributeNotFoundException("Attribute is not readable");


    Descriptor desc    = attrInfo.getDescriptor();
    Descriptor mmbDesc = mbeanInfo.getMBeanDescriptor();

    // retrieve the relevant values from the attribute's descriptor
    long lastUpdate    = 0;
    long currTimeLimit = -1;
    Object field       = null;
    Object result      = desc.getFieldValue(VALUE);
    Object getMethod   = desc.getFieldValue(GET_METHOD);

    // last update timestamp to check cache validity
    if((field = desc.getFieldValue(LAST_UPDATED_TIME_STAMP))!=null)
      lastUpdate  = Long.parseLong(field.toString());

    // get currencyTimeLimit from MBean descriptor first, overwrite
    // with attribute's descriptor field, if available
    if((field = mmbDesc.getFieldValue(CURRENCY_TIME_LIMIT))!=null)
      currTimeLimit = Long.parseLong(field.toString()) * 1000;
    if((field = desc.getFieldValue(CURRENCY_TIME_LIMIT))!=null)
      currTimeLimit = Long.parseLong(field.toString()) * 1000;

    // if getMethod is specified and cache is stale, invoke it
    if (getMethod != null) {
      long time        = System.currentTimeMillis();

      if (time > lastUpdate + currTimeLimit) {

        result = mbean.invoke((String)getMethod, null, null);

        // update descriptor
        desc.setField(VALUE, result);
        desc.setField(LAST_UPDATED_TIME_STAMP, new Long(time));
        mbeanInfo.setDescriptor(desc, "attribute");
      }
    }

    return result;
  }

  public boolean setValue(Object value) throws MBeanException,
      ReflectionException, InstanceNotFoundException  {

    ModelMBeanInfo mbeanInfo = (ModelMBeanInfo)mbean.getMBeanInfo();
    ModelMBeanAttributeInfo attrInfo = mbeanInfo.getAttribute(name);

    // check if we're allowed to write this attribute
    if (!attrInfo.isWritable())
      throw new RuntimeException("Attribute is not writeable");

    Descriptor desc    = attrInfo.getDescriptor();
    Descriptor mmbDesc = mbeanInfo.getMBeanDescriptor();

    // retrieve the relevant descriptor values
    Object setMethod   = desc.getFieldValue(SET_METHOD);
    Object oldValue    = desc.getFieldValue(VALUE);
    Object newValue    = value;

    // if setMethod specified, invoke it
    if (setMethod != null) {
      mbean.invoke(
          (String)setMethod,
          new Object[] {  value} ,
          new String[] {  attrInfo.getType() }
      );
    }


    long persistPeriod   = 0;
    long lastUpdate      = 0;
    Object field         = null;
    String persistPolicy = mmbDesc.getFieldValue(PERSIST_POLICY).
                           toString();

    if ((field = desc.getFieldValue(PERSIST_POLICY))!=null)
      persistPolicy = field.toString();

    if ((field = mmbDesc.getFieldValue(PERSIST_PERIOD))!=null)
      persistPeriod = Long.parseLong(field.toString());

    if ((field = desc.getFieldValue(PERSIST_PERIOD))!=null)
      persistPeriod = Long.parseLong(field.toString());

    if ((field = desc.getFieldValue(LAST_UPDATED_TIME_STAMP))!=null)
      lastUpdate    = Long.parseLong(field.toString());

    // update descriptor
    desc.setField(LAST_UPDATED_TIME_STAMP,
        "" + System.currentTimeMillis());
    desc.setField(VALUE, value);
    mbeanInfo.setDescriptor(desc, "attribute");

    // send attribute change notification
    mbean.sendAttributeChangeNotification(
        new Attribute(name, oldValue),
        new Attribute(name, newValue)
    );

    // persistence
    if (persistPolicy != null) {
      if (persistPolicy.equalsIgnoreCase(ON_UPDATE)) {
        mbean.store();
        return true;
      }
      else if (persistPolicy.equalsIgnoreCase(NO_MORE_OFTEN_THAN)) {
        long interval = System.currentTimeMillis() - lastUpdate;
        if (interval > persistPeriod) {
          mbean.store();
          return true;
        }
        else
          return false;
      }
    }

    return false;
  }

}

Persistence

The XMBean class implements a basic file-based persistence. It uses object streams from java.io package to read and write the MBean metadata to a location pointed by the persistLocation and persistName descriptor fields. The implementation is shown in Listing 8.11.

Example 8.11. XMBean.java (PersistentMBean Implementation)

// XMBean continued...

// PersistentMBean interface

public void load() throws MBeanException,
    InstanceNotFoundException {

 // throw new UnsupportedOperationException();
 if (metadata == null)
   return;

 Descriptor d = metadata.getMBeanDescriptor();
 String dir   = (String)d.getFieldValue(PERSIST_LOCATION);
 String file  = (String)d.getFieldValue(PERSIST_NAME);

 if (file != null) {
   try {
     File f = new File(dir, file);
     FileInputStream fis = new FileInputStream(f);
     ObjectInputStream ois = new ObjectInputStream(fis);

     metadata = (ModelMBeanInfoSupport)ois.readObject();
   }
   catch (Exception e) {
     System.out.println("Error loading MBean state");
   }
 }
}

public void store() throws MBeanException,
    InstanceNotFoundException {

 try {
   Descriptor d = metadata.getMBeanDescriptor();
   String dir  = (String)d.getFieldValue(PERSIST_LOCATION);
   String file = (String)d.getFieldValue(PERSIST_NAME);

   File f = new File(dir, file);
   FileOutputStream fos   = new FileOutputStream(f);
   ObjectOutputStream oos = new ObjectOutputStream(fos);

   oos.writeObject(metadata);
   oos.flush();
   oos.close();
 }
 catch (IOException e) {
   throw new MBeanException(e, "Error in persisting MBean.");
 }
}

The persistence implementation serializes the whole contents of the MBeanInfo and its associated objects to the file system. This is a heavy handed persistence implementation and probably should not be used with the OnUpdate persistence policy. However, it should be quite usable for the NoMoreOftenThan or OnTimer persistence policies. For more fine-grained persistence you can create different implementations for the store() method. For a more modular approach it would be advisable to create a separate interface that abstracts the persistence implementation from the Model MBean. You could then plug in persistence implementations that make use of JDBC or JDO API, or even delegate the persistence to an Entity EJB. Similar approaches to abstracting the metadata generation with a specific interface will be shown in the sectionMetadata Generation” later in this chapter.

ModelMBeanNotificationBroadcaster Implementation

The bulk of the methods required to be implemented by a Model MBean implementation is due to the different types of notification broadcaster implementations. Luckily, their implementation is easy with the help of the NotificationBroadcasterSupport class. Most methods can be implemented by writing one or few lines of code.

Listing 8.12 shows the complete XMBean implementation. It includes the DynamicMBean implementation, PersistentMBean implementation, and ModelMBeanNotificationBroadcaster implementation. What is still missing is the metadata generation from the XML file. This will be covered in the next section, Metadata Generation.

Example 8.12. XMBean.java

package book.jmx.examples;

import java.net.*;
import java.io.*;
import java.util.*;
import java.lang.reflect.*;
import javax.management.*;
import javax.management.modelmbean.*;
import javax.management.loading.*;

public class XMBean implements ModelMBean, MBeanRegistration,
    XMBeanConstants {

  protected MBeanServer server = null;
  // sequence numbers for notifications
  protected long notifierSequence      = 0;
  protected long attrNotifierSequence  = 0;

  // support for generic notification listeners
  private NotificationBroadcasterSupport notifier =
      new NotificationBroadcasterSupport();

  // maps mbean attribute and operation names to
  // corresponding XMBeanAttribute and XMBeanOperation
  protected Map attributeMap          = new HashMap();
  protected Map operationMap          = new HashMap();

  // managed resource, resource type, model mbean metadata
  // and object name reference
  protected Object resource           = null;
  protected String resourceType       = null;
  protected ModelMBeanInfo metadata   = null;
  protected ObjectName name           = null;


  // Constructors.

  public XMBean() { }

  public XMBean(ModelMBeanInfo info) throws MBeanException {
    setModelMBeanInfo(info);
  }


  // ModelMBean interface

  public void setModelMBeanInfo(ModelMBeanInfo metadata)
      throws MBeanException, RuntimeOperationsException {

    if (metadata == null) {
      throw new IllegalArgumentException(
          "The Model MBeanInfo cannot be null."
      );
    }

    this.metadata = metadata;
  }

  public void setManagedResource(Object ref, String resourceType)
      throws MBeanException,
             InstanceNotFoundException,
             InvalidTargetObjectTypeException {

    if (ref == null) {
      throw new IllegalArgumentException(
          "Resource reference cannot be null."
      );
    }

    // check that is a supported resource type
    if (!isSupportedResourceType(resourceType)) {
      throw new InvalidTargetObjectTypeException(
          "Unsupported resource type: " + resourceType
      );
    }

    this.resource = ref;
    this.resourceType = resourceType;
  }

  protected boolean isSupportedResourceType(
      String resourceType) {

    if (resourceType == null)             return false;
    if (resourceType.equals(OBJECT_REF))  return true;

    return false;
  }


  // ModelMBeanNotificationBroadcaster interface

  public void addNotificationListener(
      NotificationListener l, NotificationFilter filter,
      Object hback) {

    notifier.addNotificationListener(l, filter, hback);
  }

  public void removeNotificationListener(
      NotificationListener l)
      throws ListenerNotFoundException {

    notifier.removeNotificationListener(l);
  }

  public void addAttributeChangeNotificationListener(
      NotificationListener l, String attributeName,
      Object hback) throws MBeanException {

    AttributeChangeNotificationFilter filter =
        new AttributeChangeNotificationFilter();

    filter.enableAttribute(attributeName);

    notifier.addNotificationListener(l, filter,hback);
  }

  public void removeAttributeChangeNotificationListener(
      NotificationListener l, String attributeName)
      throws MBeanException, ListenerNotFoundException {

    notifier.removeNotificationListener(l);
  }

  public void sendNotification(String message)
      throws MBeanException {

    Notification notif = new Notification(
        GENERIC_MODELMBEAN_NOTIFICATION, // type
        this,                            // source
        ++notifierSequence,              // sequence number
        message                          // message
    );

    sendNotification(notif);
  }

  public void sendNotification(Notification notif)
      throws MBeanException {

    notifier.sendNotification(notif);
  }

  public void sendAttributeChangeNotification(
      AttributeChangeNotification notif)
      throws MBeanException {
    notifier.sendNotification(notif);
  }

  public void sendAttributeChangeNotification(
      Attribute oldValue, Attribute newValue)
      throws MBeanException {

    String attr = oldValue.getName();
    String type = oldValue.getClass().getName();

    AttributeChangeNotification notif =
        new AttributeChangeNotification(
            this,                          // source
            ++attrNotifierSequence,        // seq. #
            System.currentTimeMillis(),    // time stamp
            "" + attr + " changed from "   // message
            + oldValue + " to " + newValue,
            attr, type,                    // name & type
            oldValue, newValue             // values
        );

    notifier.sendNotification(notif);
  }

  public MBeanNotificationInfo[] getNotificationInfo() {

    int size = metadata.getNotifications().length;
    MBeanNotificationInfo[] notifInfo = metadata.getNotifications();

    MBeanNotificationInfo[] modelInfo =
        new MBeanNotificationInfo[size + 2];

    for (int i = 0; i < size ;++i)
      modelInfo[i] = notifInfo[i];

    Descriptor descr1 = new DescriptorSupport();
    descr1.setField("name", "generic");
    descr1.setField("descriptorType", "notification");
    descr1.setField("severity", "5");

    ModelMBeanNotificationInfo generic = new ModelMBeanNotificationInfo(
        new String[] {  GENERIC_MODELMBEAN_NOTIFICATION } ,
        "generic",
        "A generic Model MBean notification.",
        descr1
    );

    Descriptor descr2 = new DescriptorSupport();
    descr2.setField("name", AttributeChangeNotification.class.getName());
    descr2.setField("descriptorType", "notification");

    ModelMBeanNotificationInfo attrChange = new ModelMBeanNotificationInfo(
        new String[] {  AttributeChangeNotification.ATTRIBUTE_CHANGE } ,
        AttributeChangeNotification.class.getName(),
        "Notifies a change in attribute's value.",
        descr2
    );

    modelInfo[size-2] = generic;
    modelInfo[size-1] = attrChange;

    return modelInfo;
  }


  // PersistentMBean interface

  public void load() throws MBeanException,
      InstanceNotFoundException {

    throw new UnsupportedOperationException();
  }

  public void store() throws MBeanException,
      InstanceNotFoundException {

   try {
     Descriptor d = metadata.getMBeanDescriptor();
     String dir  = (String)d.getFieldValue(PERSIST_LOCATION);
     String file = (String)d.getFieldValue(PERSIST_NAME);

     File f = new File(dir, file);
     FileOutputStream fos   = new FileOutputStream(f);
     ObjectOutputStream oos = new ObjectOutputStream(fos);

     oos.writeObject(metadata);
   }
   catch (IOException e) {
     throw new MBeanException(e, "Error in persisting MBean.");
   }
    //throw new UnsupportedOperationException();
  }


  // MBeanRegistration interface

  public ObjectName preRegister(MBeanServer server,
                                ObjectName name)
                                throws Exception {

    // store the server reference
    this.server = server;
    this.name    = name;

    // create attribute and operation maps
    attributeMap = createAttributeMap(metadata.getAttributes());
    operationMap = createOperationMap(metadata.getOperations());

    return name;
  }

  private Map createAttributeMap(MBeanAttributeInfo[] attributes)
      throws MBeanException  {

    Map attrMap = new HashMap();

    for (int i = 0; i < attributes.length; ++i) {
      String name = attributes[i].getName();

      ModelMBeanAttributeInfo info =
          (ModelMBeanAttributeInfo)attributes[i];

      attrMap.put(name, new XMBeanAttribute(this, info));
    }

    return attrMap;
  }

  private Map createOperationMap(MBeanOperationInfo[] operations)
      throws MBeanException {

    Map operMap = new HashMap();

    for (int i = 0; i < operations.length; ++i) {
      String name = operations[i].getName();
      MBeanParameterInfo[] params = operations[i].getSignature();

      for (int j = 0; j < params.length; ++j)
        name += params[j].getType();

      ModelMBeanOperationInfo info =
          (ModelMBeanOperationInfo)operations[i];

      XMBeanOperation operation = new XMBeanOperation(this, info);
      operMap.put(name, operation);

    }

    return operMap;
  }


  public void postRegister(Boolean registrationSuccessful) { }
  public void preDeregister() throws Exception { }
  public void postDeregister() { }


  // DynamicMBean interface

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

    XMBeanAttribute attr =
        (XMBeanAttribute)attributeMap.get(attribute);

    if (attr == null)
      throw new AttributeNotFoundException();

    return attr.getValue();
  }


  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;
  }


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

    String attrName  = attribute.getName();
    Object attrValue = attribute.getValue();

    XMBeanAttribute attr =
        (XMBeanAttribute)attributeMap.get(attrName);

    if (attr == null)
      throw new AttributeNotFoundException();

    try {
      attr.setValue(attrValue);
    }
    catch (InstanceNotFoundException e) {

      // may be thrown by PersistentMBean.store()

      throw new MBeanException(e);
    }
  }

  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;
  }


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

    String method = actionName;

    if (signature != null) {
      for (int i = 0; i < signature.length; ++i)
        method += signature[i];
    }

    XMBeanOperation operation =
        (XMBeanOperation)operationMap.get(method);

    if (operation == null) {
      throw new ReflectionException(
         new IllegalArgumentException("unknown operation")
      );
    }

    return operation.invoke(params);
  }

  public MBeanInfo getMBeanInfo() {
    return (MBeanInfo)metadata;
  }

}

At this point you can try and compile the Java files:

XMBean.java, XMBeanAttribute.java, XMBeanOperation.java and XMBeanConstants.java.

C: Examples> javac -d . -classpath .;jmx-1_0_1-ri_bin jmx lib jmxri.jar XMBean.java
XMBean.java XMBeanAttribute.java XMBeanOperation.java XMBeanConstants.java

The end result should be a working Model MBean implementation. However, the most interesting part is still left to implement. In the next section you will modify the classes to include an automated management interface creation from an external XML file.

Metadata Generation

The example implementation in this section will create the management interface from an XML file. You will define a document type definition (DTD) for the XML document instances and see the required code for parsing the XML.

The metadata generation will be abstracted by a MetaDataBuilder interface. You will be able to later extend the XMBean class with different implementations that can parse XML document instances conforming to different DTDs or with implementations that access a database or LDAP directory to retrieve and build the MBean's management interface, as illustrated in Figure 8.3. It is also possible to create builder implementations that use Java introspection on existing classes or interfaces to create the management interface for a Model MBean. This will add convenience to defining the management interface similar to Standard MBeans.

MetaDataBuilder interface abstracts the generation of metadata object instances.

Figure 8.3. MetaDataBuilder interface abstracts the generation of metadata object instances.

XML Document Instances and DTD

The XML document instances will contain all the basic elements of the MBean management interface metadata that you have seen so far. You will be able to declare the MBean's management operations, attributes, notifications and constructors using the XML format. In addition, you can declare the behavioral features, persistence and caching, that the XMBean implementation supports via the Model MBean descriptors.

The root element for the XML document instance will be an <mbean> tag. The <mbean> tag will contain a collection of MBeanconstructors, operations, attributes, and notifications. For example, a simple document instance that declares a management operation start() and a read-write management attribute Port could be written as shown in Listing 8.13.

Example 8.13. Basic XMBean XML Document Instance

<!xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mbean SYSTEM "file:/C:/Examples/xmbean.dtd">

<mbean>
  <operation><name>start</name>
    <impact>ACTION</impact>
  </operation>

  <attribute>
    <name>Port</name>
    <type>int</type>
    <access>read-write</access>
  </attribute>
</mbean>

The <operation> element assumes a void return type if nothing is declared explicitly. It also contains a nested element <impact> that declares the impact of the operation: ACTION, INFO or ACTION_INFO. The <attribute> element defines the runtime type of the management attribute and its access (read-only, write-only, read-write) using nested elements <type> and <access>, respectively. A more complete example of the XML document instance is shown in Listing 8.14. It shows the management interface of the User resource introduced in Part I of the book.

Example 8.14. User.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mbean SYSTEM "file:/C:/Examples/xmbean.dtd">

<mbean>
  <constructor>
    <name>Default Constructor</name>
  </constructor>

  <attribute>
    <name>ID</name>
    <type>java.lang.String</type>
    <access>read-only</access>
  </attribute>

  <attribute>
    <name>Name</name>
    <type>java.lang.String</type>
    <access>read-write</access>
  </attribute>

  <attribute>
    <name>Address</name>
    <type>java.lang.String</type>
    <access>read-write</access>
  </attribute>

  <attribute>
    <name>PhoneNumbers</name>
    <type>[Ljava.lang.String;</type>
    <access>read-write</access>
  </attribute>

  <attribute>
    <name>Password</name>
    <type>java.lang.String</type>
    <access>write-only</access>
  </attribute>

  <operation>
    <name>printInfo</name>
    <return-type>java.lang.String</return-type>
    <impact>INFO</impact>
  </operation>

  <operation>
    <name>addPhoneNumber</name>
    <parameter>
      <name>number</name>
      <type>java.lang.String</type>
    </parameter>
    <return-type>void</return-type>
    <impact>ACTION</impact>
  </operation>

  <operation>
    <name>removePhoneNumber</name>
    <parameter>
      <name>index</name>
      <type>int</type>
    </parameter>
    <return-type>void</return-type>
    <impact>ACTION</impact>
  </operation>

</mbean>

Parsing the XML Document

As was mentioned at the beginning of this section, the creation of the metadata objects for the MBean's management interface is abstracted as a MetaDataBuilder interface (see Listing 8.15). This interface declares one build() method that should return a ModelMBeanInfo instance containing the MBean's management interface description.

To load, parse, and validate the XMBean XML document, you will use the JDOM library, which offers a Java programming interface for loading and manipulating XML data. The JDOM library is licensed under an Apache-style Open Source license and can be downloaded from http://www.jdom.org for free. The version in use at the time of the writing is JDOM Beta 7. See the Appendix A for the detailed instructions on downloading and installing the library.

To load and generate the Model MBean metadata from the XML file, add the following constructor to the XMBean implementation:

public XMBean(String url, String resource) throws MBeanException {

  try {
    MetaDataBuilder builder = new XMLMetaDataBuilder(resource, url);
    setModelMBeanInfo(builder.build());
    setManagedResource(DefaultLoaderRepository.loadClass(resource)
        .newInstance(), OBJECT_REF);

    try {
      load();
    }  catch (InstanceNotFoundException e) { }
  }
  catch (ClassNotFoundException e) {
    throw new MBeanException(
        e, "Unable to load class " + resource
    );
  }
  catch (InvalidTargetObjectTypeException e) {
    throw new Error("Invalid resource type 'ObjectReference'.");
  }
  catch (JMException e) {
    throw new MBeanException(e);
  }
  catch (Exception e) {
    e.printStackTrace();
    throw new MBeanException(e);
  }
}

The constructor creates an instance of an XMLMetaDataBuilder class which implements the MetaDataBuilder interface to load and create the management interface that conforms to the DTD in Listing 8.16. You can later add new implementations of the MetaDataBuilder interface to generate the MBean metadata from different document types, for example the Common Information Model (CIM) DTD defined by the Distributed Managament Task Force (DMTF). You can also implement builders that retrieve the management interface definition from a database or use existing Java interfaces via introspection to create the MBean metadata.

The implementation of the XMLMetaDataBuilder class is shown in Listing 8.17. It parses the XML file and creates the required metadata objects for the Model MBean. It also creates the descriptors for each metadata object if they have been defined in the XML document. For example, to create an MBean that caches all of its attribute values for 5 seconds, and maps an Active management attribute to an isActive getter operation can be declared as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mbean SYSTEM "file:/C:/Examples/xmbean.dtd">

<mbean currencyTimeLimit="5">
  <attribute getMethod="isActive">
    <name>Active</name>
    <type>boolean</type>
    <access>read-only</access>
  </attribute>

  <operation>
    <name>isActive</name>
    <return-type>boolean</return-type>
    <impact>INFO</impact>
  </operation>
</mbean>

To compile the modified XMBean.java and the new MetaDataBuilder.java and XMLMetaDataBuilder.java files execute the following command:

C: Examples> javac -d . -classpath .;jmx-1_0_1-ri_bin jmx lib jmxri.jar; jdom-b7
Parsing the XML Document build jdom.jar;jdom-b7 lib xerces.jar XMBean.java MetaDataBuilder.java
Parsing the XML Document XMLMetaDataBuilder.java

Notice that the jdom.jar and xerces.jar libraries are now required from the jdom-b7 directory to compile the XMLMetaDataBuilder class.

You should also package the XMBean implementation as a separate package that you will use in the next chapters for implementing MBeans for the JMX Distributed Services level.

C: Examples> jar cvf xmbean.jar book/jmx/examples/XMBean*.class book/jmx/examples
Parsing the XML Document/*MetaDataBuilder.class

This should create a file xmbean.jar to your working directory you can later use.

Example 8.15. MetaDataBuilder.java

package book.jmx.examples;

import javax.management.*;
import javax.management.modelmbean.*;

public interface MetaDataBuilder {

  public ModelMBeanInfo build() throws JMException;

}

Example 8.16. xmbean.dtd

<!—                                                               —>
<!— DTD for externalizing the definition of the                   —>
<!— JMX management interfaces.                                    —>
<!—                                                               —>

<!—
  The <mbean> element is the root element of the document containing the
  required elements for describing the management interface of one
  MBean (constructors, attributes, operations and notifications). It
  also includes an optional description element that can be used to
  describe the purpose of the MBean and attributes for persistence
  policy and attribute caching.
—>
<!ELEMENT mbean (description?, constructor*, attribute*, operation*,
         notification*)>
<!ATTLIST mbean persistPolicy
               (Never | OnUpdate | NoMoreOftenThan | OnTimer) "Never"
                persistPeriod     NMTOKEN #IMPLIED
                persistLocation   CDATA   #IMPLIED
                persistName       CDATA   #IMPLIED
                currencyTimeLimit NMTOKEN #IMPLIED>

<!—
  The constructor element describes the constructors of an MBean
  that are exposed to the management application. The optional
  description element can be used to to describe the use of the
  constructor.
—>
<!ELEMENT constructor (description?, name, parameter*)>

<!—
  The <attribute> element describes the management attributes of an
  MBean. The <name> element contains the attribute's name and the <type>
  element contains a fully qualified class name of the attribute's
  type.

  The optional <access> element defines the access type (read-only,
  write-only, read-write) of this attribute. Valid values are:
        <access>read-only</access>
        <access>write-only</access>
        <access>read-write</access>

  If <access> element is not specified, read-write access is assumed.
—>
<!ELEMENT attribute (description?, name, type, access?)>
<!ATTLIST attribute persistPolicy     CDATA #IMPLIED
                    getMethod         CDATA #IMPLIED
                    setMethod         CDATA #IMPLIED
                    persistPeriod     NMTOKEN #IMPLIED
                    currencyTimeLimit NMTOKEN #IMPLIED >

<!—
  The <operation> element describes a management operation of an MBean.
  The <name> element contains the operation's name and the <parameter>
  elements describe the operation's signature. The <return-type> element
  must contain a fully qualified class name of the return type from
  this operation.

  If <return-type> is not specified, void is assumed.

  The impact element describes the operation's impact on the MBean's
  state when invoked. The valid values are:
      <impact>ACTION</impact>
      <impact>INFO</impact>
      <impact>ACTION_INFO</impact>

  If <impact> is not specified, ACTION_INFO is assumed.

—>
<!ELEMENT operation (description?, name, parameter*, return-type?,
         impact?)>

<!—
  The <notification> element describes a management notification. The <name>
  element contains the fully qualified name of the notification class and
  the <notification-type> element contains the dot-separated notification
  type string.
—>
<!ELEMENT notification (description?, name, notification-type+)>

<!ELEMENT parameter (description?, name, type)>

<!ELEMENT name               (#PCDATA)>
<!ELEMENT description        (#PCDATA)>
<!ELEMENT type               (#PCDATA)>
<!ELEMENT access             (#PCDATA)>
<!ELEMENT impact             (#PCDATA)>
<!ELEMENT return-type        (#PCDATA)>
<!ELEMENT notification-type  (#PCDATA)>

Example 8.17. XMLMetaDataBuilder.java

package book.jmx.examples;

import java.net.*;
import java.util.*;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanConstructorInfo;
import javax.management.JMException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.Descriptor;
import javax.management.modelmbean.*;

import org.jdom.*;
import org.jdom.input.*;

public class XMLMetaDataBuilder
    implements MetaDataBuilder, XMBeanConstants {

  private URL url          = null;
  private String className = null;


  // Constructors.

  public XMLMetaDataBuilder(String resourceClassName, URL url) {
    this.url = url;
    this.className = resourceClassName;
  }

  public XMLMetaDataBuilder(String resourceClassName, String url)
      throws MalformedURLException {

    this(resourceClassName, new URL(url));
  }


  // MetaDataBuilder implementation.

  public ModelMBeanInfo build() throws JMException {
    try {
      SAXBuilder builder = new SAXBuilder();

      builder.setValidation(true);

      Element root       = builder.build(url).getRootElement();
      List constructors  = root.getChildren("constructor");
      List operations    = root.getChildren("operation");
      List attributes    = root.getChildren("attribute");
      List notifications = root.getChildren("notifications");
      String description = root.getChildText("description");

      Attribute persistPolicy   = root.getAttribute(PERSIST_POLICY);
      Attribute persistPeriod   = root.getAttribute(PERSIST_PERIOD);
      Attribute persistLocation = root.getAttribute(PERSIST_LOCATION);
      Attribute persistName     = root.getAttribute(PERSIST_NAME);
      Attribute currTimeLimit   = root.getAttribute(CURRENCY_TIME_LIMIT);

      // create MBean descriptor
      Descriptor descr = new DescriptorSupport();
      descr.setField("name", className);
      descr.setField("descriptorType", "mbean");

      if (persistPolicy != null)
        descr.setField(PERSIST_POLICY, persistPolicy.getValue());
      if (persistPeriod != null)
        descr.setField(PERSIST_PERIOD, persistPeriod.getValue());
      if (persistLocation != null)
        descr.setField(PERSIST_LOCATION, persistLocation.getValue());
      if (persistName != null)
        descr.setField(PERSIST_NAME, persistName.getValue());
      if (currTimeLimit != null)
         descr.setField(CURRENCY_TIME_LIMIT, currTimeLimit.getValue());

      ModelMBeanInfo info = buildMBeanMetaData(
          description, constructors, operations,
          attributes, notifications, descr
      );

      return info;
    }
    catch (JDOMException e) {
      throw new MBeanException(e, "Error parsing the XML file.");
    }
  }


  // builder methods

  protected ModelMBeanInfo buildMBeanMetaData(String description,
      List constructors, List operations, List attributes,
      List notifications, Descriptor descr) {

    ModelMBeanOperationInfo[] operInfo     =
        buildOperationInfo(operations);
    ModelMBeanAttributeInfo[] attrInfo     =
        buildAttributeInfo(attributes);
    ModelMBeanConstructorInfo[] constrInfo =
        buildConstructorInfo(constructors);
    ModelMBeanNotificationInfo[] notifInfo =
        buildNotificationInfo(notifications);

    ModelMBeanInfo info = new ModelMBeanInfoSupport(
        className, description, attrInfo, constrInfo,
        operInfo, notifInfo, descr
    );

    return info;
  }

  protected ModelMBeanConstructorInfo[] buildConstructorInfo(
      List constructors) {

    Iterator it = constructors.iterator();
    List infos = new ArrayList();

    while (it.hasNext()) {
      Element constr = (Element)it.next();
      String name    = constr.getChildTextTrim("name");
      String descr   = constr.getChildTextTrim("description");
      List params    = constr.getChildren("parameter");

      MBeanParameterInfo[] paramInfo =
          buildParameterInfo(params);

      ModelMBeanConstructorInfo info      =
          new ModelMBeanConstructorInfo(name, descr, paramInfo);

      infos.add(info);
    }

    return (ModelMBeanConstructorInfo[])infos.toArray(
        new ModelMBeanConstructorInfo[0]);
  }

  protected ModelMBeanOperationInfo[] buildOperationInfo(List operations) {

    Iterator it = operations.iterator();
    List infos  = new ArrayList();

    while (it.hasNext()) {
      Element oper  = (Element)it.next();
      String name   = oper.getChildTextTrim("name");
      String descr  = oper.getChildTextTrim("description");
      String type   = oper.getChildTextTrim("return-type");
      String impact = oper.getChildTextTrim("impact");
      List params   = oper.getChildren("parameter");

      MBeanParameterInfo[] paramInfo =
          buildParameterInfo(params);

      // defaults to ACTION_INFO
      int operImpact = MBeanOperationInfo.ACTION_INFO;

      if (impact.equals("INFO"))
        operImpact = MBeanOperationInfo.INFO;
      else if (impact.equals("ACTION"))
        operImpact = MBeanOperationInfo.ACTION;
      else if (impact.equals("ACTION_INFO"))
        operImpact = MBeanOperationInfo.ACTION_INFO;

      // default return-type is void
      if (type == null)
        type = "void";

      ModelMBeanOperationInfo info = new ModelMBeanOperationInfo(
          name, descr, paramInfo, type, operImpact
      );

      infos.add(info);
    }

    return (ModelMBeanOperationInfo[])infos.toArray(
        new ModelMBeanOperationInfo[0]);
  }


  protected ModelMBeanNotificationInfo[]
      buildNotificationInfo(List notifications) {

    Iterator it = notifications.iterator();
    List infos  = new ArrayList();

    while (it.hasNext()) {
      Element notif   = (Element)it.next();
      String name     = notif.getChildTextTrim("name");
      String descr    = notif.getChildTextTrim("description");
      List notifTypes = notif.getChildren("notification-type");

      Iterator iterator = notifTypes.iterator();
      List types = new ArrayList();

      while (iterator.hasNext()) {
        Element type = (Element)iterator.next();
        types.add(type.getTextTrim());
      }

      ModelMBeanNotificationInfo info = new ModelMBeanNotificationInfo(
          (String[])types.toArray(), name, descr
      );

      infos.add(info);
    }

    return (ModelMBeanNotificationInfo[])infos.toArray(
        new ModelMBeanNotificationInfo[0]
    );
  }

  protected ModelMBeanAttributeInfo[]
      buildAttributeInfo(List attributes) {

    Iterator it = attributes.iterator();
    List infos  = new ArrayList();

    while (it.hasNext()) {
      Element attr       = (Element)it.next();
      String name        = attr.getChildTextTrim("name");
      String description = attr.getChildTextTrim("description");
      String type        = attr.getChildTextTrim("type");
      String access      = attr.getChildTextTrim("access");

      Attribute persistPolicy = attr.getAttribute(PERSIST_POLICY);
      Attribute persistPeriod = attr.getAttribute(PERSIST_PERIOD);
      Attribute setMethod     = attr.getAttribute(SET_METHOD);
      Attribute getMethod     = attr.getAttribute(GET_METHOD);
      Attribute currTimeLimit = attr.getAttribute(CURRENCY_TIME_LIMIT);

      Descriptor descr = new DescriptorSupport();
      descr.setField("name", name);
      descr.setField("descriptorType", "attribute");

      if (persistPolicy != null)
        descr.setField(PERSIST_POLICY, persistPolicy.getValue());
      if (persistPeriod != null)
        descr.setField(PERSIST_PERIOD, persistPeriod.getValue());
      if (setMethod != null)
        descr.setField(SET_METHOD, setMethod.getValue());
      if (getMethod != null)
        descr.setField(GET_METHOD, getMethod.getValue());
      if (currTimeLimit != null)
        descr.setField(CURRENCY_TIME_LIMIT, currTimeLimit.getValue());

      // defaults read-write
      boolean isReadable = true;
      boolean isWritable = true;

      if (access.equalsIgnoreCase("read-only"))
        isWritable = false;

      else if (access.equalsIgnoreCase("write-only"))
        isReadable = false;


      ModelMBeanAttributeInfo info = new ModelMBeanAttributeInfo(
          name, type, description, isReadable, isWritable, false, descr
      );

      infos.add(info);
    }

    return (ModelMBeanAttributeInfo[])infos.toArray(
        new ModelMBeanAttributeInfo[0]
    );
  }


  protected MBeanParameterInfo[] buildParameterInfo(List parameters) {

    Iterator it = parameters.iterator();
    List infos = new ArrayList();

    while (it.hasNext()) {
      Element param = (Element)it.next();
      String name   = param.getChildTextTrim("name");
      String type   = param.getChildTextTrim("type");
      String descr  = param.getChildTextTrim("description");

      MBeanParameterInfo info = new MBeanParameterInfo(name, type, descr);

      infos.add(info);
    }

    return (MBeanParameterInfo[])infos.toArray(new MBeanParameterInfo[0]);
  }

}

Summary

The JMX specification defines a Model MBean and a range of functionality that can be incorporated in the implementation. However, most of the functionality described in the specification is optional and may not be implemented by the JMX implementations. This chapter has shown you how to build your own implementation of the ModelMBean interface and has given you ideas where and how the Model MBean can be extended to suit your own needs.

You built a Model MBean that supported basic attribute caching, method mapping and persistence. In addition, you created an implementation for retrieving the management interface definition from an XML file. You should have a basic understanding of the implementation details to add more comprehensive features to this Model MBean implementation: additional sources for management interface definitions, more robust persistence implementation and improved invocation control with security, logging, transactions, and so on.

The JMX specification defines a very flexible and customizable Model MBean. You should be able to add new descriptor types and functionality relatively easily to this base implementation.

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

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