How Do Model MBeans Work?

Like all other MBean types, model MBeans must be created and registered with the MBean server, and, as with dynamic MBeans, the management interface of your resource is exposed through metadata classes. The similarities end there.

Every compliant JMX implementation must ship a class called RequiredModelMBean. This class is instantiated and registered with the MBean server, and it implements several interfaces (including DynamicMBean) that make the life of the instrumentation developer easier. But what is it that makes a model MBean tick? What makes a model MBean work and live up to its reputation (which we discussed in the previous section)? The rest of this section is devoted to answering those questions.

Model MBean Descriptors

The key difference between instrumenting a resource as a model MBean versus as a dynamic MBean is the Descriptor interface. A class implementing this interface describes certain properties of the MBean to the agent level of the JMX architecture, other MBeans, and management applications. Each descriptor contains one or more fields, which have corresponding Object values. Think of a field as a property of the MBean that gives some other party in the system (usually a management application) a little “nugget” of information about the MBean or one of its features (such as an attribute or operation). Of course, in order to exploit the full power of a descriptor, the management application must know about model MBeans. If the management application knows nothing of model MBeans, it simply treats the MBean as it would any other. Unfortunately, it is then unable to exploit the additional metadata contained in the descriptor. Example 4-1 shows the Descriptor interface.

Example 4-1. The Descriptor interface

public interface Descriptor extends java.io.Serializable {
  
  public Object getFieldValue(String fieldName)
    throws RuntimeOperationsException;
  
  public void setField(String fieldName, Object fieldValue)
    throws RuntimeOperationsException;
  
  public String[] getFields(  );
  
  public String[] getFieldNames(  );
  
  public Object[] getFieldValues(String[] fieldNames); 
  
  public void removeField(String fieldName);
  
  public void setFields(String[] fieldNames, Object[] fieldValues)
     throws RuntimeOperationsException;
  
  public Object clone(  ) throws RuntimeOperationsException;
  
  public boolean isValid(  ) throws RuntimeOperationsException;
}

As you can see from Example 4-1, the Descriptor interface revolves around the idea of a field. A field is a name/value pair in which the name is a String that contains the name of the field and the value is an Object that is the value of the field.

Fields have two uses. First, field values are used internally by the JMX implementation—for example, to determine when to retrieve the value of an attribute from the internal cache or when to invoke the getter for that attribute. The second use of Descriptor fields is to provide more information to the agent level or a management application about an MBean or one of its features. This information can then be exploited by any agent or management application that is aware of model MBeans. We will discuss these two types of fields separately, starting with those fields that are used by the JMX implementation (which, for the purposes of this discussion, is the JMX RI). Among the fields used by the JMX RI are several that are required, which will be pointed out when they are presented. None of the fields used by the agent level or management applications are required for JMX compliance, but some of those fields have constraints on the possible values they may have. These will also be pointed out when they are presented.

The fields used by the JMX RI are:

  • class

  • currencyTimeLimit

  • default

  • descriptorType (required)

  • export

  • getMethod

  • log

  • logFile

  • name (required)

  • persistPeriod

  • persistPolicy

  • role

  • setMethod

  • severity

  • value

  • visibility

All of these fields are predefined by the JMX specification and are considered to be reserved. They are discussed in detail in the sections that follow.

class

This field is not required and applies only to operations. The value of this field is the string representation of the Class object of the managed resource. For example, if an instance of the application class Queue is instrumented as a model MBean, the value of this field would be "sample.model.Queue". If the object is an Integer, the string representation of its Class object would be "java.lang.Integer". Remember, the string representation of a Class object is always fully qualified.

Example: "class=java.lang.Integer"

currencyTimeLimit

This field is not required and applies to attributes and operations. The value of this field is a String containing the number of seconds that the value field of an attribute or the lastReturnedValue field of an operation is valid. Each time an attribute’s getter is called, the value field is updated with the latest value for the attribute, and the lastUpdatedTimeStamp field is set to the current system time.

Once the number of seconds specified by currencyTimeLimit plus the value of lastUpdatedTimeStamp exceeds the current system time, the attribute’s value is considered stale. At any time before that, however, when the attribute’s value is requested, the value field is simply returned to the caller.

By the same token, when an operation is invoked, the lastReturnedValue field is updated and lastReturnedTimeStamp is set to the current system time. Once the number of seconds specified by currentTimeLimit plus the value of lastReturnedTimeStamp exceeds the current system time, the lastReturnedValue is considered stale and the operation is invoked.

This is how caching is performed in the JMX RI. It minimizes the impact of the management infrastructure on application performance for attributes and operations whose values and returned values, respectively, do not necessarily need to be accessed in real time.

If the value of this field is 0, the getter for the attribute is always called. If the value of this field is -1, the attribute’s value is never considered to be stale.

Example: "currencyTimeLimit=5"

default

This field is not required and applies only to attributes. The value of this field is a reference to an object of the same type as the attribute. For example, suppose that the attribute’s type is Float. The value of default would then be a Float object that contains the default value.

This value is returned only if no getMethod field is defined. If a getMethod is defined, this field is ignored by the RI. This field can be set only by using the setField( ) method of the Descriptor object.

descriptorType

This field is required and applies to the MBean, attributes, operations (including constructors), and notifications. The value of this field is a String that contains one of the following fixed values:

MBean

The descriptor is for an MBean.

attribute

The descriptor is for an attribute.

operation

The descriptor is for an operation or constructor, or for the getter or setter for an attribute.

notification

The descriptor is for a notification.

Any other value will result in an exception being thrown when you attempt to register the model MBean with the MBean server.

Example: "descriptorType=attribute"

export

