Java Dynamic Management Kit 5.0 Tutorial

Chapter 8 Base Agent

The base agent demonstrates the programming details of writing an agent application. We will cover how to access the MBean server, use it to create MBeans, and then interact with those MBeans. Everything that we could do through the HTML adaptor can also be done through the code of your agent application.

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

The base agent is functionally equivalent to the minimal agent, but instead of writing the smallest agent, we will demonstrate good defensive programming with error checking. We will create the same three connectivity MBeans and do some of the same management operations that we did through the browser interface.

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 the main examplesDir (see Directories and Classpath in the Preface).

This chapter covers the following topics:

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 8–1 Constructor for the Base Agent

public BaseAgent() {
    // Enable the TRACE level according to the level set 
	  // in system properties
    try {
        TraceManager.parseTraceProperties();
    }
    catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
    }

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

    // Retrieves ID of the MBean server from the delegate
    try {
        System.out.println("ID = "+ myMBeanServer.getAttribute(
            new ObjectName(ServiceName.DELEGATE), "MBeanServerId"));
    } catch (Exception e) {
        e.printStackTrace();
        System.exit(1);
    }
}

In the first statement of the constructor, we enable tracing messages for the agent. The tracing enables us to see internal information at runtime, which is useful for debugging. See Setting Trace Messages for specifying tracing parameters on the command line. Then we create the MBean server through its static factory class (see MBean Server Implementation and Factory). Its reference is stored in a variable with class-wide scope so that all internal methods have access to the MBean server. Finally, we retrieve the MBean server identification string, for informational purposes only.

After the MBean server is initialized, we will create the same three communication MBeans that we created in the minimal agent.

Creating an MBean (Method 1)

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 8–2 and the following code samples highlights the important statements that vary between the three methods.


Example 8–2 Creating an MBean (Method 1)

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

try {
    // We know that the HTML adaptor provides a default object name
    ObjectInstance htmlAdaptorInstance =
        myMBeanServer.registerMBean(htmlAdaptor, null);
    echo("CLASS NAME  = " + htmlAdaptorInstance.getClassName());
    echo("OBJECT NAME = " + 
    htmlAdaptorInstance.getObjectName().toString());
} catch(Exception e) {
    e.printStackTrace();
    System.exit(0);
}

// Now we need to start the html protocol adaptor explicitly
htmlAdaptor.start();
    
