Friday, July 11, 2008

The JMX Model MBean

In this blog entry, I will be demonstrating the development of a simple Model MBean. In a previous blog entry, I demonstrated some key characteristics of the MXBean. The MXBean is more straightforward to develop and use, but the additional complexity of the Model MBean does bring several benefits.

The two primary benefits that I like to use Model MBeans to achieve are the increased descriptive nature of the Model MBean and the ability to "wrap" non-JMX classes or resources with the Model MBean. The latter advantage is the primary reason that the Spring Framework uses Model MBeans in its JMX support. The Model MBean enables Spring to allow developers to write plain Java classes that Spring can then expose as MBeans by "wrapping" the developed simple objects as managed resources. Spring also illustrates the descriptive nature of the Model MBean. Spring accomplishes this with annotation-based descriptive specification as I discussed in a previous blog entry. In short, the Spring Framework takes advantage of the two most important advantages (in my opinion) of the Model MBean by using them to wrap non-JMX objects and by leveraging their great descriptive ability.

In the remainder of this blog entry, I will show the rather verbose code for handling Model MBeans by hand. I'll also show the extra detail displayed in JConsole thanks to the Model MBean.

The first code listing is for the plain Java class that has no knowledge of JMX or the fact that it will be managed.

SimpleCalculator.java


package dustin.jmx.modelmbeans;

