Java Dynamic Management Kit 4.1 Tutorial

Part IV Agent Services

The minimal and base agents presented earlier in this tutorial are manageable but are created empty. In this lesson we will examine agent services, MBeans that interact with your resources to provide management intelligence at the agent level. These services allow your agents to perform local operations on their resources that used to be performed by the remote manager. This frees you from having to implement them in the management application, and it reduces the amount of communication throughout your management solution.

In the Java Dynamic Management architecture, agents perform their own monitoring, avoiding constant manager polling. Agents also handle their own logical relations between objects, providing advanced operations that no longer require a relational database in the manager.

Java Dynamic Management agents are also much smarter, using discovery to be aware of their peers. They also connect to other agents to mirror remote resources, thus providing a single point of entry into a whole hierarchy of agents. Finally, agents are freed from their initial environment settings through dynamic downloading, allowing managers to effectively push new classes to an agent. As new resources and new services are developed, they can be loaded into deployed agents, boosting their capabilities without impacting their availability.

The power of agent services is multiplied by their whole dynamic nature. Agent services can come and go as they are required, either as determined by the needs of agent's smart resources or on demand from the management application. The agent capability of self-management through the services is completely scalable: small devices might only allow monitors with some logic, whereas a server might embed the algorithms and networking capabilities to oversee its terminals autonomously.

This lesson contains the following topics:

Chapter 8 The M-Let Class Loader

The "Dynamic" in Java Dynamic Management Kit not only stands for dynamic loading, it also stands for dynamic downloading. The agent service that provides this functionality is the m-let class loader. M-let stands for management applet, an HTML-style tag that tells the class loader how to retrieve the desired class. Using this information, the m-let loader can retrieve an MBean class from a remote location given as a URL (uniform resource locator) and create it in the agent.

The m-let resides in a separate text file which acts as a loading manifest. The contents of the m-let file let you specify any number of classes to load, possibly a different source for each, arguments for the class constructor, and the object name for the instantiated MBean. Since this mechanism is sometimes too heavy, the m-let loader can also be used to load classes directly and create MBeans in the agent.

The m-let loader is a service implemented as an MBean, so it can be called either directly by the agent or remotely by a management application. It can also be managed remotely, which allows a manager to effectively "push" MBeans to an agent: the manager instantiates the m-let loader in an agent and instructs it to load classes from a predetermined location.

Class loading is significantly different between Java 2 and JDK 1.1.x, and this leads to different implementations of the m-let loader. We cover these in separate sections. The example code in the MLetAgent and MLetClient directories of the main examplesDir corresponds to the Java version which you specified during installation (see "Directories and Classpath" in the preface).

Contents:

The M-Let Loader (JDK 1.1)

In an agent application, we might need to load MBeans from remote hosts during the initialization. Or we might have local threads which need to load MBeans in the agent. In this example we demonstrate how to create the m-let loader service and use it to dynamically load new MBean classes.

In an installation of the Java Dynamic Management Kit for the JDK 1.1.x, the m-let loader is the MLetSrv class in the javax.management.loading package. It is an MBean which needs to be registered in the MBean server before we can use it to load classes.


Example 8-1 Instantiating the MLetSrv Class

MBeanServer server = MBeanServerFactory.createMBeanServer();

// Get the domain name from the MBeanServer.
String domain = server.getDefaultDomain();

// Create a new MLetSrv MBean and add it to the MBeanServer.
String mletClass = "javax.management.loading.MLetSrv";
ObjectName mletName = new ObjectName(domain + ":name=" + mletClass);
server.createMBean(mletClass, mletName);

There is no special initialization that needs to be performed for the m-let loader.

Loading MBeans from a URL

In order to download an MBean, we must first have its corresponding m-let definition in an HTML file. In our example, we define the following file with three MLET tags:


Example 8-2 The M-Let File

<HTML>
<MLET
  CODE=Square.class
  ARCHIVE=Square.jar
  NAME=MLetExample:name=Square,id=1
>
<ARG TYPE=java.lang.Integer VALUE=10>
</MLET>
<MLET
  CODE=EquilateralTriangle.class
  ARCHIVE=EquilateralTriangle.jar
  NAME=MLetExample:name=EquilateralTriangle,id=1
>
<ARG TYPE=java.lang.Integer VALUE=8>
</MLET>
<MLET
  CODE=EquilateralTriangle.class
  ARCHIVE=EquilateralTriangle.jar
  NAME=MLetExample:name=EquilateralTriangle,id=2
>
<ARG TYPE=java.lang.Integer VALUE=15>
</MLET>
</HTML>

This file tells the m-let loader to create three MBeans with the given object names, using the given classes in the jar files. The jar files must be located in the same directory as this file, regardless of whether the directory is on a local or remote host. The MLET tag may also specify a CODEBASE, which is an alternate location for the jar file. The MLET tag is fully defined in the JMX specification.

Now we are ready to call the performLoadURL method of our m-let loader. In parsing the result vector, we use our knowledge of the class names that we wrote in the m-let file.


Example 8-3 Calling the performLoadURL Method

ObjectName squareMLetClassLoader = null;
ObjectName triangleMLetClassLoader = null;

// The url string is read from the command line
Object mletParams[] = {url};
String mletSignature[] = {"java.lang.String"};
Vector mbeanList = (Vector) server.invoke(
    mletName, "performLoadURL", mletParams, mletSignature);

for (Enumeration enum = mbeanList.elements();
     enum.hasMoreElements(); ) {
    Object element = enum.nextElement();
    if (element instanceof Vector) {
        // Success, we retrieve the new object name
        Vector v = (Vector) element;
        ObjectInstance objectInstance = (ObjectInstance) v.elementAt(0);
        ObjectName classLoaderObjectName = (ObjectName) v.elementAt(1);
        if (objectInstance.getClassName().equals("Square")) {
            // Retrieve MBean that loaded the class Square
            squareMLetClassLoader = classLoaderObjectName;

        } else if (objectInstance.getClassName().equals(
                       "EquilateralTriangle")) {
            // Retrieve MBean that loaded the class EquilateralTriangle
            triangleMLetClassLoader = classLoaderObjectName;
        }
        echo("\tOBJECT NAME = " + objectInstance.getObjectName());
    } else {
        // Failure, find out why
        echo("\tEXCEPTION = " + ((Throwable)element).getMessage());
    }
}

The result of the call to performLoadURL is a Vector object containing as many elements as there are MLET tags in the file designated by the URL. Each element is either a vector containing the object instance of the new MBean and the object name of its class loader, or a Throwable object containing the exception or error that prevented the MBean from being loaded.

In the result, we obtain the object name of the MBeans that were created from the downloaded classes. The management architecture specified by JMX is designed so that objects are manipulated through the MBean server, not by direct reference. Therefore, downloaded classes are directly registered in the MBean server by the m-let loader, and the caller never receives a direct reference to the new object.

The object names of the class loaders are references to the internal class loader objects used by the m-let service to actually fetch the classes. We save them because they can be used if we ever need to instantiate these classes again. We will see how in the next section.

Shortcut for Loading MBeans

Loading MBeans from a URL requires some preparation and additional files. In some cases, we don't have the ability to create files ahead of time or modify them when we need different classes. In these cases, we would just like to load a class from a jar file and create its MBean.

The MLetSrv is not a class loader, we only ask it to load a class from a URL and it instantiates its private class loader for doing this. Even though the internal class loader object used by the m-let loader is a public type, it should not be instantiated to act as a class loader. The m-let loader stores internal information about its private class loaders, and it won't be able to handle one outside of its control.

Instead, use the class loader name that is returned when an MBean is successfully loaded. You can specify this class loader name when creating a class through the MBean server. You will be able to create new MBeans from the same class or from other classes in the associated archive (jar file).

This implies that you must first call the performLoadURL with a known URL and a known m-let file. The m-let loader will create one class loader for each code-base specified in the file, and one for the code-base of the file itself. For example, the class loader name returned with the "Square" MBean name is the one used to load its class from the Square.jar file in the same directory as the HTML file. We can create other instances of that MBean now just through the MBean server, without needing to call the m-let loader.

The following code sample uses the object name references that were declared and assigned in Example 8-3.


Example 8-4 Loading Classes Directly

// Create a new Square MBean from its class in the Square.jar file
String squareClass = "Square";
ObjectName squareName = new ObjectName(
    "MLetExample:name=" + squareClass + ",id=2");
Object squareParams[] = {new Integer(12)};
String squareSignature[] = {"java.lang.Integer"};
server.createMBean(squareClass, squareName, squareMLetClassLoader,
                   squareParams, squareSignature);

// Create a new EquilateralTriangle MBean from its class in the
// EquilateralTriangle.jar file
String triangleClass = "EquilateralTriangle";
ObjectName triangleName = new ObjectName(
    "MLetExample:name=" + triangleClass + ",id=3");
Object triangleParams[] = {new Integer(20)};
String triangleSignature[] = {"java.lang.Integer"};
server.createMBean(triangleClass, triangleName, triangleMLetClassLoader,
                   triangleParams, triangleSignature);

Loading classes directly in this manner implies that the code of the agent or of the MBean must be programmed with the knowledge of the class named in the m-let file, and if needed, the knowledge of other classes in the jar file from which the class was finally loaded.

Running the M-Let Agent Example

To run the m-let agent example for the JDK 1.1.x, you must have installed the Java Dynamic Management Kit for 1.1, and set your classpath accordingly. This example is located in the examplesDir/MLetAgent/ directory, see "Directories and Classpath" in the preface for details.

In our example, we have two MBeans representing geometrical shapes. Before running the example, we compile them and create a jar file for each. We also compile the agent application at the same time.


$ cd examplesDir/MLetAgent/
$ javac -classpath classpath *.java

$ jar cf Square.jar Square.class SquareMBean.class
$ rm Square.class SquareMBean.class

$ jar cf EquilateralTriangle.jar EquilateralTriangle.class \
EquilateralTriangleMBean.class
$ rm EquilateralTriangle.class EquilateralTriangleMBean.class

Since the MBean classes are only found in the jar files now, they cannot be found in our usual classpath, even if it includes the current directory (.). However, these jar files are given as the archive in the MLET tags of the HTML file, so the m-let loader should find them.