The meaning of this field is somewhat vague, as it relates to the export policy of an MBean in a distributed environment (and the distributed services level of the JMX architecture has yet to be specified). The value of the field is the fully qualified class name of an object that is capable of making the MBean locatable—that is, an object that allows the MBean to be found in a distributed environment. If the MBean is not locatable, or if this behavior is not supported, the value of this field should be set to F.

Example: "export=F"

getMethod

This field is not required and applies only to attributes. Although it is not required, if you want to be able to access the value of an attribute, this field is a must. If this field is not provided, the MBean server tries to use the default field value or the value field value to provide a value. The value of getMethod is a String that contains the name of the method on the managed resource that acts as the getter for the attribute.

Example: "getMethod=isQueueFull"

log

This field is not required and applies only to notifications in the current RI. The value of this field is a Boolean object that contains true if notifications are to be logged or false if they are not. If you want to set this field value to a Boolean object, you must use the setField( ) method of the Descriptor object. The value of this field may also be T for true or F for false.

Example: "log=T"

logFile

This field is not required and applies only to notifications in the current RI. However, this field is required if log is set to true. If log is true but the logFile field either is not set or is set to an invalid value, no logging will be performed. The value of this field is a String that contains the full path to the log file to which notifications will be logged. If the value of logFile is not fully qualified, the log file will be written to the current directory.

Example: "logFile=/usr/home/steve/jmxlog"

name

This field is required and applies to the MBean, attributes, operations (including constructors) and notifications. The value of this field is a String that must match exactly what is passed as the name parameter to the appropriate metadata constructor. For example, suppose we have an attribute called WorkFactor. When we create the ModelMBeanAttributeInfo object, we must pass WorkFactor as the name parameter (which happens to be the first parameter) to the constructor.

Example: "name=WorkFactor"

Tip

The JMX specification states that this field’s value is case-sensitive. However, in the JMX 1.0 RI, this does not appear to be the case. When instrumenting your resources, be careful to ensure that you match the case between this field and the name parameter of the metadata object. In future releases of the RI, case-sensitivity may be in place, causing your instrumentation to break.

persistPeriod

This field is not required and applies to the MBean and attributes. The value of this field is a String containing the number of seconds that should elapse before the attribute value is written to persistent storage. The value of this field may also be a reference to an Integer object. If you want to set this field value to an Object reference, you must use the setField( ) method of the Descriptor object. This field is valid only if persistPolicy is set to OnTimer or NoMoreOftenThan; otherwise, it is ignored.

Example: "persistPeriod=10"

persistPolicy

This field is not required and applies to the MBean and attributes. The value of this field is a String that contains one of the following values:

Never

The attribute is never written to persistent storage.

OnTimer

The attribute value is persisted whenever the number of seconds specified by the persistPeriod field expires.

OnUpdate

The attribute value is persisted whenever the attribute’s value changes.

NoMoreOftenThan

The attribute value is persisted only when the minimum number of seconds that should expire, as specified by the persistPeriod field, have elapsed.

Example: "persistPolicy=OnTimer"

role

This field is not required and applies only to operations. The following values for this field are currently recognized by the JMX RI:

operation

Used for operations only

constructor

Used for constructors only

getter

Used if the operation to be invoked is the getter for an attribute

setter

Used if the operation to be invoked is the setter for an attribute

Example: "role=getter"

setMethod

This field is not required and applies only to attributes. Although it is not required, if you want to be able to modify the value of an attribute, this field is a must. The value of setMethod is a String that contains the name of the method on the managed resource that acts as the setter for the attribute.

Example: "setMethod=setQueueSize"

severity

This field is not required and applies only to notifications. The JMX specification defines seven numeric String values, ranging from 0 to 6, as shown in Table 4-1.

Table 4-1. Predefined severity values and meanings

Value

Meaning

0

Unknown or indeterminate

1

Nonrecoverable

2

Critical or failure

3

Major or severe

4

Minor, marginal, or error

5

Warning

6

Normal or informative

You may create your own values for severity, but consider those summarized in Table 4-1 to be reserved. This helps to ensure compatibility between vendors.

Example: "severity=3"

value

This field is not required and applies only to attributes. This field acts as a cache for the current value of the attribute. Each time an attribute’s value is accessed or changed, this field is updated to reflect the current value. This field can be used in conjunction with currencyTimeLimit to minimize the impact of instrumentation on application performance, by acting as a cache for get requests of the attribute’s current value. When currencyTimeLimit expires, the next get request for the attribute invokes the attribute’s getter and the value is updated. If you want to set this field value to an Object reference, you must use the setField( ) method of the Descriptor object.

Example: "value=SomeStringValue"

visibility

This field is not required and provides a built-in abstraction mechanism for MBeans. There are four predefined values, ranging from 1 to 4. At the least abstract level, 1, the MBean is nearly always visible to any management application. At the greatest abstraction level, 4, the MBean is the least visible. The concrete meanings of “nearly always visible” and “least visible” are not clear in the specification. The meaning of this field will certainly require some sort of agreement between instrumentation developers and management application developers and will most likely be firmed up in a future version (or maintenance release) of the specification.

Example: "visibility=1"

Other fields

Three additional fields are predefined by the JMX specification and are considered to be reserved: presentationString, iterable, and messageID.

The interpretation of the values of each of these fields is covered fairly well (if somewhat necessarily vaguely) in the JMX specification, and we won’t discuss them further here. Why not? Remember, one of the strengths of descriptors (and hence of model MBeans) is that they provide a richer set of metadata than is available for dynamic MBeans. The JMX specification provides general guidelines for interpreting predefined field values, but it does not constrain the model MBean instrumentation developer or the management application in terms of exactly how they are to interpret these values.