/**
* Simple calculator class intended to demonstrate how a class with no knowledge
* of JMX or management can be "wrapped" with ModelMBeans.
*
* @author Dustin
*/
public class SimpleCalculator
{
/**
* 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)
{
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)
{
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)
{
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)
{
return dividend / divisor;
}
}


The next code listing is the Model MBean handling and registration. This is a rather long code listing, indicative of the verbosity of the Model MBean. Note that JMX books have provided frameworks and libraries to reduce this code and Daniel Fuchs shows in his blog entry Dynamic MBeans, Model MBeans, and POJOS... how one could use reflection to reduce Model MBean code. Even so, Model MBeans still require more tedious coding and can be a little more tricky than the Standard MBean or the MXBean. If a developer is using the Spring Framework in their application, then Spring's support of Model MBeans makes these particularly easy to apply. When not using Spring, one might consider the Apache Commons Modeler Component to support Model MBeans. With all that said, here is the code listing.


package dustin.jmx.modelmbeans;

import java.lang.management.ManagementFactory;
import javax.management.Descriptor;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
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.RuntimeOperationsException;
import javax.management.modelmbean.DescriptorSupport;
import javax.management.modelmbean.InvalidTargetObjectTypeException;
import javax.management.modelmbean.ModelMBean;
import javax.management.modelmbean.ModelMBeanInfoSupport;
import javax.management.modelmbean.ModelMBeanOperationInfo;
import javax.management.modelmbean.RequiredModelMBean;

/**
* The purpose of this class is to demonstrate use of ModelMBeans in the raw.
*/
public class ModelMBeanDemonstrator
{
private MBeanServer mbeanServer;

/**
* Constructor.
*/
public ModelMBeanDemonstrator()
{
this.mbeanServer = ManagementFactory.getPlatformMBeanServer();
}

/**
* Construct the meta information for the SimpleCalculator ModelMBean
* operations and the operations' parameters.
*
* @return
*/
private ModelMBeanOperationInfo[] buildModelMBeanOperationInfo()
{
//
// 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 ModelMBeanOperationInfo addOperationInfo =
new ModelMBeanOperationInfo(
"add",
"Integer Addition",
new MBeanParameterInfo[] {augendParameter, addendParameter},
Integer.TYPE.toString(),
ModelMBeanOperationInfo.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 ModelMBeanOperationInfo subtractOperationInfo =
new ModelMBeanOperationInfo(
"subtract",
"Integer Subtraction",
new MBeanParameterInfo[] {minuendParameter, subtrahendParameter},
Integer.TYPE.toString(),
ModelMBeanOperationInfo.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 ModelMBeanOperationInfo multiplyOperationInfo =
new ModelMBeanOperationInfo(
"multiply",
"Integer Multiplication",
new MBeanParameterInfo[] {factorOneParameter, factorTwoParameter},
Integer.TYPE.toString(),
ModelMBeanOperationInfo.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 ModelMBeanOperationInfo divideOperationInfo =
new ModelMBeanOperationInfo(
"divide",
"Integer Division",
new MBeanParameterInfo[] {dividendParameter, divisorParameter},
Double.TYPE.toString(),
ModelMBeanOperationInfo.INFO );

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

/**
* Build descriptor.
*
* @return Generated descriptor.
*/
private Descriptor buildDescriptor()
{
final Descriptor descriptor = new DescriptorSupport();
descriptor.setField("name", "ModelMBeanInTheRaw");
descriptor.setField("descriptorType", "mbean");
return descriptor;
}

/**
* Set the provided ModelMBean to manage the SimpleCalculator resource.
*
* @param modelMBeanToManageResource ModelMBean to manage the SimpleCalculator
* resource.
*/
private void setModelMBeanManagedResource(
final ModelMBean modelMBeanToManageResource )
{
try
{
modelMBeanToManageResource.setManagedResource(
new SimpleCalculator(),
"ObjectReference");
}
catch (RuntimeOperationsException ex)
{
System.err.println(ex.getMessage());
}
catch (InstanceNotFoundException ex)
{
System.err.println(ex.getMessage());
}
catch (InvalidTargetObjectTypeException ex)
{
System.err.println(ex.getMessage());
}
catch (MBeanException ex)
{
System.err.println(ex.getMessage());
}
}

/**
* Create a ModelMBean the "old fashioned" way.
*
* @return ModelMBean created directly without framework.
*/
private ModelMBean createRawModelMBean()
{
RequiredModelMBean modelmbean = null;
try
{
final ModelMBeanInfoSupport modelMBeanInfo =
new ModelMBeanInfoSupport(
SimpleCalculator.class.getName(),
"A simple integer calculator.",
null, // attributes
null, // constructors
buildModelMBeanOperationInfo(),
null, // notifications
buildDescriptor() );
modelmbean = new RequiredModelMBean(modelMBeanInfo);
setModelMBeanManagedResource(modelmbean);
}
catch (MBeanException mbeanEx)
{
System.err.println( "ERROR trying to create a ModelMBean:\n"
+ mbeanEx.getMessage() );
}

return modelmbean;
}

/**
* Register the provided ModelMBean in the MBeanServer using the provided
* ObjectName String.
*
* @param modelMBean ModelMBean to be registered with the MBeanServer.
* @param objectNameString ObjectName of the registered ModelMBean.
*/
private void registerModelMBean(
final ModelMBean modelMBean,
final String objectNameString)
{
try
{
final ObjectName objectName =
new ObjectName(objectNameString);
this.mbeanServer.registerMBean( modelMBean,
objectName );
}
catch (MalformedObjectNameException badObjectName)
{
System.err.println( "ERROR trying to generate ObjectName based on "
+ objectNameString + ":\n"
+ badObjectName.getMessage() );
}
catch (InstanceAlreadyExistsException objectNameAlreadyExists)
{
System.err.println( "ERROR due to ObjectName ["
+ objectNameString + "] already existing: "
+ objectNameAlreadyExists.getMessage() );
}
catch (MBeanRegistrationException badMBeanRegistration)
{
System.err.println( "ERROR trying to register MBean with ObjectName "
+ objectNameString + ":\n"
+ badMBeanRegistration.getMessage() );
}
catch (NotCompliantMBeanException nonCompliantMBean)
{
System.err.println( "ERROR trying to register nonconforming MBean "
+ "with ObjectName of " + objectNameString
+ nonCompliantMBean.getMessage() );
}
}

/**
* Create a ModelMBean in the raw and register it with the MBeanServer.
*/
public void createAndRegisterRawModelMBean()
{
final String rawModelMBeanObjectNameString = "modelmbean:type=raw";
registerModelMBean(createRawModelMBean(), rawModelMBeanObjectNameString);
}

/**
* 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());
}
}

/**
* Demonstate use of ModelMBean.
*
* @param args the command line arguments
*/
public static void main(String[] args)
{
final ModelMBeanDemonstrator me = new ModelMBeanDemonstrator();
me.createAndRegisterRawModelMBean();
pause(1000000);
}
}


When the above code is run, the operations in SimpleCalculator can then be accessed via JConsole as shown in the next four images (click on images to see larger versions). The images demonstrate the descriptive information available for the MBean and for each operation thanks to the Model MBean's extraordinary descriptive abilities. They also show the results of one of the operations.

ModelMBean Descriptive Information Displayed




The Exposed ModelMBean Operations




ModelMBean-Exposed Operation Works




ModelMBean Operation Descriptive Detail




I have attempted to demonstrate the power and flexibility of the ModelMBean. I did not show specification of the metadata for constructors, notifications, and attributes, but they generally follow the same pattern as shown for operations. ModelMBeans are another example of the typical trade-off of greater flexibility for increase complexity. However, there are ways to reduce this complexity as discussed previously. These include use of reflection, use of the Spring Framework, use of the Apache Commons Modeler Component, and use of JBoss XMBeans.

1 comment:

Liv said...

It is a bit awkward indeed to use just the out-of-the box JDK classes to deal with ModelMBean's; Spring helps, however, for those working outside the Spring framework there is also Apache Modeler -- which pretty much achieves what Spring JMX does: allows programmers to easily wrap up a normal Java bean in a ModelMBean and have it registered with the MBeanServer.
Unfortunately, due to Spring framework's large coverage nowadays, this project doesn't have the "PR" it might deserve. And maybe related to this (though it does create a bit of a vicious circle :) the code and docco on Apache's side suffers a bit...