Dynamic MBean Inheritance Patterns

In this section, we will look at how the introspection process performed by the MBean server causes the inheritance patterns available to dynamic MBeans to differ from those available to standard MBeans.

Suppose that our application has the inheritance graph shown in Figure 3-2.

UML diagram showing a possible inheritance scenario for the sample application

Figure 3-2. UML diagram showing a possible inheritance scenario for the sample application

In this scenario, each class (with the exception of Supplier and Consumer) explicitly implements the DynamicMBean interface. Based on the information in the figure, what would you expect the management interface of, say, Controller to be? If you said that it would be the union of the management interface of Basic and Controller, you would be mistaken. You may recall from the previous chapter that using inheritance pattern #4 would allow a child class to augment its management interface with that of its parent class. However, no such inheritance patterns are available for dynamic MBeans.

What do you suppose the management interface of Supplier is? In fact, it may be a better question to ask whether Supplier is an MBean at all. The answer is yes, Supplier is indeed an MBean (in fact, a dynamic MBean), because it may inherit the dynamic MBean interface from its parent class. How is this possible? When Supplier is registered with the MBean server, the MBean server performs its introspection and looks to see whether Supplier exposes a management interface. Because it does not, the MBean server next looks to its parent class, Worker. The MBean server notices that Worker does implement an MBean interface, declares Supplier to be an MBean, and delegates all MBean functionality to Worker (where the management interface is actually implemented).

Let’s look at another example. Suppose that we have the inheritance scenario shown in Figure 3-3, again using the classes from the application.

Another possible inheritance scenario, shown in UML notation

Figure 3-3. Another possible inheritance scenario, shown in UML notation

Notice that Worker does not implement DynamicMBean. What impact do you suppose that has on the management interface exposed by Supplier and Consumer? If you said that Supplier and Consumer are dynamic MBeans whose management interface looks exactly like that of Basic, you are correct. Let’s look again at the introspection that is performed by the MBean server, using Supplier as an example. Supplier does not implement an MBean interface, so the MBean server moves up the inheritance graph to Worker and notices that it does not implement an MBean interface either. The MBean server continues up the inheritance graph to Worker’s parent class, Basic, and notices that it implements the DynamicMBean interface, so it declares Supplier a dynamic MBean and delegates all MBean functionality to Basic. The MBean server will continue walking the inheritance graph during its introspection process either until an MBean interface is found or until the top of the inheritance tree is reached without finding an MBean interface (at which point a NotCompliantMBeanException is thrown).

Warning

A class cannot implement both DynamicMBean and a standard MBean interface. Suppose that Queue had been declared as:

public class Queue extends Basic
  implements QueueMBean, DynamicMBean {
// . . .
}

When the MBean server performs introspection, it will detect that Queue is attempting to implement both a standard and a dynamic MBean interface and will throw a NotCompliantMBeanException.

As I mentioned earlier, unlike standard MBeans, whose management interfaces can be composed from the MBean interfaces they explicitly implement as well as any MBean interfaces implemented by their parent class (and their parent’s parent class, and so on), dynamic MBeans’ interfaces do not aggregate. Rather, the management interface exposed by a dynamic MBean is that of the nearest implementation of DynamicMBean as the inheritance graph is traversed during introspection. If the MBean server detects an implementation of DynamicMBean, inheritance traversal stops. In other words, while a standard MBean can use the inheritance patterns discussed in Chapter 2 to create an aggregate of the management interface of its parent class, a dynamic MBean cannot.

However, all is not lost. There are two approaches that we can take to achieve the aggregation we desire, while at the same time writing perfectly compliant dynamic MBeans: explicit superclass exposure and superclass delegation.

Explicit superclass exposure means that we will write code inside the child class’s implementation of DynamicMBean to explicitly expose attributes and operations from the parent class’s management interface. These attributes are explicitly mentioned by name in the code. This approach offers us more control over what is exposed on the child class’s management interface and allows us to selectively expose only those attributes and operations from the parent class that we deem necessary on the child class’s management interface. However, this means that we are essentially writing code in the child class that has already been written in the parent class, which results in duplicate code and a larger code base.