This openness of field values leads us into our next topic of discussion: user-defined field values. Are they allowed? Absolutely. The JMX specification does not prohibit the model MBean instrumentation developer, the agent level developer, or the management application developer from agreeing on a specific set of field values, as long as they do not conflict with the reserved field names we have already discussed.

So, how do you create a descriptor? As we have already seen, the Descriptor interface defines the contract between the instrumentation level and any other level of the JMX architecture. The JMX implementation must ship with at least one concrete class that implements the Descriptor interface. In the RI, this class is called DescriptorSupport . (It is a common pattern in the RI for interfaces to be implemented by classes named by adding “Support” to the name of the interface.) DescriptorSupport provides several constructors, but we will only look at a few of the more interesting ones here. Note that in the following discussion we’ll use the terms descriptor, Descriptor, and DescriptorSupport synonymously (descriptor is a generic term for the Descriptor interface or a class such as DescriptorSupport that implements it, respectively).

The easiest way to create a descriptor is to use the default constructor:

// . . .
  Descriptor desc = new DescriptorSupport(  );
// . . .

What then? The next step is to set the fields that make up the descriptor, using the setField( ) method:

// . . .
import javax.management.Descriptor;
import javax.management.modelmbean.DescriptorSupport;
// . . .
  Descriptor desc = new DescriptorSupport(  );
  desc.setField("name", "WorkFactor");
  desc.setField("descriptorType", "attribute");
  desc.setField("getMethod", "getWorkFactor");
// . . .

DescriptorSupport also provides three constructors that let you do all of this at once. One lets you pass an XML-like String object that contains the field names and values. Its signature is:

public DescriptorSupport(String inStr)
  throws MBeanException, 
         RuntimeOperationsException, 
         XMLParseException {
// . . .
}

This XML-like String must be of the format:

<descriptor>
  <field name=fieldname1 value=fieldvalue1></field>
  <field name=fieldname2 value=fieldvalue2></field>
    . . .
  <field name=fieldnameN value=fieldvalueN></field>
</descriptor>

where fieldname1 is the name of the first field, fieldvalue1 is its corresponding value, and so on. Let’s use the WorkFactor attribute described earlier to demonstrate how to use the DescriptorSupport constructor:

// . . .
import javax.management.Descriptor;
import javax.management.modelmbean.DescriptorSupport;
// . . .
  String xmlString =
    "<descriptor>" +
      "<field name=name value=WorkFactor></field>" +
      "<field name=descriptorType value=attribute></field>" +
      "<field name=getMethod value=getWorkFactor></field>" +
    "</descriptor>";
  Descriptor desc = new DescriptorSupport(xmlString);
// . . .

Warning

If you think this XML String is not well formed, you are correct; notice that what appear to be XML attributes in the field tag are not surrounded by single or double quotes. I am not certain if this is intentional—if it’s simply an oversight, it will certainly be fixed in a future version of the JMX RI. Regardless, this is how the JMX RI behaves, so it deserves mention.

If you need to set a number of fields whose values are String objects, you can create a String array and pass it to another DescriptorSupport constructor. This constructor’s signature is:

public DescriptorSupport(String[] fields) {
// . . .
}

The String objects in the array are of the format name=value, where name is the name of the field and value is its String value. Using the WorkFactor example again, here’s how to create a descriptor with this constructor:

// . . .
import javax.management.Descriptor;
import javax.management.modelmbean.DescriptorSupport;
// . . .
  String[] fields = new String[] {
    "name=WorkFactor",
    "descriptorType=attribute",
    "getMethod=getWorkFactor"
  };
  Descriptor desc = new DescriptorSupport(fields);
// . . .

This constructor parses through the String objects in the array and sets each field accordingly.

The third and final constructor we’ll look at in this section takes two arguments: a String array containing the names of the fields and an Object array containing references to the objects that represent the field values. The signature for this constructor is:

public DescriptorSupport(String[] fieldNames, Object[] fieldValues)
  throws RuntimeOperationsException {
// . . .
}

Note that the number of items in the String array must match the number of items in the Object array, or none of the fields will be set for the descriptor you are trying to create. Let’s again use the WorkFactor attribute from earlier to demonstrate how to use this constructor:

// . . .
import javax.management.Descriptor;
import javax.management.modelmbean.DescriptorSupport;
// . . .
  String[] fieldNames = new String[] {
    "name", "descriptorType", "getMethod"
  };
  Object[] fieldValues = new Object[] {
    "WorkFactor", "attribute", "getWorkFactor"
  };
  Descriptor desc = new DescriptorSupport(fieldNames, fieldValues);
// . . .

We used an attribute here to demonstrate how to create descriptor objects, but the principles are exactly the same for an MBean, operation (including a constructor), or notification. The only differences are the allowable field names.

A final note about the DescriptorSupport constructors: all of these constructors are valid ways to create DescriptorSupport objects, and none is preferable over another. However, the following two constructors will be used most often throughout the rest of this book:

  1. Default constructor, setField( ) approach

  2. String array parameter constructor

Every attempt will be made to give these two constructors equal treatment in the examples that follow. However, bear in mind that you are free to use any of the constructors (that is, after all, why they are there) as your needs dictate.

By now you should be familiar with the DescriptorSupport constructors available to you to create descriptors for your MBeans, attributes, operations, and notifications. While descriptors provide metadata to other levels of the JMX architecture, as well as to management applications, we must still provide the MBean server with metadata classes that satisfy the DynamicMBean behavior of our model MBeans. In the next section, we will look at how to create the metadata classes that are required to describe the management interface to the MBean server.

Describing the Management Interface

