Friday, August 15, 2008

Extraordinary Standard MBeans

Standard MBeans are commonly used for several reasons. They are easy to use, the interfaces can be applied to client-side proxies, and the interfaces allow for easy Javadoc-based documentation of the management interface on the Standard MBean. MXBeans share these advantages and add a few of their own such as looser required naming and package conventions and greater interoperability support via Open MBean Types.

However, as discussed in my blog entry The JMX Model MBean, there are some disadvantages of Standard MBeans (and MXBeans) that Model MBeans can address. The most significant reason for using the Model MBean (to wrap existing non-JMX resources such as employed in the Spring Framework) is not possible with Standard or MXBeans due to their static nature. However, the advantage of being able to provide information on operations, parameters, constructors, attributes, and notifications is not a sole advantage of Model MBeans or even of the class of dynamic MBeans. I will demonstrate in this blog entry one approach to specifying operation metadata for Standard MBeans and MXBeans that can be displayed in JConsole and other clients that know how to handle MBeanInfo.

The key to providing additional metadata above and beyond what is normally associated with a Standard MBean is use of the class javax.management.StandardMBean. The description portion of this class's Javadoc documentation shows two ways to use this class. They are using StandardMBean's public constructors or by having the MBean implementation class extend the StandardMBean class (but still implement the same interface). Using the StandardMBean class allows the naming conventions normally associated with Standard MBeans to be relaxed because arbitrary implementation classes and interfaces can be associated with this class. However, in this blog entry, I will keep the normal implementation/interface naming conventions and use the class StandardMBean to provide metadata to the Standard MBean and MXBean.

My first code listing shows a class called DustinMBean that extends StandardMBean so that I can override the StandardMBean.cacheMBeanInfo method.

DustinMBean.java

package dustin.jmx;

import javax.management.MBeanInfo;
import javax.management.StandardMBean;

/**
* Child of StandardMBean overridden for customization purposes.
*
* @author Dustin
*/
public class DustinMBean extends StandardMBean
{
/**
* Single constructor accepting MBean implementation class, MBean interface,
* and whether or not it is an MXBean (Standard MBean if not MXBean). Note
* that my parent, StandardMBean, has more than one constructor, but I'm only
* using one of them.
*
* @param implementation MBean implementation class.
* @param mbeanInterface MBean implementation class's interface.
* @param isMXBean true if MXBean; false if Standard MBean.
*/
public DustinMBean(
T implementation,
Class mbeanInterface,
boolean isMXBean )
{
super(implementation, mbeanInterface, isMXBean);
}

/**
* Specify an MBeanInfo to be used with a StandardMBean-turned-DynamicMBean.
*
* @param mbeanInfo MBeanInfo describing this standard MBean turned into a
* Dynamic MBean.
*/
public void setMBeanInfo(final MBeanInfo mbeanInfo)
{
cacheMBeanInfo(mbeanInfo);
}
}


The class shown above makes it easy to set a Standard MBean's MBeanInfo instance.

I will be using a Standard MBean and an MXBean in this blog entry. The next two code listings show the interface and implementation class for the Standard MBean.

SimpleCalculatorMBean.java (Standard MBean Interface)

package dustin.jmx;

/**
* Interface for a Standard MBean example using the Simple Calculator.
*
* @author Dustin
*/
public interface SimpleCalculatorMBean
{
/**
* Calculate the sum of the augend and the addend.
*
* @param augend First integer to be added.
* @param addend Second integer to be added.
* @return Sum of augend and addend.
*/
public int add(final int augend, final int addend);

/**
* Calculate the difference between the minuend and subtrahend.
*
* @param minuend Minuend in subtraction operation.
* @param subtrahend Subtrahend in subtraction operation.
* @return Difference of minuend and subtrahend.
*/
public int subtract(final int minuend, final int subtrahend);

/**
* Calculate the product of the two provided factors.
*
* @param factor1 First integer factor.
* @param factor2 Second integer factor.
* @return Product of provided factors.
*/
public int multiply(final int factor1, final int factor2);

/**
* Calculate the quotient of the dividend divided by the divisor.
*
* @param dividend Integer dividend.
* @param divisor Integer divisor.
* @return Quotient of dividend divided by divisor.
*/
public double divide(final int dividend, final int divisor);

/**
* Provides the type of operation last executed against this calculator class.
*
* @return Type of operation last executed against this calculator.
*/
public OperationType whatWasTheLastOperation();
}