Superclass delegation means that we will write code in the child class to delegate the attribute set( )/get( ) and operation invoke( ) calls to the parent class through the super keyword, should the attribute or operation not be found on the child class. This approach results in a cleaner implementation, because we don’t have to know what the management interface of the superclass looks like; we only need to know that it has one. The drawback of this approach is that it forces us into a wholesale exposure of the parent class’s management interface on the child class.

Let’s look at these two approaches one at a time, starting with explicit superclass exposure.

Explicit Superclass Exposure

Remember how each attribute and operation is assigned a name? Explicit superclass exposure just means that we are going to include else if blocks in the getAttribute( ) , setAttribute( ) , and invoke( ) methods that explicitly mention the names of the attributes (in the case of getAttribute( ) and setAttribute( )) and operations (in the case of invoke( )). These three methods will then call the appropriate getter, setter, or method, respectively. Let’s look at an example from the application.

Queue inherits from Basic, and Basic implements the DynamicMBean interface for its attributes (TraceOn, DebugOn, and NumberOfResets) and its operations (enableTracing, disableTracing, enableDebugging, and disableDebugging). Recall from Example 3-3 how we created all of the metadata classes for Queue’s management interface in Queue’s constructor. If we are going to augment Queue’s management interface with attributes and operations from Basic’s management interface, we must expose those attributes and operations on the MBeanInfo instance that is returned from Queue’s getMBeanInfo( ) method. Thus, we must create the metadata classes for these attributes and operations in Queue’s constructor. Example 3-4 shows how to do this, showing enough of Example 3-3 to provide you some context.

Example 3-4. Creating metadata classes to expose attributes and operations from Queue’s parent class, Basic, through explicit superclass exposure

public class Queue extends Basic implements DynamicMBean {
// . . .
  private MBeanInfo _MBeanInfo;
  // . . .
  public Queue(int queueSize) {
    // . . .
    // Attributes
    int numberOfParentAttributes = 2;
                      MBeanAttributeInfo[] attributeInfo = 
                        new MBeanAttributeInfo[numberOfParentAttributes + 10];
    attributeInfo[0] = new MBeanAttributeInfo(
      "QueueSize", Integer.TYPE.getName(  ),
      "Maximum number of items the queue may contain at one time.",
      true, true, false);
    // . . .
    attributeInfo[9] = new MBeanAttributeInfo(
      "RemoveWaitTime", Long.TYPE.getName(  ),
      "No. milliseconds spent waiting to remove because Queue was empty.",
      true, false, false);
    attributeInfo[10] = new MBeanAttributeInfo(
                        "NumberOfResets", Integer.TYPE.getName(  ),
                        "The number of times reset(  ) has been called.",
                        true, false, false);
                      attributeInfo[11] = new MBeanAttributeInfo(
                        "TraceOn", Boolean.TYPE.getName(  ),
                        "Indicates whether or not tracing is on.",
                        true, false, true);
    // . . .
    // Constructors
    // . . .
    // Operations
    int numberOfParentOperations = 2;
                      MBeanOperationInfo[] operationInfo =
                        new MBeanOperationInfo[numberOfParentOperations+3];  
    MBeanParameterInfo[] parms = new MBeanParameterInfo[0];
    operationInfo[0] = new MBeanOperationInfo(
      "suspend", "Suspends processing of the Queue.",
      parms, Void.TYPE.getName(  ), MBeanOperationInfo.ACTION);
    operationInfo[1] = new MBeanOperationInfo(
      "resume", "Resumes processing of the Queue.",
      parms, Void.TYPE.getName(  ), MBeanOperationInfo.ACTION);
    operationInfo[2] = new MBeanOperationInfo(
      "reset", "Resets the state of this MBean.",
      parms, Void.TYPE.getName(  ), MBeanOperationInfo.ACTION);
    operationInfo[3] = new MBeanOperationInfo(
                        "enableTracing", "Enables tracing.",
                        parms, Void.TYPE.getName(  ), MBeanOperationInfo.ACTION);
                      operationInfo[4] = new MBeanOperationInfo(
                        "disableTracing", "Disables tracing.",
                        parms, Void.TYPE.getName(  ), MBeanOperationInfo.ACTION);
    // . . .
    // Notifications
    // no notifications for this MBean, also no parent notifications
    MBeanNotificationInfo[] notificationInfo = new MBeanNotificationInfo[0];
    // . . .
    // MBeanInfo
    _MBeanInfo = new MBeanInfo(
      "Queue",
      "Queue MBean",
      attributeInfo,
      constructorInfo,
      operationInfo,
      notificationInfo
    );
  }
// . . .
}