As with dynamic MBeans, every feature of a model MBean, including the MBean itself, must have a corresponding metadata class. Furthermore, for model MBeans, each metadata class must contain a descriptor. In this section, we will examine the metadata classes that are used to describe the management interface of your managed resource. You may notice a similarity between the layout of this section and the corresponding section in Chapter 3. Recall that a model MBean implements the DynamicMBean interface, so it should be no surprise that model MBean metadata classes resemble their dynamic MBean counterparts. In fact, as we will see, each of the model MBean metadata classes (with the exception of ModelMBeanInfo, which is an interface) extends the corresponding dynamic MBean metadata class.

There are five metadata classes of interest:

ModelMBeanAttributeInfo

Each instance of this class describes one attribute of the MBean’s management interface.

ModelMBeanContructorInfo

Each instance of this class describes one of the MBean’s public constructors and may contain one or more MBeanParameterInfo instances (MBeanParameterInfo was discussed in detail in the previous chapter).

ModelMBeanOperationInfo

Each instance of this class describes one of the operations on the MBean’s management interface and may contain one or more MBeanParameterInfo instances.

ModelMBeanNotificationInfo

Each instance of this class describes one group of notifications emitted by the MBean.

ModelMBeanInfoSupport

The top-level container of metadata. This class implements the ModelMBeanInfo interface, which is how the agent level and management applications access the metadata for a model MBean. Each MBean requires only one instance of this class to completely describe its management interface.

As mentioned earlier, each of these metadata classes (with the exception of ModelMBeanInfo) extends its dynamic MBean counterpart. These relationships are shown in Figure 4-1 in UML notation.

UML diagram showing inheritance of model MBean metadata classes from dynamic MBean metadata classes

Figure 4-1. UML diagram showing inheritance of model MBean metadata classes from dynamic MBean metadata classes

As shown in Figure 4-2, the relationships between model MBean metadata classes are similar to those between dynamic MBean metadata classes.

UML diagram showing the relationships between ModelMBeanInfoSupport and the other model MBean metadata classes

Figure 4-2. UML diagram showing the relationships between ModelMBeanInfoSupport and the other model MBean metadata classes

As with dynamic MBeans, the aggregation mechanism used in MBeanInfoSupport for each MBean feature is an array. As we saw in Figure 4-1, all of the model MBean metadata classes extend their dynamic MBean counterparts and are able to take advantage of the basic structure contained in them. We will discuss each of the model MBean classes below. MBeanParameterInfo is discussed at length in the previous chapter, so we will not cover it here.

Recall from our discussion of the DescriptorSupport class and the Descriptor interface that it does not matter whether the descriptor fields are created in a String array and passed to the DescriptorSupport constructor or created with the setField( ) method of Descriptor. Both approaches will be used in the examples throughout the rest of this chapter to demonstrate that they are equally valid.

ModelMBeanAttributeInfo

All but one of the essential properties that must be set for a model MBean attribute are the same as those that must be set for a dynamic MBean attribute, so they will not be discussed here. The lone exception is the descriptor that must be set for a model MBean attribute. Recall from the previous chapter that there are two constructors that are used to set the essential properties for an MBeanAttributeInfo instance. One of these constructors uses the Java reflection API, and the other explicitly sets all of the essential properties. These same properties (in addition to the descriptor) exist on ModelMBeanAttributeInfo, which inherits from MBeanAttributeInfo. Four constructors are available to you on ModelMBeanAttributeInfo, as shown in Example 4-2.

Example 4-2. The significant constructors of ModelMBeanAttributeInfo

// . . .
public class ModelMBeanAttributeInfo extends MBeanAttributeInfo
  implements DescriptorAccess, Cloneable {
  // . . .
  public ModelMBeanAttributeInfo(String name,
                                 String description,
                                 Method getter,
                                 Method setter) 
    throws javax.management.IntrospectionException {
    // . . .
  }
  public ModelMBeanAttributeInfo(String name,
                                 String description,
                                 Method getter,
                                 Method setter,
                                 Descriptor descriptor) 
    throws javax.management.IntrospectionException {
    // . . .
  }
  public ModelMBeanAttributeInfo(String name,
                                 String type,
                                 String description,
                                 boolean isReadable,
                                 boolean isWritable,
                                 boolean isIs) {
    // . . .
  }
  public ModelMBeanAttributeInfo(String name,
                                 String type,
                                 String description,
                                 boolean isReadable,
                                 boolean isWritable,
                                 boolean isIs,
                                 Descriptor descriptor) {
    // . . .
  }
  // . . .
}

Notice the emphasized lines in Example 4-2. The second and fourth constructors are each passed a reference to a Descriptor object. The first and second constructors are otherwise identical, as are the third and fourth. As mentioned earlier, every model MBean attribute contains a Descriptor object that provides a richer set of metadata than that of the metadata class.

In the first and third constructors in Example 4-2, a default descriptor is created by ModelMBeanAttributeInfo. The default descriptor contains the following predefined fields:

  • descriptorType

  • displayName

  • iterable

  • name

Each of these fields (with the exception of iterable) was discussed in a previous section. For two of these fields, the values are literal strings: the value for descriptorType is "attribute", and the value for iterable is "F". The values for both displayName and name are set to the name parameter that was passed to the constructor. Consider the following example, which results in a descriptor whose name and displayName fields are set to "WorkFactor":

// . . .
  ModelMBeanAttributeInfo[] attributeInfo = new ModelMBeanAttributeInfo[1];
  attributeInfo[0] = new ModelMBeanAttributeInfo(
    "WorkFactor",
    "java.lang.Integer",
    "Amount of work performed per work unit.",
    true,
    false,
    false
  );
// . . .