SimpleCalculator.java (Standard MBean Implementation)

package dustin.jmx;

/**
* Simple calculator class intended to demonstrate how a class with no knowledge
* of JMX or management can be used as a Standard MBean.
*
* @author Dustin
*/
public class SimpleCalculator implements SimpleCalculatorMBean
{
private OperationType lastOperation = OperationType.NO_OPERATIONS_INVOKED;

/**
* Calculate the sum of the augend and the addend.
*
* @param augend First integer to be added.
* @param addend Second integer to be added.
* @return Sum of augend and addend.
*/
public int add(final int augend, final int addend)
{
lastOperation = OperationType.INTEGER_ADDITION;
return augend + addend;
}

/**
* Calculate the difference between the minuend and subtrahend.
*
* @param minuend Minuend in subtraction operation.
* @param subtrahend Subtrahend in subtraction operation.
* @return Difference of minuend and subtrahend.
*/
public int subtract(final int minuend, final int subtrahend)
{
lastOperation = OperationType.INTEGER_SUBTRACTION;
return minuend - subtrahend;
}

/**
* Calculate the product of the two provided factors.
*
* @param factor1 First integer factor.
* @param factor2 Second integer factor.
* @return Product of provided factors.
*/
public int multiply(final int factor1, final int factor2)
{
lastOperation = OperationType.INTEGER_MULTIPLICATION;
return factor1 * factor2;
}

/**
* Calculate the quotient of the dividend divided by the divisor.
*
* @param dividend Integer dividend.
* @param divisor Integer divisor.
* @return Quotient of dividend divided by divisor.
*/
public double divide(final int dividend, final int divisor)
{
lastOperation = OperationType.INTEGER_DIVISION;
return dividend / divisor;
}

/**
* Provides the type of operation last executed against this calculator class.
*
* @return Type of operation last executed against this calculator.
*/
public OperationType whatWasTheLastOperation()
{
return this.lastOperation;
}
}


The two code listings immediately above are the interface and implementation for the Standard MBean. The next two listings are for the interface and implementation for a very similar MXBean. The MXBean is not really any more complicated than the Standard MBean, but I needed to name it something different.

LessSimpleCalculatorMXBean.java (MXBean Interface)

package dustin.jmx;

/**
* Interface for a standard MXBean example using the Simple Calculator.
*
* @author Dustin
*/
public interface LessSimpleCalculatorMXBean
{
/**
* Calculate the sum of the augend and the addend.
*
* @param augend First integer to be added.
* @param addend Second integer to be added.
* @return Sum of augend and addend.
*/
public int add(final int augend, final int addend);

/**
* Calculate the difference between the minuend and subtrahend.
*
* @param minuend Minuend in subtraction operation.
* @param subtrahend Subtrahend in subtraction operation.
* @return Difference of minuend and subtrahend.
*/
public int subtract(final int minuend, final int subtrahend);

/**
* Calculate the product of the two provided factors.
*
* @param factor1 First integer factor.
* @param factor2 Second integer factor.
* @return Product of provided factors.
*/
public int multiply(final int factor1, final int factor2);

/**
* Calculate the quotient of the dividend divided by the divisor.
*
* @param dividend Integer dividend.
* @param divisor Integer divisor.
* @return Quotient of dividend divided by divisor.
*/
public double divide(final int dividend, final int divisor);

/**
* Provides the type of operation last executed against this calculator class.
*
* @return Type of operation last executed against this calculator.
*/
public OperationType whatWasTheLastOperation();
}