As you can see from Example 3-4, creating the necessary metadata classes requires only knowledge of the attributes and operations and extra code. Notice that we didn’t create metadata for the DebugOn attribute or the enableDebugging and disableDebugging operations. This is just to show you that when you select explicit superclass exposure as the management interface inheritance approach for your dynamic MBeans, you can pick and choose which attributes and operations to expose from the parent class.

We must also make modifications to Queue’s implementation of DynamicMBean, as discussed in the next section.

getAttribute( )

Implementing this method is a simple matter of adding the same number of else if blocks as the number of attributes we’re exposing from the parent class:

public class Queue extends Basic implements DynamicMBean {
// . . .
  public Object getAttribute(String attributeName) 
    throws AttributeNotFoundException,
           MBeanException,
           ReflectionException {
    Object ret = null;
  
    if (attributeName.equals("QueueSize")) {
      ret = new Integer(getQueueSize(  ));
    }
    // . . .
    else if (attributeName.equals("RemoveWaitTime")) {
      ret = new Long(getRemoveWaitTime(  ));
    }
    else if (attributeName.equals("NumberOfResets")) {
                        ret = new Integer(getNumberOfResets(  ));
                      }
                      else if (attributeName.equals("TraceOn")) {
                        ret = new Boolean(isTraceOn(  ));
                      }
    else throw new AttributeNotFoundException(
      "Queue.getAttribute(  ): ERROR: " +
      "Cannot find " + attributeName + " attribute.");
    }
    return ret;
  }
// . . .
}

We exposed two attributes from the parent class, so we write two else if blocks for those attributes and delegate to the appropriate method. We know we can call this method because of Java inheritance.

setAttribute( )

There are no writable attributes on Basic, but if there were, the logic would be similar to that of getAttribute( ). Suppose, for the purposes of example, that TraceOn is a writable attribute. In that case, the implementation of setAttribute( ) would look like this:

public class Queue extends Basic implements DynamicMBean {
// . . .
  public void setAttribute(Attribute attribute) 
    throws AttributeNotFoundException,
           InvalidAttributeValueException,
           MBeanException,
           ReflectionException {
  String name = attribute.getName(  );
  Object value = attribute.getValue(  );
  // See if attribute is on parent class
  if (name.equals("QueueSize")) {
    setQueueSize(((Integer)value).intValue(  ));
  }
  else if (name.equals("TraceOn")) {
                      setTraceOn(((Boolean)value).booleanValue(  ));
                    }
  else throw new AttributeNotFoundException(
    "Queue.getAttribute(  ): ERROR: " +
    "Cannot find " + attributeName + " attribute.");
  }
// . . .
}

If the name of the attribute to set is “TraceOn,” we simply invoke the setter for the attribute.

invoke( )

You are probably starting to see a pattern here. When you use explicit superclass exposure as the management interface inheritance mechanism for your MBeans, you simply include extra else if blocks for the attributes and, in this case, the operations that are to be exposed.

The following example shows how to use this approach to modify invoke( ) to expose Basic’s management interface methods:

public class Queue extends Basic implements DynamicMBean {
// . . .
  public Object invoke(String operationName,
                       Object params[],
                       String signature[])
      throws MBeanException,
             ReflectionException {
    Object ret = Void.TYPE;
    ret = Void.TYPE;
    if (operationName.equals("suspend")) {
      suspend(  );
    }
    else if (operationName.equals("resume")) {
      resume(  );
    }
    else if (operationName.equals("enableTracing")) {
                        enableTracing(  );
                      }
                      else if (operationName.equals("disableTracing")) {
                        disableTracing(  );
                      }
    else {
      throw new ReflectionException(
            new NoSuchMethodException(operationName),
                "Queue.invoke(  ): ERROR: " +
                "Cannot find the operation " + operationName + "!");
    }
    return ret;
  }
// . . .
}