The agent requires you to specify the URL of the m-let file on command line. We have left this file in the examples directory, but you could place it and the jar files on a remote machine. With the Korn shell on the Solaris platform, you would type the following command:


$ java -classpath classpath Agent file:${PWD}/GeometricShapes.html

In the output of the agent, you can see it create the m-let service MBean, and then load the HTML file which specifies the three MBeans to be loaded. Once these have been loaded, we can see the two MBeans that were loaded directly through the class loader shortcut.

This agent uses the tracing mechanism, and you can select to receive the messages from the m-let loader by specifying the -DINFO_MLET property on the command line. The tracing mechanism is covered in the Java Dynamic Management Kit 4.1 Tools Reference guide and in the Javadoc API of the Trace class.

The agent then launches an HTML adaptor so that we can easily view the new MBeans. In them we can see that the values contained in the ARG tags of the m-let file were used to initialize the MBeans. Point your web browser to the following URL and click on the MBeans in the MLetExample domain:http://localhost:8082/. When you are finished, type <Control-C> in the window where you launched the agent.

M-Let Loading from a Manager (JDK1.1)

Like the other agent services of the Java Dynamic Management Kit, the m-let loader is an MBean and fully manageable from a remote manager. A manager application might want an agent to load a new MBean to represent a new resource or provide a new management service. In this example, we show a manager which interacts with the m-let loader of an agent to load exactly the same MBeans as in the previous example.

The agent that we will manage only contains an RMI connector and an HTML adaptor when it is launched. We will use the RMI connector to access the agent and perform all of our management operations. You can then view the new MBeans through the HTML adaptor.

The manager is just a simple application which creates its connector client, does its management operations and exits. Here is the code of its main method and the constructor that it calls.


Example 8-5 The main Method of the M-Let Manager

public Client() {
    
    // Enable the trace mechanism
    [...]

    // Connect a new RMI connector client to the agent
    connectorClient = new RmiConnectorClient();

    // Use the default address (localhost)
    RmiConnectorAddress address = new RmiConnectorAddress();
    try {
        connectorClient.connect(address);
    } catch (Exception e) {
        echo("Could not connect to the agent!");
        e.printStackTrace();
        System.exit(1);
    }
}

public static void main(String[] args) {

    // Parse command line arguments.
    [...]

    // Call the constructor to establish the connection
    Client client = new Client();

    // Run the MLet example (see below)
    client.runMLetExample();

    // Disconnect connector client from the connector server.
    client.connectorClient.disconnect();

    System.exit(0);
}

Asking the Agent to Load Classes

Now that the manager is connected to the client, we can "push" classes to it. We do this by first creating an m-let loader service, then having that loader create MBeans from the classes designated by our HTML file. The following code is taken from manager's runMLetExample method. The code is identical to the code of the agent example, except that we now go through the RemoteMBeanServer interface of the connector client instead of directly through the MBean server.


Example 8-6 Calling the performLoadURL Method Remotely

// Get the domain name from the Agent
String domain = connectorClient.getDefaultDomain();

// Create a new MLetSrv MBean and add it to the Agent
String mletClass = "javax.management.loading.MLetSrv";
ObjectName mletName = new ObjectName(domain + ":name=" + mletClass);
connectorClient.createMBean(mletClass, mletName);
[...]

// Create and register new Square and EquilateralTriangle MBeans
// by means of an HTML document containing MLET tags
// The url string is read from the command line
ObjectName squareMLetClassLoader = null;
ObjectName triangleMLetClassLoader = null;

Object mletParams[] = {url};
String mletSignature[] = {"java.lang.String"};
Vector mbeanList = (Vector) connectorClient.invoke(
    mletName, "performLoadURL", mletParams, mletSignature);

for (Enumeration enum = mbeanList.elements(); enum.hasMoreElements(); ) {
    Object element = enum.nextElement();
    if (element instanceof Vector) {
        // Success, we retrieve the new object name
        Vector v = (Vector) element;
        ObjectInstance objectInstance = (ObjectInstance) v.elementAt(0);
        ObjectName classLoaderObjectName = (ObjectName) v.elementAt(1);
        if (objectInstance.getClassName().equals("Square")) {
            // Retrieve the MBean that loaded the Square

            squareMLetClassLoader = classLoaderObjectName;
        } else if (objectInstance.getClassName().equals(
                       "EquilateralTriangle")) {
            // Retrieve the MBean that loaded the EquilateralTriangle
            triangleMLetClassLoader = classLoaderObjectName;
        }
        echo("\tOBJECT NAME = " + objectInstance.getObjectName());
    } else {
        // Failure, find out why
        echo("\tEXCEPTION = " + ((Throwable)element).getMessage());
    }
}

As in the agent application, we may need a shortcut for instantiating other MBeans without specifying an m-let file. Again, we can use an existing class loader from a previously loaded class to download the same classes again. We use the createMBean method of the connector client which lets us specify a class loader name. The following code is the rest of the manager's runMLetExample method, and it is also nearly identical to the agent's code.


Example 8-7 Asking the Agent to Load Classes Directly

// Create a new Square MBean from its class in the Square.jar file.
String squareClass = "Square";
ObjectName squareName = new ObjectName(
    "MLetExample:name=" + squareClass + ",id=2");
Object squareParams[] = {new Integer(12)};
String squareSignature[] = {"java.lang.Integer"};
connectorClient.createMBean(squareClass, squareName,
    squareMLetClassLoader, squareParams, squareSignature);

// Create a new EquilateralTriangle MBean from its class in the
// EquilateralTriangle.jar file.
String triangleClass = "EquilateralTriangle";
ObjectName triangleName = new ObjectName(
    "MLetExample:name=" + triangleClass + ",id=3");
Object triangleParams[] = {new Integer(20)};
String triangleSignature[] = {"java.lang.Integer"};
connectorClient.createMBean(triangleClass, triangleName,
    triangleMLetClassLoader, triangleParams, triangleSignature);

Simulating a "push" of the MBeans in this way is plausible, since the management application can specify a URL where it controls the contents of the HTML file and knows which classes are available.

Running the M-Let Manager Example

The MBeans in the agent and manager (client) examples are identical, and we will set up the example in exactly the same manner.


$ cd examplesDir/MLetClient/
$ javac -classpath classpath *.java

$ jar cf Square.jar Square.class SquareMBean.class
$ rm Square.class SquareMBean.class

$ jar cf EquilateralTriangle.jar EquilateralTriangle.class \
EquilateralTriangleMBean.class
$ rm EquilateralTriangle.class EquilateralTriangleMBean.class

The manager is written to be run on the same host as the agent application. If you wish to run it on a different host, you will need to modify the code for the Client class constructor where the agent address is specified (see Example 8-5). You could place the jar files and the m-let file on a remote machine and specify its new URL as the parameter to the manager application; we run the example with this file in the current directory.

Before launching the manager, you must launch the agent. Here we give commands for launching the applications from the same terminal window running the Korn shell. On the Windows NT platform, you will have to launch each application in a separate window.


$ java -classpath classpath Agent &
$ java -classpath classpath Client file:${PWD}/GeometricShapes.html

In the output of the manager, you can see it create the m-let service MBean, and then ask it to load the HTML file. Finally, we can see the two MBeans that were loaded directly through the class loader shortcut. If you connect to the agent in a web browser at the following URL: http://localhost:8082/ and reload its agent view every time the manager pauses, you can see the MBeans as they are created.

The agent terminates after it disconnects its connector client. When you are finished viewing the agent, type the following commands to stop the agent application:


$ fg
java [...] Agent <Control-C>
^C$ 

The M-Let Loader (Java 2)

In the version of the Java Dynamic Management Kit for Java 2, the m-let loader is itself a class loader object. It extends the URLClassLoader class of the java.net package to simplify the downloading service it provides.

The m-let loader service is an instance of the MLet class in the javax.management.loading package. It is also an MBean that can be accessed remotely. It provides m-let file loading and the shortcut method seen in the version for the JDK 1.1. In addition, it inherits the behavior which lets it be used directly as a class loader, without requiring an m-let file.

We will start by demonstrating the usage of the m-let service as it would be used in an agent or in an MBean. In our example, the agent application creates an MBean server and then the m-let loader.


Example 8-8 Instantiating the MLet Class

// Parse debug properties and command line arguments.
[...]

// Instantiate the MBean server
MBeanServer server = MBeanServerFactory.createMBeanServer();
String domain = server.getDefaultDomain();

// Create a new MLet MBean and add it to the MBeanServer.
String mletClass = "javax.management.loading.MLet";
ObjectName mletName = new ObjectName(domain + ":name=" + mletClass);
server.createMBean(mletClass, mletName);

There is no special initialization that needs to be performed before loading classes through an m-let file.

Loading MBeans from a URL

In this example we will only load EquilateralTriangle MBeans through the m-let file. We use the same m-let file which is shown in Example 8-2, but without the tags for the Square MBeans.

The code for downloading the MBeans specified in the m-let file is also similar. In the Java 2 version of the m-let loader, only the name of the method to call and the format of its return value is different. In this code we call the getMBeansFromURL method and analyze the result:


Example 8-9 Calling the getMBeansFromURL Method

// the url_2 string is read from the command line
echo("\tURL = " + url_2);
Object mletParams_2[] = {url_2};
String mletSignature_2[] = {"java.lang.String"};
Set mbeanSet = (Set) server.invoke(mletName, "getMBeansFromURL",
     mletParams_2, mletSignature_2);

for (Iterator i = mbeanSet.iterator(); i.hasNext(); ) {
    Object element = i.next();
    if (element instanceof ObjectInstance) {
        // Success, we display the new MBean's name
        echo("\tOBJECT NAME = " + ((ObjectInstance)element).getObjectName());
    } else {
        // Failure, we display why
        echo("\tEXCEPTION = " + ((Throwable)element).getMessage());
    }
}

The structure of the returned set is much simpler than in the case of the JDK 1.1 loader. In the JDK 1.1 version, the m-let loader handles separate class loader objects, one for each code-base it has accessed. In the Java 2 version, the m-let loader is the class loader, and it handles just a list of code-bases that it has accessed directly. You can view this list by calling the getURLs method of the m-let loader MBean.