LessSimpleCalculator.java (MXBean Implementation)

package dustin.jmx;

/**
* Simple calculator class intended to demonstrate how a class with no knowledge
* of JMX or management can be used as an MXBean.
*
* @author Dustin
*/
public class LessSimpleCalculator implements LessSimpleCalculatorMXBean
{
private OperationType lastOperation = OperationType.NO_OPERATIONS_INVOKED;

/**
* Calculate the sum of the augend and the addend.
*
* @param augend First integer to be added.
* @param addend Second integer to be added.
* @return Sum of augend and addend.
*/
public int add(final int augend, final int addend)
{
lastOperation = OperationType.INTEGER_ADDITION;
return augend + addend;
}

/**
* Calculate the difference between the minuend and subtrahend.
*
* @param minuend Minuend in subtraction operation.
* @param subtrahend Subtrahend in subtraction operation.
* @return Difference of minuend and subtrahend.
*/
public int subtract(final int minuend, final int subtrahend)
{
lastOperation = OperationType.INTEGER_SUBTRACTION;
return minuend - subtrahend;
}

/**
* Calculate the product of the two provided factors.
*
* @param factor1 First integer factor.
* @param factor2 Second integer factor.
* @return Product of provided factors.
*/
public int multiply(final int factor1, final int factor2)
{
lastOperation = OperationType.INTEGER_MULTIPLICATION;
return factor1 * factor2;
}

/**
* Calculate the quotient of the dividend divided by the divisor.
*
* @param dividend Integer dividend.
* @param divisor Integer divisor.
* @return Quotient of dividend divided by divisor.
*/
public double divide(final int dividend, final int divisor)
{
lastOperation = OperationType.INTEGER_DIVISION;
return dividend / divisor;
}

/**
* Provides the type of operation last executed against this calculator class.
*
* @return Type of operation last executed against this calculator.
*/
public OperationType whatWasTheLastOperation()
{
return this.lastOperation;
}
}


Both the Standard MBean and MXBean code listings above use the OperationType enum. The main reason for adding an enum into the mix is to demonstrate differences between Standard MBean and MXMBean.

OperationType.java

package dustin.jmx;

/**
* Simple enum representing a type of calculator operation.
*
* @author Dustin
*/
public enum OperationType
{
INTEGER_ADDITION,
INTEGER_SUBTRACTION,
INTEGER_MULTIPLICATION,
INTEGER_DIVISION,
NO_OPERATIONS_INVOKED
}


With the Standard MBean, MXBean, enum, and DustinMBean classes all shown above, it is time to look at the class that calls DustinMBean to turn the Standard and MXBeans into dynamic MBeans with metadata descriptions.

StandardMBeanDemonstrator.java

package dustin.jmx;

import java.lang.management.ManagementFactory;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.ReflectionException;