One drawback of using explicit superclass exposure is that the child class must have explicit knowledge of the parent class’s management interface. Also, this approach is more susceptible to errors when code changes to the parent class are necessary. However, you must weigh these risks against the benefits of being able to selectively expose attributes and operations from the parent class’s management interface on the child class and base your decision of whether or not to use this approach on that information.

Superclass Delegation

This is the more generic of the two approaches to dynamic MBean management interface inheritance. With superclass delegation, when figuring out which attribute to get/set or operation to invoke, the child class first looks at its own management interface and then, if the attribute or operation is not found, delegates to its parent class (which may delegate to its parent class, and so on). This requires changes to how the metadata is created for the child class. Let’s look at an example from the application.

Queue inherits from Basic, and Basic implements the DynamicMBean interface for its attributes (TraceOn, DebugOn, and NumberOfResets) and its operations (enableTracing, disableTracing, enableDebugging, and disableDebugging). Recall from Example 3-4 how we created all of the metadata classes for Queue’s management interface in Queue’s constructor. If we are going to augment Queue’s management interface with attributes and operations from Basic’s management interface, we must expose those attributes and operations on the MBeanInfo instance that is returned from Queue’s getMBeanInfo( ) method. Thus, we must create the metadata classes for these attributes and operations in Queue’s constructor. Example 3-5 shows how to do this, showing enough of Example 3-4 to provide you some context.

Example 3-5. Creating metadata classes to expose attributes and operations from Queue’s parent class, Basic, through superclass delegation

public class Queue extends Basic implements DynamicMBean {
// . . .
  private MBeanInfo _MBeanInfo;
  // . . .
  public Queue(int queueSize) {
    MBeanInfo parentInfo = super.getMBeanInfo(  );
    // . . .
    // Attributes
    MBeanAttributeInfo[] parentAttributes = parentInfo.getAttributes(  );
                      int numberOfParentAttributes = parentAttributes.length;
                      MBeanAttributeInfo[] attributeInfo = 
                        new MBeanAttributeInfo[numberOfParentAttributes + 10];
                      System.arraycopy(parentAttributes,0,attributeInfo,0,numberOfParentAttributes);
    attributeInfo[numParentAtts+0] = new MBeanAttributeInfo(
      "QueueSize", Integer.TYPE.getName(  ),
      "Maximum number of items the queue may contain at one time.",
      true, true, false);
    attributeInfo[numParentAtts+1] = new MBeanAttributeInfo(
      "NumberOfConsumers", Integer.TYPE.getName(  ),
      "The number of consumers pulling from this Queue.",
      true, false, false);
    // . . .
    // Constructors
    // . . .
    // Operations
    MBeanOperationInfo[] parentOperations = parentInfo.getOperations(  );
                      int numberOfParentOperations = parentOperations.length;
                      MBeanOperationInfo[] operationInfo =
                        new MBeanOperationInfo[numberOfParentOperations+2];  
                      System.arraycopy(parentOperations,0,operationInfo,0,numberOfParentOperations);
    MBeanParameterInfo[] parms = new MBeanParameterInfo[0];
    operationInfo[numParentOps+0] = new MBeanOperationInfo(
      "suspend", "Suspends processing of the Queue.",
      parms, Void.TYPE.getName(  ), MBeanOperationInfo.ACTION);
    operationInfo[numParentOps+1] = new MBeanOperationInfo(
      "resume", "Resumes processing of the Queue.",
      parms, Void.TYPE.getName(  ), MBeanOperationInfo.ACTION);
    // . . .
    // Notifications
    MBeanNotificationInfo[] parentNotifications = parentInfo.getNotifications(  );
    int numberOfParentNotifications = parentNotifications.length;
    // no notifications for this MBean, use parent notifications
    MBeanNotificationInfo[] notificationInfo = 
                        new MBeanNotificationInfo[numberOfParentNotifications+0];
                      System.arraycopy(parentNotifications,0,notificationInfo,0,
                     numberOfParentNotifications);
    // . . .
    // MBeanInfo
    _MBeanInfo = new MBeanInfo(
      "Queue",
      "Queue MBean",
      attributeInfo,
      constructorInfo,
      operationInfo,
      notificationInfo
    );
  }
// . . .
}

