Java Dynamic Management Kit 5.0 Tutorial

Part IV Agent Services

The minimal and base agents presented in Part II are manageable but are created empty. In this part 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 push new classes to an agent effectively. As new resources and new services are developed, they can be loaded into deployed agents, boosting their capabilities without affecting their availability.

The benefit of agent services is increased by their dynamic nature. Agent services can come and go as they are required, either as determined by the needs of an 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 part contains the following chapters:

Chapter 14 M-Let Class Loader

The “dynamic” in Java Dynamic Management Kit (DMK) 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 and create it in the agent.

The m-let resides in a separate text file that 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. Because 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.

This chapter covers the following topics:

M-Let Loader

In Java DMK 5.0, the m-let loader is a class loader object that 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 a shortcut method. In addition, can 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 14–1 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 done before loading classes through an m-let file.

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 two MLET tags:


Example 14–2 The M-Let File

<HTML>
<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 two 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 can also specify a CODEBASE, which is an alternate location for the JAR file. The MLET tag is fully defined in the JMX specification.

To download the MBeans specified in the m-let file we call the getMBeansFromURL method and analyze the result:


Example 14–3 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 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 of 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 14–4 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

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

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 14–5 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 download a class. Once it is added, you can load it as many times as necessary by calling createMBean directly.

Because this loading mechanism does not use the MLET tag, the programmer must ensure that either the downloaded class provides its own object name or, as in Example 14–5, the agent provides one.

The fact that the m-let loader is also a class loader into which you can load multiple URLs raises the issue of name spaces. If there are 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. To specify one of them precisely, do not add the URL of the second code-base to the m-let loader. Instead, 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, you must have installed the Java DMK, 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 host. With the Korn shell on the Solaris platform, 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 that 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 5.0 Tools Reference and in the Javadoc API of the com.sun.jdmk.TraceManager class (for receiving traces) and the com.sun.jdmk.trace.Trace class (for producing traces).

The agent then starts 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 have finished, press Control-C in the window where you started the agent.

M-Let Loading From a Manager

Because 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 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. Started with only 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 the manager is very simple:


Example 14–6 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 from the 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 14–7 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 14–8 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 enables us to ask the agent to create almost any MBean, imitating a class “push” mechanism.


Example 14–9 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. To run it on a different host, modify the code for the Client class constructor where the agent address is specified (see Example 14–6). You can place the JAR files and the m-let file on a remote host and specify its new URL as the parameter to the manager application. We run the example with this file in the current directory.

Before starting the manager, you must start the agent. Here we give commands for starting the applications from the same terminal window running the Korn shell. On the Windows 2000 platform, you will have to start 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. Connect to the agent in a web browser at the following URL:

http://localhost:8082/

Reload its agent view every time the manager pauses, to see the MBeans as they are created.

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


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

Secure Class Loading

Because class loading exposes an agent to external classes, the Java DMK offers security within the m-let service.

Code Signing

Code signing is a security measure that you can use to identify the originator of a downloaded class. The m-let service will enforce code signatures if it is instantiated in secure mode. One of the constructors of the MLetSrv class takes a boolean parameter that specifies the security mode. For obvious security reasons, the security mode cannot be modified once the m-let service is instantiated.

When the m-let service is running in secure mode, it will only load classes and native libraries that are signed by a trusted party. A trusted party is identified by a key: this key was used to sign the code and a copy of the key is given to all parties that want to download the signed class. Therefore, you must identify trusted keys in your agent before attempting to download their signed classes.


Note –

Downloading native libraries always requires a custom security manager, regardless of whether they are trusted or not.


In the MLet class, security is not determined when you instantiate the m-let service. Rather, security is enabled or disabled for your entire agent application, including any class loaders used by the m-let service.

To enable security, start your agent applications with the java.lang.SecurityManager property on the command line. Then, when the m-let service loads a class through one of its class loaders, the class loader will check the origin and signature of the class against the list of trusted origins and signatures.

The tools involved in signing a class file are the jar, keytool, and jarsigner utilities. On the host where the agent application will download a class, you define a set of permissions for signatures and URL origins. Then, you need to use the policytool utility to generate a java.policy file containing the trusted signatures. Refer to the JDK documentation for the description of these utilities.

When the agent application is started with a security manager, it will check this policy file to ensure that the origin and signature of a downloaded class match a trusted origin and a trusted signature. If they do not match, the code is not trusted and cannot be loaded.

When the agent application is started without the security manager, all classes and native libraries can be downloaded and instantiated, regardless of their origin and signature, or lack thereof.

Chapter 15 Relation Service

The relation service defines and maintains logical relations between MBeans in an agent. It acts as a 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 can 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 unregistered 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 (DMK) also exposes the interface classes for defining your own relation objects. By creating relation instances as objects, you can implement them as MBeans that 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).

This chapter covers the following topics:

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 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 single 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 unified modeling language (UML) modeling of its corresponding association.

Figure 15–1 Comparison of the Relation Models

Diagram comparing the JMX and UML relation models

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; if they are needed they are the responsibility of the designer. In our example, the designer needs 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 types 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 15–1 Relation Service MBean

// 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 15–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 can 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 15–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 calling process 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 15–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 that 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 each Role object is added to the role list.