This example uses the third constructor from Example 4-2 (use of the first constructor requires more code). For more information on the use of the reflection API for this constructor, see Section 3.2.1.1 in the previous chapter.

Notice the second parameter to the MBeanAttributeInfo constructor in this example. Even though the attribute type for WorkFactor is the fundamental type int, we pass "java.lang.Integer" to the ModelMBeanAttributeInfo constructor. There appears to be an oversight in the JMX 1.0 RI: when the getter for an attribute whose return value is a fundamental type is invoked, the return value is wrapped in its corresponding JDK wrapper class. Thus, even though the getter for WorkFactor returns an int, the reflection API (which is what the JMX RI uses under the hood to perform the invocation) wraps the return value in a java.lang.Integer. However, the RI does not take this into account, and upon returning from the getter, the RI checks to see if the return value matches what was expected.

Say we create the metadata using the proper value (notice parameter #2):

// . . .
  attributeInfo[0] = 
    new ModelMBeanAttributeInfo(
      "WorkFactor",
      Integer.TYPE.getName(  ),
      "Amount of work performed per work unit.",
      true,
      false,
      false
    );
// . . .

The RI looks at the return value from the getter (which has been wrapped in a java.lang.Integer), compares it to what is in the metadata (which is the string representation of a fundamental int), declares the return value to be bad, and throws an exception. This is clearly not what we intended—to work around the problem, we have to specify the JDK wrapper class name when we create the metadata class.

If you would like to create your own descriptor instead of using the default descriptor, you can use the second and fourth constructors shown in Example 4-2. The second constructor uses Method objects and the Java reflection API and differs from the fourth constructor only in that regard. Use of this constructor is otherwise identical to that of the corresponding MBeanAttributeInfo constructor, which was covered in the previous chapter, and will not be presented here. The following example shows how to create a ModelMBeanAttributeInfo object using the fourth constructor for the read-only attribute WorkFactor:

// . . .
  Descriptor desc = new DescriptorSupport(  );
  desc.setField("name", "WorkFactor");
  desc.setField("descriptorType", "attribute");
  desc.setField("getMethod", "getWorkFactor");
  
  ModelMBeanAttributeInfo[] attributeInfo = new ModelMBeanAttributeInfo[1];
  attributeInfo[0] = new ModelMBeanAttributeInfo(
    "WorkFactor",
    "java.lang.Integer",
    "Amount of work performed per work unit.",
    true,
    false,
    false,
    desc
  );
// . . .

Notice that we create the Descriptor object by instantiating the DescriptorSupport class, as we discussed earlier in this chapter. We used the setField( ) method in this example, but there are many ways to set the fields of the descriptor. The use of the other DescriptorSupport constructors was discussed at length earlier in this chapter.

ModelMBeanConstructorInfo

All but one of the essential properties that must be set for a model MBean constructor are the same as those that must be set for a dynamic MBean constructor, so they will not be discussed here. The lone exception is the descriptor that must be set for a model MBean constructor. Recall from the previous chapter that there are two constructors that are used to set the essential properties for an MBeanConstructorInfo instance. One of these constructors uses the Java reflection API, and the other explicitly sets all of the essential properties. These same properties (in addition to the descriptor) exist on ModelMBeanConstructorInfo, which inherits from MBeanConstructorInfo. Four constructors are available to you on ModelMBeanConstructorInfo, as shown in Example 4-3.

Example 4-3. The significant constructors of ModelMBeanConstructorInfo

public class ModelMBeanConstructorInfo extends MBeanConstructorInfo 
  implements DescriptorAccess, Cloneable {
// . . .
  public ModelMBeanConstructorInfo(String description,
                                   Constructor constructorMethod) {
  // . . .
  }
  public ModelMBeanConstructorInfo(String description,
                                   Constructor constructorMethod,
                                   Descriptor descriptor) {
  // . . .
  }
  public ModelMBeanConstructorInfo(String name,
                                   String description,
                                   MBeanParameterInfo[] signature) {
  // . . .
  }
  public ModelMBeanConstructorInfo(String name,
                                   String description,
                                   MBeanParameterInfo[] signature,
                                   Descriptor descriptor) {
  // . . .
  }
// . . .
}

Notice the emphasized lines in Example 4-3. The second and fourth constructors are each passed a reference to a Descriptor object. The first and second constructors are otherwise identical, as are the third and fourth.

In the first and third constructors in Example 4-3, a default descriptor containing the following predefined fields is created by ModelMBeanConstructorInfo:

  • descriptorType

  • displayName

  • name

  • role

Each of these fields was discussed in a previous section. For two of these fields, the values are literal strings: the value for descriptorType is "operation", and the value for role is "constructor". The values for both displayName and name are set to the name parameter that was passed to the constructor. Consider the following example:

// . . .
  ModelMBeanConstructorInfo[] constructorInfo = new ModelMBeanConstructorInfo[1];
  constructorInfo[0] = new ModelMBeanConstructorInfo(
    "DefaultConstructor",
    "The default constructor",
    new MBeanParameterInfo[0]
  );
// . . .

This example uses the third constructor from Example 4-3. The ModelMBeanConstructorInfo object created here results in a descriptor whose name and displayName fields are set to "DefaultConstructor".

If you would like to create your own descriptor, you can use the second and fourth constructors shown in Example 4-3. The second constructor uses Constructor objects and the Java reflection API and differs from the fourth constructor only in that regard. The following example shows how to create a ModelMBeanConstructorInfo object using the fourth constructor for an MBean’s default constructor:

// . . .
  Descriptor desc = new DescriptorSupport(  );
  desc.setField("name", "DefaultConstructor");
  desc.setField("descriptorType", "operation");
  desc.setField("role", "constructor");
  desc.setField("displayName", "The Default Constructor");
  
  ModelMBeanConstructorInfo[] constructorInfo = new ModelMBeanConstructorInfo[1];
  constructorInfo[0] = new ModelMBeanConstructorInfo(
    "DefaultConstructor",
    "The default constructor",
    new MBeanParameterInfo[0],
    desc
  );

Notice that in this example the name and displayName fields do not have the same value, as is the case when a default descriptor is created for a ModelMBeanConstructorInfo object. This is one advantage of creating your own descriptor.

What if you simply want to expose all of the public constructors for your MBean? This method was covered thoroughly in the previous chapter; however, it bears repeating here, because this is by far the simplest way to create ModelMBeanConstructorInfo objects.

In the following example, we create a descriptor and pass it explicitly to the ModelMBeanConstructorInfo’s second constructor:

// . . .
  Constructor[] constructors = this.getClass().getConstructors(  );
  ModelMBeanConstructorInfo[] constructorInfo = new 
    ModelMBeanConstructorInfo[constructors.length];
  for (int aa = 0; aa < constructors.length; aa++) {
    Descriptor desc = new DescriptorSupport(
                        new String[] {
                          ("name=" + constructors[aa].getName(  )),
                          "descriptorType=operation",
                          "role=constructor"
                        };
                      );
                      constructorInfo[aa] = new ModelMBeanConstructorInfo(
                        "Constructs a Basic MBean.", // description
                        constructors[aa]             // java.lang.reflect.Constructor
                        desc
                      );
  }
// . . .

Notice that the displayName field has been omitted from this example. Recall from our discussion of the required descriptor fields that only name and descriptorType are required, so we are free to omit displayName.

ModelMBeanOperationInfo

All but one of the essential properties that must be set for a model MBean operation are exactly the same as those that must be set for a dynamic MBean operation, so they will not be discussed here. The lone exception is the descriptor that must be set for a model MBean operation. Recall from the previous chapter that there are two constructors that are used to set the essential properties for an MBeanOperationInfo instance. One of these constructors uses the Java reflection API, and the other explicitly sets all of the essential properties. These same properties (in addition to the descriptor) exist on ModelMBeanOperationInfo, which inherits from ModelMBeanOperationInfo. There are four constructors of interest on ModelMBeanOperationInfo, as shown in Example 4-4.

Example 4-4. The significant constructors of ModelMBeanOperationInfo

public class ModelMBeanOperationInfo extends MBeanOperationInfo 
  implements DescriptorAccess {
// . . .
  public ModelMBeanOperationInfo(String description,
                                 Method operationMethod) {
  // . . .
  }
  public ModelMBeanOperationInfo (String description,
                                  Method operationMethod,
                                  Descriptor descriptor) }
  // . . .
  }
  public ModelMBeanOperationInfo(String name,
                                 String description,
                                 MBeanParameterInfo[] signature, 
                                 String type,
                                 int impact) {
  // . . .
  }
  public ModelMBeanOperationInfo(String name,
                                 String description,
                                 MBeanParameterInfo[] signature, 
                                 String type,
                                 int impact,
                                 Descriptor descriptor) {
  // . . .
  }
