Imagine that you just built the next killer Java application, and it's selling like hotcakes. However, even though (being a good developer), you built in enough debug and tracing information to monitor the state of your live application units, you're missing the ability to monitor each and every class and their attributes. Although you could build monitoring services as part of your application, it takes the focus away from your core business, the application itself. It would be nice if you could monitor your application for the state of its classes, attributes, and operations. Java Management Extensions (JMX) lets you do just that. However, this article isn't about JMX, it's about how to easily configure JMX using the Jakarta Commons Modeler component. Specifically, it's about how to use the Modeler component to create model MBeans, which are used to monitor resources in your application.

Say hello to Modeler

The Modeler component was created from source code taken from Tomcat 4 development. Recognizing that creating metadata information about managed resources in code is a tedious process, the Tomcat developers extracted this information from code to an external XML-based file. They further realized that this extraction could easily be made useful for other managed components—not only server-based projects like Tomcat but any place that needed to use model MBeans. Therefore, they created the Modeler package to make this service available across the board.

As an application developer, it's nice to know at a glance the services that Modeler provides. The following list shows these services:

  • It provides a service for reading and parsing XML-based configuration information for model MBeans.
  • It provides a central registry for storing this configuration information, which also forms the basis for easily creating ModelMBeanInfo objects from this information.
  • It provides an alternate to the RequiredModelMBean object, which is a default implementation of the ModelMBean interface in JMX. This alternate is called BaseModelMBean. However, this alternate only supports management of objects that are of type objectReference (as opposed to RequiredModelMBean, which supports other types such as EJBHandle and RMIReference).
  • It provides a Java Naming and Directory Interface (JNDI) context for storing information about MBeans. This is, in effect, an alternate view of the registry and provides a logging mechanism for changes made to managed beans.
  • It provides a set of custom Ant tasks, which allow the creation of MBeans and reading of descriptor files from within an Ant build script.

Let's see these services in action with the help of some examples. We'll start with the transfer of ModelMBeanInfo information from code to XML file.

Creating metadata in XML

Modeler makes it easy to manage application components by combining the flexibility of model MBeans with the ease of writing XML configuration information about modeled components. The information about modeled components, which we also call metadata, describes these components for the model MBeans that are registered in the MBeanServer. This allows management of these components via JMX. Information from JMX agents passes to the model MBeans and on to the actual managed components through the mapping provided by this metadata information. This information can get very complex if done in code.

Before we discuss how to create this information via an external file, let's look at the registry provided by Modeler. The registry is the central component of Modeler, and it manages interaction with the MBeanServer for management components.

The central registry

A registry is a central place for managing components in Modeler. It's used to load, register, and manage MBeans. Its primary function is to load metadata information from an XML file, which by default is called mbeans-descriptors.xml, and then transfer this information from the XML syntax into ModelMBeanInfo for the model MBeans and application components that it will be mapped to. The registry relies on classes in the org.apache.commons.modeler.modules package for reading this information.

To use the registry, you use the factory method getRegistry(Object key, Object guard). It creates a default registry if one doesn't exist for the key that you specify. This implies that there can be several registries in Modeler, differentiated by a key. This is true, and it gives you greater flexibility in using the same environment for separating multiple application components based on this key. However, before you can use this feature, you must enable it by calling the static method setUseContextClassLoader(boolean enable) and passing in a true value for the enable parameter. Doing so creates a HashMap for storing registries based on keys. If you haven't enabled multiple registries and wish to use the single default registry, you need to pass a value of null to the key parameter of the getRegistry() method.

The second parameter for the getRegistry() method accepts an object called guard and is used to prevent unauthorized access to the registry. If you want to restrict access to a registry of your application components, use the following procedure:

  1. Create the registry for the first time by calling Registry registry = getRegistry(null, null) or Registry registry = getRegistry(yourKey, null), as the case may be.
  2. Decide on an object that you wish to become the guard for your registry. It can be as simple as a String passphrase, or a more complex object. For the purposes of this example, let's use the String passphrase "Jupiter."
  3. Set this passphrase to be the guard for your registry by calling registry.setGuard("Jupiter");.
  4. The next time you want to access your registry, you'll need to use getRegistry(null, "Jupiter") or getRegistry(yourKey, "Jupiter").
  5. Any component trying to call your registry without supplying the correct value of the guard will get a null value.