/**
* This class is intended to demonstrate a more powerful Standard MBean
* available from overriding the javax.management.StandardMBean class.
*
* @author Dustin
*/
public class StandardMBeanDemonstrator
{
/**
* Pause for the specified number of milliseconds.
*
* @param millisecondsToPause Milliseconds to pause execution.
*/
public static void pause(final int millisecondsToPause)
{
try
{
Thread.sleep(millisecondsToPause);
}
catch (InterruptedException threadAwakened)
{
System.err.println("Don't wake me up!\n" + threadAwakened.getMessage());
}
}

/**
* Construct the meta information for the SimpleCalculator
* Standard-turned-Dynamic MBeans operations and the operations' parameters.
*
* Note that this method was adapted from a very similar method for
* constructor Model MBean operation info data that was discussed in the
* method buildModelMBeanOperationInfo() in the class
* ModelMBeanDemonstrator in the blog entry "The JMX Model MBean"
* (http://marxsoftware.blogspot.com/2008/07/jmx-model-mbean.html).
*
* @return Metadata about MBean's operations.
*/
private static MBeanOperationInfo[] buildMBeanOperationInfo()
{
//
// Build the PARAMETERS and OPERATIONS meta information for "add".
//

final MBeanParameterInfo augendParameter =
new MBeanParameterInfo(
"augend",
Integer.TYPE.toString(),
"The first parameter in the addition (augend)." );
final MBeanParameterInfo addendParameter =
new MBeanParameterInfo(
"addend",
Integer.TYPE.toString(),
"The second parameter in the addition (addend)." );

final MBeanOperationInfo addOperationInfo =
new MBeanOperationInfo(
"add",
"Integer Addition",
new MBeanParameterInfo[] {augendParameter, addendParameter},
Integer.TYPE.toString(),
MBeanOperationInfo.INFO );


//
// Build the PARAMETERS and OPERATIONS meta information for "subtract".
//

final MBeanParameterInfo minuendParameter =
new MBeanParameterInfo(
"minuend",
Integer.TYPE.toString(),
"The first parameter in the substraction (minuend)." );

final MBeanParameterInfo subtrahendParameter =
new MBeanParameterInfo(
"subtrahend",
Integer.TYPE.toString(),
"The second parameter in the subtraction (subtrahend)." );

final MBeanOperationInfo subtractOperationInfo =
new MBeanOperationInfo(
"subtract",
"Integer Subtraction",
new MBeanParameterInfo[] {minuendParameter, subtrahendParameter},
Integer.TYPE.toString(),
MBeanOperationInfo.INFO );

//
// Build the PARAMETERS and OPERATIONS meta information for "multiply".
//

final MBeanParameterInfo factorOneParameter =
new MBeanParameterInfo(
"factor1",
Integer.TYPE.toString(),
"The first factor in the multiplication." );

final MBeanParameterInfo factorTwoParameter =
new MBeanParameterInfo(
"factor2",
Integer.TYPE.toString(),
"The second factor in the multiplication." );

final MBeanOperationInfo multiplyOperationInfo =
new MBeanOperationInfo(
"multiply",
"Integer Multiplication",
new MBeanParameterInfo[] {factorOneParameter, factorTwoParameter},
Integer.TYPE.toString(),
MBeanOperationInfo.INFO );

//
// Build the PARAMETERS and OPERATIONS meta information for "divide".
//

final MBeanParameterInfo dividendParameter =
new MBeanParameterInfo(
"dividend",
Integer.TYPE.toString(),
"The dividend in the division." );

final MBeanParameterInfo divisorParameter =
new MBeanParameterInfo(
"divisor",
Integer.TYPE.toString(),
"The divisor in the division." );

final MBeanOperationInfo divideOperationInfo =
new MBeanOperationInfo(
"divide",
"Integer Division",
new MBeanParameterInfo[] {dividendParameter, divisorParameter},
Double.TYPE.toString(),
MBeanOperationInfo.INFO );

//
// Build the PARAMETERS and OPERATIONS meta information for
// "whatWasTheLastOperation" method.
//

final MBeanOperationInfo lastOperationOperationInfo =
new MBeanOperationInfo(
"whatWasTheLastOperation",
"Last Calculator Operation Performed",
null,
"OperationType",
MBeanOperationInfo.INFO );

return new MBeanOperationInfo[]
{ addOperationInfo, subtractOperationInfo,
multiplyOperationInfo, divideOperationInfo,
lastOperationOperationInfo };

}

/**
* Create a SimpleCalculator-specific MBeanInfo.
*
* @param mbeanClassName Name of MBean class being used.
* @param description Description for MBean display.
* @return Generated MBeanInfo.
*/
private static MBeanInfo createCalculatorMBeanInfo(
final String mbeanClassName,
final String description )
{

final MBeanInfo mbeanInfo =
new MBeanInfo(
mbeanClassName, // underlying class name
description, // description of MBean meant for humans
null, // attributes
null, // constructors
buildMBeanOperationInfo(), // operations
null); // notifications
return mbeanInfo;
}

/**
* Register the provided MBean (implementation and interface provided) with
* the provided ObjectName on the provided MBeanServer as a Standard MBean
* if isMXBean is false or as an MXBean if isMXBean is true.
*
* @param mBeanClass MBean implementation class.
* @param interfaceClass MBean interface.
* @param objectNameStr ObjectName under which MBean will be registered.
* @param isMXBean true if this is MXBean; false if Standard MBean.
* @param mbs MBean Server on which to register MBean.
*/
private static void registerStandardMBean(
final Class mBeanClass,
final Class interfaceClass,
final String objectNameStr,
final boolean isMXBean,
final MBeanServer mbs)
{
try
{
final Object objectForMBean = mBeanClass.newInstance();
final ObjectName objectName = new ObjectName(objectNameStr);
final DustinMBean standardMBean =
new DustinMBean(
mBeanClass.cast(objectForMBean),
interfaceClass,
isMXBean);
standardMBean.setMBeanInfo(
createCalculatorMBeanInfo(
mBeanClass.getName(),
"Simple Standard MBean-turned Dynamic Example.") );
mbs.registerMBean(standardMBean, objectName);
System.out.println(
"MBean with ObjectName " + objectNameStr + " based on class "
+ mBeanClass.getCanonicalName() + " and interface "
+ interfaceClass.getCanonicalName() + " has been registered.");
}
catch (InstantiationException ex)
{
System.err.println(
"Unable to instantiate provided class or interface.\n"
+ ex.getMessage() );
}
catch (IllegalAccessException ex)
{
System.err.println(
"Cannot access the provided class for a new instance.\n"
+ ex.getMessage() );
}
catch (MalformedObjectNameException ex)
{
System.err.println(
"Cannot create an ObjectName " + objectNameStr + ":\n"
+ ex.getMessage() );
}
catch (InstanceAlreadyExistsException ex)
{
System.err.println(
"An MBean instance already exists with name " + objectNameStr
+ ":\n" + ex.getMessage() );
}
catch (MBeanRegistrationException ex)
{
System.err.println(
"Failed to register MBean " + objectNameStr + " based on class "
+ mBeanClass.getCanonicalName() + ":\n" + ex.getMessage() );
}
catch (NotCompliantMBeanException ex)
{
System.err.println(
"The class " + mBeanClass.getCanonicalName() + " is not a "
+ "compliant MBean: " + ex.getMessage() );
}
}

/**
* Create and register an MBean given the provided MBean's implementation
* class name, the desired Object Name for the MBean, and the MBeanServer
* on which to register the MBean.
*
* @param mBeanClass MBean implementation class.
* @param objectNameStr ObjectName to be used for the MBean instance.
* @param mbs MBean Server that will be hosting the MBean.
*/
private static void createAndRegisterStandardMBean(
final Class mBeanClass,
final String objectNameStr,
final MBeanServer mbs)
{

String standardMBeanClassName = "";
try
{
final ObjectName standardMBeanName = new ObjectName(objectNameStr);
standardMBeanClassName = mBeanClass.getCanonicalName();
mbs.createMBean(standardMBeanClassName, standardMBeanName);
System.err.println(
"Standard MBean " + standardMBeanName + " created and registered "
+ "using the class " + standardMBeanClassName );
}
catch (ReflectionException ex)
{
System.err.println(
"Problem trying to get name of class for MBean:\n"
+ ex.getMessage() );
}
catch (InstanceAlreadyExistsException ex)
{
System.err.println(
"MBean already exists and is registered with name "
+ objectNameStr + ":\n" + ex.getMessage() );
}
catch (MBeanRegistrationException ex)
{
System.err.println(
"Error trying to register MBean " + objectNameStr + ":\n"
+ ex.getMessage() );
}
catch (NotCompliantMBeanException ex)
{
System.err.println(
"Class " + standardMBeanClassName + " is not a compliant MBean:\n"
+ ex.getMessage() );
}
catch (MalformedObjectNameException ex)
{
System.err.println( "Bad objectname " + objectNameStr + ":\n"
+ ex.getMessage() );
}
catch (MBeanException ex)
{
System.err.println(
"MBeanException encountered trying to register class "
+ standardMBeanClassName + " as MBean with ObjectName of "
+ objectNameStr + ":\n" + ex.getMessage() );
}
}

/**
* Main executable for running Standard and MXBean examples.
*
* @param arguments The command line arguments.
*/
public static void main(String[] arguments)
{
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

// Create and register a standard MBean the standard way.
createAndRegisterStandardMBean(
SimpleCalculator.class, // SimpleCalculator is Standard MBean
"standardmbean:type=standard", // ObjectName string
mbs); // MBean server to register on

// Create and register an MXBean the standard way.
createAndRegisterStandardMBean(
LessSimpleCalculator.class,
"standardmbean:type=mxbean",
mbs);

// Register a standard MBean using the StandardMBean-extended class.
registerStandardMBean(
SimpleCalculator.class,
SimpleCalculatorMBean.class,
"dynamicmbean:type=standard",
false,
mbs);

// Register an MXBean using the StandardMBean-extended class.
registerStandardMBean(
LessSimpleCalculator.class,
LessSimpleCalculatorMXBean.class,
"dynamicmbean:type=mxbean",
true,
mbs);

pause(100000);
}
}