// . . .
}

Notice the emphasized lines in Example 4-4. The second and fourth constructors are each passed a reference to a Descriptor object. The first and second constructors are otherwise identical, as are the third and fourth.

In the first and third constructors in Example 4-4, a default descriptor containing the following predefined fields is created by ModelMBeanOperationInfo:

  • descriptorType

  • displayName

  • name

  • role

Each of these fields was discussed in a previous section. For two of these fields, the values are literal strings: the value for descriptorType is "operation", and the value for role is "operation". The values for both displayName and name are set to the name parameter that was passed to the constructor. Consider the following example:

// . . .
  ModelMBeanOperationInfo[] operationInfo = new ModelMBeanOperationInfo[1];
  operationInfo[0] = new ModelMBeanOperationInfo(
    "reset",
    "Resets the state of this MBean.",
    new MBeanParameterInfo[0],
    Void.TYPE.getName(  ),
    MBeanOperationInfo.ACTION
  );
// . . .

This example uses the third constructor from Example 4-4. The ModelMBeanOperationInfo object created here results in a descriptor whose name and displayName fields are set to "reset".

If you would like to create your own descriptor, you can use the second and fourth constructors shown in Example 4-4. The second constructor uses Constructor objects and the Java reflection API and differs from the fourth constructor only in that regard. Use of this constructor is otherwise identical to that of the corresponding constructor of MBeanOperationInfo—covered in the previous chapter—and will not be presented here. The following example shows how to create a ModelMBeanOperationInfo object using the fourth constructor:

// . . .
  Descriptor desc = new DescriptorSupport(
    new String[] {
      "name=reset",
      "descriptorType=operation",
      "role=operation",
      ("class=" + this.getClass().getName(  ))
    }
  );
  
  ModelMBeanOperationInfo[] operationInfo = new ModelMBeanOperationInfo[1];
  operationInfo[0] = new ModelMBeanOperationInfo(
    "reset",
    "Resets the state of this MBean.",
    new MBeanParameterInfo[0],
    Void.TYPE.getName(  ),
    MBeanOperationInfo.ACTION,
    desc
  );

The only difference between the previous two examples is that we created our own descriptor in the second. There are several advantages to creating your own descriptors, not the least of which is improved code readability. Notice that the class field was set to this.getClass().getName( ). This information is used by RequiredModelMBean when making the actual invocation of the reset( ) method.

Warning

At the time of this writing, there is a bug in the JMX 1.0 RI when allowing ModelMBeanOperationInfo to create a default descriptor for a management operation that causes any invocation of that operation to fail. If you create a descriptor and pass it to ModelMBeanOperationInfo’s constructor, you will avoid this problem.