This behavior means that the getMBeansFromURL method does not need to return the object names of class loaders it has used. Instead it just returns either the object instance of the downloaded and registered MBean or a Throwable object in case or an error or an exception. These are returned in a Set object containing as many elements as there are MLET tags in the target m-let file.

Shortcut for Loading MBeans

This behavior also simplifies any repeated loading of the classes after they have been loaded from an m-let file. Because the m-let loader has already used the code-base of the MBean, it is available to be used again. All you need to do is specify the object name of the m-let loader as the class loader when creating the MBean.

You can also load other MBeans in the same code-base, once the code-base has been accessed by a call to the getMBeansFromURL method. In our example we will just download another MBean of the EquilateralTriangle class.


Example 8-10 Reloading Classes in the M-Let Class Loader

// Create another EquilateralTriangle MBean from its class
// in the EquilateralTriangle.jar file.
String triangleClass = "EquilateralTriangle";
ObjectName triangleName = new ObjectName(
    "MLetExample:name=" + triangleClass + ",id=3");
Object triangleParams[] = {new Integer(20)};
String triangleSignature[] = {"java.lang.Integer"};

server.createMBean(triangleClass, triangleName, mletName,
                   triangleParams, triangleSignature);

Again, loading classes from known code-bases or reloading a class directly from its jar file implies that the agent or MBean programmer has some knowledge of the code-bases and jar file contents at runtime.

Loading MBeans Directly

Since the m-let loader object is a class loader, you can use it to load classes directly, without needing to define an m-let file. This is the main advantage of the Java 2 version of the m-let loader service.

Before you can load an MBean directly, you need to add the URL of its code-base to the m-let loader's internal list. Then we just use the m-let loader's object name as the class loader name when creating the MBean. Here is the code to do this in the agent example:


Example 8-11 Using the M-Let MBean as a Class Loader

// Add a new URL to the MLet class loader
// The url_1 string is read from the command line
Object mletParams_1[] = {url_1};
String mletSignature_1[] = {"java.lang.String"};
server.invoke(mletName, "addURL", mletParams_1, mletSignature_1);

// Create a Square MBean from its class in the Square.jar file.
String squareClass = "Square";
ObjectName squareName = new ObjectName(
    "MLetExample:name=" + squareClass);
Object squareParams[] = {new Integer(10)};
String squareSignature[] = {"java.lang.Integer"};
server.createMBean(squareClass, squareName, mletName,
                   squareParams, squareSignature);

You only need to add the URL to the m-let loader the first time you want to download a class. Once it is added, we can load it as many times as necessary by calling createMBean directly.

Since this loading mechanism doesn't use the MLET tag, the programmer must insure that either the downloaded class provides its own object name or, as in the example above, the agent provides one.

The fact that the m-let loader is also a class loader into which you can load multiple URLs brings up the issue of name spaces. If there exists two classes with the same name within the code-bases defined by the set of all URLs, the m-let loader will load one of them non-deterministically. In order to specify one of them precisely, you shouldn't add the URL of the second code-base to the m-let loader. Instead, you will have to create a second m-let loader MBean to which you can add the URL for the second version of the class. In this case, you will have one m-let MBean that can load one version of the class and another m-let MBean that can load the other.

Running the M-Let Agent Example

To run the m-let agent example for Java 2, you must have installed the Java Dynamic Management Kit for 1.2, and set your classpath accordingly. This example is located in the examplesDir/MLetAgent/ directory, see "Directories and Classpath" in the preface for details.

In our example, we have two MBeans representing geometrical shapes. Before running the example, we compile them and create a jar file for each. We also compile the agent application at the same time.


$ cd examplesDir/MLetAgent/
$ javac -classpath classpath *.java

$ jar cf Square.jar Square.class SquareMBean.class
$ rm Square.class SquareMBean.class

$ jar cf EquilateralTriangle.jar EquilateralTriangle.class \
EquilateralTriangleMBean.class
$ rm EquilateralTriangle.class EquilateralTriangleMBean.class

The agent command line requires you to specify first the URL of a jar file for directly loading the Square class, then the URL of the m-let file. We have left these files in the examples directory, but you could place them on a remote machine. With the Korn shell on the Solaris platform, you would type the following command:


$ java -classpath classpath Agent \ 
file:${PWD}/Square.jar file:${PWD}/GeometricShapes.html

In the output of the agent, you can see it create the m-let loader MBean, and then download classes to create MBeans. It starts with the direct loading of the Square class, and then loads from the HTML file which specifies two EquilateralTriangle MBeans to be loaded. Once these have been loaded, we can see the third one that is loaded through the class loader shortcut.

This agent uses the tracing mechanism, and you can select to receive the messages from the m-let loader by specifying the -DINFO_MLET property on the command line. The tracing mechanism is covered in the Java Dynamic Management Kit 4.1 Tools Reference guide and in the Javadoc API of the Trace class.

The agent then launches an HTML adaptor so that we can easily view the new MBeans. In them we can see that the values contained in the ARG tags of the m-let file were used to initialize the MBeans. Point your web browser to the following URL and click on the MBeans in the MLetExample domain: http://localhost:8082/. When you are finished, type <Control-C> in the window where you launched the agent.

M-Let Loading from a Manager (Java 2)

Since the MLet class is an MBean, the m-let loader service is fully manageable from a remote manager. This lets a manager create an m-let loader in an agent, add URLs to its list of code-bases, and create MBeans whose classes must be downloaded first.

Using the Java 2 m-let class loader, we can again implement a "push" mechanism originating from a management application. In fact, it is even easier due to the direct class loading that the MLet MBean allows.

In the example, our manager will create an m-let loader in an agent and have it load new MBean classes. Launched with only the an RMI connector and an HTML adaptor, we can see at the end that the agent contains all of the new MBeans loaded from jar files, along with the m-let loader MBean. The initialization of manager is very simple:


Example 8-12 The main Method of the Manager Application

public Client() {
    
    // Enable the trace mechanism
    [...]

    // Connect a new RMI connector client to the agent
    connectorClient = new RmiConnectorClient();

    // Use the default address (localhost)
    RmiConnectorAddress address = new RmiConnectorAddress();
    try {
        connectorClient.connect(address);
    } catch (Exception e) {
        echo("Could not connect to the agent!");
        e.printStackTrace();
        System.exit(1);
    }
}

public static void main(String[] args) {

    // Parse command line arguments.
    [...]

    // Call the constructor to establish the connection
    Client client = new Client();

    // Run the MLet example (see below)
    client.runMLetExample();

    // Disconnect connector client from the connector server.
    client.connectorClient.disconnect();

    System.exit(0);
}

Asking the Agent to Load Classes

Now that the manager is connected to the client, we can ask it to load classes. First we have it create an m-let loader MBean that we can use to download classes. Then we demonstrate the various ways of loading classes:

The following code is taken from manager's runMLetExample method. The code is identical to the code of the agent example, except that we now go through the RemoteMBeanServer interface of the connector client instead of directly through the MBean server.


Example 8-13 Calling the getMBeansFromURL Method Remotely

// Get the domain name from the MBeanServer.
String domain = connectorClient.getDefaultDomain();

// Create a new MLet MBean and add it to the MBeanServer
String mletClass = "javax.management.loading.MLet";
ObjectName mletName = new ObjectName(domain + ":name=" + mletClass);
connectorClient.createMBean(mletClass, mletName);

[...]

// Create new EquilateralTriangle MBeans through MLET tags
// The url_2 string is read from the command line
Object mletParams_2[] = {url_2};
String mletSignature_2[] = {"java.lang.String"};
Set mbeanSet = (Set) connectorClient.invoke(
    mletName, "getMBeansFromURL", mletParams_2, mletSignature_2);

for (Iterator i = mbeanSet.iterator(); i.hasNext(); ) {
    Object element = i.next();
    if (element instanceof ObjectInstance) {
        // Success
        echo("OBJECT NAME = " + ((ObjectInstance)element).getObjectName());
    } else {
        // Failure
        echo("EXCEPTION = " + ((Throwable)element).getMessage());
    }
}

Now that the class loader has used the code-base of the jar file, we can create more of the MBeans from the same jar file. We invoke the createMBean method of the server with the object name of the class loader.


Example 8-14 Reloading MBeans from an Existing Code-Base

// Create another EquilateralTriangle MBean from the same jar file
// used in the MLET file
String triangleClass = "EquilateralTriangle";
ObjectName triangleName = new ObjectName(
    "MLetExample:name=" + triangleClass + ",id=3");
Object triangleParams[] = {new Integer(20)};
String triangleSignature[] = {"java.lang.Integer"};
connectorClient.createMBean(triangleClass, triangleName, mletName,
                            triangleParams, triangleSignature);

Finally, if we have a different code-base not associated with an m-let file, we can give its URL directly to the loader. This allows us to ask the agent to create almost any MBean, imitating a class "push" mechanism.


Example 8-15 Implementing a "Push" Operation

// Add a new URL to the MLet MBean to look for classes
// The url_1 string is read from the command line
Object mletParams_1[] = {url_1};
String mletSignature_1[] = {"java.lang.String"};
connectorClient.invoke(mletName, "addURL",
                       mletParams_1, mletSignature_1);

// Create a new Square MBean from its class in the Square.jar file
String squareClass = "Square";
ObjectName squareName = new ObjectName(
    "MLetExample:name=" + squareClass);
Object squareParams[] = {new Integer(10)};
String squareSignature[] = {"java.lang.Integer"};
connectorClient.createMBean(squareClass, squareName, mletName,
                            squareParams, squareSignature);

In this way, the manager can make code available on the network, and it can direct its agents to load the classes to create new MBeans ready for management. This mechanism can be used to distribute new resources, provide new services,or update applications, all under the control of the manager.

Running the M-Let Manager Example

The MBeans in the agent and manager (client) examples are identical, and we will set up the example in exactly the same manner.


$ cd examplesDir/MLetClient/
$ javac -classpath classpath *.java

$ jar cf Square.jar Square.class SquareMBean.class
$ rm Square.class SquareMBean.class

$ jar cf EquilateralTriangle.jar EquilateralTriangle.class \
EquilateralTriangleMBean.class
$ rm EquilateralTriangle.class EquilateralTriangleMBean.class

