Java Dynamic Management Kit 5.1 Tutorial

Chapter 5 Base Agent

An agent application is a program written in the Java language that contains an MBean server and a means of accessing its functionality. The base agent demonstrates the programming details of writing an agent application. We will cover the MBean server classes, how to reference MBeans, how to access the MBean server, use it to create MBeans, and then interact with those MBeans.

Interacting programmatically with the MBean server gives you more flexibility in your agent application. This chapter presents the MBean server and covers the three main ways to use it to create MBeans, how to interact with these MBeans, and how to unregister them.

The program listings in this tutorial show only functional code: comments and output statements have been modified or removed to conserve space. However, all management functionality has been retained for the various demonstrations. The complete source code is available in the BaseAgent and StandardMBean example directories located in examplesDir/current (see Directories and Classpath in the Preface).

This chapter covers the following topics:

5.1 MBean Server Classes

Before writing an agent application, it is important to understand the functionality of the MBean server. It is actually an interface and a factory object defined by the agent specification level of the JMX specification. The Java DMK provides an implementation of this interface and factory. The factory object finds or creates the MBean server instance, making it possible to substitute different implementations of the MBean server.

5.1.1 MBeanServer Interface

The MBeanServer interface is an extension of the MBeanServerConnection interface, and represents the agent-side interface for interacting with MBeans.

The specification of the interface defines all operations that can be applied to resources and other agent objects through the MBean server. Its methods can be divided into three main groups:

5.1.2 MBean Server Implementation, Builder and Factory

The MBeanServer implementation class in Java DMK implements the JdmkMBeanServer and MBServerInt interfaces if the property javax.management.builder.initial has been set to use com.sun.jdmk.JdmkMBeanServerBuilder. This implementation of MBeanServer wraps the JMX MBean server.

The older MBeanServerInt interface and the MBeanServerImpl class are still implemented for reasons of backwards compatibility, but in Java DMK 5.1, JdmkMBeanServer is the prefered interface.

The MBeanServerFactory makes the agent application independent of the MBean server implementation, and thus allows applications to provide custom MBeanServer implementations. It resides in the Java virtual machine and centralizes all MBean server instantiation. The MBeanServerFactory class provides two static methods:

You must use the MBeanServerFactory classes to create an MBean server so that other objects can obtain its reference by calling the findMBeanServer method. This method enables dynamically loaded objects to find the MBean server in an agent that has already been started.

5.2 Referencing MBeans

Most agent applications interact with MBeans through the MBean server. It is possible for an object to instantiate an MBean class itself, which it can then register in the MBean server. In this case, it keeps a programmatic reference to the MBean instance. All other objects can only interact with the MBean through its management interface exposed by the MBean server.

In particular, service MBeans and connectivity MBeans rely solely on the MBean server to access resources. The MBean server centralizes the access to all MBeans. It unburdens all other objects from having to keep numerous object references. To ensure this function, the MBean server relies on object names to identify MBean instances uniquely.

5.2.1 Object Names

Each MBean object registered in the MBean server is identified by an object name. The same MBean class can have multiple instances, but each must have a unique name. The ObjectName class encapsulates an object name composed of a domain name and a set of key properties. The object name can be represented as a string in the following format:

DomainName:property=value[,property2=value2]*

The DomainName, the property and value can be any alphanumeric string, as long as it does not contain any of the following characters: : , = * ?. All elements of the object name are treated as literal strings, meaning that they are case sensitive.

5.2.1.1 MBean Domains

A domain is an abstract category that can be used to group MBeans arbitrarily. The MBean server lets you search easily for all MBeans with the same domain. For example, all connectivity MBeans in the minimal server could have been registered into a domain called Communications.

Since all object names must have a domain, the MBeans in an MBean server necessarily define at least one domain. When the domain name is not important, the MBean server provides a default domain name that you can use. By default, it is called the DefaultDomain, but you can specify a different default domain name when creating the MBean server from its factory.

5.2.1.2 Key Properties

A key is a property-value pair that can also have any meaning that you assign to it. An object name must have at least one key. Keys and their values are independent of the MBean's attributes. The object name is a static identifier that identifies the MBean, whereas attributes are the exposed, runtime values of the corresponding resource. Keys are not positional and can be given in any order to identify an MBean.

Keys provide the specificity for identifying a unique MBean instance. For example, an object name for the HTML protocol adaptor MBean might be: Communications:protocol=html,port=8082, assuming the port will not change.

5.2.1.3 Usage of Object Names

All MBeans must be given an object name that is unique. It can be assigned by the MBean's preregistration method, if the MBean supports preregistration (see the Javadoc API of the MBeanRegistration interface). Or it can be assigned by the object that creates or registers the MBean, which overrides the one given during preregistration. However, if neither of these assign an object name, the MBean server will not create the MBean and will raise an exception. Once an MBean is instantiated and registered, its assigned object name cannot be modified.