The highlighted lines in Example 3-5 are the lines that must be added to create the necessary metadata classes. Actually, we’re only making a copy of the reference to the metadata classes that were created by the parent class. We could instead have chosen to clone( ) the instances, but this approach seemed a reasonable one for the purpose at hand. Once we get the MBeanInfo instance that contains the metadata for the parent class, it is a simple matter of making sure the attribute, operation, and notification arrays are large enough to accommodate the attributes, operations, and notifications for both the Queue class and its parent class. That’s it; it’s pretty straightforward.

We must also make modifications to Queue’s implementation of DynamicMBean, as discussed in the next section.

getAttribute( )

This method must have the same signature as its parent method, so we first check to see if the requested attribute is available on the child class and then, if it is not, delegate to the parent class’s getAttribute( ) method:

public class Queue extends Basic implements DynamicMBean {
// . . .
  public Object getAttribute(String attributeName) 
    throws AttributeNotFoundException,
           MBeanException,
           ReflectionException {
    Object ret = null;
    if (attributeName.equals("QueueSize")) {
      ret = new Integer(getQueueSize(  ));
    }
    // . . .
    else {
                        ret = super.getAttribute(attributeName);
                      }
    return ret;
  }
// . . .
}

I omitted some of the lines of code in this example, for the sake of brevity. (If you compare this example to the corresponding example from the explicit superclass exposure pattern, you can see how I simplified the code.) If the requested attribute is not available on the parent class, we can simply let the AttributeNotFoundException propagate out. The message generated will be from the parent class, but we can always surround the superclass delegation with a try/catch block and augment or modify any exception thrown by the superclass.

setAttribute( )

There are no writable attributes on either Queue or Basic, but superclass delegation is a generic approach, so the child class makes no assumptions about access to the attributes on the parent class. The following example shows how to implement setAttribute( ) using this approach:

public class Queue extends Basic implements DynamicMBean {
// . . .
  public void setAttribute(Attribute attribute)
    throws AttributeNotFoundException,
           InvalidAttributeValueException,
           MBeanException,
           ReflectionException {
    String name = attribute.getName(  );
    Object value = attribute.getValue(  );
    if (name.equals("QueueSize")) {
      setQueueSize(((Integer)value).intValue(  ));
    }
    super.setAttribute(attribute);
  }
// . . .
}

This example doesn’t contain the contrived writable TraceOn attribute of Basic, but notice how much simpler the code is compared to the corresponding explicit superclass exposure example from earlier in this chapter. If the attribute is not found on the child class, we simply delegate to the parent class. If an AttributeNotFoundException is thrown, we simply let it propagate out of this method.

invoke( )

Superclass delegation works the same way for this method as well. First we check to see if the method is exposed on the child class interface. If it is not, we delegate to the superclass:

public class Queue extends Basic implements DynamicMBean {
// . . .
  public Object invoke(String operationName,
                       Object params[],
                       String signature[])
    throws MBeanException,
           ReflectionException {
    Object ret = Void.TYPE;
    ret = Void.TYPE;
    if (operationName.equals("suspend")) {
      suspend(  );
    }
    else if (operationName.equals("resume")) {
      resume(  );
    }
    else {
                        super.invoke(operationName, params, signature);
                      }
    return ret;
  }
// . . .
}

Again, the code is greatly simplified because we delegate to the superclass if the method to invoke is not recognized.

One drawback of this approach is that you cannot selectively perform management interface inheritance; superclass delegation is generic and exposes the entire management interface of the parent class. However, this also means that changes to the parent class’s management interface will not ripple through the child class, as is the case with explicit superclass exposure.

Mixing It Up

You may be wondering at this point why you can’t simply selectively expose the desired attributes from the parent management interface and generically delegate in the DynamicMBean implementation. There is no reason that you cannot take this approach; it’s entirely up to you.

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

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