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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.