while (htmlAdaptor.getState() == CommunicatorServer.STARTING) {
    sleep(1000);
}
echo("STATE = " + 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.

In the minimal agent, we saw that 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, 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.

Creating an MBean (Method 2)

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


Example 8–3 Creating an MBean (Method 2)

ObjectInstance httpConnectorInstance = null;
try {
    String httpConnectorClassName = "com.sun.jdmk.comm.HttpConnectorServer";
    // Let the HTTP connector server provide its default name
    httpConnectorInstance =
        myMBeanServer.createMBean(httpConnectorClassName, null);
} catch(Exception e) {
    e.printStackTrace();
    System.exit(0);
}
// We need the object name to refer to our MBean
ObjectName httpConnectorName = httpConnectorInstance.getObjectName();
echo("CLASS NAME  = " + httpConnectorInstance.getClassName());
echo("OBJECT NAME = " + httpConnectorName.toString());

// Now we demonstrate the bulk getter of the MBean server
try {
    String att1 = "Protocol";
    String att2 = "Port";
    String attNames[]= {att1, att2};
    AttributeList attList =
        myMBeanServer.getAttributes(httpConnectorName, attNames);
    Iterator attValues = attList.iterator();
    echo("\t" + att1 + "\t=" + ((Attribute) attValues.next()).getValue());
    echo("\t" + att2 + "\t=" + ((Attribute) attValues.next()).getValue());

} catch (Exception e) {
    e.printStackTrace();
    System.exit(0);
}

// Now we explicitly start the HTTP connector server
try {
    myMBeanServer.invoke(httpConnectorName, "start",
                         new Object[0], new String[0]);

    // waiting to leave starting state...
    while (new Integer(CommunicatorServer.STARTING).equals
           (myMBeanServer.getAttribute(httpConnectorName,"State"))) {
        sleep(1000);
    }
    echo("STATE = " + 
         myMBeanServer.getAttribute(httpConnectorName, "StateString"));
} catch (Exception e) {
    e.printStackTrace();
    System.exit(0);
}
[...]

One 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 14, 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.

Creating an MBean (Method 3)

The last way of creating an MBean relies on the instantiate method of the MBean server. In addition, we use a nondefault constructor to instantiate the class with a different behavior.


Example 8–4 Creating an MBean (Method 3)

CommunicatorServer rmiConnector = null;
Object[] params = {new Integer(8086)};
String[] signature = {new String("int")};
try {
    String RmiConnectorClassName = "com.sun.jdmk.comm.RmiConnectorServer";
    // specify the RMI port number to use as a parameter to the constructor
    rmiConnector = (CommunicatorServer)myMBeanServer.instantiate(
                       RmiConnectorClassName, params, signature);
} catch(Exception e) {
    e.printStackTrace();
    System.exit(0);
}

try {
    // Let the RMI connector server provides its default name
    ObjectInstance rmiConnectorInstance =
        myMBeanServer.registerMBean(rmiConnector, null);
    
    // Confirm the class and default object name
    echo("CLASS NAME  = " + rmiConnectorInstance.getClassName());
    echo("OBJECT NAME = 
	   " + rmiConnectorInstance.getObjectName().toString());
} catch(Exception e) {
    e.printStackTrace();
    System.exit(0);
}

// Now we explicitly start the RMI connector server
rmiConnector.start();

// waiting to leave starting state...
while (rmiConnector.getState() == CommunicatorServer.STARTING) {
    sleep(1000);
}
echo("STATE = " + rmiConnector.getStateString());

// Check that the RMI connector server is started
if (rmiConnector.getState() != CommunicatorServer.ONLINE) {
    echo("Cannot start the RMI connector server");
    System.exit(0);
}
[...]

As in Example 8–2, we instantiate and register the MBean in separate steps. First, we 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.

In our case, we specify an integer parameter to set the port through one of the constructors of the RmiConnectorServer class. Then, we register the MBean with the registerMBean method of the MBean server, as in Example 8–2.

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 14, M-Let Class Loader for more information on class loading.

Because we do not take advantage of the class loaders here, we could have just called the class's constructor directly. The main advantage 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.

There are other combinations of instantiating and registering MBeans for achieving the same result. For example, we could use the default constructor and then set the port attribute of the MBean before starting it. Other combinations are left as an exercise to the reader.

Managing MBeans

In Creating an MBean (Method 2), we rely totally on the MBean server to create and access an MBean. The code example in that section demonstrates how to 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 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 8–5 Processing MBean Information

private MBeanServer server = null; // assigned by MBeanServerFactory

private void printMBeanInfo(ObjectName name) {

    echo("Getting the management information for  " + name.toString() );
    MBeanInfo info = null;

    try {
        info = server.getMBeanInfo(name);
    } catch (Exception e) {
        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();
    // Note: the class necessarily has at least one constructor
    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());
        }
    } 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.

Filtering MBeans

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 8–6 Unregistering MBeans

public void removeAllMBeans() {

    try {
        Set allMBeans = myMBeanServer.queryNames(null, null);
        for (Iterator i = allMBeans.iterator(); i.hasNext();) {
            ObjectName name = (ObjectName) i.next();

            // Do not unregister the MBean server delegate
            if ( ! name.toString().equals( ServiceName.DELEGATE ) ) {
                echo("Unregistering " + name.toString() );
                myMBeanServer.unregisterMBean(name);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
        System.exit(0);
    }
}

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 described in 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.

Running the Base Agent Example

The examplesDir/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/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 Running the Standard MBean Example and 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
    

Setting Trace Messages

Because the base agent enables internal tracing (see Example 8–1), you can also set the trace level and trace output on the command line. The tracing mechanism is explained in the Java Dynamic Management Kit 5.0 Tools Reference and in the Javadoc API of the com.sun.jdmk.TraceManager class (for receiving traces) and the com.sun.jdmk.trace.Trace class (for producing traces). The simplest way to get the default tracing is to specify the filename for a trace log on the java command line:


$ java -classpath classpath -DTRACE_OUTPUT=filename BaseAgent

Agent Output

In addition to any trace information, this agent displays output for the three types of MBean creation.

When the connection MBeans have been created, it is possible to connect to the agent through one of the protocols. Management applications can connect through the HTTP and RMI connector servers, as described in Connector Servers. If you connect to the base agent through the HTML adaptor, you could follow the same procedures as with the minimal agent.

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