ModelMBeanNotificationInfo

All but one of the essential properties that must be set for a model MBean notification are exactly the same as those that must be set for a dynamic MBean notification, so they will not be discussed here. The lone exception is the descriptor that must be set for a model MBean notification. Recall from the previous chapter that there is a single constructor that is used to set the essential properties for an MBeanNotificationInfo instance. These same properties (in addition to the descriptor) exist on ModelMBeanNotificationInfo, which inherits from MBeanNotificationInfo. There are two constructors of interest on ModelMBeanNotificationInfo, as shown in Example 4-5.

Example 4-5. The significant constructors of ModelMBeanNotificationInfo

public class ModelMBeanNotificationInfo extends MBeanNotificationInfo 
  implements DescriptorAccess, Cloneable {
// . . .
  public ModelMBeanNotificationInfo(String[] notifTypes,
                                    String name,
                                    String description) {
  // . . .
  }
  public ModelMBeanNotificationInfo(String[] notifTypes,
                                    String name,
                                    String description, 
                                    Descriptor descriptor) {
  // . . .
  }
// . . .
}

Notice the emphasized line in Example 4-5. The second constructor is passed a reference to a Descriptor object, but the two constructors are otherwise identical.

In the first constructor, a default descriptor containing the following predefined fields is created by ModelMBeanNotificationInfo:

  • descriptorType

  • displayName

  • name

  • severity

Each of these fields was discussed in a previous section. For two of these fields, the values are literal strings: the value for descriptorType is "notification", and the value for severity is "5" (i.e., Warning). The values for both displayName and name are set to the name parameter that was passed to the constructor. Consider the following example, which uses the same notification types that we discussed in the previous chapter (refer to Section 3.2.1.5 for more information):

// . . .
  String[] notificationTypes = new String[] {
    "sample.Queue.stalled.queueFull",
    "sample.Queue.stalled.queueEmpty"
  };
  ModelMBeanNotificationInfo[] notificationInfo = new ModelMBeanNotificationInfo[1];
  notificationInfo[0] = new ModelMBeanNotificationInfo(
    notificationTypes,
    "StalledQueueNotifications",
    "Potential stall notifications emitted by the Queue."
  );
// . . .

In this example, the first constructor of ModelMBeanNotificationInfo is called, resulting in a default descriptor whose name and displayName fields are set to "StalledQueueNotifications".

If you would like to create your own descriptor, use the second constructor. The only difference between the two constructors is the addition of a Descriptor parameter. Creating a descriptor is very straightforward, as we have already seen. The following example shows how to use the second constructor, which allows you to pass a Descriptor:

// . . .
  String[] notificationTypes = new String[] {
    "sample.Queue.stalled.queueFull",
    "sample.Queue.stalled.queueEmpty"
  };
  ModelMBeanNotificationInfo[] notificationInfo = new ModelMBeanNotificationInfo[1];
  Descriptor desc = new DescriptorSupport(
                      new String[] {
                        "name=StalledQueueNotifications",
                        "descriptorType=notification",
                        "severity=3"
                      };
                    );
  notificationInfo[0] = new ModelMBeanNotificationInfo(
    notificationTypes,
    "StalledQueueNotifications",
    "Potential stall notifications emitted by the Queue.",
    desc
  );
// . . .

In this example, a descriptor is created for potential stall conditions of the Queue class. The severity field is set to "3", indicating that a stall condition is very serious and may require immediate operator intervention. Other than the creation of the descriptor, the previous two examples are effectively identical.

ModelMBeanInfo

All but one of the essential properties that must be set for a model MBean are the same as those that must be set for a dynamic MBean, so they will not be discussed here. The lone exception is the descriptor that must be set for a model MBean. Recall from the previous chapter that there is a single constructor that is used to set the essential properties for an MBeanInfo instance. These same properties (in addition to the descriptor) exist on ModelMBeanInfo. The difference between MBeanInfo and ModelMBeanInfo is that ModelMBeanInfo is an interface and cannot be instantiated.

There is a class in the RI called ModelMBeanInfoSupport that provides an implementation of the ModelMBeanInfo interface and constructors to create it in exactly the same fashion as the other metadata classes. These constructors are shown in Example 4-6.

Example 4-6. The significant constructors of ModelMBeanInfoSupport

public class ModelMBeanInfoSupport extends MBeanInfo 
  implements ModelMBeanInfo, java.io.Serializable {
// . . .
  public ModelMBeanInfoSupport(String className, 
                               String description,    
                               ModelMBeanAttributeInfo[] attributes,      
                               ModelMBeanConstructorInfo[] constructors,
                               ModelMBeanOperationInfo[] operations,
                               ModelMBeanNotificationInfo[] notifications) {
  // . . .
  }
  public ModelMBeanInfoSupport(String className, 
                               String description,    
                               ModelMBeanAttributeInfo[] attributes,      
                               ModelMBeanConstructorInfo[] constructors,
                               ModelMBeanOperationInfo[] operations,
                               ModelMBeanNotificationInfo[] notifications,
                               Descriptor mbeandescriptor) {
  // . . .
  }
// . . .
}

In the first constructor, a default descriptor is created by ModelMBeanInfoSupport. The default descriptor contains the predefined fields and values listed in Table 4-2.

Table 4-2. Fields and corresponding values for a model MBean default descriptor

Field

Value

descriptorType

"mbean"

displayName

"ModelMBeanInfoSupport"

export

"F"

log

"F"

name

"ModelMBeanInfoSupport"

visibility

"1"