Once a registry has been created, it can be used to load metadata information and associate it with model MBeans. Let's look at how this is done in the next section.

Loading and registering metadata

The registry provides several methods for loading the information from the mbeans-descriptors file. The default, and the most convenient method, is to read the data from an XML file. As we said before, the default XML file is called mbeans-descriptors.xml. You can load this file either as a URL object, as a File object, or as an InputStream, and then call loadMetadata(Object source) to pass any of these three objects in as a parameter. The method figures out the type of the object and tries to retrieve information from it accordingly.

It isn't necessary that this file be an XML file (or that it be called mbeans-descriptors). You can use serialized versions of your metadata information, but the file loaded via a URL, File, or InputStream must end with a .ser extension.

Listing 1 shows an XML file that contains the metadata information for our TimerApplication example. It contains only basic information about the attributes delay and message.

Listing 1. XML descriptor for TimerApplication

 

<?xml version="1.0"?> <!DOCTYPE mbeans-descriptors PUBLIC "-//Apache Software Foundation//DTD Model MBeans Configuration File" "http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">

<mbeans-descriptors>

<mbean name="TimerApplication"description="Prints Time and message after a delay" type="com.manning.commons.chapter11.TimerApplicationOriginal">

<attribute name="delay"description="Delay in Milli Seconds"type="int"/> <attribute name="message"description="Message to Print"type="java.lang.String"/>

</mbean>

</mbeans-descriptors>

The root element, <mbeans-descriptor>, clearly states that this is a file containing MBeans information. Next, information about individual MBeans is encapsulated in individual <mbean> elements. The only MBean defined in the above XML file is TimerApplication, and it contains information about it in three attributes.

The allowed attributes for the <mbean> element are listed here:

  • name: Class name of the MBean, without package information, or a unique name for it within the MBeanServer.
  • description: Human-readable description of the MBean.
  • type: Fully qualified type of the Mbean-managed component.
  • domain: Domain in which this MBean should be registered in the MBeanServer.
  • group: Name of the group this MBean belongs to.
  • className: Fully qualified class name of the model MBean implementation to use. The default is RequiredModelMBean, supplied by JMX.

Note that none of the attributes are essential. This might seem strange, but it's plausible because Modeler uses reflection on the managed component to try to figure out the best values for the attributes, if they aren't provided in the XML descriptor file.

We define two attribute elements, which correspond to the delay and message attributes of TimerApplication. Each attribute element contains three attributes, like the attributes for the mbean element, giving more information about each attribute. However, you can define several more attributes for the attribute element, as follows:

  • name: Name of the property in your application component.
  • description: Human-readable description of the attribute.
  • type: Fully qualified type of the attribute. Primitive types can be used as is; Modeler converts them into corresponding wrapper classes.
  • displayName: Name to use for this attribute if you want it to be different from the name attribute.
  • getMethod: Name of the method used to retrieve the value of this property. However, it's used only if the property doesn't follow JavaBeans get/set conventions.
  • setMethod: Name of the method used to set the value of this property. Again, use it only if the property doesn't follow JavaBeans get/set conventions.
  • is: Specifies whether this property represents a Boolean value and, if it does, whether it uses isXXX as the method for getting the value of this property. You can use this attribute and indicate a true condition by setting it to true or yes.
  • readable: Indicates that this property is readable by JMX agents if set to true or yes.
  • writable: Indicates that this property is writable by JMX Agent agents if set to true or yes.

The two element attributes <mbean> and <attribute> and the surrounding <mbean> element information are sufficient to describe our TimerApplication. Now we need to know how to register this information with the Modeler registry.

In Listing 2, we'll use the Modeler registry to load the information for the TimerApplication from an XML file.

Before you run this code, make sure that you have commons-modeler.jar and commons-logging.jar libraries in your CLASSPATH in addition to jmxri.jar and jmxtools.jar.

Listing 2. Using Modeler to build ModelMBeanInfo for TimerApplication

 

package com.manning.commons.chapter11;

import java.net.URL;

import javax.management.ObjectName;

import org.apache.commons.modeler.Registry;

import com.sun.jdmk.comm.HtmlAdaptorServer;

