7.2 Instrumentation

I l @ ve RuBoard

Instrumentation is the layer of the JMX architecture closest to the MBean resource. The purpose of instrumentation is to define the management interface for an MBean so that an operator looking at a management console can see what is happening with the resource (via the MBean's attributes), control it as necessary (through the MBean's operations), and be sent events (via the MBean's notifications) should a condition with the resource require attention.

7.2.1 Avoid Overinstrumentation

An MBean should have "just the right amount" of instrumentation. But what does that mean, exactly? Well, it depends on who will be looking at the information provided by the management interface of your MBean. Will it be a developer, or an operator at a management console? The answer is important. Because management is focused on the health of a resource, an MBean should contain the minimum amount of information necessary to ensure that the resource is functioning within normal parameters. It is up to you to define these parameters. Remember, there is overhead associated with management, so use it carefully .

At a fundamental level, management consists of three aspects:

Monitoring

The current state of what's going on in the system. It consists of an MBean's attributes.

Controlling

How to impose control on the system. It consists of an MBean's operations.

Events

Notification of something potentially harmful happening in the system. It is an MBean notification.

Let's put this in a context to which most of us can relate: an automobile. An automobile is a system that can be monitored and controlled, and can send events to its operator. Here are some examples of each of these aspects of management:

Monitoring

To monitor what is happening in cars, manufacturers provide gauges that measure speed, fuel, the rate of rotation of revolving shafts, oil pressure, battery power, and engine temperature. These enable drivers to monitor the current state of their cars to better manage their operation.

Controlling

To control cars, manufacturers provide control points such as the ignition switch, steering wheel, accelerator, brake and clutch pedals, shift lever, and headlamp switch.

Events

Car manufacturers provide warning lights to tell drivers if something is happening with the car's systems that they need to know about. For instance, warning lights indicate when the fuel level is low, the engine temperature is too high, a door is ajar, or the emergency brake is engaged. And when the ignition is turned off, cars even have a warning bell that sounds when the driver has left the lights on or the keys in the ignition switch.

Here is a simple questionnaire that can help you decide what to include in the management interface of a management-focused MBean.

7.2.1.1 Monitoring (attributes)

A resource's management attributes make up its state, which gives an operator cues regarding its health. When identifying management attributes, ask:

  • Does the value of this attribute convey information to an operator that is related to the health of the underlying MBean resource? If the answer is yes, include the attribute on the management interface.

    For example, the fuel gauge in your car tells you the health of your car's fuel supply. Without this information you would have to guess how much fuel is in the tank based on when you last refueled and how far you've driven in the meantime.

  • Does the value of this attribute convey information to the operator that, within a given operational context, one or more control points should be activated to keep the system healthy ? If yes, include the attribute on the management interface.

    For example, if you notice that your car's speedometer indicates that you are exceeding the speed limit, you need to let off of the accelerator pedal (and maybe even press the brake pedal) so that you can slow the car's speed. However, in a different part of town where the speed limit is higher (the operational context), that same speed might be just fine.

7.2.1.2 Controlling (operations)

This really boils down to one question: what actions must be performed on the resource to ensure the health of the system? Here are some examples using the automobile scenario:

  • The car must be accelerated (accelerator pedal).

  • The car must be slowed (brake pedal).

  • The car must be steered (steering wheel).

In this example, as the operator of your car, you must have these control points to keep the system (and you along with it) healthy.

However, not all MBean resources need to have management operations. It is possible that resources in your application will require no control at all. Simply accessing their attribute values and receiving their notifications is sufficient for that resource type. For other resources, it will be necessary for an operator to exert control over the resource to keep the system healthy.

7.2.1.3 Events (notifications)

An event results in an unsolicited notification to the operator that the event has occurred. To identify the events associated with a resource, look at the management attributes and the range of possible values the attribute can have. Ask:

  • What is the "normal" range (or set) of values for this attribute?

  • Is there a low or high threshold value?

An event signals some condition regarding the state of the resource; usually the management attribute currently has a value that requires attention from the operator. For example, the low fuel warning light in your car comes on whenever the fuel level drops below some predefined level set by your car's manufacturer. When the fuel level drops below the predefined value (the event), the light comes on (the notification) and is a cue for you to check the fuel gauge (the management attribute), indicating that you need to find a gas station.

Once you have identified the possible events, ask a simple question: must this event be reported to the operator for the system to remain healthy? If the answer is yes, a notification type should be created for the event.

7.2.2 Implement MBeanRegistration

Sometimes a JMX agent is an MBean and might need to use the services provided by the MBean server to interact with other MBeans that are registered in the same MBean server. To do so, the agent needs a reference to the MBean server. In addition, a mechanism is needed for MBeans to provide their own object names when the agent is generic or is provided by another vendor. Finally, MBeans that are using system resources such as connections need to release these resources when the MBeans are no longer needed by the application.

The MBeanRegistration interface provides the ability to handle all these situations. It defines four methods :

 public interface MBeanRegistration {    public ObjectName preRegister (MBeanServer server, ObjectName name)     throws java.lang.Exception;   public void postRegister (Boolean registrationDone);   public void preDeregister(  ) throws java.lang.Exception;   public void postDeregister(  ); } 

The MBean server invokes these methods on MBeans that implement this interface. You can probably guess, based on the names of the methods when they are called during the process of registering and unregistering your MBean. There are several advantages to implementing this interface:

  • The MBean gets the opportunity to store a reference to the MBean server in which it is registered. Using this reference, the MBean can now interact with other MBeans, using the MBean server as a communications bus.

  • The MBean gets a chance to provide its own object name if the ObjectName passed to preRegister( ) is null . This can be useful for generic agents that simply know how to register MBeans but are not responsible for naming them.

  • This interface can be used to allocate (in pre/postRegister( ) ) and free (in pre / postDeregister( ) ) system resources such as a JDBC connection or a socket. This is an important advantage of MBeans that is not available to straight Java classes because it is not known when a Java class will be garbage-collected once it becomes eligible.

  • The MBean can perform application-specific processing related to its lifetime as a managed resource.

7.2.3 Use Dynamic MBeans

Dynamic MBeans provide significant benefits over standard MBeans for MBean resources that are deployed in production systems. As opposed to standard MBeans, whose metadata is created and maintained by the JMX implementation, the MBean developer creates dynamic MBean metadata. Therein lies the key to the power and flexibility of dynamic MBeans. However, with this power and flexibility comes a clear trade-off: complexity. Dynamic MBeans are not trivial to implement. But don't worry, at the end of this section I will provide a few recommendations that will make life easier when implementing dynamic MBeans.

The JMX specification defines an interface called DynamicMBean and three types of dynamic MBeans that implement it:

Dynamic MBeans

The root type of dynamic MBean ”an implementation of the DynamicMBean interface and nothing more.

Open MBeans

MBeans that are described using flexible, open MBean metadata types, allowing a management application to manipulate MBeans whose attribute values are user -defined types, without having to access the bytecode for those types.

Model MBeans

MBeans that are described using special model MBean metadata classes. Each JMX implementation is required to provide a concrete model MBean implementation called RequiredModelMBean .

The significant benefits of dynamic MBeans over standard MBeans are:

  • Each MBean feature can have a description.

  • You, as the MBean developer, have control over the creation of the metadata that describes the management interface an operator sees.

  • The management interface can change dynamically.

  • There are fewer source files to maintain.

Let's look at each benefit in more detail.

7.2.3.1 Each MBean feature can have a description

An MBean feature is a constructor, a parameter, an attribute, an operation, or a notification. Metadata about standard MBean features is created by the JMX implementation; you are not given a way to specify a description. And while it is possible to create dynamic MBean feature metadata without providing descriptions, you should never do so . These descriptions can be critical for an operator to understand how to properly interpret what he is looking at in the management console (and to decide whether to page you at 2:00 A.M.!). Here are some categories you should keep in mind when describing MBean features:

Constructors

The purpose of the constructor, including any special consequences of using one constructor versus another (if more than one constructor is provided).

Parameters

The valid values, which can be a range of minimum to maximum values, or discrete values, which allow the operator to safely (i.e., without exceptions being thrown) invoke a management operation or constructor.

Attributes

The piece of management information the attribute represents, along with the "safe" values of the attribute, which can be a range of values or discrete values. This information can help the operator determine whether the attribute value is within acceptable limits and might even include a procedure for restoring the attribute value to an acceptable value.

Operations

A description of what the operation does and any consequences that might occur as a result of invoking the operation.

Notifications

A description of the notification type, [1] the conditions under which the notification will be sent, and a possible list of steps that might be taken to resolve the condition that resulted in the notification.

[1] The MBeanNotificationInfo metadata object actually contains an array of notification types, but I recommend using a single MBeanNotificationInfo object for each notification type that is emitted by the MBean.

7.2.3.2 You, as the MBean developer, have control over the creation of the metadata . . .

It is entirely up to you to make sure that the management interface advertised by the getMBeanInfo( ) method of DynamicMBean maps correctly to the underlying implementation. Because you create the metadata, MBean features can have intuitive names displayed to the operator, regardless of the names of those features in the underlying implementation. Ideally, the name of the underlying feature is the same as it appears on the management interface. However, if you have a compelling reason to make the feature name of the underlying resource different from the name you want the operator to see as part of the MBean metadata (say, the source code for the underlying resource is out of your control), you can do so.

In addition, it is unclear how standard MBeans that emit notifications have that information communicated to a management console. Because the specification provides no standard mechanism for the MBean server to determine what notifications an MBean emits (it can only check to see if the MBean class implements NotificationBroadcaster ), the JMX implementation cannot create metadata for notifications emitted by standard MBeans.

7.2.3.3 The management interface can change dynamically

The management interface of a standard MBean is static. Any changes to the management interface require a recompile/redeploy step. However, the management interface of a dynamic MBean can change at runtime. This can be a useful feature for keeping the management interface as slim as possible: only those features that are required based on the current state of the MBean are added to the management interface of the MBean. Let's look at an example.

Suppose you have an MBean that represents a queue with a management operation that allows the operator to suspend activity in the queue with a method called suspend( ) . When this operation is invoked, no more items can be added or removed from the queue. It then makes sense that there should be a management operation called resume( ) that allows activity to resume. However, this operation is not necessary when the queue is not suspended . In the implementation of suspend( ) , you can write code to dynamically add the resume( ) method to the management interface, as shown in Example 7-1.

Example 7-1. Queue MBean with suspend capability
 import javax.management.*; public class Queue implements javax.management.DynamicMBean { //  . . .    private MBeanInfo _mbi;   public MBeanInfo getMBeanInfo(  ) {     return _mbi;   } //  . . .    public synchronized void suspend(  ) {     _isSuspended = true; // Set member variable.     MBeanInfo mbi = this.getMBeanInfo(  );     MBeanOperationInfo[  ] ops = mbi.getOperations(  );     MBeanOperationInfo[  ] newOps = new MBeanOperationInfo[ops.length];     for (int aa = 0; aa < newOps.length; aa++) {       MBeanOperationInfo op = ops[aa];       // Replace "suspend" with "resume".  if (op.getName(  ).equals("suspend")) {   newOps[aa] = new MBeanOperationInfo(  "  resume",  // Name           "  Resumes activity in the queue.",  // Description  new MBeanParameterInfo[0],  // Operation signature  Void.TYPE.getName(  ),  // Return type  MBeanOperationInfo.ACTION  // Impact  );  } else {         newOps[aa] = ops[aa];       }     }     // MBeanInfo is immutable, so create new one.     MBeanInfo newMbi = new MBeanInfo(       mbi.getClassName(  ),       mbi.getDescription(  ),       mbi.getAttributes(  ),       mbi.getConstructors(  ),  newOps  ,       mbi.getNotifications(  )     );     _mbi = newMbi;     notifyAll(  );   } //  . . .  } 

Essentially, all you need to do is search the MBeanOperationInfo array for the suspend( ) method's metadata. Once you find it, simply create a new MBeanOperationInfo metadata object for the resume( ) method and place it in the array at the same location. All other operation metadata is used as is. Because MBeanInfo is immutable, you must create a new instance of it.

You also replace resume( ) with suspend( ) when resume( ) is invoked and switch out the operation metadata in a similar fashion. Example 7-2 shows this.

Example 7-2. MBean with suspend( ) replacing resume ( )
 import javax.management.*; public class Queue implements javax.management.DynamicMBean { //  . . .    private MBeanInfo _mbi;   public MBeanInfo getMBeanInfo(  ) {     return _mbi;   } //  . . .    public synchronized void resume(  ) {     _isSuspended = false; // Set member variable.     MBeanInfo mbi = this.getMBeanInfo(  );     MBeanOperationInfo[  ] ops = mbi.getOperations(  );     MBeanOperationInfo[  ] newOps = new MBeanOperationInfo[ops.length];     for (int aa = 0; aa < newOps.length; aa++) {       MBeanOperationInfo op = ops[aa];       // Replace "resume" with "suspend".       if (op.getName(  ).equals("resume")) {         newOps[aa] = new MBeanOperationInfo(           "suspend",                         // Name           "Suspends activity in the queue.", // Description           new MBeanParameterInfo[0],         // Operation signature           Void.TYPE.getName(  ),                  // Return type           MBeanOperationInfo.ACTION          // Impact         );       } else {         newOps[aa] = ops[aa];       }     }     // MBeanInfo is immutable, so create new one.     MBeanInfo newMbi = new MBeanInfo(       mbi.getClassName(  ),       mbi.getDescription(  ),       mbi.getAttributes(  ),       mbi.getConstructors(  ),       newOps,       mbi.getNotifications(  )     );     _mbi = newMbi;     notifyAll(  );   } // . . .  } 

When the queue is not suspended, the only action the operator can take is to suspend the queue because this is the only operation available. By the same token, when the queue is suspended, the only allowable action is to resume processing. This behavior is similar to how a graphical user interface application might add or remove items from a menu based on the state of the application.

A well-designed MBean should have the ability to alter its management interface to match those attributes, operations, and notifications that are appropriate to its current state.

7.2.3.4 There are fewer source files to maintain

Every time the management interface of an MBean changes, you must change two source files: the MBean interface and the class that implements it. This means that by using dynamic MBeans, you cut in half the number of MBean-related source files that have to be maintained!

7.2.4 Use a Dynamic MBean Fa §ade Pattern

Much of the code for a dynamic MBean is similar from one dynamic MBean to another. Hence, there are two primary tasks on which to focus:

  • Creating metadata to describe each feature of the MBean

  • Implementing the DynamicMBean interface

Dynamic MBean metadata classes are immutable, so the only way to set their various properties is to use a constructor. In addition, a metadata class must be created ”and code written ”for every feature on the management interface of a resource, resulting in lots of code that basically looks the same. A simplified approach to creating dynamic MBean metadata would help to reduce code clutter (and hence, readability). Here is an example of how to create a dynamic MBean metadata class for an attribute called MyAttribute of type Integer that is read-only:

 MBeanAttributeInfo attribute = new MBeanAttributeInfo(   "MyAttribute",            // Name   Integer.class.getName(  ),  // Type   "My very own attribute.", // Description   true,                     // Is Readable?   false,                    // Is Writeable?   false);                   // isIs (i.e., is boolean?) 

Of course, you could have squeezed all the parameters onto one line of code, but that would sacrifice readability (and could introduce errors). For example, JMX will allow you to create incorrect metadata for the attribute we just looked at:

 MBeanAttributeInfo attribute = new MBeanAttributeInfo(   "MyAttribute",            // Name  Long.class.getName(  ),  // Type   "My very own attribute.", // Description   true,                     // Is Readable?   false,                    // Is Writeable?   false);                   // isIs (i.e., is boolean?) 

In this example, this attribute is incorrectly described as a java.lang.Long instead of its correct type, java.lang.Integer . This mistake will not be caught until runtime, and can lead to hours of needless debugging to fix. The reverse situation could also occur if the data type of the attribute changes (as a result of, say, maintenance to the resource class), but you forget to modify the metadata to match the attribute's new type. Again, this could lead to wasted hours of debugging.

Much of the code for the second task, to implement the DynamicMBean interface, is similar from implementation to implementation. For example, the getAttribute( ) method is passed the name of an attribute and retrieves its value. Suppose MBean A has an attribute A, and MBean B has an attribute B. MBean A's implementation of getAttribute( ) might look like this:

 public class MBeanA implements DynamicMBean {   //  . . .    public Object getAttribute(String name) throws // Some exceptions {     try {  if (name.equals("A") {   return this.getA(  );  }     } catch (Exception e) {       // Handle . . .      }   } } 

while MBean B's implementation might look like this:

 public class MBeanB implements DynamicMBean {   //  . . .    public Object getAttribute(String name) throws // Some exceptions {     try {  if (name.equals("B") {   return this.getB(  );  }     } catch (Exception e) {       // Handle . . .      }   } } 

In this extremely simple example, only the highlighted lines of code are different, and all they do is figure out if the requested attribute is supported and delegate to the appropriate getter. Suppose you introduce a class called DynamicMBeanFacade , which looks similar to the class shown in Example 7-3.

Example 7-3. DynamicMBeanFacade
 public class DynamicMBeanFacade implements DynamicMBean {   public DynamicMBeanFacade(Object managedResource) {     //  . . .    }   public void addAttribute(String name, String description) {     //  . . .    }   public void removeAttribute(String name) {     //  . . .    }   public void addOperation(String name, String description) {     //  . . .    }   public void removeOperation(String name) {     //  . . .    }   public void addNotification(String type, String description) {     //  . . .    }   public void removeNotification(String type) {     //  . . .    }   // DynamicMBean interface implementation here . . .  } 

There is a method that adds and removes each feature to be defined on the management interface of the resource. For example, to add the MyAttribute attribute that we saw earlier, the agent code looks like this:

 public void someAgentMethod(  ) {   try {     MBeanServer mbs = /* Obtain somehow. */     DynamicMBeanFacade resource =      (DynamicMBeanFacade) mbs.instantiate("MyClass");  resource.addAttribute("MyAttribute", "My very own attribute");  //  . . .    } catch (Exception e) {     // Handle . . .    } } 

This pattern describes the use of a fa §ade over these two tasks and provides three key benefits:

  • Metadata creation is hidden behind the fa §ade, resulting in cleaner, more readable code.

  • Metadata creation is less error-prone for attributes and operations because the reflection API is used to introspect the managed resource class to ensure correct data types.

  • The number of lines of code to be maintained in your application is reduced because the DynamicMBean implementation is hidden behind the fa §ade class and can be reused for every MBean in your application.

Here's how it works. The agent creates an instance of DynamicMBeanFacade and invokes the appropriate methods to describe the attributes, operations, and notifications (i.e., the features) that will be part of the management interface of the managed resource. The agent is only required to pass the name and description of the feature. DynamicMBeanFacade uses Java's reflection API to introspect the managed resource and discover data type information for attributes. For operations, DynamicMBeanFacade determines the return type and any parameter names and their types. Only those attributes and operations that actually exist on the underlying resource can be added to the management interface, which presupposes some knowledge of the underlying resource on the part of the agent.

In the following sections, we will take a quick look at some of the methods of DynamicMBeanFacade and how they can be implemented for each MBean feature type, which should give you an idea of how an implementation of DynamicMBeanFacade might look. For a complete source listing of the DynamicMBeanFacade , refer to http://www.oreilly.com/catalog/javaebp for information on how to download the sample application code for this chapter.

You can employ two strategies when using this pattern:

  • The managed resource class is a subclass of the DynamicMBeanFacade class.

  • The managed resource is already a subclass of some other class and uses DynamicMBeanFacade via containment.

For the sake of brevity, the examples in this section will use the first strategy because it is slightly easier to implement. However, the implementations are remarkably similar, and I will briefly describe the differences at the end of this section.

To implement the addAttribute( ) and addOperation( ) methods of DynamicMBeanFacade , follow these steps:

  1. Search the metadata for the specified feature. If it's not found, throw an exception.

  2. Set various properties of the feature. For attributes, these include whether the attribute is readable and writeable, and its type. For operations, these properties indicate the return type and information about the operation's parameters.

  3. Create dynamic MBean metadata specific to that feature. For attributes, the metadata class is MBeanAttributeInfo , and for operations, it is MBeanOperationInfo (and MBeanParameterInfo for the operation's parameters).

  4. Add the newly created metadata to the MBeanInfo object that describes the management interface of the managed resource.

7.2.4.1 Attributes

For each attribute in the managed resource, the agent calls addAttribute( ) , passing the name and description of the attribute. Based on the JMX design patterns, DynamicMBeanFacade is expecting the name of the attribute to be preceded by get or is for readable attributes and/or set for writeable attributes. If the attribute is called MyAttribute , the implementation of addAttribute( ) will introspect the managed resource for getMyAttribute( ) and/or setMyAttribute( ) ( isMyAttribute( ) is allowed as a getter for boolean attribute types).

As you saw earlier, describing the management interface is very simple. Of course, the trade-off is that the bulk of the work falls to the implementation of DynamicMBeanFacade . Even still, it's not terribly complicated. Let's look at a very simple implementation of the addAttribute( ) method, as shown in Example 7-4.

Example 7-4. Implementation of the addAttribute( ) method
 public class DynamicMBeanFacade implements DynamicMBean { //  . . .    public synchronized void addAttribute(String name, String description)      throws DynamicMBeanFacadeException {     Class current = this.getClass(  );     boolean isReadable = false;     boolean isWriteable = false;     boolean isIs = false;     String type = null;     Method[  ] methods = current.getMethods(  );     for (int aa = 0; aa < methods.length; aa++) {       String methodName = methods[aa].getName(  );       if (methodName.equals("get" + name)) {         isReadable = true;         type = methods[aa].getReturnType(  ).getName(  );       } else if (methodName.equals("set" + name)) {         Class[  ] parmTypes = methods[aa].getParameterTypes(  );         if (parmTypes.length > 1) {           continue;         }         else {           isWriteable = true;           type = parmTypes[0].getName(  );         }       } else if (methodName.equals("is" + name)) {         type = methods[aa].getReturnType(  ).getName(  );         if (type.equals(Boolean.TYPE.getName(  ))) {           isReadable = true;           isIs = true;         } else type = null;       }     }     if (!isReadable && !isWriteable)       throw new DynamicMBeanFacadeException("Attribute not found.");  MBeanAttributeInfo attribute = new MBeanAttributeInfo(   name, type, description, isReadable, isWriteable, isIs);  // Recreate MBeanInfo using new attribute metadata.   } } 

This method searches all the public methods of the managed resource class for a getter and/or setter for the specified attribute. If the attribute is found, an MBeanAttributeInfo metadata object for the attribute is created and added to the existing attribute metadata by recreating the MBeanAttributeInfo array. The MBeanInfo object that describes the management interface is recreated (because MBeanInfo instances are immutable) using this new attribute metadata array.

When an agent wishes to remove an attribute from the management interface, it invokes the removeAttribute( ) method, passing the name of the attribute to remove. In this case, DynamicMBeanFacade does not need to introspect the managed resource class; it only needs to search the metadata that describes the attributes of the management interface and remove the metadata describing the specified attribute.

7.2.4.2 Operations

When the agent wishes to add an operation to the management interface, it invokes the addOperation( ) method, passing the name and description of the operation. The implementation of addOperation( ) must then introspect the managed resource to ensure that the operation actually exists and create the necessary metadata to describe the operation, including metadata for any parameters to the operation. The metadata is then added to the MBeanOperationInfo array that is part of the MBeanInfo object that describes the management interface, as shown in Example 7-5.

Example 7-5. DynamicMBeanFacade using metadata
 public class DynamicMBeanFacade implements DynamicMBean { //  . . .    public synchronized void addOperation(String name, String description)     throws DynamicMBeanFacadeException {     Class current = this.getClass(  );     StringreturnType = null;     Method[  ] methods = current.getMethods(  );     Method theMethod = null;     for (int aa = 0; aa < methods.length; aa++) {       if (methods[aa].getName(  ).equals(name)) {         theMethod = methods[aa];         break;  // Take only the first one encountered.       }     }     if (theMethod =  = null)       throw new DynamicMBeanFacadeException("Operation not found.");  MBeanOperationInfo newOp =   new MBeanOperationInfo(description, theMethod);  // Recreate MBeanInfo using new operation metadata.   } //  . . .  } 

The algorithm used to create the operation metadata, adding it to the operation metadata array, and recreating the MBeanInfo object should look familiar: this process is almost identical to addAttribute( ) . In Example 7-5, I have used an alternate constructor of MBeanOperationInfo that takes a java.reflect.Method object. This constructor will perform further introspection and create the necessary metadata to describe the operation's parameters.

When an agent wishes to remove an operation from the management interface, it invokes the removeOperation( ) method, passing the name of the operation to remove. In this case, DynamicMBeanFacade does not need to introspect the managed resource class; it only needs to search the metadata that describes the operations of the management interface and remove the metadata describing the specified operation.

7.2.4.3 Notifications

When the agent wishes to add a notification emitted by a managed resource to the resource's management interface, it invokes the addNotification( ) method, passing the type (i.e., name) of the notification and a description of the notification. Unlike attributes and operations, there is really no way for DynamicMBeanFacade to introspect the managed resource class to see if the notifications exist because it is entirely up to the managed resource to define and emit JMX notifications and communicate this information to the agent developer a priori through, say, documentation. Assume that the managed resource implements the NotificationBroadcaster interface, as shown in Example 7-6.

Example 7-6. Implementing the NotificationBroadcaster interface
 public class DynamicMBeanFacade implements DynamicMBean { //  . . .  public synchronized void addNotification(String type  ,  String description) throws DynamicMBeanFacadeException {   MBeanNotificationInfo notif = new MBeanNotificationInfo(   new String[  ] {type}  ,       "  javax.management.Notification"  ,  description);  MBeanNotificationInfo[  ] notifs = _mbi.getNotifications(  );     MBeanNotificationInfo[  ] newNotifs =       new MBeanNotificationInfo[notifs.length + 1];     System.arraycopy(notifs, 0, newNotifs, 0, notifs.length);  newNotifs[notifs.length] = notif;  // Recreate MBeanInfo with new notification metadata array.   } //  . . .  } 

When an agent wishes to remove a notification from the management interface, it invokes removeNotification( ) , passing the notification type.

7.2.4.4 DynamicMBean implementation

Implementing the DynamicMBean interface is actually very straightforward, as you'll see. Example 7-7 shows how to implement invoke( ) , which you can use as the implementation backbone of all the other methods of DynamicMBean .

Example 7-7. Implementing the DynamicMBean invoke( ) method
 public class DynamicMBeanFacade implements DynamicMBean { //  . . .    public Object invoke(String actionName, Object[  ] params, String[  ] signature)     throws javax.management.MBeanException, javax.management.ReflectionException {     Object ret = null; // Return code     try {       Class[  ] sig = new Class[signature.length];       for (int bb = 0; bb < signature.length; bb++)         sig[bb] = this.getClassFromClassName(signature[bb]);       Class mr = this.getClass(  );       Method method = mr.getMethod(actionName, sig);       ret = method.invoke(_managedResource, params);     } catch (Exception e) {       throw new ReflectionException(e);     }     return ret;   } //  . . .  } 

This is not the most robust implementation possible, but even this simple implementation does a lot in remarkably few lines of code. This method creates the array of Class objects that represents the method signature, uses this signature to obtain a Method object from the reflection API, and invokes the method. Any exceptions that arise will be caught and rethrown in the form of a javax.management.ReflectionException .

7.2.5 Externalize the Description of an MBean's Management Interface

The purpose of a management interface is to define the contract between an operator and the resources in your application. JMX defines patterns and an API for defining the management interfaces for your MBeans. A standard MBean's management interface is static because it is defined using a Java interface. As a result, you are required to code the instrumentation necessary to support the definition of the management interface according to JMX MBean design patterns. A dynamic MBean's management interface is described programmatically through the use of metadata classes. The necessary instrumentation to support the definition of the management interface is a combination of your implementation of DynamicMBean and the methods in the underlying MBean resource. Regardless of the type of MBean strategy you choose, the definition of the management interface is internalized, or internal to the MBean resource code.

The main issue with instrumenting and defining a resource's management interface entirely in source code is that only a Java programmer can change it. Should the management interface need to be altered at deployment time, for example, the deployer needs to have Java programming skills, or access to a Java programmer.

This is not a new concern and is one that has been addressed in a number of ways. The Enterprise JavaBeans (EJB) specification provides a mechanism to handle deployment-time modifications to the runtime behavior of Enterprise beans through the env-entry element of the ejb-jar file deployment descriptor. The bean provider offers an enterprise bean with more flexibility by externalizing certain control points specific to the deployment environment.

Externalizing the definition of the management interface of an MBean has similar benefits, allowing you to move the definition of an MBean's management interface outside your code base. You still have to write code for operations, send notifications, and sense and maintain attribute values. But the description of the management interface will be maintained outside the code base. You cannot use this best practice if your MBeans are standard MBeans because a standard MBean's management interface definition is in a Java interface. [2]

[2] OK, strictly speaking you can, but it would involve reading the external definition of the management interface, creating a Java interface on the fly, and invoking the Java compiler to compile the interface into your code base before starting your application.

The benefits of this best practice are:

  • The management interface is maintained outside the code base, allowing the management interface to be configured at deployment time.

  • Different MBean configurations can be stored, retrieved, and versioned, allowing different configurations to be deployed more quickly.

There are many ways to maintain the external representation of an MBean interface such as a text file, relational database, or LDAP. Regardless of the medium, the language used to express the definition will be XML. In the example implementation in this section we will use an XML document that resides on the local filesystem. The XML grammar used will be sufficient to describe management attributes, operations, and notifications. The XML document will be validated using a DTD.

In a generic sense, the agent must:

  • Instantiate the managed resource.

  • Read and parse the XML definition for the managed resource. Some mechanism must be defined to tie the resource with the correct tags in the XML document.

  • Create a dynamic MBean implementation for the managed resource and register it with the MBean server.

In the implementation we will look at, the agent instantiates the managed resource, reads the XML definition for the management interface definition that corresponds to the managed resource class, and creates a DynamicMBeanFacade (which we looked at earlier in this chapter) to use as the MBean for the managed resource. The DTD that defines the structure of valid XML documents for this example is defined as:

 <!ELEMENT application (mbean+)>     <!ELEMENT mbean (description?, (attribute  operation  notification)+)> <!ATTLIST mbean className CDATA #REQUIRED>     <!ELEMENT description (#PCDATA)>     <!ELEMENT attribute (#PCDATA)> <!ATTLIST attribute name CDATA #REQUIRED>     <!ELEMENT operation (#PCDATA)> <!ATTLIST operation name CDATA #REQUIRED>     <!ELEMENT notification (#PCDATA)> <!ATTLIST notification type CDATA #REQUIRED> 

That's about as simple as it gets. This DTD defines XML documents that describe one or more MBeans. For each MBean, there is an optional description for the MBean, and there must be at least one attribute, operation, or notification. Attributes and operations must have an attribute called name , and notifications must have an attribute called type . The description of an attribute, operation, or notification is provided optionally as the value of that element.

Example 7-8 shows an MBean called Queue that has the following features:

AddWaitTime (Attribute)

The total time (in milliseconds ) that all supplier threads have waited to add items to the queue because it was full.

NumberOfItemsProcessed (Attribute)

The number of items that have been removed from the queue.

QueueEmpty (Attribute)

Will be true if the queue is currently empty.

QueueFull (Attribute)

Will be true if the queue is currently full.

QueueSize (Attribute)

The maximum number of items that can coexist in the queue at any one time. Also referred to as the queue depth.

RemoveWaitTime (Attribute)

The total amount of time (in milliseconds) that all consumer threads have waited to remove items from the queue because it was empty.

Suspended (Attribute)

Will be true if activity in the queue is currently suspended.

TraceOn (Attribute)

Will be true if trace-level logging is currently enabled.

disableTracing (Operation)

Disables logging of trace-level information.

enableTracing (Operation)

Enables logging of trace-level information to provide more detailed runtime diagnostic information.

resume (Operation)

Resumes activity in the queue. All previously blocked threads are notified that they can become eligible for scheduling.

suspend (Operation)

Suspends activity in the queue. All threads are blocked until the resume operation is invoked.

sample.Queue.queueStalled (Notification)

This notification is sent when logic internal to the queue has determined that the queue is stalled in some way. This generally means that no processing has occurred for more than 10,000 milliseconds (10 seconds) and that other attributes of this class need to be investigated to resolve the problem.

Information such as this is communicated to the deployer, who does not necessarily have access to the source code. Let's suppose that all the deployer knows are the available attributes, operations, and notifications that can be defined on the management interface through the use of an XML file whose structure is dictated by the DTD we looked at earlier. Example 7-8 shows how such an XML definition of the management interface for the Queue class might look.

Example 7-8. XML definition of the management interface for Queue
 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE application SYSTEM "mbean.dtd"> <!-- Root element: application --> <application> <!--=  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =--> <!-- Queue MBean definition --> <!--=  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =--> <mbean className="jmxbp.Queue"> <description>This MBean is a managed queue to which one or more supplier threads is adding  items (objects) and one or more consumer threads is removing them.</description> <!-- ********** --> <!-- Attributes --> <!-- ********** --> <attribute name="NumberOfItemsProcessed"> The number of items that have been removed from  the queue.</attribute> <attribute name="AddWaitTime"> The total time (in milliseconds) all supplier threads have  waited to add items to the queue because it was full.</attribute> <attribute name="Suspended">Will be true if the queue is currently suspended.</attribute> <attribute name="QueueFull">Will be true if the queue is currently full.</attribute> <attribute name="QueueEmpty">Will be true if the queue is currently empty.</attribute> <attribute name="RemoveWaitTime"> The total amount of time (in milliseconds) that all  consumer threads have waited to remove items from the queue because it was empty. </attribute> <attribute name="QueueSize"> The maximum number of items that can coexist in the queue at  any one instant. Also referred to as the queue depth.</attribute> <!-- ********** --> <!-- Operations --> <!-- ********** --> <operation name="suspend"> Suspends activity in the queue. All threads are blocked until  the resume operation is invoked.</operation> <operation name="resume"> Resumes activity in the queue. All previously blocked threads  are notified that they might become eligible for scheduling.</operation> <operation name="enableTracing">Enables logging of trace-level information to provide more  detailed runtime diagnostic information.</operation> <operation name="disableTracing">Disables logging of trace-level information.</operation> <!-- ************* --> <!-- Notifications --> <!-- ************* --> <notification type="sample.Queue.queueStalled"> This notification is sent when logic  internal to the queue has determined that the queue is stalled in some way. This generally  means that no processing has occurred for more than 10000 ms (10 seconds) and that other  attributes of this class need to be investigated to resolve the problem.</notification> </mbean> </application> 

In this simple example, only the jmxbp.Queue class is defined as an MBean. When the application is started, the agent reads in the XML document describing the management interface for jmxbp.Queue and uses the DynamicMBeanFacade from earlier in the chapter. In this example, JAXP is used to process the XML file and is included in the example download for this chapter. This is a working example with some of the details elided for simplicity. The following code comments show the major steps involved:

 try {   DynamicMBeanFacade mbean = null;   String className = "jmxbp.Queue";   Queue queue = (Queue)mbs.instantiate(className);       // Set up the XML parser.   DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(  );   dbf.setValidating(true);   DocumentBuilder db = dbf.newDocumentBuilder(  );   Document config = db.parse(new File("c:homemyapp.xml"));       // Look through document for element nodes called "mbean".   NodeList nodes = n.getChildNodes(  );   for (int aa = 0; aa < nodes.getLength(  ); aa++) {     Node node = nodes.item(aa);     if (node.getNodeType(  ) =  = Node.ELEMENT_NODE &&         node.getNodeName(  ).equals("mbean")) {       NamedNodeMap atts = node.getAttributes(  );           // If "className" attribute equals className, read all MBean attributes,       // operations, and notifications.       Node att = atts.getNamedItem("className");       if (att.getNodeValue(  ).equals(className)) {         String mbeanDesc = this.getElementText(node);         mbean = new DynamicMBeanFacade(resource, mbeanDesc);         NodeList features = node.getChildNodes(  );         for (int cc = 0; cc < features.getLength(  ); cc++) {           Node feature = features.item(cc);           if (feature.getNodeType(  ) =  = Node.ELEMENT_NODE) {             NamedNodeMap atts2 = feature.getAttributes(  );                 // Add attribute, operation, or notification to the management interface,             // providing its name and description from the XML document.             if (feature.getNodeName(  ).equals("attribute")) {               Node nameNode = atts2.getNamedItem("name");               String attName = nameNode.getNodeValue(  );               String attDesc = this.getElementText(feature);               mbean.addAttribute(attName, attDesc);             } else if (feature.getNodeName(  ).equals("operation")) {               Node nameNode = atts2.getNamedItem("name");               String opName = nameNode.getNodeValue(  );               String opDesc = this.getElementText(feature);               mbean.addOperation(opName, opDesc);             } else if (feature.getNodeName(  ).equals("notification")) {               Node nameNode = atts2.getNamedItem("type");               String notifName = nameNode.getNodeValue(  );               String notifDesc = this.getElementText(feature);               mbean.addNotification(notifName, notifDesc);             }           }         } // for (int cc  . . .        } // att.getNodeValue(  ).equals(className)     }     if (mbean != null)       break;   } } catch (Exception e) {   // Handle . . .  } 

Keep in mind that this is only one possible way to implement externalization of the MBean interface. It is not necessary to define a DTD (or XML schema) to validate the XML definition, but it makes the agent code simpler to write because the XML parser can be used to enforce constraints, such as the presence of certain attributes, for example.

7.2.6 Use createMBean( ) to Create MBeans

One of the purposes of the MBean server is to act as a communications bus for interacting with MBeans. You should never manipulate an MBean through a direct object reference, for several reasons:

  • Only registered MBeans can be manipulated via the MBean server. Part of the registration process is an introspection of the MBean to ensure that it conforms to the requirements of the JMX specification. Only compliant MBeans should be considered manageable.

  • Only agents in the same process as an MBean can manipulate it through an object reference. This presupposes knowledge of the deployment of the MBean relative to the agent and should be avoided to minimize maintenance issues.

  • Java Specification Request (JSR) 160, or JMX 1.2 Remoting, specifies security requirements for JMX implementations. [3] Direct manipulations of an MBean through an object reference are not subject to JMX security checks and should be avoided.

    [3] At the time of this writing, JMX 1.2 Remoting is due for release by the end of 2002.

  • In the same vein, creating MBeans via the Java new keyword should also be avoided. The MBeanServer interface implemented by the MBean server provides a method ( instantiate( ) ) to create instances of MBeans, which return a reference to the newly created MBean object. To register an existing MBean, the agent calls registerMBean( ) . The MBeanServer interface also provides a method called createMBean( ) that combines these two steps, but never exposes a reference to the MBean object outside the JMX implementation.

I l @ ve RuBoard


The OReilly Java Authors - JavaT Enterprise Best Practices
The OReilly Java Authors - JavaT Enterprise Best Practices
ISBN: N/A
EAN: N/A
Year: 2002
Pages: 96

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net