Like the other model MBean metadata classes, the second constructor of ModelMBeanInfoSupport takes as its last parameter a Descriptor object. The following example shows how to create a ModelMBeanInfoSupport object using the second constructor. This example also shows relevant portions of the previous examples to provide you with some context.

// . . .
  ModelMBeanAttributeInfo[] attributeInfo =
    new ModelMBeanAttributeInfo[1];
  // create attribute metadata
  ModelMBeanConstructorInfo[] constructorInfo =
    new ModelMBeanConstructorInfo[1];
  // create constructor metadata
  ModelMBeanOperationInfo[] operationInfo =
    new ModelMBeanOperationInfo[1];
  // create operation metadata
  ModelMBeanNotificationInfo[] notificationInfo = 
  // create notification metadata
  ModelMBeanInfo mbeanInfo = new ModelMBeanInfoSupport(
    "ModeMBean",
    "A Model MBean",
    attributeInfo,
    constructorInfo,
    operationInfo,
    notificationInfo
  );
// . . .

As you can imagine, this example would be quite lengthy if all of the code necessary to create the ModelMBeanInfo object were shown. However, we have already discussed at length how to create the other metadata classes (with their optional descriptors), so you should be adequately prepared to create ModelMBeanInfo objects.

DescriptorAccess

DescriptorAccess is a simple interface that must be implemented by each of the metadata classes so that access to the descriptor is available. The DescriptorAccess interface is defined as:

public interface DescriptorAccess
{
  public Descriptor getDescriptor(  );
  public void setDescriptor(Descriptor inDescriptor);
}

By implementing this interface, metadata classes provide a means to access or even replace their existing descriptors. Recall from earlier in this chapter, when we discussed how to create the metadata classes, that each metadata class provides a default descriptor if none is specified when the class is instantiated. At first glance, it may appear that you must either create your own descriptor or put up with the default descriptor. However, through the DescriptorAccess interface, you can create a metadata object, access its descriptor, and modify or add fields to it. Consider the following code snippet, where we create a ModelMBeanAttributeInfo object without specifying a Descriptor on the constructor call:

ModelMBeanAttributeInfo[] attributeInfo = new ModelMBeanAttributeInfo[1];
attributeInfo[0] = new ModelMBeanAttributeInfo(
  "WorkFactor",
  "java.lang.Integer",
  "Amount of work performed per work unit.",
  true,
  false,
  false
);

As you may recall, this will result in a default descriptor with certain predefined fields. What if you want to modify or add fields to the default descriptor? Because ModelMBeanAttributeInfo implements the DescriptorAccess interface, this is straightforward. Suppose that after the previous code snippet executes, we want to modify the displayName property and add a persistPolicy property. Here’s what we would do:

// . . .
ModelMBeanAttributeInfo[] attributeInfo = new ModelMBeanAttributeInfo[1];
attributeInfo[0] = new ModelMBeanAttributeInfo(
  "WorkFactor",
  "java.lang.Integer",
  "Amount of work performed per work unit.",
  true,
  false,
  false
);
Descriptor desc = attributeInfo[0].getDescriptor(  );
desc.setField("displayName", "Work Factor");
                  desc.setField("persistPolicy=never");
// . . .

The DescriptorAccess interface gives you the option of modifying a default descriptor. If you are happy with most of the field values that are set with a default descriptor, this approach may save you a few lines of code per metadata object.

RequiredModelMBean

Every compliant JMX implementation is required to implement a model MBean called RequiredModelMBean . The managed resource that wishes itself to be instrumented using the RequiredModelMBean (or possibly even another model MBean, if the implementation provides more than the one concrete model MBean) obtains a reference to the MBean server, and then a reference to a new instance of the model MBean. Once the resource has the model MBean reference, it uses the metadata classes to configure the management interface it wants to expose. In this section, most of the information comes straight from the JMX RI.

RequiredModelMBean must implement at least three interfaces in order to be compliant. These interfaces are discussed below.

ModelMBean

This interface must be implemented by every concrete model MBean and is defined as:

public interface ModelMBean 
  extends DynamicMBean,
          PersistentMBean,
          ModelMBeanNotificationBroadcaster {
  
public void setModelMBeanInfo(ModelMBeanInfo inModelMBeanInfo) 
  throws MBeanException, RuntimeOperationsException;
  
public void setManagedResource(Object mr, String mr_type)
  throws MBeanException,
         RuntimeOperationsException, 
         InstanceNotFoundException,
         InvalidTargetObjectTypeException;
}

The model MBean metadata and the resource to be managed are set through this interface. Once the ModelMBeanInfo object has been created, setModelMBeanInfo( ) is called. This establishes the management interface of the resource to be managed, which is set through a call to setManagedResource( ) . The second argument to setManagedResource( ) is a String and must be one of the following predefined values:

  • ObjectReference

  • Handle

  • IOR

  • EJBHandle

  • RMIReference

The only value we will use in the examples in this book is ObjectReference. We will see later in this chapter how to use this interface.

DynamicMBean

We discussed the DynamicMBean interface at length in the previous chapter, so we won’t discuss it here. Note, however, that every compliant concrete model MBean (such as RequiredModelMBean) must also implement DynamicMBean.

PersistentMBean

This interface provides a persistence mechanism for every model MBean and is defined as:

public interface PersistentMBean {
  public void load(  ) throws MBeanException,
                            RuntimeOperationsException,
                            InstanceNotFoundException;
  public void store(  ) throws MBeanException,
                             RuntimeOperationsException,
                             InstanceNotFoundException;
}

Once a reference to a model MBean is obtained by the agent level, the state of the model MBean may be persisted or restored from a persistent store by invoking store( ) or load( ), respectively.

Get Java Management Extensions now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.