public class TimerApplicationModelerAgent {

public TimerApplicationModelerAgent() { URL url=this.getClass().getResource("/timer_app_11.6.xml");

HtmlAdaptorServer adaptor = new HtmlAdaptorServer(); TimerApplicationOriginal timerApp = new TimerApplicationOriginal(15000, "The time is now: ");

ObjectName adaptorName = null; ObjectName timerAppName = null;

try {

Registry registry = Registry.getRegistry(null, null); registry.loadMetadata(url);

timerAppName = new ObjectName("TimerDomain:name=timerApp");

registry.registerComponent(timerApp,timerAppName, "com.manning.commons.chapter11.TimerApplicationOriginal");

adaptorName = new ObjectName("TimerDomain:name=adaptor, port=8082"); adaptor.setPort(8082);

registry.registerComponent(adaptor,adaptorName, "com.sun.jdmk.comm.HtmlAdaptorServer");

adaptor.start(); } catch(Exception e) { System.err.println(e); } }

public static void main(String[] args) { TimerApplicationModelerAgent agent =new TimerApplicationModelerAgent(); } }

Using Modeler transforms the code required to create ModelMBeanInfo dramatically. Once the registry has been created and loaded with basic metadata information about application components (using information from a file we've called timer_app_11.6.xml instead of the default mbeans-descriptors.xml), it's a breeze to register components on it by calling the registerComponent method. Note that we also tried to register the HtmlAdaptorServer component with the registry. This doesn't work completely because the timer_app_11.6.xml doesn't contain metadata information about the HtmlAdaptorServer. At runtime, Modeler tries its best by guessing information about the component using reflection; you only get a list of attributes and other reflexive operations when you look at its details from the browser by navigating to localhost:8082. Information about the current state of the attributes is unavailable because Modeler doesn't know how to extract it.

Page 2 of 3

One important element that we didn't include in Listing 1 is <descriptor>. A <descriptor> element can be used in the mbeans-descriptors file to provide information about a surrounding element. The following code snippet shows how this can be done for the message attribute from Listing 1:

 <attribute name="message"description="Message to Print"
      type="java.lang.String">
   <descriptor>
      <field name="name" value="message" />
      <field name="descriptorType" value="attribute" />
      <field name="displayName" value="Msg" />
   </descriptor>
</attribute> 

It should be easy to see how we would add more information in our mbeans-descriptors file now. For example, if we wanted to include information about the constructors for the TimerApplication, we only need to add the following element in this file, anywhere inside the <mbean> element tag:

 <constructor displayName="Constructor 1"name="TimerApplication">
<parameter name="delay"description="Delay in milliseconds"type="int" />
<parameter name="message"description="Message to print"type="java.lang.String" />
</constructor> 

You can similarly add tags for the operations supported by your application component. Although TimerApplication doesn't contain any specific operations, the following code snippet shows how you can add information about the set method of the delay attribute:

 <operation name="setDelay"description="Set the value of the Delay attribute"
   impact="ACTION"returnType="void">
<parameter name="delay"description="Delay in milliseconds"type="long"/>
</operation> 

Listing 3 puts all this information together in a final XML file for our TimerApplication:

Listing 3. Final XML file containing metadata about TimerApplication

 

<?xml version="1.0"?> <!DOCTYPE mbeans-descriptors PUBLIC "-//Apache Software Foundation//DTD Model MBeans Configuration File" " http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">

<mbeans-descriptors>

<mbean name="TimerApplication"description="Prints Time and message after a delay" type="com.manning.commons.chapter11.TimerApplicationOriginal"> <attribute name="delay"description="Delay in Milli Seconds"type="int"/> <attribute name="message"description="Message to Print" type="java.lang.String"> <descriptor> <field name="name" value="message" /> <field name="descriptorType" value="attribute" /> <field name="displayName" value="Msg" /> </descriptor> </attribute>

<constructor displayName="Constructor 1"name="TimerApplication"> <parameter name="delay"description="Delay in milliseconds"type="int" /> <parameter name="message"description="Message to print" type="java.lang.String" /> </constructor>

<operation name="setDelay"description="Set the value of the Delay attribute" impact="ACTION"returnType="void"> <parameter name="delay"description="Delay in milliseconds"type="long"/>

</operation>

</mbean>

</mbeans-descriptors>

In addition to services for loading and registering MBeans from XML files, Modeler provides Ant tasks for integration within a build process. These tasks include loading, creating, and modifying MBeans. Let's discuss these tasks next.

Modeler and Ant

The Ant tasks for Modeler reside in the package org.apache.commons.modeler.ant. These tasks make it convenient to integrate lifecycle management of MBeans from within an Ant build file. Before you run any of these tasks, make sure that commons-modeler.jar, commons-logging.jar, jmxri.jar, and jmxtools.jar are set to be in the CLASSPATH for Ant. The easiest way to do this is to keep these libraries in the ANT_HOME\lib directory.

In the next few sections, we'll use Ant to load a Modeler registry, create an MBean, and manipulate it, all from within an Ant build file. We'll start with a rudimentary build file and add targets and tasks to it as we know more about them. Let's start with the Registry task.

Registry (mbeans-descriptors) task

The Registry task, also called the mbeans-descriptors task, lets you load the registry information from a file or a URL. It also lets you store this information in a serialized copy. Listing 4 shows the first target in our build file for Ant. This target includes a task for loading the registry.

Listing 4. First draft of the Modeler build file: Loading and saving the registry

 

<project name="Commons-Modeler Ant Tasks" default="all" basedir=".">

<taskdef name="mbeans-descriptors"classname= "org.apache.commons.modeler.ant.RegistryTask"/>

<target name="buildRegistry"> <mbeans-descriptors file="timer_app_11.8.xml"out="output.txt"/> </target>

<target name="all" depends="buildRegistry" />

</project>

As you can see, this build file contains two targets. The target buildRegistry contains a single task called mbeans-descriptors, and the target all is the default target that will be used to run all the other targets. The mbeans-descriptors task accepts two attributes. The file attribute points to the location of the file from which the registry is to be loaded, and the out attribute points to the location of the serialized file where the registry is to be dumped. You can ignore the out attribute if you don't wish to get a serialized copy of your registry, but the file attribute is required. Instead of the file attribute, however, you can use the attribute resource, which attempts to load the registry information independently using the current classloader's getResource() method.

Other than loading the registry and dumping a serialized version of it, there isn't much else you can do with the Registry task. The MBean and JMX-Operation tasks, discussed next, are much more useful.

MBean and JMX-Operation tasks

The MBean task, defined by the org.apache.commons.modeler.ant.MLETTask class, lets you create managed beans. Once an MBean has been created, you can invoke operations on it by using the JMX-Operation task, which is defined by the org.apache.commons.modeler. ant.JmxInvoke class. Listing 5 continues Listing 4 and adds a new target called createMBean.

Listing 5. Creating and managing MBeans via the Modeler build file

 

<project name="Commons-Modeler Ant Tasks" default="all" basedir=".">

<taskdef name="mbeans-descriptors"classname= "org.apache.commons.modeler.ant.RegistryTask"/> <taskdef name="mbean"classname="org.apache.commons.modeler.ant.MLETTask"/> <taskdef name="jmx-operation"classname= "org.apache.commons.modeler.ant.JmxInvoke"/>

<target name="buildRegistry"> <mbeans-descriptors file="timer_app_11.8.xml" out="output.txt"/> </target>

<target name="createMBean">

<mbean name="DefaultDomain:name=HtmlAdaptorServer" code="com.sun.jdmk.comm.HtmlAdaptorServer"> </mbean>

<jmx-operation objectName="DefaultDomain:name= HtmlAdaptorServer"operation="start"/> <mbean name="DefaultDomain:name=timerApp"code= "com.manning.commons.chapter11.TimerApplication"> <arg type="long" value="3000"/> <arg type="java.lang.String" value="Hello! The Time is now: "/> </mbean> <input>Press Return key to continue...</input> </target>

<target name="all" depends="buildRegistry, createMBean" />

</project>

In this listing, we create two MBeans: HtmlAdaptorServer, which is created so we can view the status of the MBeans through a browser; and TimerApplication:

  1. An MBean is created and registered in the MBeanServer using the MBean task. This task requires, at the very least, values for name and code attributes. The name attribute should be set to the desired ObjectName for this MBean, and the code attribute should be set to the fully qualified class name of the MBean. Thus, here we set name to DefaultDomain:name=HtmlAdaptorServer for the ObjectName of the HtmlAdaptorServer and code to com.sun.jdmk.comm. HtmlAdaptorServer.

    We also supply arguments for the TimerApplication class. If no arguments are supplied using the <arg> element tags, the no-argument constructor of the class specified by the code attribute is invoked. In this case, we do need to supply these arguments, because our TimerApplication class doesn't contain a no-argument constructor. These arguments are supplied using the <arg> element tag, which takes two attributes: type specifies the data type of the argument; and value is, well, the value to be passed for this argument. Arguments are passed to the constructor in the order in which they're specified.

    Warning: Bug alert!
    Unfortunately, the org.apache.commons.modeler.ant. MLETTask class only works for String argument types. This means that when you run the code in Listing 5 as a build file using Ant, the long argument type raises an error. Nothing can be done until the Commons Modeler team releases a fix that addresses this issue. In the meantime, you can use a patch, which is available from this book's Website.
  2. It is easy to invoke an operation on an MBean that has been registered in the MBeanServer using the jmx-operation task. To use this task, you need to identify the MBean on which the operation is to be performed, by specifying it as a value to the objectName attribute—in this case, the HtmlAdaptorServer. The actual method/operation that is to be invoked is specified by the operation attribute—in this case, the start method. You can supply arguments for this operation, similar to supplying arguments for the MBean constructors.

    Warning
    The jmx-operation task also suffers from the String-values-only bug. A patch is available on this book's Website.

Using Ant, run this build file on a command line. When the build pauses waiting for your input, start a browser and navigate to localhost:8082. You'll be able to see the MBeans that we've registered using this build file, and you can manage them via the browser interface.

In the next section, we'll look at our final Ant task, jmx-attribute, which lets you set the value of MBean attributes.

JMXSet (jmx-attribute) task

The jmx-attribute task is specified using the org.apache.commons.modeler.ant.JmxSet class in the Modeler package. It lets you set the value of attributes via the Ant build file on MBeans that have been previously registered in an MBeanServer using the mbean task.

Listing 6 completes our build file for Modeler by including an example of the jmx-attribute task.

Listing 6. The complete Modeler build file showing all the tasks

 

<project name="Commons-Modeler Ant Tasks" default="all" basedir=".">

<taskdef name="mbeans-descriptors" classname="org.apache.commons.modeler.ant.RegistryTask"/> <taskdef name="mbean" classname="org.apache.commons.modeler.ant.MLETTask"/> <taskdef name="jmx-operation"classname= "org.apache.commons.modeler.ant.JmxInvoke"/> <taskdef name="jmx-attribute"classname= "org.apache.commons.modeler.ant.JmxSet"/>

<target name="buildRegistry"> <mbeans-descriptors file="timer_app_11.8.xml" out="output.txt"/> </target>

<target name="createMBean">

<mbean name="DefaultDomain:name=HtmlAdaptorServer" code="com.sun.jdmk.comm.HtmlAdaptorServer"> </mbean>

<jmx-operation objectName="DefaultDomain:name=HtmlAdaptorServer" operation="start"/>

<mbean name="DefaultDomain:name=timerApp"code= "com.manning.commons.chapter11.TimerApplication"> <arg type="long" value="3000"/> <arg type="java.lang.String" value="Hello! The Time is now: "/> </mbean> <input>Press Return key to continue...</input> </target>

<target name="setAttribute"> <jmx-attribute objectName="DefaultDomain:name=timerApp"attribute= "message"value="THE TIME IS NOW: "/> <input>Press Return key to continue...</input> </target>

<target name="all" depends="buildRegistry, createMBean, setAttribute" />

</project>

The jmx-attribute task sets the value of an MBean attribute identified by the objectName and attribute attributes. Here, we're changing the value of the message that is printed.

Warning
Again, there is no type conversion for non-String types in the jmx-attribute task. A patch for this task is also available on the book's Website.

Summary

Modeler is a Commons component that simplifies building model MBeans for JMX integration. It does this by providing a registry, which can load information about model MBeans from a variety of sources. This article introduced Modeler using a variety of examples. I explained how the Modeler registry works and gave examples of how to write XML files that contain metadata information about model MBeans. I ended the article by showing examples of Ant and Modeler integration.

Vikram Goyal is a Java developer by day and a technical writer by night. He has worked with Java since its inception and remembers the time when Tomcat meant an animal and not a servlet engine. Goyal regularly writes how-to articles on open source projects and his series of articles on Jakarta Commons was the first such effort to make some sense out of the chaotic world that Jakarta Commons is. Goyal enjoys working with open source technologies, team leading and Civilization 3. He lives in Brisbane, Australia, with his wife.
Page 3 of 3

Learn more about this topic