Example 15–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 can 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 that 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 can 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 operations 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 that 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 could not be read or written is returned in a RoleUnresolvedList. This list contains RoleUnresolved objects that name the role that could not 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 objects in 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 cease to 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 can 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 consistency in this case, the relation service must remove all relations in which a role no longer has the cardinality defined in its relation type. The process of determining invalid relations and removing them is called purging.

The relation service listens for unregistration notifications of the MBean server delegate, and will need to purge its MBeans whenever one is received. It must determine if the MBean removed 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 that the programmer must set to determine whether purges are done automatically or not.

When the purge flag is true, the relation service will purge its data immediately after every unregistration notification. However, the purge operation can be resource intensive for large sets of relation data. In this case the managing application can 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 unregistrations and few relation operations, it might make sense to only purge the relation service manually before each operation. Or the automatic purge flag can be temporarily set to false while executing time-critical operations that need to remove MBeans, but that will not access the relation service.

There are two possible consequences of an unpurged relation service. Roles in a relation can reference object names that no longer have an associated MBean. Or worse, the object name might 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 that 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 is 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.

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 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 15–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 15–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.

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 that 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's own methods for getting and setting roles, thereby taking advantage of their built-in consistency mechanisms.


Example 15–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 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 unregistration notifications in order to find out if the object name is no longer valid.

When instantiating our SimpleRelation MBean, we use the relation type defined in Example 15–3. We also reuse the role list from Example 15–5 that 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 15–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.

To Run the Relation Service Example
  1. Compile all files in this directory with the javac command. For example, on the Solaris platform with the Korn shell, type:


    $ cd examplesDir/Relation/
    $ javac -classpath classpath *.java
    
  2. Start the relation service 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 started, 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.

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

Chapter 16 Cascading Agents

The cascading service enables 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 that 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 can 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 master agent, and a manager does not 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 enables 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 (DMK) 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 that are mirrored in the top agent of a large cascading hierarchy.

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

This chapter contains the following topics:

CascadingAgent MBean

You should create one CascadingAgent MBean for every subagent you want 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 that 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 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 can itself contain cascading agents and mirror MBeans, all of which are mirrored again in the master agent. This effectively enables cascading hierarchies of arbitrary depth and width.

Two master agents can also connect to the same subagent. This is similar to the situation where two managers connect to the same agent and can 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 that we will connect to the RMI connector server of the subagent on port 1099 of the local host. In fact, this is the same as the default values when instantiating a cascading agent MBean, but we also want 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 16–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 writable attributes:

Neither of these attributes can 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 can 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 unregistered. The MBean server delegate in the master agent will send an unregistration notification for each mirror MBean as it is removed.

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 can 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 you can perform on the original MBean can be performed identically on its mirror MBean. You can 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 its 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 16–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 that it accesses. If this information is necessary, the MBeans should expose some kind of identification through their attributes, operations, or object names.

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 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 that 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 can 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 that 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 subagent. 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.

To Run the Cascading Example
  1. 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
    
  2. Start 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
    
  3. Wait for the agent to be completely initialized, then start the master agent with the following command:


    $ java -classpath classpath MasterAgent
    

    When started, 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.

  4. You can 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.

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

    Subagent

    http://localhost:8082/

    Master Agent

    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

  3. Reload the agent view on the master agent.

    The mirror MBeans for the last two agents 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.

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

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

  6. 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 is not 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.

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

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

  9. If you have finished with the agents, press Enter in both of their terminal windows to exit the applications.

Chapter 17 Discovery Service

The discovery service enables you to discover Java dynamic management agents in a network. This service relies on a discovery client object that 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 can also use a discovery monitor that detects when discovery responders are started or stopped.

The combination of these functions enables interested applications to establish a list of active agents and keep it up to date. 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 wants to know about the available agents. However, agent applications are free to use the discovery service because they might 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 from the file in the Discovery directory located in the main examplesDir (see “Directories and Classpath” in the Preface).

This chapter covers the following topics:

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 started and periodically search again for information about the communicators that might 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.

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, enabling you to discover different groups of agents.

Since Java DMK 5.0, the discovery services enable users to specify a local interface from which to send out multicast messages. This is useful when working on a system that has multihome interfaces connecting to disconnected multinetworks. In addition, the DiscoveryResponder constructor enables you to specify the host address to be used to build the discovery response. The address can be specified either as an IPV4 or IPv6 address, or as a host name.

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 17–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 can 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 host'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 information 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 can 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 17–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 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 17–1 Unicast Response Mode

Unicast response mode

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 17–2 Multicast Response Mode

Multicast response mode

Passive Discovery

In passive discovery, the entity seeking knowledge about agents listens for the activation or deactivation of their discovery responders. 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 17–3 Passive Discovery of Discovery Responders

Diagram showing passive discovery of discovery responders

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

Discovery Responder

The agents that are configured 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 17–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 that 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 can 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 can 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 17–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 17–5 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 starting 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, type:


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


    $ java -classpath classpath DiscoveryAgent
    

    The agent application registers an HTML adaptor on the default port (8082).

  2. Press Enter to initialize the discovery components.

    The discovery service is now ready, except for the discovery responder that has not been started yet.

  3. Press Enter a second time to activate the discovery responder.

    The discovery monitor that 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.

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

  5. Now start 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 are 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. The first application receives the activation message of the second responder. Then, the active discovery will 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.

  6. 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:

    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.

  7. When you have 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 will detect the deactivation of the first discovery responder.