You can encode any meaning into the domain and key strings. The MBean server handles them as literal strings. The contents of the object name should be determined by your management needs. Keys can be meaningless serial numbers if MBeans are always handled programmatically. On the other hand, the keys can be human-readable to simplify their translation to the graphical user interface of a management application. With the HTML protocol adaptor, object names are displayed directly to the user.

5.2.2 ObjectInstance of an MBean

An object instance represents the complete reference of an MBean in the MBean server. It contains the MBean's object name and its Java class name. Object instances are returned by the MBean server when an MBean is created or in response to queries about MBeans. Since the object name and class name cannot change over the life of a given MBean, its returned object instance will always have the same value.

You cannot modify the class or object name in an object instance. This information is read-only. The object name is used to refer to the MBean instance in any management operation through the MBean server. The class name can be used to instantiate similar MBeans or introspect characteristics of the class.

5.3 Agent Application

The base agent is a standalone application with a main method, but it also has a constructor so that it can be instantiated dynamically by another class.


Example 5–1 Constructor for the Base Agent

public BaseAgent() {

    echo("\n\tInstantiating the MBean server of this agent...");
    mbs = MBeanServerFactory.createMBeanServer();

    // Retrieves ID of the MBean server from
    // the associated MBean server delegate
    //
    echo("\n\tGetting the ID of the MBean server from the " +
             "associated MBean server delegate ...");
    try {
         echo("\tID = " +
             mbs.getAttribute(new ObjectName(ServiceName.DELEGATE),
                              "MBeanServerId"));
    } catch (Exception e) {
        e.printStackTrace();
        System.exit(1);
    }

The MBean server is created through the static MBeanServerFactory object, and we store its object reference. Its true type is hidden by the factory object, that casts the returned object as an MBeanServer interface. The MBean server is the only functional class referenced directly in this application.

After the MBean server is initialized, we will create three communication MBeans.

5.4 Creating an MBean Using the registerMBean Method

The methods of the MBean server enable you to create an MBean in three different ways. The base agent demonstrates all three ways, and we will discuss the advantages of each.

One way of creating an MBean consists of first instantiating its class and then registering this instance in the MBean server. Registration is the internal process of the MBean server that takes a manageable resource's MBean instance and exposes it for management.

Bold text in Example 5–2 and the following code samples highlights the important statements that vary between the three methods.


Example 5–2 Creating an MBean Using the registerMBean Method

// instantiate the HTML protocol adaptor object to use the default port
CommunicatorServer htmlAdaptor = new HtmlAdaptorServer();

try {
    // We let the HTML adaptor provide a default object name
    ObjectInstance htmlAdaptorInstance =
        mbs.registerMBean(htmlAdaptor, null);
    echo("\tCLASS NAME  = " + htmlAdaptorInstance.getClassName());
    echo("\tOBJECT NAME = " + 
    htmlAdaptorInstance.getObjectName().toString());
} catch(Exception e) {
    e.printStackTrace();
    System.exit(1);
}
htmlAdaptor.start();

// Waiting to leave starting state...    
while (htmlAdaptor.getState() == CommunicatorServer.STARTING) {
    sleep(1000);
}
echo("\tSTATE = " + htmlAdaptor.getStateString());
[...]

In this first case, we instantiate the HtmlAdaptorServer class and keep a reference to this object. We then pass it to the registerMBean method of the MBean server to make our instance manageable in the agent. During the registration, the instance can also obtain a reference to the MBean server, something it requires to function as a protocol adaptor.

The HTML adaptor gives itself a default name in the default domain. Its Javadoc API confirms this, so we can safely let it provide a default name. We print the object name in the object instance returned by the registration to confirm that the default was used.

Once the MBean is registered, we can perform management operations on it. Because we kept a reference to the instance, we do not need to go through the MBean server to manage this MBean. This enables us to call the start and getStateString methods directly. The fact that these methods are publicly exposed is particular to the implementation. The HTML adaptor is a dynamic MBean, so without any prior knowledge of the class, we would have to go through its DynamicMBean interface.

In a standard MBean you would call the implementation of its management interface directly. Because the HTML adaptor is a dynamic MBean, the start method is just a shortcut for the start operation. For example, we could start the adaptor and get its StateString attribute with the following calls:

htmlAdaptor.invoke("start", new Object[0], new String[0]);
echo("STATE = " + (String)htmlAdaptor.getAttribute("StateString"));

This type of shortcut is not specified by the Java Management Extensions (JMX) specification, nor is its functionality necessarily identical to that of the start operation exposed for management. In the case of the HTML adaptor, its Javadoc API confirms that it is identical, and in other cases, it is up to the MBean programmer to guarantee this functionality if it is offered.

5.5 Creating an MBean Using the createMBean Method

A second way to create an MBean is the single createMBean method of the MBean server. In this case, the MBean server instantiates the class and registers it all at once. As a result, the caller never has a direct reference to the new object. This is not demonstrated in the BaseAgent example, but the advantage of this method for creating MBeans is that the instantiation and registration are done in one call. In addition, if we have registered any class loaders in the MBean server, they will automatically be used if the class is not available locally. See Chapter 12, M-Let Class Loader for more information on class loading.

The major difference is that we no longer have a reference to our MBean instance. The object instance that was only used for display purposes in the previous example now gives us the only reference we have on the MBean: its object name.

One disadvantage of this method is that all management of the new MBean must now be done through the MBean server. For the attributes of the MBean, we need to call the generic getter and setter of the MBean server, and for the operations we need to call the invoke method. When the agent needs to access the MBean, having to go through the MBean server adds some complexity to the code. However, it does not rely on any shortcuts provided by the MBean, making the code more portable and reusable.

The createMBean method is ideal for quickly starting new MBeans that the agent application does not need to manipulate. In just one call, the new objects are instantiated and exposed for management.

5.6 Creating an MBean Using the instantiate Method

The last way of creating an MBean relies on the instantiate method of the MBean server. In addition, we can use a nondefault constructor to instantiate the class with a different behavior. This method is not demonstrated in the BaseAgent example, but as in Example 5–2, we instantiate and register the MBean in separate steps.

First, you instantiate the class using the instantiate method of the MBean server. This method lets you specify the constructor you want use when instantiating. Note that we could also have specified a constructor to the createMBean method in the previous example.

To specify a constructor, you must give an array of objects for the parameters and an array of strings that defines the signature. If these arrays are empty or null, the MBean server will try to use the default no-parameter constructor. If the class does not have a public no-parameter constructor, you must specify the parameters and signature of a valid public constructor.

One advantage of this creation method is that the instantiate method of the MBean server also supports class loaders. If any are registered in the MBean server, they will automatically be used if the class is not available locally. See Chapter 12, M-Let Class Loader for more information on class loading.

The main advantage of this method is that, like the first method of MBean creation, we retain a direct reference to the new object. The direct reference again enables us to use the MBean's shortcut methods explicitly.

5.7 Managing MBeans

In 5.5 Creating an MBean Using the createMBean Method, you rely totally on the MBean server to create and access an MBean. You get attributes and invoke operations through the MBean server. Here we will concentrate on the usage of MBean metadata classes when accessing MBeans that represent resources.

We will rely on the StandardAgent and DynamicAgent classes presented in Part I. As mentioned in 2.3.1 Comparison With the SimpleStandard Example, the two are nearly identical. The same code works for any registered MBean, whether standard or dynamic. We examine the method for displaying MBean metadata that is common to both.


Example 5–3 Processing MBean Information

private MBeanServer server = null; // assigned by MBeanServerFactory

private void printMBeanInfo(ObjectName mbeanObjectName, String mbeanName) {

    echo("    using the getMBeanInfo method of the MBeanServer");
    sleep(1000);
    MBeanInfo info = null;
    try {
         info = server.getMBeanInfo(mbeanObjectName);
    } catch (Exception e) {
          echo("\t!!! Could not get MBeanInfo object for " +
               mbeanName + " !!!");
          e.printStackTrace();
          return;
    }
    echo("\nCLASSNAME: \t"+ info.getClassName());
    echo("\nDESCRIPTION: \t"+ info.getDescription());
    echo("\nATTRIBUTES");
    MBeanAttributeInfo[] attrInfo = info.getAttributes();
    if (attrInfo.length>0) {
        for(int i=0; i<attrInfo.length; i++) {
            echo(" ** NAME: \t"+ attrInfo[i].getName());
            echo("    DESCR: \t"+ attrInfo[i].getDescription());
            echo("    TYPE: \t"+ attrInfo[i].getType() +
                 "\tREAD: "+ attrInfo[i].isReadable() +
                 "\tWRITE: "+ attrInfo[i].isWritable());
        }
    } else echo(" ** No attributes **");

    echo("\nCONSTRUCTORS");
    MBeanConstructorInfo[] constrInfo = info.getConstructors();
    for(int i=0; i<constrInfo.length; i++) {
        echo(" ** NAME: \t"+ constrInfo[i].getName());
        echo("    DESCR: \t"+ constrInfo[i].getDescription());
        echo("    PARAM: \t"+ constrInfo[i].getSignature().length +
                 " parameter(s)");
    }

    echo("\nOPERATIONS");
    MBeanOperationInfo[] opInfo = info.getOperations();
    if (opInfo.length>0) {
        for(int i=0; i<constrInfo.length; i++) {
            echo(" ** NAME: \t"+ opInfo[i].getName());
            echo("    DESCR: \t"+ opInfo[i].getDescription());
            echo("    PARAM: \t"+ opInfo[i].getSignature().length +
                     " parameter(s)");
        }
    } else echo(" ** No operations ** ");

    echo("\nNOTIFICATIONS");
    MBeanNotificationInfo[] notifInfo = info.getNotifications();
    if (notifInfo.length>0) {
        for(int i=0; i<constrInfo.length; i++) {
            echo(" ** NAME: \t"+ notifInfo[i].getName());
            echo("    DESCR: \t"+ notifInfo[i].getDescription());
            String notifTypes[] = notifInfo[i].getNotifTypes();
            for (int j = 0; j < notifTypes.length; j++) {
                echo("    TYPE: \t" + notifTypes[j]);
            }
        }
    } else echo(" ** No notifications **");
}

The getMBeanInfo method of the MBean server gets the metadata of an MBean's management interface and hides the MBean's implementation. This method returns an MBeanInfo object that contains the MBean's description. We can then get the lists of attributes, operations, constructors, and notifications to display their descriptions. Recall that the dynamic MBean provides its own meaningful descriptions and that the standard MBean descriptions are default strings provided by the introspection mechanism of the MBean server.

5.8 Filtering MBeans With the Base Agent

The base agent does very little filtering because it does very little management. Usually, filters are applied programmatically to get a list of MBeans to which some operations apply. There are no management operations in the MBean server that apply to a list of MBeans. You must loop through your list and apply the desired operation to each MBean.

Before exiting the agent application, we do a query of all MBeans so that we can unregister them properly. MBeans should be unregistered before being destroyed because they might need to perform some actions before or after being unregistered. See the Javadoc API of the MBeanRegistration interface for more information.


Example 5–4 Unregistering MBeans

public void removeMBeans() {
        
        try {
            echo("Unregistering all the registered MBeans except " +
                 "the MBean server delegate\n");
            echo("    Current MBean count = " + mbs.getMBeanCount() 
                 + "\n");
            Set allMBeans = mbs.queryNames(null, null);
            for (Iterator i = allMBeans.iterator(); i.hasNext(); ) {
                ObjectName name = (ObjectName) i.next();
                if (!name.toString().equals(ServiceName.DELEGATE)) {
                    echo("\tUnregistering " + name.toString());
                    mbs.unregisterMBean(name);
                }
            }
            echo("\n    Current MBean count = " + mbs.getMBeanCount() 
                +"\n");
            echo("done\n");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

We use the queryNames method because we only need the object names to operate on MBeans. The null object name as a filter gives us all MBeans in the MBean server. We then iterate through the resulting set and unregister each one, except for the MBean server delegate. As we shall see in 6.1.2 MBean Server Delegate, the delegate is also an MBean and so it will be returned by the query. However, if we unregister it, the MBean server can no longer function and cannot remove the rest of our MBeans.

We recognized the delegate by its standard name which is given by the static field ServiceName.DELEGATE. The ServiceName class provides standard names and other default properties for communications and service MBeans. It also provides the version strings that are exposed by the delegate MBean. Note that, because the delegate is the only MBean created directly by the MBean server, it is the only one whose name cannot be overridden during its registration. The delegate object name is always the same, so we are always sure to detect it.

5.9 Running the Base Agent Example

The examplesDir/current/BaseAgent/ directory contains the source file of the BaseAgent application.

To Run the Base Agent Example
  1. Compile the BaseAgent.java file in this directory with the javac command.

    For example, on the Solaris platform, type:


    $ cd examplesDir/current/BaseAgent/
    $ javac -classpath classpath *.java
    

    Again, we do not need the MBean classes at compile time, but they will be needed at runtime, because we do not use a dynamic class loader. You will need to have compiled the standard and dynamic MBean classes as described in 1.3 Running the Standard MBean Example and 2.3 Running the Dynamic MBean Example. If you want to load any other class in the base agent, you must include its directory or JAR file in the classpath.

  2. To run the example, update your classpath to find the MBeans and start the agent class:


    $ java -classpath classpath:../StandardMBean:../DynamicMBean BaseAgent
    

5.9.1 Agent Output

This agent displays output for the MBean created.

When the connection MBeans have been created, it is possible to connect to the agent through the HTML adaptor.

When you have finished, press Enter to remove all MBeans from the agent and exit the agent application.