The manager is written to be run on the same host as the agent application. If you wish to run it on a different host, you will need to modify the code for the Client class constructor where the agent address is specified (see Example 8-12). You could place the jar files and the m-let file on a remote machine and specify its new URL as the parameter to the manager application; we run the example with this file in the current directory.

Before launching the manager, you must launch the agent. Here we give commands for launching the applications from the same terminal window running the Korn shell. On the Windows NT platform, you will have to launch each application in a separate window.


$ java -classpath classpath Agent &
$ java -classpath classpath Client \ 
file:${PWD}/Square.jar file:${PWD}/GeometricShapes.html

In the output of the manager, you can see it create the m-let service MBean, and then load all of the MBeans from different sources. If you connect to the agent in a web browser at the following URL: http://localhost:8082/ and reload its agent view every time the manager pauses, you can see the MBeans as they are created.

The agent terminates after it disconnects it connector client. When you are finished viewing the agent, type the following commands to stop the agent application:


$ fg
java [...] Agent <Control-C>
^C$ 

Chapter 9 The Relation Service

The relation service defines and maintain logical relations between MBeans in an agent. It acts as central repository of relation types and relation instances, and it ensures the consistency of all relations it contains. Local and remote applications that access the agent can define new types of relations and then declare a new instance of a relation between MBeans.

You may only create relations that respect a known relation type, and the service allows you to create new relation types dynamically. Then you can access the relations and retrieve the MBeans in specific roles. The relation service listens for MBeans being deregistered and removes relation instances that no longer fulfill their relation type. It sends notifications of its own to signal when relation events occur.

The Java Dynamic Management Kit also exposes the interfaces classes for defining your own relation objects. By creating relation instances as objects, you can implement them as MBeans which can expose operations on the relation they represent.

Like the other services, the relation service is instrumented as an MBean, allowing remote access and management.

The code samples in this topic are taken from the files in the Relation directory located in the main examplesDir (see "Directories and Classpath" in the preface).

Contents:

Defining Relations

A relation is composed of named roles, each of which defines the cardinality and class of MBeans that will be put into association with the other roles. A set of one or more roles defines a relation type. The relation type is a template for all relation instances that wish to associate MBeans representing its roles. We use the term relation to mean a specific instance of a relation that associates existing MBeans according to the roles in its defining relation type.

For example, we can say that Books and Owner are roles. Books represents any number of owned books of a given MBean class, and Owner is a book owner of another MBean class. We might define a relation type containing these two roles and call it Personal Library: it represents the concept of book ownership.

The following diagram represents this sample relation, as compared to the UML modeling of its corresponding association.

Figure 9-1 Comparison of the Relation Models

Graphic

There is a slight difference between the two models. The UML association implies that each one of the Books can only have one owner. Our relation type only models a set of roles, guaranteeing that a relation instance has one Owner MBean and any number of MBeans in the role of Books.


Note -

The relation service does not do inter-relation consistency checks, they are the responsibility of the designer if they are needed. In our example, the designer would need to ensure that the same book MBean does not participate in two different Personal Library relations, while allowing it for an owner MBean.


In the rest of this topic, we will see how to handle the roles, relation type and relation instances through the relation service. We will use the names and relations presented in the programming example. First of all, we instantiate the relation service as we would any other MBean and register it in our agent's MBean server.


Example 9-1

// Create the RelationService MBean
String relServClassName = 
    "javax.management.relation.RelationService";
ObjectName relServObjName = new ObjectName(
    "DefaultDomain:type=javax.management.relation.RelationService1");

// We use a constructor which takes a boolean parameter
// to set the ImmediatePurge mode for relation consistency
Object[] params = new Object[1];
params[0] = new Boolean(true);
String[] signature = new String[1];
signature[0] = "boolean";

server.createMBean(theRelServClassName, theRelServObjName,
                   params, signature);

The relation service exposes an attribute called Active that is false until its MBean is registered with the MBean server. All of the operations that handle relation or role MBeans, either directly or indirectly, will throw an exception when the service is not active.


Defining Role Information

Before we can create relation instances, we need a relation type, and before we can define a relation type, we need to represent the information about its roles. This information includes:

The name can be any string that is manipulated by the Java String class. It will be used to retrieve the corresponding MBeans when accessing a relation instance. The multiplicity is limited to the range between the minimum and maximum number of MBeans required for this role to fulfilled. The read-write permissions apply to all of the MBeans in the role, since the value of a role is read or written as a complete list.

The role information is represented by the RoleInfo class. In our example, we define two different roles:


Example 9-2 Instantiating RoleInfo Objects

// Define two roles in an array
// - container: SimpleStandard class/read-write access/multiplicity: 1..1
// - contained: SimpleStandard class/read-write access/multiplicity: 0..n

RoleInfo[] roleInfoArray = new RoleInfo[2];
String role1Name = "container";
roleInfoArray[0] =
    new RoleInfo( role1Name, "SimpleStandard",
                  true, true,
                  1, 1,
                  null);

String role2Name = "contained";
roleInfoArray[1] =
    new RoleInfo( role2Name, "SimpleStandard",
                  true, true,
                  0, -1,
                  null);

We build an array of RoleInfo objects that is intended to define a relation type, so it needs to define a valid set of roles. All role names must be unique within the array, and none of the array's elements may be null. Also, the minimum and maximum cardinalities must define a range of at least one integer.

Defining Relation Types

We define a relation type in the relation service by associating a name for the relation type with a non-empty array of RoleInfo objects. These are the parameters to the service's createRelationType method that we call through the MBean server.


Example 9-3 Defining a Relation Type

try {
    String relTypeName = "myRelationType";

    Object[] params = new Object[2];
    params[0] = relTypeName;
    params[1] = roleInfoArray;
    String[] signature = new String[2];
    signature[0] = "java.lang.String";
    // get the string representing the "RoleInfo[]" object
    signature[1] = (roleInfoArray.getClass()).getName();

    server.invoke( relServObjName, "createRelationType",
                   params, signature);

} catch (Exception e) {
    echo("\tCould not create the relation type " + relTypeName);
    printException(e);
}

The relation type name given by the caller must be unique among all relation type names already created in the relation service. Relations will refer to this name to define their type, and the service will verify that the roles of the relation match the role information in this type.

The relation service provides methods for managing the list of relation types it stores. The removeRelationType removes the type's definition from the relation service and also removes all relation instances of this type. This mechanism is further covered in "Maintaining Consistency".

Other methods give the list of all currently defined relation types or the role information associated with a given type. Role information is always obtained through the name of the relation type where it is defined. Here we show the subroutine that our example uses to print out all role and type information.


Example 9-4 Retrieving Relation Types and Role Information

try {
    echo("\n-> Retrieve all relation types");
    Object[] params1 = new Object[0];
    String[] signature1 = new String[0];
    ArrayList relTypeNameList = (ArrayList)
    (server.invoke( relServObjName, "getAllRelationTypeNames",
                    params1, signature1));

    for (Iterator relTypeNameIter = relTypeNameList.iterator();
         relTypeNameIter.hasNext(); ) {
        String currRelTypeName = (String)(relTypeNameIter.next());
        echo("\n-> Print role info for relation type " +
             currRelTypeName);
        Object[] params2 = new Object[1];
        params2[0] = currRelTypeName;
        String[] signature2 = new String[1];
        signature2[0] = "java.lang.String";
        ArrayList roleInfoList = (ArrayList)
            (server.invoke( relServObjName,"getRoleInfos",
                            params2, signature2));
        printList(roleInfoList);
    }
} catch (Exception e) {
    echo("\tCould not browse the relation types");
    printException(e);
}

Creating Relations

Now that we have defined a relation type, we can use it as a template for creating a relation. A relation is a set of roles which fulfills all of the role information of the relation type. The Role object contains a role name and value which is the list of MBeans that fulfills the role. The RoleList object contains the set of roles used when setting a relation or getting its role values.

In order to create a relation we must provide a set of roles whose values will initialize the relation correctly. In our example we use an existing SimpleStandard MBean in each role that we have defined. Their object names are added to the value list for each role. Then the each Role object is added to the role list.


Example 9-5 Initializing Role Objects and Creating a Relation

[...] // define object names and create SimpleStandard MBeans

// Instantiate the roles using the object names of the MBeans
ArrayList role1Value = new ArrayList();
role1Value.add(mbeanObjectName1);
Role role1 = new Role(role1Name, role1Value);

ArrayList role2Value = new ArrayList();
role2Value.add(mbeanObjectName2);
Role role2 = new Role(role2Name, role2Value);

RoleList roleList1 = new RoleList();
roleList1.add(role1);
roleList1.add(role2);

String relId1 = relTypeName + "_instance";

try {
    Object[] params = new Object[3];
    params[0] = relId1;
    params[1] = relTypeName;
    params[2] = roleList1;
    String[] signature = new String[3];
    signature[0] = "java.lang.String";
    signature[1] = "java.lang.String";
    signature[2] = "javax.management.relation.RoleList";
    server.invoke(theRelServObjName, "createRelation",
                  params, signature);
} catch(Exception e) {
    echo("\tCould not create the relation " + RelId1);
    printException(e);
}

The createRelation method will raise an exception if the provided roles do not fulfill the specified relation type. You may omit Role objects for roles that allow a cardinality of 0; their values will be initialized with an empty list of object names. The relation service will check all provided object names to ensure they are registered with the MBean server. Also, the relation identifier name is a string which must be unique among all relations in the service.

The corresponding removeRelation method is also exposed for management operations. It is also called internally to keep all relations coherent, as described in "Maintaining Consistency". In both cases, removing a relation means that you can no longer access it through the relation service, and the isRelation operation will return false when given its relation identifier. Also, its participating MBeans will no longer be associated through the roles in which they belonged. The MBeans continue to exist unaltered otherwise and may continue to participate in other relations.

Operations of the Relation Service

In addition to the creation and removal methods for relation types and instances, the relation service provides operations for finding related MBeans, determining the role of a given MBean, and accessing the MBeans of a given role or relation.

Query Operations

Once relations have been defined, the relation service allows you to do searches based on the association of objects that the relations represent. The following operation perform queries on the relations:

Accessing Roles

Once you have a relation identifier, you will probably want to access its role values. The relation service provides getters and setters for roles, as well as bulk operations to get or set several or all roles in a relation. Remember that the value of a role is a list of MBeans, and a role is identified by a name string.

Input parameters to setter operations are the same Role and RoleList classes that are used to create relations. Bulk getters and setters return a RoleResult object which contains separate lists for successful and failed individual role operations.

Inside a role result, the list of roles and values that were successfully accessed are given in a RoleList instance. The information about roles that couldn't be read or written is returned in a RoleUnresolvedList. This list contains RoleUnresolved objects that name the role which couldn't be accessed and an error code explaining the reason, such as an invalid role name or the wrong cardinality for a role. The error codes are defined as static final fields in the RoleStatus class.

Maintaining Consistency

All relation service operations that set the role of a relation always verify that the role value fulfills its definition in the relation type. An incorrect MBean type, the wrong cardinality of the role, or an unknown role name will prevent that role from being modified, guaranteeing that the relation always fulfills it relation type.

As we shall see in "Objects Representing Relations", relations and relation types can also be objects that are external to the relation service. In this case, they are responsible for maintaining their own role-consistency. The relation service MBean exposes methods to assist these object is verifying that their roles conform to their defined relation type. In all cases of an external implementation of a relation, the object designer is responsible for ensuring its coherence.

Removing a relation type can cause existing relations to no longer have a defining type. The policy of the removeRelationType operation is to assume that the caller is aware of existing relations of the given type. Instead of forbidding the operation, this method removes the relations that were defined by the given type. It is the designer's responsibility to first call the findRelationsOfType operation to determine if any existing relations will be affected.

MBeans participating in a relation may be removed from the MBean server by some other management operation, thereby modifying the cardinality of a role where they were referenced. In order to maintain the consistency in this case, the relation service must remove all relations in which a role no longer has the cardinality defined in it relation type. The process of determining invalid relations and removing them is called a purge.

The relation service listens for deregistration notifications of the MBean server delegate, and will need to purge its MBeans whenever one is received. It must determine if the removed MBean was involved in any relations, and if so, whether its removal violates the cardinality of each role where it appears. The relation service exposes the boolean PurgeFlag attribute which the programmer must set to determines whether purges are done automatically or not.

When the purge flag is true, the relation service will purge its data immediately after every deregistration notification. However, the purge operation can be resource intensive for large sets of relation data. In that case the managing application may set the purge flag to false and only call the purgeRelations operation to purge the data when needed.

For example, if there are many MBean deregistrations and few relation operations, it may make sense to only purge the relation service manually before each operation. Or the automatic purge flag may be temporarily set to false while executing time-critical operations that need to remove MBeans, but that won't access the relation service.

There are two possible consequences of an unpurged relation service. Roles in a relation may reference object names which no longer have an associated MBean. Or worse, the object name may have been reassigned to another MBean, leading to a totally incoherent state. The designer of the management solution is responsible for setting the purge policy so that operations will always access consistent relation values.

Relation Service Notifications

The relation service is a notification broadcaster that notifies listeners of events affecting relations. It sends RelationNotification objects in the following cases:

There are three equivalent notification types for events affecting relations defined as external MBeans (see "Objects Representing Relations"). In addition to the role and relation information, these notifications contain the object name of this MBean.

Objects Representing Relations

When creating relation types and instances, their representations are handled internally and only accessed through the interface of the relation service. However, the service also allows you to create external objects which represent relations, and then add them under the service's control to access them through its operations.

One advantage of representing relation types and instances as classes that they can perform their initialization in the class constructor. Applications can then instantiate the classes and add them to the relation service directly, without needing to code for their creation. The relation type classes can be downloaded to agent applications for use throughout the management architecture.

Another advantage is that relations are represented as MBeans and must be registered in the MBean server. This means that management applications can get role information directly from the relation MBean instead of going through the relation service.

The main advantage of an external relation class is that it can be extended to add properties and methods to a relation. They can be accessible through attributes and operations in the relation MBean so that they are also exposed for management. These extensions can represent more complex relations and allow more flexible management architectures.

With the power of manipulating relations comes the responsibility of maintaining the relation model. A relation MBean can expose an operation for adding an object name to a role, but its implementation must first ensure that the new role value will conform to the relation's type. Then, it must also instruct the relation service to send a role update notification. The relation service MBean exposes methods for maintaining consistency and performing required operations. It is the programmer's responsibility to call the relation service when necessary in order to maintain the consistency of its relation data.

The RelationTypeSupport Class

A class must implement the RelationType interface in order to be considered a representation of a relation type. The methods of this interface are used to access the the role information that makes up a relation type. Since relation types are immutable within the relation service, there are no methods for modifying the role information.

The RelationTypeSupport class is the implementation of this interface that is used internally by the relation service to represent a relation type. By extending this class, you can quickly write new relation type classes with all the required functionality. The class has a method for adding roles to the information that is exposed; this method can be called by the class constructor to initialize all roles. Our simple example does just this, and there is little other functionality that can be added to a relation type object.


Example 9-6 Extending the RelationTypeSupport Class

import javax.management.relation.*;

public class SimpleRelationType extends RelationTypeSupport {

    // Constructor
    public SimpleRelationType(String theRelTypeName) {

        super(theRelTypeName);

        // Defines the information for two roles
        // - primary: SimpleStandard class/read-write/cardinality=2
        // - secondary: SimpleStandard class/read-only/cardinality=2
        try {
            RoleInfo primaryRoleInfo =
                new RoleInfo("primary", "SimpleStandard",
                             true, true,
                             2, 2,
                             "Primary description");
            addRoleInfo(primaryRoleInfo);

            RoleInfo secondaryRoleInfo =
                new RoleInfo("secondary", "SimpleStandard",
                             true, false,
                             2, 2,
                             "Secondary description");
            addRoleInfo(secondaryRoleInfo);
        } catch (Exception exc) {
            throw new RuntimeException(exc.getMessage());
        }
    }
}

We now use our class to instantiate an object representing a relation type. We then call the relation service's addRelationType operation to make this type available in the relation service. Thereafter, it is manipulated through the service's operations and there is no way to distinguish it from other relation types that have been defined.


Example 9-7 Adding an Externally Defined Relation Type

String usrRelTypeName = "SimpleRelationType";
SimpleRelationType usrRelType =
    new SimpleRelationType("SimpleRelationType");
try {
    Object[] params = new Object[1];
    params[0] = usrRelType;
    String[] signature = new String[1];
    signature[0] = "javax.management.relation.RelationType";
    server.invoke( relServObjName, "addRelationType",
                   params, signature);
} catch(Exception e) {
    echo("\tCannot add user relation type");
    printException(e);
}

The role information defined by a relation type should never change once the type has been added to the relation service. This is why the RelationTypeSupport class is not an MBean: it would make no sense to manage it remotely. All of the information about its roles is available remotely through the relation service MBean.

The RelationSupport Class

The external class representation of a relation instance must implement the Relation interface so that it can be handled by the relation service. The RelationSupport class is the implementation provided which is also used internally by the service.

The methods of the relation interface expose all of the information needed to operate on a relation instance: the getters and setters for roles and the defining relation type. Because the relation support class must represent any possible relation type, it has a constructor which takes a relation type and a role list, and it uses a generic mechanism internally to represent any roles.

You could implement a simpler relation class that implements a specific relation type, in which case it would know and initialize its own roles. The class could also interact with the relation service to create its specific relation type before adding itself as a relation.

However, the simplest way to define a relation object is to extend the RelationSupport class and provide any additional functionality you require. In doing so, you can rely on the relation support class' own methods for getting and setting roles, thereby taking advantage of their built-in consistency mechanisms.


Example 9-8 Extending the RelationSupport Class

import javax.management.ObjectName;
import javax.management.relation.*;

public class SimpleRelation extends RelationSupport
    implements SimpleRelationMBean {

    // Constructor
    public SimpleRelation(String theRelId,
                          ObjectName theRelServiceName,
                          String theRelTypeName,
                          RoleList theRoleList)
        throws InvalidRoleValueException,
               IllegalArgumentException {

        super(theRelId, theRelServiceName, theRelTypeName, theRoleList);
    }

    [...] // Implementation of the SimpleRelationMBean interface
}

Our MBean's SimpleRelationMBean interface itself extends the RelationSupportMBean in order to expose its operations for management. In order to represent a relation, the class must be an MBean registered in the MBean server. This allows the relation service to rely on deregistration notifications in order to know if the object name is no longer valid.

When instantiating our SimpleRelation MBean, we use the relation type defined in Example 9-3. We also reuse the role list from Example 9-5 which contains a single MBean in each of two roles. Before adding the relation to the relation service, we must create it as an MBean in the MBean server. We then call the addRelation operation of the relation service with our relation's object name.


Example 9-9 Creating an External Relation MBean

// Using relTypeName="myRelationType"
// and roleList1={container,contained}

String relMBeanClassName = "SimpleRelation";
String relId2 = relTypeName + "_instance";
ObjectName relMBeanObjName = new ObjectName(
    "DefaultDomain:type=SimpleRelation2";

try {
    Object[] params1 = new Object[4];
    params1[0] = relId2;
    params1[1] = relServObjName;
    params1[2] = relTypeName;
    params1[3] = roleList1;
    String[] signature1 = new String[4];
    signature1[0] = "java.lang.String";
    signature1[1] = "javax.management.ObjectName";
    signature1[2] = "java.lang.String";
    signature1[3] = "javax.management.relation.RoleList";
    server.createMBean(relMBeanClassName, relMBeanObjName,
                       params1, signature1);
} catch(Exception e) {
    echo("\t Could not create relation MBean for relation " + relId2);
    printException(e);
}

// Add our new MBean as a relation to the relation service
try {
    Object[] params2 = new Object[1];
    params2[0] = relMBeanObjName;
    String[] signature2 = new String[1];
    signature2[0] = "javax.management.ObjectName";
    server.invoke(relServObjName, "addRelation",
                  params2, signature2);
} catch(Exception e) {
    echo("\t Could not add relation MBean " + relMBeanObjName);
    printException(e);
}

After a relation instance is added to the relation service, it can be accessed normally like other relations. Management operations can either operate on the MBean directly or access it through its identifier in the relation service. Two methods of the service are specific to external relation MBeans:

In our example, our MBean does not expose any functionality that interacts with the relation service. It relies fully on the support class and only adds the implementation of its own simple MBean interface. In a more realistic example, the MBean would expose attributes or operations related to its role values.

For example, Personal Library could be a unary relation type containing the Books role. We could then design an Owner MBean to be a relation of this type. In addition to exposing attributes about the owner, the MBean would give the number of books in the library, return an alphabetized list of book titles, and provide an operation for selling a book. All of these would need to access the role list, and the sale operation would need to modify the role to remove a book MBean.

All of these operations would need to keep the consistency of the relation. To do this, the relation service exposes several methods that relation MBeans must call:

Running the Relation Service Example

The examplesDir/Relation directory contains all of the files for the agent application and the associated MBeans.

Compile all files in this directory with the javac command. For example, on the Solaris platform with the Korn shell, you would type:


$ cd examplesDir/Relation/
$ javac -classpath classpath *.java

To run the relation service example, launch its application with the following command. Be sure that the classes for the SimpleRelationType and SimpleRelation MBean can be found in its classpath.


$ java -classpath classpath RelationAgent

When launched, the application first creates the RelationService MBean and then begins its long demonstration of operations on roles, types and relations. The output of each step is separated by a horizontal line to detect its starting point in the scrolling display.

Press <Enter> to step through the example when the application pauses, or <Control-C> at any time to exit.

Chapter 10 Cascading Agents

The cascading service allows you to access the MBeans of a subagent directly through the MBean server of the master agent. The service is implemented in the CascadingAgent MBean which connects to a remote subagent and makes all of the subagent's MBeans visible in the master agent. An agent can have several subagents, and subagents may themselves cascade other agents, forming a hierarchy of cascading agents.

By connecting to the root of an agent hierarchy, managers can have a single access point to many resources and services. All MBeans in the hierarchy are manageable through the top-most master agent, and a manager doesn't need to worry about their physical location. Like the other services, the cascading agent is implemented as an MBean that can be managed dynamically. This allows the manager to control the structure of the agent hierarchy, adding and removing subagents as necessary.

In particular, the cascading service MBean can work with any protocol connector, including any custom implementation. Those supplied by the Java Dynamic Management Kit give you the choice of using RMI, HTTP, or HTTPS. The cascading service also lets you specify a filter for selecting precisely the MBeans of the subagent that will be mirrored in the master agent. This mechanism lets you limit the number of MBeans which are mirrored in the top-most agent of a large cascading hierarchy.

The code samples in this topic are taken from the files in the Cascading directory located in the main examplesDir (see "Directories and Classpath" in the preface).

Contents:

The CascadingAgent MBean

You should create one CascadingAgent MBean for every subagent you wish to manage through the master agent. Each connects to an agent and mirrors all of that agent's registered MBeans in the master agent's MBean server. No other classes are required or need to be generated in order to represent the MBeans.

The agent whose MBean server contains an active cascading service is called a master agent in relation to the other agent which is mirrored. The agent to which the cascading service is connected is called the subagent in relation to its master agent. We say that it creates mirror MBeans to represent the subagent's MBeans. See "The Mirror MBeans in the Master Agent" for a description of these objects.

A master agent can have any number of subagents, each controlled individually by a different CascadingAgent MBean. A subagent may itself contain cascading agents and mirror MBeans, all of which are mirrored again in the master agent. This effectively allows cascading hierarchies of arbitrary depth and width.

Two master agents may also connect to the same subagent. This is similar to the situation where two managers connect to the same agent and may access the same MBean. If the implementation of a management solution permits such a condition, it is the designer's responsibility to handle any synchronization issues in the MBean.

The connection between two agents resembles the connection between a manager and an agent. The cascading service MBean relies on a connector client, and the subagent must have the corresponding connector server. The subagent's connector server must already be instantiated, registered with its MBean server, and ready to receive connections.

In our example application, we use the RMI connector client which we will connect to the RMI connector server of the subagent on port 1099 of the localhost. In fact, this is the same as the default values when instantiating a cascading agent MBean, but we also wish to specify a pattern for selecting the MBeans to mirror. By default, all MBeans of the subagent are mirrored in the master agent; we provide an object name pattern to only select those in the subagent's CascadedDomain.


Example 10-1 Connecting to a Subagent

ObjectName mbeanObjectName = null;
String domain = server.getDefaultDomain();
mbeanObjectName = new ObjectName(domain + ":type=CascadingAgent");
[...]

RmiConnectorAddress address = new RmiConnectorAddress(
    java.net.InetAddress.getLocalHost().getHostName(),
    1099,
    "name=RmiConnectorServer");
CascadingAgent remAgent = new CascadingAgent(
    address,
    "com.sun.jdmk.comm.RmiConnectorClient",
    new ObjectName("CascadedDomain:*"),
    null);
ObjectInstance remAgentInstance =
    server.registerMBean(remAgent, mbeanObjectName);

[...] // Output omitted
// Now we explicitly start the cascading agent
// as it is not started automatically
//
echo("\nStarting the cascading agent...");
[...]
server.invoke(mbeanObjectName, "start", null, null);
sleep(1000);
echo("\tIs ACTIVE = " + server.getAttribute(mbeanObjectName, "Active"));  
echo("done");

Before the subagent's MBeans are mirrored, the CascadingAgent MBean must be registered in the master agent's MBean server, and its "mirroring" must be started. When you invoke the start operation of the cascading service MBean, it will connect it to its designated subagent and create one mirror MBean to represent each MBean in the subagent. When its Active attribute becomes true, the cascading mechanism is ready to use.

The CascadingAgent MBean exposes two writeable attributes:

Neither of these attributes may be modified when the cascading service is active. You must first call the MBean's stop operation: this will remove all of the mirror MBeans for the given subagent and disconnect from the subagent's connector server. You may then modify the address or the class of the connector client. The new values will be used when you start the mirroring again: this lets you change subagents or even change protocols.

When the cascading service is stopped or its MBean is removed from the master agent, all of its mirror MBeans are deregistered. The MBean server delegate in the master agent will send a deregistration notification for each mirror MBean as it is removed.

The Mirror MBeans in the Master Agent

Once the cascading service is active, you interact directly with the mirror MBeans representing the subagent's MBeans. You may access and manage a mirror MBean as if you are connected to the subagent and accessing or managing the original MBean. The mirror MBeans are actual MBean objects registered in the master agent's MBean server with the same object name.

All management operations that could be performed on the original MBean can be performed identically on its mirror MBean. You may modify attributes, invoke operations and add or remove listeners, all with exactly the same result as if the manager were connected to the subagent when performing the action.

The behavior of a mirror MBean is to transmit the action to the subagent's MBean and return with an answer or result. The actual computation is performed by the original MBean running in its own agent.

In our example, we know that there is a timer MBean that was created in the subagent. Once the cascading service is active for our subagent, we operate the timer through it local mirror MBean. We can never have the direct reference to a mirror MBean, so we always invoke operations through the master agent's MBean server.


Example 10-2 Managing Mirrored MBeans

// Here we know the object name of the MBean we want to access, the
// object name of its mirrored MBean in the master agent will be identical.
ObjectName timerName = new ObjectName("CascadedDomain:type=timer");

echo("\n>>>  Ask the Timer MBean to send a notification every 5 seconds ");
java.util.Date currentDate = new java.util.Date();
Object params[] = { 
    "Timer",
    "Message",
    new Integer(5),
    new java.util.Date (currentDate.getTime() + new Long (2).longValue()),
    new Long(1000) };

String signatures[]={ "java.lang.String",
                      "java.lang.String",
                      "java.lang.Object",
                      "java.util.Date",
                      "long"};

server.invoke(timerName, "addNotification", params, signatures);
server.invoke(timerName, "start", null, null);

echo("\n>>>  Add ourselves as a listener to the Timer MBean");       
server.addNotificationListener(timerName, this, null, null);

echo("\nPress <Enter> to remove the listener from the Timer MBean ");
waitForEnterPressed();
server.removeNotificationListener(timerName, this);

For the managing application, the mirror MBean in the master agent is the MBean. Unregistering a mirror MBean in the master agent will unregister the mirrored MBean in the subagent. If you want to control the number of mirror objects without removing the originals, you must use filters and/or queries of the subagent's MBeans in the constructor of the cascading service MBean.

The mirror and its mechanism make the cascading totally transparent: a manager has no direct way of knowing whether an object is a mirror or not. Neither does it have any direct information about the topology of the cascading hierarchy rooted at the agent which it accesses. If this information is necessary, the MBeans should expose some kind of identification through their attributes, operations, or object names.

The Class of a Mirror MBean

Mirror MBeans are implemented as dynamic MBeans; they are instances of the CascadeGenericProxy class. The cascading service gives them the MBeanInfo object that they will expose and establishes their connection with the original MBean. The MBean information contains the class name of the original MBean, not their own class name. Exposing this borrowed class name guarantees that the cascading service is completely transparent.

The symmetry of the Java Dynamic Management architecture means that this cascading mechanism is scalable to any number of levels. The mirror object of a mirror object is again an instance of the CascadeGenericProxy class, and it borrows the same object name and class name. Any operation on the top-most mirror will be propagated to its subagent, where the intermediate mirror will send it its own subagent, and so forth. The cost of cascading is the cost of accessing the subagent: the depth of your cascading hierarchy should be adapted to your management solution.

Because the cascading service MBean instantiates and controls all mirror MBeans, the CascadeGenericProxy class should never be instantiated through a management operation, nor by the code of the agent application. We have described it here only to provide an example application of dynamic MBeans.

Cascading Issues

In this section, we explain some of the design issues that are determined by the implementation of the cascading service.

Dynamic Mirroring

Any changes in the subagent's MBeans are automatically applied to the set of mirror MBeans, to ensure that both master agent and subagent remain consistent.

When an MBean is unregistered from the subagent, the cascading service MBean removes its mirror MBean from the master agent. When a new MBean is registered in the subagent, the cascading service instantiates its mirror MBean, sets up its connection to the new MBean, and registers the mirror with the master agent's MBean server.

Both of these mechanisms scale to cascading hierarchies: adding or removing an MBean in the master agent will trigger a notification that any cascading service connected to the master agent will receive. This will start a chain reaction up to the top of the hierarchy. Removing an MBean from the middle of a hierarchy also triggers a similar reaction down to the original MBean which is finally removed.

Dynamic unregistration only applies to subagent MBeans that are actually mirrored. Dynamic registration is also subject to filtering, as described in the next section.

MBean Filtering

When the cascading service MBean is instantiated, you may pass it an object name pattern and a query expression. These will be applied to the list of MBeans in the subagent to determine those that will be mirrored in the master agent. The filtering will be in effect for the life of the cascading service connected to this MBean.

Filtering the mirrored MBeans reduces the number of MBeans in the master agent. It also provides a way of identifying mirror MBeans in the master agent, as in our example where cascading is limited to MBeans in the CascadedDomain.

Both the object name pattern and query expression will be used to filter any new MBean which is registered in the subagent. If the new MBean meets the filter criteria, it will become visible and mirrored in the master agent. Since the query expression applies to attribute values of the MBean, you must be careful to initialize the new MBean before registering it so that its values are significant when the filter is applied by the cascading service.

Naming in Cascading Agents

Mirror MBeans are registered in the master agent with the same object name as the mirrored MBean in the sub-agent. If the registration fails in the master agent's MBean server, no error is raised and no action is taken: the corresponding MBean will simply not be mirrored in the master agent.

The most likely cause for registration to fail is that the object name already exists in the master agent. An MBean cannot be registered if its chosen object name already exists in the MBean server.

If your management solution has potential naming conflicts, you will need a design that is guaranteed to assign unique object names throughout the cascade hierarchy. You can set the default domain name in your subagents or use the MBeanServerId attribute of the delegate MBean to give MBeans a unique object name.

Running the Cascading Example

The examplesDir/Cascading directory contains all of the files for the two agent applications, along with a simple MBean.

Compile all files in this directory with the javac command. For example, on the Solaris platform with the Korn shell, you would type:


$ cd examplesDir/Cascading/
$ javac -classpath classpath *.java

To run the cascading example, launch the subagent in another terminal window with the following command. Be sure that the classes for the SimpleStandard MBean can be found in its classpath.


$ java -classpath classpath SubAgent

Wait for the agent to be completely initialized, then launch the master agent with the following command:


$ java -classpath classpath MasterAgent

When launched, the master agent application first creates the CascadingAgent MBean and then sets up its connection to the subagent. The master agent then performs operations on the mirrored MBeans of the subagent. Press <Enter> to step through the example when the application pauses.

You may also interact with the example through the HTML adaptor of the master agent and subagent. If you are still receiving timer notification on the master agent, press <Enter> once more to remove the listener, but leave both agent applications running.

Interacting with a Cascade Hierarchy
  1. Open two browser windows side by side and load the following URLs:

    The Subagent 

    The Master Agent 

    http://localhost:8082/

    http://localhost:8084/

    In the subagent, you should see the timer MBean in the CascadedDomain and a SimpleStandard MBean in the DefaultDomain.

    The master agent is recognizable by the cascading service MBean in the DefaultDomain. Otherwise it has an identical timer MBean registered in the CascadedDomain: this is the mirror for the timer in the subagent. The SimpleStandard MBean is not mirrored because our cascading service instance filters with the following object name pattern:

    CascadedDomain:*

  2. Create four MBeans of the SimpleStandard class in following order:

    On the Master Agent: 

    CascadedDomain:name=SimpleStandard,number=1

    On the Subagent: 

    CascadedDomain:name=SimpleStandard,number=1

     

    CascadedDomain:name=SimpleStandard,number=2

     

    CascadedDomain:name=SimpleStandard,number=3

    When you are finished, reload the agent view on the master agent: you should see that the mirror MBeans for the last two have been created automatically. Look at the MBean view of either of these mirror MBeans on the master agent: their class name appears as SimpleStandard.

  3. In the master agent, set a new value for the State string attribute of all 3 of its SimpleStandard MBeans.

    When you look at the corresponding MBeans in the subagent, you see that number=2 and number=3 were updated by their mirror MBean. However, number=1 has not changed on the subagent: because it was created first in the master agent, it is not mirrored and exists separately on each agent.

  4. In the subagent, invoke the reset operation of all 3 of its SimpleStandard MBeans.

    When you inspect the MBeans in the master agent, the values for number=2 and number=3 were reset. Remember that the HTML adaptor must get the values of attributes for displaying them, so they were correctly retrieved from the mirrored MBeans that we reset.

  5. In the master agent, unregister MBeans number=1 and number=2, then update the agent view of the subagent.

    In the subagent, you should still see the local version of number=1, but number=2 has been removed at the same time as its mirror MBean.

    We are in a state where number=1 is a valid MBean for mirroring but it isn't currently mirrored. This incoherence results from the fact that we did not have unique object names throughout the cascading hierarchy. Only new MBeans are mirrored dynamically, following the notification that signals their creation. We would have to stop and restart the master agent's cascading service MBean to mirror number=1.

  6. In the subagent, unregister MBeans number=1 and number=3, then update the agent view on the master agent.

    The mirror MBean for number=3 was automatically removed by the cascading service, so none of the MBeans we added now remain.

  7. Invoke the stop operation of the CascadingAgent MBean in the master agent.

    The last mirror MBean for the timer is removed from the master agent. The two agents are no longer connected.

  8. If you are finished with the agents, press <Enter> in both of their terminal windows to exit the applications.

Chapter 11 The Discovery Service

The discovery service enables you to discover Java Dynamic Management agents in a network. This service relies on a discovery client object which sends out multicast requests to find agents. In order to be discovered, an agent must have a registered discovery responder in its MBean server. Applications may also use a discovery monitor which detects when discovery responders are launched or stopped.

The combination of this functionality allows interested applications to establish a list of active agents and keep it current. In addition to knowing about the existence of an agent, the discovery service provides the version information from an MBean server's delegate and the list of communication MBeans that are currently registered. The discovery service uses the term communicators to designate a set of MBeans consisting of protocol adaptors and connector server objects.

Often, the discovery client and the discovery monitor are located in a manager application that wishes to know about the available agents. However, agent applications are free to use the discovery service since they may require such information for cascading (see "Cascading Agents") or for any other reason. To simplify using the discovery service in an agent, all of its components are implemented as MBeans.

The code samples in this topic are taken from the file in the Discovery directory located in the main examplesDir (see "Directories and Classpath" in the preface).

Contents:

Active Discovery

In active discovery, the discovery client initiates searches for agents on the network. It involves a discovery client that sends the discovery request and a discovery responder in each agent that responds. Each instance of the responder supplies the return information about the agent in which it is registered. The return information is represented in the discovery client by a vector of discovery response objects.

The application containing the discovery client can initiate a search at any time. For example, it might do a search when it is first launched and periodically search again for information about the communicators which may have changed. For each search, the discovery client broadcasts a request and waits for the return information from any responders.

In the following sections, we describe each of these objects in further detail.

The Discovery Client

The DiscoveryClient class provides methods to discover agents. The active discovery operation sends a discovery request to a multicast group and waits for responses. These messages are proprietary and are not exposed to the user. Discovery clients can only discover agents listening on the same multicast group and port, so your design must coordinate this information between the discovery client and responders.

You can instantiate and perform searches from multiple discovery clients in a single application. Each discovery client can be configured to use different multicast groups or ports, allowing you to discover different groups of agents.

In our example, the discovery client is in an agent application: we register it as an MBean and interact with it through the MBean server.


Example 11-1 Instantiating and Initializing a Discovery Client

// build the DiscoveryClient MBean ObjectName
//
ObjectName discoveryClientMBeanObjectName =
    new ObjectName(domain + "name=myDiscoveryClient") ;

// Create, register and start the DiscoveryClient MBean
//
try {
    ObjectInstance discoveryClientObjectInstance =
        myMBeanServer.createMBean(
            "com.sun.jdmk.discovery.DiscoveryClient",
            discoveryClientMBeanObjectName) ;
    myMBeanServer.invoke (discoveryClientMBeanObjectName,
        "start", null, null) ;

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

The default multicast group is 224.224.224.224 and the default port is 9000. These may be set to other values through the multicastGroup and multicastPort attributes, but only when the state of the discovery client is OFFLINE. Before initiating searches, you must call the discovery client's start method. This will create its multicast socket and join the multicast group used for broadcasting its discovery request.

The scope of the discovery request depends on the time-to-live used by the multicast socket. Time-to-live is defined by the Java class java.net.MulticastSocket to be a number between 1 and 255. By default, the time-to-live is 1, which corresponds to the machine's local area network. You can modify this value at any time by setting the discovery client's TimeToLive attribute.

By default, a discovery client waits for responses for one second after it has sent a discovery request. This period can be customized by setting a value in milliseconds for its TimeOut attribute. When setting this attribute, you should take into account estimated time for a round-trip of a network packet using the given time-to-live.

Performing a Discovery Operation

An application triggers a search operation by invoking the findMBeanServers or findCommunicators methods on an active DiscoveryClient object. Using the current settings, it will send the multicast request and block for the timeout period. At the end of the timeout period, these methods return the responses that were received.

Both methods return a vector of DiscoveryResponse objects. This class exposes methods for retrieving information about the MBean server and the registered communicator MBeans in the agent. The MBean server informations is the same as that exposed by that agent's MBean server delegate. The communicators are identified by ConnectorAddress objects and indexed by object name in a hash table.

Both search methods return the information about the agent's MBean server. The hash table of communicator MBeans is always empty for discovery responses returned by the findMBeanServers method. Otherwise, you can extract object names and protocol information from the hash table. One way of distinguishing the communicator MBeans is to rely on the default names provided by the ServiceName class.


Note -

All discovery messages sent between components of the discovery service are compatible between applications running different versions of the Java SDK or different versions of the Java Dynamic Management Kit (4.x only). However, these different configurations are not compatible for subsequent management operations through connectors. You may use the getImplementationVersion method of the DiscoveryResponse object to determine both the Java SDK and product version numbers.


In our example, we request all information about the agents and use a simple subroutine to print out all information in the discovery responses.


Example 11-2 Performing a Discovery Operation

// Discover all JDMK agents with a registered discovery responder
Vector discoveryResponses = (Vector) myMBeanServer.invoke (
    discoveryClientMBeanObjectName,"findCommunicators", null, null) ;

echo("We have found " + discoveryResponses.size() + " JDMK agent(s): ");
for (Enumeration e = discoveryResponses.elements();
     e.hasMoreElements();) {
    DiscoveryResponse discoveryResponse =
        (DiscoveryResponse)e.nextElement() ;
    printDiscoveryResponse(discoveryResponse) ;
}
[...]
private void printDiscoveryResponse(DiscoveryResponse discoveryResponse) {

    // display information about the agent's MBean server
    echo("\t MBeanServerId = " + discoveryResponse.getMBeanServerId())  ;
    echo("\t\t host name        = " + discoveryResponse.getHost())  ;
    [...]
    // display information about communicator objects, if any
    if (discoveryResponse.getObjectList() != null) {
        for( Enumeration e= discoveryResponse.getObjectList().keys();
             e.hasMoreElements(); ) {
            ObjectName o = (ObjectName) e.nextElement();
            echo("\t\t Communicator name        = " + o ) ;
        }
    }
}

On the agent side, the discovery responder automatically replies to discovery requests. Any active, registered responder in the same multicast group that is reached within the given time-to-live of the request will respond. It will automatically gather the requested information about its MBean server and send the response. The settings of the responder do not affect its automatic reply to discovery requests. In "The Discovery Responder" we will cover how its settings control passive discovery.

In active discovery, the discovery client controls all parameters of a search it initiates, including the response mode of the discovery responder. The discovery client determines whether responses are sent back on a different socket (unicast) or sent to the same multicast group. The default is unicast: if you want to use the multicast response mode, set the PointToPointResponse attribute to false before initiating the discovery.

Unicast Response Mode

When the PointToPointResponse boolean attribute is true, the discovery client specifies unicast mode in its discovery requests. The responder will create a datagram socket for sending the response only to the discovery client. As shown in the following diagram, each responder will send its response directly back to the discovery client. The datagram socket used by each responder is bound to its local host address; this cannot be customized.

Figure 11-1 Unicast Response Mode

Graphic

Multicast Response Mode

When the PointToPointResponse boolean attribute is false, the discovery client specifies multicast mode in its requests. The discovery responder will use the existing multicast socket to send response, broadcasting it to the same multicast group as the request. As shown in the following diagram, every member of the multicast group will receive the message, but only the discovery client can make use of its contents. Multicast mode avoids having to open another socket for the response, but all of the responses will create traffic in each application's socket.

Figure 11-2 Multicast Response Mode

Graphic

Passive Discovery

In passive discovery, the entity seeking knowledge about agents listens for their discovery responders being activated or deactivated. When discovery responders are started or stopped, they send out a proprietary message that contains all discovery response information. The DiscoveryMonitor object waits to receive any of these messages from the multicast group.

A discovery monitor is often associated with a discovery client. By relying on the information from both, you can keep an up-to-date list of all agents in a given multicast group.

Figure 11-3 Passive Discovery of Discovery Responders

Graphic

Therefore, configuring and starting the discovery responder is an important step to the overall discovery strategy of your applications.

The Discovery Responder

The agents that wish to be discovered must have an active DiscoveryResponder registered in their MBean server. The responder plays a role in both active and passive discovery:

Both types of messages are proprietary and their contents are not exposed to the user. These messages contain information about the MBean server, its delegate's information and a list of communicator MBeans, unless not requested by the discovery client.

In our example we create the discovery responder in the MBean server and then activate it.


Example 11-3 Initializing a Discovery Responder

// Set the domain name for the demo
//
String domain = "DiscoveryDemo:" ;

// build the DiscoveryResponder MBean ObjectName
//
ObjectName discoveryResponderMBeanObjectName =
    new ObjectName(domain + "name=myDiscoveryResponder");

// Create and register the DiscoveryResponder MBean
//
try {
    ObjectInstance discoveryResponderObjectInstance =
        myMBeanServer.createMBean(
            "com.sun.jdmk.discovery.DiscoveryResponder",
            discoveryResponderMBeanObjectName) ;
    // we don't start the responder until our monitor is listening

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

[...]

try {
    myMBeanServer.invoke (discoveryResponderMBeanObjectName,
        "start", null, null) ;
} catch(Exception e) {
    echo("\tDiscoveryResponder MBean was already started.") ;
}

The discovery responder has attributes for exposing a multicast group and a multicast port. These attributes define a multicast socket that the responder will use to receive discovery requests. It will also send activation and deactivation messages to this multicast group. When sending automatic responses to discovery requests, the time-to-live is provided by the discovery client. The responder's time-to-live attribute is only used when sending activation and deactivation messages.

We use the default settings of the discovery responder which are the multicast group 224.224.224.224 on port 9000 with time-to-live of 1. In order to modify these values, you need to set the corresponding attributes before starting the discovery responder MBean. You may also specify them in the class constructor. If the responder is active, you will need to stop it before trying to set any of these attributes. In that way, it will send a deactivation message using the old values and then an activation message with the new values.

Discovery Monitor

The discovery monitor is a notification broadcaster: when it receives an activation or deactivation message from a discovery responder, it sends a discovery responder notification to its listeners. Once its parameters are configured and the monitor is activated, the discovery is completely passive. You can add or remove listeners at any time.

The DiscoveryMonitor MBean has multicast group and multicast port attributes that determine the multicast socket where it will receive responder messages. Like the other components of the discovery service, the default multicast group is 224.224.224.224 and the default port is 9000. You may specify other values for the group and port either in the constructor or through attribute setters when the monitor is off-line.

The discovery monitor in our example is registered as an MBean. We then add a listener through the MBean server as we would for any other notification broadcaster.


Example 11-4 Instantiating and Starting a Discovery Monitor

// build the DiscoveryMonitor MBean ObjectName
//
ObjectName discoveryMonitorMBeanObjectName =
    new ObjectName(domain + "name=myDiscoveryMonitor");

// Create, register and start the DiscoveryMonitor MBean
//
try {
    ObjectInstance discoveryMonitorObjectInstance =
        myMBeanServer.createMBean(
            "com.sun.jdmk.discovery.DiscoveryMonitor",
            discoveryMonitorMBeanObjectName) ;
    myMBeanServer.invoke (discoveryMonitorMBeanObjectName,
        "start", null, null);

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

// Add ourselves as a listener to the DiscoveryMonitor MBean
//
try {
    myMBeanServer.addNotificationListener(
        discoveryMonitorMBeanObjectName, this, null, null ) ;

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

The discovery monitor must be activated with the start operation before it will receive responder messages and send notifications. It will be stopped automatically if it is unregistered from its MBean server. If it is not used as an MBean, you should invoke its stop method before your application exits.

Discovery Responder Notifications

When it receives a responder's activation or deactivation message, the discovery monitor sends notification objects of the DiscoveryResponderNotification class. This notification contains the new state of the discovery responder (ONLINE or OFFLINE) and a DiscoveryResponse object with information from the agent where the responder is located.

The listener could use this information to update a list of agents in the network. In our example, the listener is the agent application itself, and the handler method only prints out the information in the notification.


Example 11-5 The Discovery Responder Notification Handler

public void handleNotification(
    javax.management.Notification notification, java.lang.Object handback) {

    // We know we will only receive this subclass, so we can do the cast
    DiscoveryResponderNotification discoveryNotif =
        (DiscoveryResponderNotification) notification;
    echo("\n>>> RECEIVED Discovery Notification FROM JDMK agent on host \"" +
        discoveryNotif.getEventInfo().getHost() + "\"");

    if ( discoveryNotif.getState().intValue() == DiscoveryMonitor.ONLINE ) {
        echo("\t DiscoveryResponder state = ONLINE");
    } else {  
        echo("\t DiscoveryResponder state = OFFLINE");
    }
    DiscoveryResponse info =
        (DiscoveryResponse) discoveryNotif.getEventInfo();

    // internal method for displaying the discovery response information
    printDiscoveryResponse(info);
}

Running the Discovery Example

The examplesDir/Discovery directory contains the source file for the agent application that demonstrates the discovery service. This agent creates the discovery client, monitor and responder in the same MBean server, so it notifies itself when the responder is online and discovers its own presence. By launching several of these applications, you can see other agents being discovered.

Compile the file in this directory with the javac command. For example, on the Solaris platform with the Korn shell, you would type:


$ cd examplesDir/Discovery/
$ javac -classpath classpath *.java
Interacting with the Discovery Example
  1. To run the discovery example, launch the agent application with the following command:


    $ java -classpath classpath DiscoveryAgent
    

    The agent application registers an HTML adaptor on the default port (8082). Then press <Enter> to initialize the discovery components. The discovery service is now ready, except for the discovery responder which hasn't been started yet.

  2. Press <Enter> a second time to activate the discovery responder.

    The discovery monitor which has already been started receives the responder's activation message and triggers the notification listener. Our handler method prints out the information from the discovery response notification, indicating that the responder is ONLINE.

    Press <Enter> again to invoke the findCommunicators method of the discovery client and display its results. The content of the discovery response is identical to the previous information, in particular, it contains the object name of the HTML adaptor in our application.

    Leave the application running in this state.

  3. Now launch another DiscoveryAgent application in another terminal window or on another host. You must specify a non-default port number for the HTML adaptor if running on the same host, for example:


    $ java -classpath classpath DiscoveryAgent 8084
    

    Both applications will be using the multicast group 224.224.224.224 and multicast port 9000, so they will detect each other's presence. Go through the steps of this second application: you should see the first application receive the activation message of the second responder. Then, the active discovery should detect both agents. If your agents are running on the same host, you can tell them apart by their unique MBeanServerId values in the response information.

  4. Before stopping either of the discovery responders, you can interact with them through their HTML adaptors. Connect to the first agent by loading the following URL in a browser:

    http://localhost:8082/

    You can see the MBeans for all of the discovery components. From the MBean view of the discovery responder, call its stop operation, modify its TimeToLive attribute, and restart it. The discovery monitor in the other agent should detect the two events and signal them to our listener.

    You can also initiate an active search through the discovery client's MBean: invoke either of its find methods. However, the HTML adaptor cannot display the contents of the resulting DiscoveryResponse object.

  5. When you are finished, stop the discovery responders on both agents and then the applications themselves by pressing <Enter> twice in each terminal window. The second agent to be stopped should see the discovery responder of the first being deactivated.