In the class above, the method that sets up the metadata [buildMBeanOperationInfo()] is extremely similar to the same method used in an earlier blog entry to set up metadata on operations for the Model MBean class.

It is inside the try block in the registerStandardMBean method that most of the action occurs in terms of using the StandardMBean-derived DustinMBean. This method is a little more complicated than it would need to be if was less generic and reflection was not used. The example usage code shown in the Javadoc for StandardMBean shows how easy it can be.

For instance, instead of using reflection like this:


final Object objectForMBean = mBeanClass.newInstance();
final ObjectName objectName = new ObjectName(objectNameStr);
final DustinMBean standardMBean =
new DustinMBean(
mBeanClass.cast(objectForMBean),
interfaceClass,
isMXBean);
standardMBean.setMBeanInfo(
createCalculatorMBeanInfo(
mBeanClass.getName(),
"Simple Standard MBean-turned Dynamic Example.") );
mbs.registerMBean(standardMBean, objectName);


it could be done less generically but much more simply like this


final ObjectName objectName = new ObjectName(objectNameStr);
final DustinMBean standardMBean =
new DustinMBean(
new SimpleCalculator(),
SimpleCalculatorMBean.class,
isMXBean);
standardMBean.setMBeanInfo(
createCalculatorMBeanInfo(
"dustin.jmx.SimpleCalculator",
"Simple Standard MBean-turned Dynamic Example.") );
mbs.registerMBean(standardMBean, objectName);


In the main() of the StandardMBeanDemonstrator class, the code demonstrates that the Standard MBean and the MXBean are each used twice, once in the standard way and once via the StandardMBean class. The standard approach depends entirely on naming conventions between implementation and interface while the StandardMBean approach allows the implementation and interface to be explicitly associated in code. The latter approach also allows extra metadata to be specified.

I won't show it here, but the whatWasTheLastOperation() operation only works in JConsole for the two uses of MXBean and not for the two uses of Standard MBean for reasons displayed in a previous blog entry.

I'll end this entry with two screen snapshots. One shows the MXBean that was created and registered with the MBeanServer in the standard way. It works perfectly well, but lacks descriptive information in JConsole. The second shows the same MXBean registered with the StandardMBean approach. It works as well, but also shows dynamic MBean level of description.

MXBean Registered on MBean Server in Standard Way




MXBean Registered on MBean Server via StandardMBean Class




In conclusion, use of the Java SE-provided StandardMBean class allows JMX developers to treat Standard and MXBeans more like dynamic MBeans. Because all MBean types support Descriptors as of Java SE 6, the differences between the types of MBeans continue to blur.

No comments: