Java Dynamic Management Kit 5.1 Tutorial

Part VI Legacy Features

This part describes features of the Java Dynamic Management Kit (Java DMK) that have been superseded by newer features, but that have been retained for purposes of backwards compatibility. Most of these features have been superseded by the implementation of the Java Management Extensions (JMX) Remote API in Java DMK 5.1. The old remote management applications described in this chapter are still fully functional, however, as are the old implementations of cascading and heartbeat.

In Part III, we saw how to manage agents programmatically using the new connectors defined by JMX Remote API. In this part, we present the older protocol connectors and proxy MBeans you can use for managing agents programmatically.

Java DMK defines the APIs needed to develop distributed management applications in the Java programming language. As of release 5.1 of Java DMK, the connectors defined by the JMX Remote API are the standard means for remote applications to establish connections with agents. However, in previous versions of Java DMK, connections were established through protocol connectors over remote method invocation (RMI), hypertext transfer protocol (HTTP), or secure HTTP (HTTPS). The principle, however, remains the same: a connector client object exposes a remote version of the MBean server interface, the connector server object in the agent transmits management requests to the MBean server and forwards any replies.

The legacy connectors and proxies described here provide an abstraction of the communication layer between agent and manager, but they also provide other mechanisms. Notification forwarding with configurable caching and pull policies lets you dynamically optimize bandwidth usage. The connector heartbeat monitors a connection, applies a retry policy, and automatically frees the resources when there is a communication failure. Finally, the connector server in an agent can act as a watchdog and refuse unauthorized requests based on the type of operation.

The implementation of JMX Remote API in Java DMK has also superseded the discovery and cascading services provided with previous versions of Java DMK. The legacy cascading and discovery services are also described in this part.

This part contains the following chapters:

Chapter 21 Legacy Protocol Connectors

Protocol connectors provide a point-to-point connection between a Java dynamic management agent and a management application. Each of the connectors relies on a specific communication protocol, but the API that is available to the management application is identical for all connectors and is entirely protocol-independent.

A connector consists of a connector server component registered in the agent and a connector client object instance in the management application. The connector client exposes a remote version of the MBean server interface. Each connector client represents one agent to which the manager wants to connect. The connector server replies to requests from any number of connections and fulfills them through its MBean server. Once the connection is established, the remoteness of the agent is transparent to the management application, except for any communication delays.

From Java Dynamic Management Kit (Java DMK) 5.1 onwards, remote connections are established via the remote method invocation (RMI) and JMX messaging protocol (JMXMP) connectors defined by Java Management Extensions (JMX) Remote API. However, for reasons of backwards compatibility, the connectors used in previous versions of Java DMK are retained as legacy features of the product.

All the connectors rely on the Java serialization package to transmit data as Java objects between client and server components. Therefore, all objects needed in the exchange of management requests and responses must be instances of a serializable class. However, the data encoding and sequencing are proprietary, and the raw data of the message contents in the underlying protocol are not exposed by the connectors.

All the legacy connectors provided with Java DMK implement a heartbeat mechanism to detect automatically when a connection is lost. This enables the manager to be notified when the communication is interrupted and when it is restored. If the connection is not reestablished, the connector automatically frees the resources that were associated with the lost connection.

The code samples in this chapter are taken from the files in the legacy/SimpleClients and legacy/HeartBeat directories located in the main examplesDir (see Directories and Classpath in the Preface).

This chapter covers the following topics:

21.1 Legacy Connector Servers

Java DMK 5.0 introduced the concept of multihome interfaces. This enables you to work in an environment in which multiple network protocols can be used at different times by the legacy connector servers.

The Java DMK legacy connector servers on the server side do not need to have specified local interfaces. By default, the connector server listens on all local interfaces.

There is a legacy connector server for each of the protocols supported in the previous versions of Java DMK: RMI, hypertext transfer protocol (HTTP) and secure HTTP (HTTPS). Support for these protocols is retained in Java DMK 5.1 for reasons of backwards compatibility. All the legacy connectors inherit from the CommunicatorServer class that is the superclass for all the legacy protocol adaptors and connector servers. This class defines the methods needed to control the port and communication settings that are common to all. Each connector server class then provides specific controls, such as the service name for RMI and authentication information for HTTP. This example covers the legacy RMI connector server, and the HTTP authentication mechanism is described in Chapter 23, Legacy Connector Security.

A connector server listens for incoming requests from its corresponding connector client, decodes that request and encodes the reply. Several connector clients can establish connections with the same connector server, and the connector server can handle multiple requests simultaneously. There only needs to be one connector server MBean per protocol to which the agent needs to respond. However, several connector servers for the same protocol can coexist in an agent for processing requests on different ports.

21.1.1 Instantiating a Legacy RMI Connector Server

The legacy RMI connector server is an MBean, so we instantiate its class and register it in the MBean server. This operation could also be performed remotely, for example if a management application wants to access an agent through an alternative protocol.

The code extract shown in Example 21–1 is taken from the BaseAgent class in the examplesDir/current/BaseAgent directory.


Example 21–1 Instantiating the Legacy RMI Connector Server

// Instantiate an RMI connector server with default port
//
CommunicatorServer rmiConnector = new RmiConnectorServer();

try {
    // We let the RMI connector server provide its default name
    ObjectName rmiConnectorName = null;
    ObjectInstance rmiConnectorInstance =
        myMBeanServer.registerMBean( rmiConnector, rmiConnectorName );

} catch(Exception e) {
    e.printStackTrace();
}

Other constructors for the legacy RmiConnectorServer class have parameters for specifying the port and the RMI service name that the connector server will use. The default constructor assigns port 1099 and name=RmiConnectorServer, as given by the static variables RMI_CONNECTOR_PORT and RMI_CONNECTOR_SERVER, respectively, of the ServiceName class. Both attributes can also be accessed through the getter and setter methods of the legacy RmiConnectorServer class.

Each legacy connector uses different parameters that are specific to its protocol. For example, the HTTP connector does not need a service name. The default values for all parameters are given by the static variables of the ServiceName class.

Registering a connector server as an MBean implies that its MBean server will handle the remote requests that it receives. However, you can specify a different object for fulfilling management requests through the setMBeanServer method that the RmiConnectorServer class inherits from the CommunicatorServer class. For security reasons, this method is not exposed in the legacy RMI connector server MBean, so it must be called from the agent application.

Registering the connector server as an MBean is optional. For example, you might not want it exposed for management. In this case, you must use the setMBeanServer method to specify an object that implements the MBeanServer interface so that it can fulfill management requests.

A user can select a local server interface for a legacy RMI connector server using the RMI property java.rmi.server.hostname.

21.1.2 Connector States

Like all communicator servers, the legacy RMI connector server has a connection state that is identified by the static variables of the CommunicatorServer class:

All connector servers are OFFLINE after their instantiation, so they must be started explicitly. Then, you must wait for a connector server to come ONLINE before it can respond to connections on its designated port.


Example 21–2 Starting the RMI Connector Server

// Explicitly start the RMI connector server
//
rmiConnector.start();

// waiting for it to leave starting state...
while (rmiConnector.getState() == CommunicatorServer.STARTING) {
    try {
        Thread.sleep( 1000 );
    } catch (InterruptedException e) {
        continue;
    }
}

Instead of blocking the application thread, you can register a listener for attribute change notifications concerning the State attribute of the connector server MBean. All connector servers implement this notification which contains both old and new values of the attribute (see 8.3 Attribute Change Notifications). Listeners in the agent can then asynchronously detect when the state changes from STARTING to ONLINE.


Note –

During the STARTING state, the legacy RMI connector server registers its RMI service name with the RMI registry on the local host for its designated port. If no registry exists, one is instantiated for the given port. Due to a limitation of version 1.4 of the Java platform, creating a second registry in the same Java virtual machine (JVM) will fail. As a workaround, before starting an RMI connector server on a new, distinct port number in an agent, you must run the rmiregistry command from a terminal on the same host. This limitation is specific to the legacy RMI connector. The HTTP protocols do not require a registry.


The stop method is used to take a connector server offline. The stop method is also called by the preDeregister method that all connector servers inherit. Stopping a connector server interrupts all requests that are pending and closes all connections that are active. When a connection is closed, all of its resources are cleaned up, including all notification listeners, and the connector client can be notified by a heartbeat notification (see 21.3 Legacy Heartbeat Mechanism). A connection that is closed can no longer be reopened. The connector client must establish a new connection when the connector server is restarted.

The setPort method that all connector servers inherit from the CommunicatorServer class enables you to change the port on which management requests are expected. You can only change the port when the connector server is offline, so it must be explicitly stopped and then restarted. The same rule applies to the setServiceName method that is specific to the RMI connector server. These methods are also exposed in the MBean interface, along with start and stop, enabling a remote management application to configure the connector server through a different connection.

21.2 Legacy Connector Clients

The manager application interacts with a connector client to access an agent through an established connection. Through its implementation of the RemoteMBeanServer interface, a legacy connector client provides methods to handle the connection and access the agent. This interface specifies nearly all of the same methods as the MBeanServer interface, meaning that an agent is fully manageable from a remote application.

Through the connector, the management application sends management requests to the MBeans located in a remote agent. Components of the management application access remote MBeans by calling the methods of the connector client for getting and setting attributes and calling operations on the MBeans. The connector client then returns the result, providing a complete abstraction of the communication layer.

By default, a legacy connector client uses the default host name, InetAddress.getLocalHost().getHostName()

21.2.1 Multihome Interfaces

The multihome interfaces introduced in Java DMK 5.0 enable you to select the interfaces for the different network protocols used by legacy connector clients.

An HTTP(S) client enables you to select a local interface to receive notifications from a server. This user-specific local interface address is sent to a server and is then used by that server to push notifications. The interface address can be specified as either an IPv4 or an IPv6 address, or as a host name.

A legacy RMI connector client does not need to specify a local interface.

21.2.2 RemoteMBeanServer Interface

All connector clients implement the RemoteMBeanServer interface to expose the methods needed to access and manage the MBeans in a remote agent. This interface allows all management operations that would be possible directly in the agent application. In fact, the methods of the connector client for accessing MBeans remotely have exactly the same name and signature as their equivalent methods in the MBeanServer interface.

Table 21–1 lists the methods that are defined identically in both the MBeanServer and the RemoteMBeanServer interfaces.

Table 21–1 Shared Methods

Type 

Signature 

* void

addNotificationListener(ObjectName name, NotificationListener listener, NotificationFilter filter, java.lang.Object handback)

ObjectInstance

createMBean(*) – All four overloaded forms of this method

* java.lang.Object

getAttribute(ObjectName name, java.lang.String attribute)

* AttributeList

getAttributes(ObjectName name, java.lang.String[] attributes)

java.lang.String

getDefaultDomain()

java.lang.Integer

getMBeanCount()

* MBeanInfo

getMBeanInfo(ObjectName name)

ObjectInstance

getObjectInstance(ObjectName name)

* java.lang.Object

invoke(ObjectName name, java.lang.String operationName, java.lang.Object[] params, java.lang.String[] signature)

boolean

isInstanceOf(ObjectName name, java.lang.String className)

boolean

isRegistered(ObjectName name)

java.util.Set

queryMBeans(ObjectName name, QueryExp query)

java.util.Set

queryNames(ObjectName name, QueryExp query)

* void

removeNotificationListener(ObjectName name, NotificationListener listener)

* void

setAttribute(ObjectName name, Attribute attribute)

* AttributeList

setAttributes(ObjectName name, AttributeList attributes)

* void

unregisterMBean(ObjectName name)

These methods are defined in the ProxyHandler interface (see 24.1.1 Legacy Local and Remote Proxies).

Components of a management application that rely solely on this common subset of methods can be instantiated in either the agent or the manager application. Such components are location-independent and can be reused either locally or remotely as management solutions evolve. This symmetry also allows the design of advanced management architectures where functionality can be deployed either in the agent or in the manager, depending on runtime conditions.

The other, unshared methods of the RemoteMBeanServer interface are used to establish and monitor the connection. 21.2.3 Establishing a Connection shows how to establish a connection and access MBeans directly through the connector client.21.3 Legacy Heartbeat Mechanism shows how to monitor a connection and detect when it is lost.

21.2.3 Establishing a Connection

The target of a connection is identified by a protocol-specific implementation of the ConnectorAddress interface. This object contains all the information that a connector client needs to establish a connection with the target agent. All address objects specify a host name and port number. An RMI address adds the required service name, and HTTP-based addresses have an optional authentication field (see 23.1 Password-Based Authentication (Legacy Connectors)). In addition, the ConnectorType string identifies the protocol without needing to introspect the address object.

In our example, the target of the connection is an active legacy RMI connector server identified by an RmiConnectorAddress object. We use the default constructor to instantiate a default address object, but otherwise, these parameters can be specified in a constructor or through setter methods. The default values of the information contained in the RmiConnectorAddress object are the following:

The RmiConnectorAddress object is used as the parameter to the connect method of the RmiConnectorClient instance. This method tries to establish the connection and throws an exception if there is a communication or addressing error. Otherwise, when the connect method returns, the connector client is ready to perform management operations on the designated agent.

The code segment shown inExample 21–3 is taken from the ClientWithoutProxy class in the examples directory.


Example 21–3 Establishing a Connection

echo("\t>> Instantiate the RMI connector client...");
connectorClient = new RmiConnectorClient();

echo("\t>> Instantiate a default RmiConnectorAddress object...");
RmiConnectorAddress address = new RmiConnectorAddress();

// display the default values
echo("\t\tTYPE\t= "   + address.getConnectorType());
echo("\t\tPORT\t= "   + address.getPort());
echo("\t\tHOST\t= "   + address.getHost());
echo("\t\tSERVER\t= " + address.getName());
echo("\t<< done <<");

echo("\t>> Connect the RMI connector client to the agent...");
try {
     connectorClient.connect( address );

} catch(Exception e) {
     echo("\t!!! RMI connector client could not connect to the agent !!!");
     e.printStackTrace();
     System.exit(1);
}

21.2.4 Managing MBeans Remotely

Once the connection to an agent is established, the management application can access that agent's MBeans through the legacy RemoteMBeanServer interface of the connector client. Invoking these methods has exactly the same effect as calling the equivalent methods directly on the MBean server instance.

It is possible to restrict access to certain methods of the MBean server when they are called through the legacy connector client, but this is performed by a security mechanism in the legacy connector server. See 23.2 Context Checking for more details.

21.2.4.1 Creating and Unregistering MBeans in the Agent

We use the createMBean method to instantiate and register an object from its class name. This class must already be available in the agent application's classpath.


Example 21–4 Creating and Unregistering an MBean Remotely

private void doWithoutProxyExample(String mbeanName) {

    try {

        // build the MBean ObjectName instance
        //
        ObjectName mbeanObjectName = null;
        String domain = connectorClient.getDefaultDomain();
        mbeanObjectName = new ObjectName( domain + ":type=" + mbeanName );

        // create and register an MBean in the MBeanServer of the agent
        //
        echo("\nCurrent MBean count in the agent = "+
             connectorClient.getMBeanCount());
        echo("\n>>> CREATE the " + mbeanName +
             " MBean in the MBeanServer of the agent:");
        String mbeanClassName = mbeanName;

        ObjectInstance mbeanObjectInstance =
        connectorClient.createMBean( mbeanClassName, mbeanObjectName );

        echo("\tMBEAN CLASS NAME      = " +
             mbeanObjectInstance.getClassName() );
        echo("\tMBEAN OBJECT NAME     = " +
             mbeanObjectInstance.getObjectName() );
        echo("\nCurrent MBean count in the agent = "+
             connectorClient.getMBeanCount() );

        [...] // Retrieve MBeanInfo and access the MBean (see below)

        // unregister the MBean from the agent 
        //
        echo("\n>>> UNREGISTERING the "+ mbeanName +" MBean");
        connectorClient.unregisterMBean(
            mbeanObjectInstance.getObjectName() );

        [...]

    } catch (Exception e) {
        e.printStackTrace();
    }
}

Example 21–4 shows the use of other calls to the remote agent, such as getDefaultDomain and getMBeanCount that have the same purpose as in an agent application.

21.2.4.2 Accessing MBean Attributes and Operations

Once you can access the object names for MBeans in the agent, you can know their management interface from their MBeanInfo object. The code in Example 21–5 is actually called in between the MBean creation and unregistration shown in Example 21–4.


Example 21–5 Retrieving the MBeanInfo Object

ObjectName mbeanObjectName = mbeanObjectInstance.getObjectName();

echo("\n>>> Getting the management information of the MBean");
MBeanInfo info = null;
try {

    info = connectorClient.getMBeanInfo( mbeanObjectName );

} catch (Exception e) {
    echo("\t!!! Could not get MBeanInfo object for "+ mbeanObjectName );
    e.printStackTrace();
    return;
}

// display content of MBeanInfo object
//
echo("\nCLASSNAME: \t"+ info.getClassName());
echo("\nDESCRIPTION: \t"+ info.getDescription());
echo("\nATTRIBUTES");
MBeanAttributeInfo[] attrInfo = info.getAttributes();
if ( attrInfo.length>0 ) {
    for( int i=0; i<attrInfo.length; i++ ) {
        echo(" ** NAME: \t"+ attrInfo[i].getName());
        echo("    DESCR: \t"+ attrInfo[i].getDescription());
        echo("    TYPE: \t"+ attrInfo[i].getType() +
             "\tREAD: "+ attrInfo[i].isReadable() +
             "\tWRITE: "+ attrInfo[i].isWritable());
    }
} else echo(" ** No attributes **");

[...]

It is then straightforward to perform management operations on MBeans through the legacy connector client. As in an agent, we call the generic getters, setters, and call methods with the object name of the MBean, the name of the attribute or operation, and any parameters. As in the methods of the MBean server, we need to use the Attribute and AttributeList classes to pass attributes as name-value pairs.


Example 21–6 Accessing an MBean Through the Legacy Connector Client

try {
    // Getting attribute values
    String State = (String)
        connectorClient.getAttribute( mbeanObjectName, "State" );
    Integer NbChanges = (Integer)
        connectorClient.getAttribute( mbeanObjectName, "NbChanges" );
    echo("\tState     = \"" + State + "\"");
    echo("\tNbChanges = " + NbChanges);

    // Setting the "State" attribute
    Attribute stateAttr = new Attribute("State", "new state from client");
    connectorClient.setAttribute(mbeanObjectName, stateAttr);

    // Invoking the "reset" operation
    Object[] params = new Object[0];
    String[] signature = new String[0];
    connectorClient.invoke(mbeanObjectName, "reset", params, signature);

} catch (Exception e) {
    e.printStackTrace();
    return;
}

All other MBean access methods are available in the same manner, such as bulk getters and setters, and the query methods.

21.2.4.3 Creating and Accessing Dynamic MBeans

In the first run of the example, the management application creates, manages, and unregisters a standard MBean in the remote agent. However, standard and dynamic MBeans are designed to be managed through the same methods, both in the MBeanServer and in the RemoteMBeanServer interfaces.

As shown in Example 21–4, the subroutine of the example application takes only a single class name parameter. The first time this subroutine is called, the example passes the class name of a standard MBean, and the second time, that of a dynamic MBean. For the example to run, the two MBeans must have an identical management interface. By extension of this special case, we see that the connector client can manage dynamic MBeans through the same methods as it manages standard MBeans, without making any distinction between the two.

21.2.5 Running the SimpleClients Example

The examplesDir/legacy/SimpleClients directory contains all of the files for three sample managers, a base agent, and some MBeans to manage. In this chapter, we run the ClientWithoutProxy application that demonstrates simple operations on MBeans through a legacy RMI connector.

To Run the Simple Client 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/legacy/SimpleClients/
    $ javac -classpath classpath *.java
    

    We do not need all the files for this chapter, but they are used in Chapter 24, Legacy Proxy Mechanism. In this demonstration, we only need the BaseAgent and ClientWithoutProxy applications, as well as the standard and dynamic MBeans.

  2. Start the agent in another terminal window on the same host with the following command:


    $ java -classpath classpath BaseAgent
    

    The agent creates an HTML protocol adaptor and a legacy RMI connector server to which the client application establishes a connection, and then it waits for management operations.

  3. Wait for the agent to be completely initialized, then start the manager with the following command:


    $ java -classpath classpath ClientWithoutProxy
    

    The client application creates the RMI connector client and establishes the connection to the base agent.

  4. Press Enter in the manager window to step through the example.

    The management application instantiates both types of MBeans, looks at their metadata, and performs management operations on them. The results of each step are displayed in the terminal window.

  5. At any time, you can view the agent through its HTML adaptor and interact with the MBeans created by the management application.

    For example, immediately after the manager creates an MBean, you could modify its attributes and see this change reflected when the connector client access the new values.

  6. Press Enter in both windows to stop the agent and manager applications.

21.3 Legacy Heartbeat Mechanism

The legacy heartbeat mechanism monitors the connection between a manager and an agent and automates the cleanup procedure when the connection is lost. This enables both the manager and the agent to release resources that were allocated for maintaining the connection.

The mechanism is entirely contained in the connector client and connector server components, no additional objects are involved. In addition, connector clients send notifications that the manager application can receive to be aware of changes in the status of a connection.

All legacy connector clients of the Java DMK implement the HeartBeatClientHandler interface to provide a heartbeat mechanism. This means that agent-manager connections over the legacy RMI, HTTP, and HTTPS connectors can be monitored and controlled in the same way. A manager application could even use the same notification handler for all connector clients where the heartbeat mechanism is activated.

21.3.1 Configuring the Heartbeat

To monitor the connection, the connector client sends periodic heartbeats (ping requests) to the connector server that acknowledges them by sending a reply (ping responses). If either heartbeat is lost, the components of the legacy connector retry until either the connection is re-established or the number of retries has been exhausted.

In a connector client, the methods specified by the HeartBeatClientHandler interface set the heartbeat period and the number of retries that are attempted. You should determine these parameters empirically to implement the desired connection monitoring behavior, taking into account the network conditions and topology between the hosts of your manager and agent applications.

In Example 21–7, the management application configures the heartbeat mechanism before the connection to an agent is established.


Example 21–7 Configuring the Heartbeat in the Connector Client

// CREATE a new RMI connector client
//
echo("\tInstantiate the RMI connector client...");
connectorClient = new RmiConnectorClient();

// SET heartbeat period to 1 sec. Default value is 10 secs
//
echo("\tSet heartbeat period to 1 second...");
connectorClient.setHeartBeatPeriod(1000);

// SET heartbeat number of retries to 2. Default value is 6 times
//
echo("\tSet heartbeat number of retries to 2 times...");
connectorClient.setHeartBeatRetries(2);

Using the same methods, the heartbeat configuration can also be modified at any time, even after the connection has been established. By default, the heartbeat mechanism is activated in a connector with a 10 second heartbeat and 6 retries, meaning that a connection that cannot be re-established within one minute is assumed to be lost.

Setting the number of heartbeat retries to zero causes a lost connection to be signalled immediately after the heartbeat fails. Setting the heartbeat period to zero deactivates the mechanism and prevents any further connection failures from being detected.

No specific configuration is necessary on the agent-side legacy connector server. It automatically responds to the heartbeat messages. These heartbeat messages contain the current heartbeat settings from the legacy connector client that also configure the connector server. In this way, both client and server apply the same retry policy, and when the configuration is updated in the connector client, it is immediately reflected in the connector server. The connector server can handle multiple connections from different management applications, each with its specific heartbeat configuration.

The legacy connector server applies its retry policy when the next expected heartbeat message is not received within the heartbeat period. From that moment, the connector server begins a timeout period that lasts 20% longer than the number of retries times the heartbeat period. This corresponds to the time during which the connector client attempts to resend the heartbeat, with a safety margin to allow for communication delays. If no further heartbeat is received in that timeout, the connection is determined to be lost.

The heartbeat ping messages also contain a connection identifier so that connections are not erroneously re-established. If a connector server is stopped, thereby closing all connections, and then restarted between two heartbeats or before the client's timeout period has elapsed, the server responds to heartbeats from a previous connection. However, the connector client detects that the identifier has changed and immediately declares that the connection is lost, regardless of the number of remaining retries.

During the timeout period, the notification push mechanism in the connector server is suspended to avoid losing notifications (see Chapter 22, Notification Forwarding in Legacy Connectors). Similarly, while the connector client is retrying the heartbeat, it must suspend the notification pull mechanism if it is in effect.

When a connection is determined to be lost, both the connector client and server free any resources that were allocated for maintaining the connection. For example, the connector server unregisters all local listeners and deletes the notification cache needed to forward notifications. Both components also return to a coherent, functional state, ready to establish or accept another connection.

The state of both components after a connection is lost is identical to the state that is reached after the disconnect method of the connector client is called. In fact, the disconnect method is called internally by the connector client when a connection is determined to be lost, and the equivalent, internal method is called in the connector server when its timeout elapses without recovering a heartbeat.

21.3.2 Receiving Heartbeat Notifications

The legacy connector client also sends notifications that signal any changes in the state of the connection. These notifications are instances of the HeartBeatNotification class. The HeartBeatClientHandler interface includes methods specifically for registering for heartbeat notifications. These methods are distinct from those of the NotificationRegistration interface that a connector client implements for transmitting agent-side notifications (see 22.1 Registering Manager-Side Listeners).


Example 21–8 Registering for Heartbeat Notifications

// Register this manager as a listener for heartbeat notifications
// (the filter and handback objects are not used in this example)
//
echo("\tAdd this manager as a listener for heartbeat notifications...");
connectorClient.addHeartBeatNotificationListener(this, null, null);

Instances of heartbeat notifications contain the connector address object from the connection that generated the event. This enables the notification handler to listen to any number of connectors and retrieve all relevant information about a specific connection when it triggers a notification. The HeartBeatNotification class defines constants to identify the possible notification type strings for heartbeat events:

Once they are established, connections can go through any number of retrying/re-established cycles and then be terminated by the user or determined to be lost and terminated automatically. When the heartbeat mechanism is deactivated by setting the heartbeat period to zero, only heartbeat notifications for normally established and normally terminated connections continue to be sent. In that case, connections can be lost but they are not detected nor indicated by a notification.

The following diagram shows the possible sequence of heartbeat notifications during a connection. Retries are enabled when the getHeartBeatRetries method returns an integer greater than zero.

Figure 21–1 Sequencing of Heartbeat Notifications

Diagram showing sequencing of heartbeat notifications

Example 21–9 shows the source code for the notification handler method in our manager class. The handler prints out the notification type and the RMI address associated with the connector that generated the notification.


Example 21–9 Heartbeat Notification Handler

public void handleNotification(Notification notification, Object handback){

    echo("\n>>> Notification has been received...");
    echo("\tNotification type = " + notification.getType());

    if (notification instanceof HeartBeatNotification) {
        ConnectorAddress notif_address =
            ((HeartBeatNotification)notification).getConnectorAddress();

        if (notif_address instanceof RmiConnectorAddress) {
            RmiConnectorAddress rmi_address =
                (RmiConnectorAddress) notif_address;

            echo("\tNotification connector address:");
            echo("\t\tTYPE   = " + rmi_address.getConnectorType());
            echo("\t\tHOST   = " + rmi_address.getHost());
            echo("\t\tPORT   = " + rmi_address.getPort());
            echo("\t\tSERVER = " + rmi_address.getName());
	        }
    }
}

In the agent application, the connector server does not emit any notifications about the state of its connections. The HTTP protocol-based connectors do provide a count of active clients, but there is no direct access to heartbeat information in an agent's connector servers.

21.3.3 Running the Legacy Heartbeat Example

The examplesDir/legacy/HeartBeat directory contains all of the files for the Agent and Client applications that demonstrate the heartbeat mechanism through an RMI connector.

To Run the Heartbeat Example: Normal Termination
  1. Compile all files in this directory with the javac command.

    For example, on the Solaris platform with the Korn shell, type:


    $ cd examplesDir/legacy/HeartBeat/
    $ javac -classpath classpath *.java
    

    To demonstrate the various communication scenarios, we will run the example three times: once to see a normal termination, once to see how the manager reacts to a lost connection, and once to see how the agent reacts to a lost connection.

  2. Start the agent on another host or in another terminal window with the following command:


    $ java -classpath classpath Agent
    

    The agent only creates the legacy RMI connector server to which the client application will establish a connection, and then it waits for management operations.

  3. Wait for the agent to be completely initialized, then start the manager with the following command, where hostname is the name of the host running the agent.

    The RMI connector in this example uses port 1099 by default. If you started the agent on the same host, you can omit the hostname and the port number:


    $ java -classpath classpath Client hostname 1099
    

    The client application creates the legacy RMI connector client, configures its heartbeat, and registers a notification listener, as seen in the code examples. When the connection is established, the listener outputs the notification information in the terminal window.

  4. Press Enter in the manager window to call the disconnect method on the connector client and stop the Client application.

    In the terminal window, the heartbeat notification listener outputs the information for the normal connection termination before the application ends.

  5. Leave the agent application running for the next scenario.

To Run the Legacy Heartbeat Example: Connector Client Reaction
  1. Start the Client application again:


    $ java -classpath classpath Client hostname 1099
    
  2. When the connection is established, press Control-C in the agent's window to stop the connector server and the agent application.

    This simulates a broken communication channel as seen by the connector client.

    Less than a second later, when the next heartbeat fails, the heartbeat retrying notification is displayed in the manager's terminal window. Two seconds later, after both retries have failed, the lost connection and terminated connection notifications are displayed.

  3. Press Enter in the manager window to exit the Client application.

To Run the Legacy Heartbeat Example: Connector Server Reaction
  1. Start the agent in debug mode on another host or in another terminal window with the following command:


    $ java -classpath classpath -DLEVEL_DEBUG Agent
    
  2. Wait for the agent to be completely initialized, then start the Client application again:


    $ java -classpath classpath Client hostname 1099
    

    When the connection is established, the periodic heartbeat messages are displayed in the debug output of the agent.

  3. This time, press Control-C in the client's window to stop the connector client and the manager application.

    This simulates a broken communication channel as seen by the connector server in the agent.

    After the heartbeat retry timeout elapses in the agent, the lost connection message is displayed in the heartbeat debugging output of the agent.

  4. Type Control-C in the agent window to stop the agent application and end the example.

Chapter 22 Notification Forwarding in Legacy Connectors

The notification mechanism that was used with the legacy connectors in versions of the Java Dynamic Management Kit (Java DMK) prior to version 5.1 has been superseded by the much simpler notifications from Java Management Extensions (JMX) Remote API. The legacy notification mechanism has been retained for reasons of backwards compatibility.

The code samples in this topic are taken from the files in the legacy/Notification example directory located in the main examplesDir (see “Directories and Classpath” in the Preface).

This chapter covers the following topics:

22.1 Registering Manager-Side Listeners

Like the other structures of Java DMK, the legacy notification mechanism is designed to be homogeneous from the agent to the manager side. For this reason, notification objects and listener interfaces in manager applications are identical to those on the agent side.

The symmetry of the interfaces also means that code for listeners can easily be reused without modification in either agent or manager applications. Listeners in managers are similar to those in agents, and they could even be identical objects in some management solutions. However, in most cases, manager-side listeners will receive different notifications and take different actions from their agent-side peers.

22.1.1 Agent-Side Broadcaster

The notification broadcasters are MBeans registered in an agent's MBean server to which our management application needs to connect. Only notifications sent by registered MBeans can be forwarded to manager applications, and a manager-side listener can receive them only by registering through a connector client or a proxy object.

Other notification broadcasters can exist independently in the manager application, but listeners need to register directly with these local broadcasters. Nothing prevents a listener object from registering both with a connector client or proxy for remote notifications and with a local broadcaster.

The code example below shows how the sample NotificationEmitter MBean sends notifications (the code for its MBean interface has been omitted). It extends the NotificationBroadcasterSupport class to reuse all of its listener registration facilities. It only contains one operation that can be called by our manager to trigger any number of notifications.


Example 22–1 Agent-Side Broadcaster MBean

import javax.management.MBeanNotificationInfo;
import javax.management.NotificationBroadcasterSupport;
import javax.management.Notification;

public class NotificationEmitter
    extends NotificationBroadcasterSupport
    implements NotificationEmitterMBean {

    // Just to make the inheritance explicit
    public NotificationEmitter() {
        super();
    }

    // Provide details about the notification type and class that is sent
    public MBeanNotificationInfo[] getNotificationInfo() {
        
        MBeanNotificationInfo[] ntfInfoArray = new MBeanNotificationInfo[1];
        
        String[] ntfTypes = new String[1];
        ntfTypes[0] = myType;
        
        ntfInfoArray[0] = new MBeanNotificationInfo( ntfTypes,
            "javax.management.Notification", 
            "Notifications sent by the NotificationEmitter");
        return ntfInfoArray;
    }  

    // The only operation: sends any number of notifications
    // whose sequence numbers go from 1 to "nb"
    public void sendNotifications( Integer nb ) {

        for (int i=1; i<=nb.intValue(); i++) {
            sendNotification(new Notification(myType, this, i));
        }
    }
    private String myType = "notification.my_notification";
}

Our MBean invents a notification type string and exposes this information through the getNotificationInfo method. To demonstrate the forwarding mechanism, we are more interested in the sequence number, which enables us to identify the notifications as they are received in the manager.

This MBean demonstrates that the broadcaster has total control over the contents of its notifications. Constructors for the Notification object enable you to specify all of the fields, even ones such as the time stamp. In this example, we control the sequence number and our chosen policy is to reset the sequence number to 1 with every call to the operation. Of course, you are free to choose the notification contents, including the time-stamping and sequence-numbering policies that fit your management solution.


Note –

Due to possible loss in the communication layer and the inherent indeterminism of thread execution, the legacy notification model does not guarantee that remote notifications will be received nor that their sequence will be preserved. If notification order is critical to your application, your broadcaster should set the sequence numbers appropriately, and your listeners should sort the received notifications.


22.1.2 Manager-Side Listener

In our simple example, the Client class itself is the listener object. Usually, a listener would be a separate instance of a special listener class and depending on the complexity of the manager, there might be several classes of listeners, each for a specialized category of notifications.


Example 22–2 The Manger-Side Listener

public class Client implements NotificationListener {

    [...] // Constructor omitted

    // Implementation of the NotificationListener interface  
    //
    public void handleNotification(Notification notif, Object handback) {

        System.out.println("Client: received a notification of type "
            + notif.getType() + "\nwith the sequence number "
            + notif.getSequenceNumber());
    }
    [...]  // main omitted
}

As explained in the notification mechanism 8.1 Overview of Notifications, a listener on the agent side is typically an MBean that receives notifications about the status of other MBeans and then processes or exposes this information in some manner. Only if a key value or some management event is observed will this information be passed to a listening manager, probably by sending a different notification.

In this manner, the notification model reduces the communication that is necessary between agents and managers. Your management solution determines how much decisional power resides in the agent and when situations are escalated. These parameters will affect your design of the notification flow between broadcasters, listeners, agents, and managers.

The usual role of a manager-side listener is to process the important information in a notification and take the appropriate action. As we shall see, our notification example is much simpler. Our goal is not to construct a real-world example, but to demonstrate the mechanisms that are built into the Java DMK.

22.1.3 Adding a Listener Through the Connector

By extension of the ClientNotificationHandler interface, the RemoteMBeanServer interface exposes methods for adding and removing listeners. The signatures of these methods are identical to those of the agent-side MBeanServer interface. The only difference is that they are implemented in the connector client classes that make the communication protocol transparent.

Our manager application uses the RMI protocol connector. After creating the connector client object, we use the methods of its RemoteMBeanServer interface to create our broadcaster MBean and then register as a listener to this MBean's notifications.


Example 22–3 Adding a Listener through the Connector

// Use RMI connector on port 8086 to communicate with the agent
System.out.println(">>> Create an RMI connector client");
RmiConnectorClient connectorClient = new RmiConnectorClient();

// agentHost was read from the command line or defaulted to localhost
RmiConnectorAddress rmiAddress = new RmiConnectorAddress(
    agentHost, 8086, com.sun.jdmk.ServiceName.RMI_CONNECTOR_SERVER);
connectorClient.connect(rmiAddress);

// Wait 1 second for connecting
Thread.sleep(1000);

// Create the MBean in the agent
ObjectName mbean = new ObjectName ("Default:name=NotificationEmitter");
connectorClient.createMBean("NotificationEmitter", mbean);

// Now add ourselves as the listener (no filter, no handback)
connectorClient.addNotificationListener(mbean, this, null, null);

You can see how similar this code is to the agent application by comparing it with the code example for 8.2.3 Adding a Listener Through the MBean Server.

If you have generated and instantiated proxy MBeans for your broadcaster MBeans, you can also register through the addNotificationListener method that they expose.

Again, the method signatures defined in a proxy MBean are identical to those of the MBeanServer or NotificationBroadcasterClient interfaces for adding or removing listeners. See the code example for 8.3.3 Adding a Listener Directly to an MBean. Listeners added through a proxy MBean receives the same notifications as listeners added to the same MBean through the interface of the connector client.


Note –

Following the Java programming model, the connector client limits its resource usage by only running one thread to notify all of its listeners. This thread calls all of the handler callback methods that have been added through this connector. Therefore, the callbacks should return quickly and use safe programming to avoid crashing the connector client.


22.2 Push Mode

The push mode of notification broadcasting only exists for the legacy connectors. The new connectors implemented in Java DMK 5.1 all use pull mode. Push mode is presented here purely for reasons of backwards compatibility with older versions of Java DMK.

Because the broadcaster and the listener are running on separate hosts or in separate virtual machines on the same host, their notifications must be forwarded from one to the other. The mechanism for doing this is completely transparent to the users of the Java DMK components.

Briefly, the legacy connector client instructs the legacy connector server to add its own agent-side listener to the designated broadcaster using the methods of the MBeans server. Then, the connector server implements a buffering cache mechanism to centralize notifications before serializing them to be forwarded to the connector client. By design, it is the connector client in the manager application that controls the buffering and forwarding mechanism for a connection.

Figure 22–1 summarizes the notification forwarding mechanism and its actors. In particular, it shows how the connector server uses internal listener instances to register locally for MBean notifications, even if this mechanism is completely hidden from the user. The path of listener registration through calls to the addNotificationListener method of the various interfaces is paralleled by the propagation of notifications through calls to the listeners' handleNotification method.

Figure 22–1 Notification Forwarding Internals

Diagram of the notification forwarding mechanism

Neither the broadcaster nor the listener need to implement any extra methods, or even be aware that the other party is remote. Only the designer needs to be aware of communication issues such as delays. You cannot expect the listener to be invoked instantaneously after a remote broadcaster sends a notification.

The forwarding mechanism allows you to configure how and when notifications are forwarded. This enables you to optimize the communication strategy between your agents and managers. There are two basic modes for notification forwarding in legacy connectors: push mode and pull mode. A notification in the connector server's cache is either pushed to the manager at the agent's initiative, or pulled by the manager at its own initiative.

The push mode for notification forwarding is the simplest because it implements the expected behavior of a notification. When a notification is sent from an MBean to its listener, it is immediately pushed to the manager-side where the listener's handler method is called. There is no delay in the caching, and if the communication layer is quick enough, the listener is invoked almost immediately after the notification is sent.

Push mode is the default forwarding policy of a newly instantiated connector client.

In our manager example, we explicitly set the connector client in push mode and then trigger the agent-side notifications.


Example 22–4 Switching to the Notification Push Mode

System.out.println("\n>>> Set notification forward mode to PUSH.");
connectorClient.setMode(ClientNotificationHandler.PUSH_MODE);

System.out.println(">>> Have our MBean broadcast 10 notifications...");
params[0] = new Integer(10);
signatures[0] = "java.lang.Integer";
connectorClient.invoke(mbean, "sendNotifications", params, signatures);

System.out.println(">>> Done.");
System.out.println(">>> Receiving notifications...\n");

// Nothing to do but wait while our handler receives the notifications
Thread.sleep(2000);

The connector client exposes the methods for controlling the agent's notification buffer. This caching buffer is not used in push mode, so these methods do not affect pushed notifications. The methods do however set internal variables that will be taken into account if and when pull mode is enabled.

The advantage of push mode is that it works without any further intervention: notifications eventually reach their remote listeners. Push mode works when the communication layer and the listener's processing capacity are adapted to the notification emission rate, or more specifically to the potential emission rate. Because all notifications are immediately sent to the manager hosts, a burst of notifications will cause a burst of traffic that might or might not be adapted to the communication layer.

If your communication layer is likely to be saturated, either your design should control broadcasters to prevent bursts of notifications, or you should use the pull mode which has this control functionality built-in. The push mode is ideal if you have reliable and fast communication between your agents and your managers. You can also dynamically switch between modes, enabling a management application to fine-tune its communication policy depending on the number of notifications that must be handled.

22.3 Pull Mode

In pull mode, notifications are not immediately sent to their remote listeners. Rather, they are stored in the connector server's internal buffer until the connector client requests that they be forwarded. Instead of being sent individually, the notifications are grouped to reduce the load on the communication layer. Pull mode has the following settings that let the manager define the notification forwarding policy:

For a given connection, there is one cache for all listeners, not one cache per listener. This cache therefore has one buffering policy whose settings are controlled through the methods exposed by the connector client. The cache buffer contains an unpredictable mix of notifications in transit to all manager-side listeners added through a given connector client or through one of its bound proxy MBeans. The buffer operations such as pulling or overflowing apply to this mix of notifications, not to any single listener's notifications.

22.3.1 Periodic Forwarding

Pull mode forwarding is necessarily a compromise between receiving notifications in a timely manner, not saturating the communication layer, and not overflowing the buffer. Notifications are stored temporarily in the agent-side buffer, but the manager-side listeners still need to receive them. Pull mode includes automatic pulling that retrieves all buffered notifications regularly.

The frequency of the pull forwarding is controlled by the pull period expressed in milliseconds. By default, when pull mode is enabled, the manager will automatically begin pulling notifications once per second. Whether or not there are any notifications to receive depends upon events in the agent.

Our manager application sets a half-second pull period and then triggers the notification broadcaster.


Example 22–5 Pulling Notifications Automatically

System.out.println(">>> Set notification forward mode to PULL.");
connectorClient.setMode(ClientNotificationHandler.PULL_MODE);

// Retrieve buffered notifications from the agent twice per second
System.out.println(">>> Set the forward period to 500 milliseconds.");
connectorClient.setPeriod(500);

System.out.println(">>> Have our MBean broadcast 20 notifications...");
params[0] = new Integer(20);
signatures[0] = "java.lang.Integer";
connectorClient.invoke(mbean, "sendNotifications", params, signatures);
System.out.println(">>> Done.");

// Wait for the handler to process all notifications
System.out.println(">>> Receiving notifications...\n");
Thread.sleep(2000);

When notifications are pulled, all notifications in the agent-side buffer are forwarded to the manager and the registered listeners. It is not possible to set a limit on the number of notifications that are forwarded except by limiting the size of the buffer (see 22.3.3 Agent-Side Buffering). Even in a controlled example such as ours, the number of notifications in the agent-side buffer at each pull period is completely dependent upon the agent's execution paths, and therefore unpredictable from the manager-side.

22.3.2 On-Demand Forwarding

You can disable automatic pulling by setting the pull period to zero. In this case, the connector client will not pull any notifications from the agent until instructed to do so. Use the getNotifications method of the connector client to pull all notifications when desired. This method will immediately forward all notifications in the agent-side buffer. Again, it is not possible to limit the number of notifications that are forwarded except by limiting the buffer size.

In this example, we disable the automatic pulling and then trigger the notification broadcaster. The notifications will not be received until we request that the connector server pull them. Then, all of the notifications will be received at once.


Example 22–6 Pulling Notifications by Request

System.out.println(">>> Use pull mode with period set to zero.");
connectorClient.setMode(ClientNotificationHandler.PULL_MODE);
connectorClient.setPeriod(0);

System.out.println(">>> Have our MBean broadcast 30 notifications...");
params[0] = new Integer(30);
signatures[0] = "java.lang.Integer";
connectorClient.invoke(mbean, "sendNotifications", params, signatures);
System.out.println(">>> Done.");

// Call getNotifications to pull all buffered notifications from the agent
System.out.println("\n>>> Press Enter to pull the notifications.");
System.in.read();
connectorClient.getNotifications();

// Wait for the handler to process all notifications
Thread.sleep(100);

In the rest of our example, we use the on–demand forwarding mechanism to control how many notifications are buffered on the agent-side and thereby test the different caching policies.


22.3.3 Agent-Side Buffering

In pull mode, notifications are stored by the connector server in a buffer until they are pulled by the connector client. Any one of the pull operations, whether on-demand or periodic, empties this buffer, and it fills up again as new notifications are triggered.

By default, this buffer will grow to contain all notifications. The ClientNotificationHandler interface defines the static NO_CACHE_LIMIT field to represent an unlimited buffer size. If the notifications are allowed to accumulate indefinitely in the cache, this can lead either to an “out of memory” error in the agent application, a saturation of the communication layer, or an overload of the manager's listeners when the notifications are finally pulled.

To change the size of the agent's cache, call the connector client's setCacheSize method. The size of the cache is expressed as the number of notifications that can be stored in its buffer. When a cache buffer of limited size is full, new notifications will overflow and be lost. Therefore, you should also choose an overflow mode when using a limited cache size. The two overflow modes are defined by static fields of the ClientNotificationHandler interface:

We demonstrate each of these modes in our sample manager, first by setting the cache size and the overflow mode, then by triggering more notifications than the cache buffer can hold.


Example 22–7 Controlling the Agent-Side Buffer

System.out.println(">>> Use pull mode with period set to zero, " +
    "buffer size set to 10, and overflow mode set to DISCARD_OLD.");
connectorClient.setMode(ClientNotificationHandler.PULL_MODE);
connectorClient.setPeriod(0);
connectorClient.setCacheSize(10, true); // see "Buffering Specifics"
connectorClient.setOverflowMode(ClientNotificationHandler.DISCARD_OLD);

System.out.println(">>> Have our MBean broadcast 30 notifications...");
params[0] = new Integer(30);
signatures[0] = "java.lang.Integer";
connectorClient.invoke(mbean, "sendNotifications", params, signatures);
System.out.println(">>> Done.");

// Call getNotifications to pull all buffered notifications from the agent
System.out.println("\n>>> Press Enter to get notifications.");
System.in.read();
connectorClient.getNotifications();

// Wait for the handler to process the 10 notifications
// These should be the 10 most recent notifications
// (the greatest sequence numbers)
Thread.sleep(100);
System.out.println("\n>>> Press Enter to continue.");
System.in.read();

// We should see that the 20 other notifications overflowed the agent buffer
System.out.println(">>> Get overflow count = " +
    connectorClient.getOverflowCount());

The overflow count gives the total number of notifications that have been discarded because the buffer has overflowed. The number is cumulative from the first manger-side listener registration until all of the manager's listeners have been unregistered. The manager application can modify or reset this value by calling the setOverflowCount method.

In our example application, we repeat the actions above, to cause the buffer to overflow again, but this time using the DISCARD_NEW policy. Again, the buffer size is ten and there are thirty notifications. In this mode, the first ten sequence numbers remain in the cache to be forwarded when the manager pulls them from the agent, and twenty more will overflow.

22.3.3.1 Buffering Specifics

When the buffer is full and notifications need to be discarded, the time reference for applying the overflow mode is the order in which notifications have arrived in the buffer. Neither the time stamps nor the sequence numbers of the notifications are considered, because neither of these are necessarily absolute; even the sequence of notifications from the same broadcaster can be non-deterministic. And in any case, broadcasters are free to set both time stamps and sequence numbers as they see fit, or even to make them null.

The second parameter of the setCacheSize method is a boolean that determines whether or not the potential overflow of the cache is discarded when reducing the cache size. If the currently buffered notifications do not fit into the new cache size and this parameter is true, excess notifications are discarded according to the current overflow mode. The overflow count is also updated accordingly.

In the same situation with the parameter set to false, the cache will not be resized. You need to check the return value of the method when you set this parameter to false. If the cache cannot be resized because it would lead to discarded notifications, you need to empty the cache and try resizing the cache size again. To empty the cache, you can either pull the buffered notifications with the getNotifications method or discard them all by calling the connector client's clearCache method.

When the existing notifications fit within the new cache size or when increasing the cache size, the second parameter of setCacheSize has no effect.

Because several managers can connect through the same connector server object, it must handle the notifications for each separately. This implies that each connected manager has its own notification buffer and its own settings for controlling this cache. The overflow count is specific to each manager as well.

22.3.3.2 Buffering Generalities

Here we have demonstrated each setting of the forwarding mechanism independently by controlling the notification broadcaster. In practice, periodic pulling, agent-side buffering and buffer overflow can all be happening at once. And you can call getNotifications at any time to do an on-demand pull of the notifications in the agent-side buffer. You should adjust the settings to fit the known or predicted behavior of your management architecture, depending upon communication constraints and your acceptable notification loss rate.

The caching policy is completely determined by the manager application. If notification loss is unacceptable, it is the manager's responsibility to configure the mechanism so that they are pulled as often as necessary. Also, the legacy notification mechanism can be updated dynamically. For example, the manager can compute the notification emission rate and update any of the settings (buffer size, pull period, and overflow mode) to minimize the risk of a lost notification.

22.4 Running the Legacy Notification Forwarding Example

The examplesDir/legacy/Notification directory contains all of the files for the broadcaster MBean, the BaseAgent application, and our Client application that is the listener object. To run the legacy notification forwarding example, we use the BaseAgent application that contains an RMI connector server.

To Run the Legacy Notification Forwarding 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/legacy/Notification/
    $ javac -classpath classpath *.java
    
  2. Start the agent on another host or in another terminal window with the following command.

    Be sure that the classes for the NotificationEmitter MBean can be found in its classpath:


    $ java -classpath classpath BaseAgent
    
  3. Wait for the agent to be completely initialized, then start the manager in another window with the following command, where hostname is the name of the host running the agent.

    If you started the agent on the same host, you can omit the hostname:


    $ java -classpath classpath Client hostname
    

    When started, the manager application first creates the NotificationEmitter MBean and then registers itself as a listener.

  4. Press Enter when the application pauses to step through the various notification forwarding situations that we have seen in this topic.

  5. Press Enter again in the manager window to exit the application.

    Leave the agent application running if you want to interact with the example through the HTML protocol adaptor of the BaseAgent.

To Interact With the Legacy Notification Forwarding Mechanism
  1. Start the manager in another window with the following command, where hostname is the name of the host running the agent.

    If you started the agent on the same host, you can omit the hostname:


    $ java -classpath classpath Client hostname
    
  2. Load the following URL in your browser and go to the MBean view of the NotificationEmitter MBean:

    http://hostname:8082/

    If you get an error, you might have to switch off proxies in your browser preference settings. Any browser on your local network can also connect to this agent using this URL.

  3. When the manager application pauses for the first time, call the sendNotifications method from your browser with a small integer as the parameter.

    The listener handles your notifications in the manager's terminal window. Because the manager is still in push mode, they are forwarded immediately.

  4. Press Enter in the manager window.

    The manager is now in pull mode with a pull period of 500 milliseconds.

  5. Through the MBean view, send 1000 notifications.

    If your agent's host is slow enough, or if your manager's host is fast enough, you might be able to see the manager pause briefly after it has processed all notifications from one period and before the next ones are forwarded.

  6. Press Enter in the manager window.

    The agent now forwards notifications by request.

  7. Through the MBean view, send 15 notifications, then press Enter again.

    The manager pulls all of the notifications: the 30 triggered by the manager and the 15 we just triggered. They were all kept in the buffer, waiting for the manager's request to forward them. Remember that the sendNotifications operation resets the sequence numbering every time it is invoked.

  8. Press Enter in the manager's window.

    The cache size is set to 10 notifications and the overflow mode to DISCARD_OLD.

  9. Through the MBean view, send 15 more notifications, then press Enter again.

    Only the last 10 of our notifications fit into the cache buffer. All the rest, including those already triggered by the manager, overflow and are discarded.

  10. Press Enter to see that the discarded notifications are tallied in the overflow count.

  11. Press Enter in the manager's window.

    The cache size is still 10 notifications and the overflow mode is set to DISCARD_NEW.

  12. Through the MBean view, send only 5 more notifications, then press Enter again.

    The first 10 of the manager-triggered notifications are received. All of the more recent notifications, including ours, overflow the cache buffer and are lost.

  13. Press Enter to see that the lost notifications are tallied in the overflow count:

    35 from Step 12 plus 25 more from this step, for a total of 60.

  14. Press Enter in the manager's window again to stop the Client application.

  15. Press Enter in the other window to stop the agent application when you have finished running the example.

Chapter 23 Legacy Connector Security

The security mechanisms presented in this chapter relate to the legacy connectors described in Chapter 21, Legacy Protocol Connectors. These legacy connectors, and hence their associated security mechanisms, have been superseded by the connectors and security mechanisms brought to Java Dynamic Management Kit (Java DMK) 5.1 by the inclusion of Java Management Extensions (JMX) Remote API, and are retained only for reasons of backwards compatibility.

There are two categories of access-control implemented in the legacy connectors: connection-level control through a password and request-level control through a context object. Context checkers work as filters between the connector server and the MBean server. The filter logic can be determined dynamically, based on the nature of the request and on a context object provided by the client.

Security in the communication layer is achieved through the cryptography of a Secure Socket Layer (SSL) and the secure hypertext transfer protocol (HTTPS) connector. Using other components of the Java platform, legacy connectors can effectively make all open communication undecipherable.

The code samples in this chapter are taken from the files in the legacy/Context example directory located in the main examplesDir (see “Directories and Classpath” in the Preface).

This chapter covers the following topics:

23.1 Password-Based Authentication (Legacy Connectors)

The simplest form of agent security you can implement in the legacy connectors is to accept management requests only if they contain a valid login identity and password. Agents recognize a given list of login-password pairs, and managers must provide a matching login and password when they try to establish a connection.

Among the legacy connectors, only the HTTP–based connectors support password–based authentication. However, both the new remote method invocation (RMI) and JMX messaging protocol (JMXMP) connectors added in Java DMK 5.1 support password-based authentication. The SNMP protocol adaptor also supports access control, but it is based on a different mechanism (see 19.1 IP-Based Access Control Lists).

By default, no authentication is enabled in the HTTP-based legacy connectors and any manager can establish a connection. The password-checking behavior is enabled by defining the list of authorized login-password pairs.

You can define this authentication information in one of the following ways:

In both cases, only the agent application has access to these methods, meaning that the agent controls the authentication mechanism. As soon as an AuthInfo object is added to the connector server through either method, all incoming requests must provide a recognized name and password. In our example, we read the authentication information from the command line and call the addUserAuthenticationInfo.


Example 23–1 Implementing Password Authentication in the Legacy HTTP Connector Server

// Here we show the code for reading the
// id-password pairs from the command line
//
int firstarg = 0;
boolean doAuthentication = (args.length > firstarg);

AuthInfo[] authInfoList;

if (doAuthentication) {
    authInfoList = new AuthInfo[(args.length - firstarg) / 2];
    for (int i = firstarg, j = 0; i < args.length; i += 2, j++)

        authInfoList[j] = new AuthInfo(args[i], args[i + 1]);

} else

    authInfoList = null;

[...] // instantiate and register an HTTP connector server

// Define the authentication list
// 
if (doAuthentication) {
    for (int i = 0; i < authInfoList.length; i++)
        http.addUserAuthenticationInfo(authInfoList[i]);
}

On the manager-side, identifiers and passwords are given in the address object, because authentication applies when the connection is established.


Example 23–2 Specifying the Login and Password in the Legacy HTTP Connector Server

// login and password were read from the command line
//
AuthInfo authInfo = null;

if (login != null) {
    authInfo = new AuthInfo( login, password );
}

// agentHost and agentPort are read from the command
// line or take on default values
//
HttpConnectorAddress addr =
    new HttpConnectorAddress(
        agentHost, agentPort, authInfo );

final RemoteMBeanServer connector =
    (RemoteMBeanServer) new HttpConnectorClient();

connector.connect( addr );

The connector is identified by the one AuthInfo object it uses to instantiate the connector address. If the agent has authentication enabled, both the login and the password must match one of the AuthInfo objects in the agent. If the agent does not perform authentication, providing a login and password has no effect because all connections are accepted.

If the authentication fails, the call to the connect method returns an exception. Normally, the client's code should catch this exception to handle this error case.

As demonstrated by the code examples, the authentication mechanism is very simple to configure. It prevents unauthorized access with very little overhead.


Note –

The HTML adaptor provides a similar authentication mechanism, where the list of accepted identities is given to the server object. In the case of the HTML protocol, the web browser is the management application that must provide a login and password. The behavior is browser-dependent, but the browser usually requests that the user type this login and password in a dialog box.


23.1.1 Running the Legacy Security Example With Authentication

The examplesDir/legacy/Context directory contains the applications that demonstrate the use of password authentication through the legacy HTTP connector.

To Run the Legacy Security Example With Authentification
  1. Compile all files in this directory with the javac command.

    For example, on the Solaris platform with the Korn shell, type:


    $ cd examplesDir/legacy/Context/
    $ javac -classpath classpath *.java
    
  2. Start the agent in a terminal window and specify a list of login-password pairs, as in the following command:


    $ java -classpath classpath ContextAgent  jack jill  billy bob
    
  3. Wait for the agent to be completely initialized, then start the manager in another window with the following command:


    $ java -classpath classpath ContextClient -ident andy bob
    

    The client application tries to establish a connection with the login andy and the password bob. The authentication mechanism refuses the connection, and the com.sun.jdmk.comm.UnauthorizedSecurityException is raised by the connector server.

  4. Start the manager again, this time with a valid identity:


    $ java -classpath classpath ContextClient -ident jack jill
    

    The connection is established and the output from management operation is displayed in both windows.

  5. Leave both applications running for the next example.

23.2 Context Checking

Context checking is a more advanced security mechanism that can perform selective filtering of incoming requests. The context is an arbitrary object provided by the client and used by the server to decide whether or not to allow the request.

Filtering and context checking are performed in between the communicator server and the MBean server. The mechanism relies on two objects called the MBeanServerForwarder and the MBeanServerChecker.

23.2.1 Filter Mechanism

The MBeanServerForwarder allows for the principle of stackable MBean servers. An MBeanServerForwarder implements the MBeanServer interface and one extra method called setMBeanServer. Its function is to receive requests and forward them to the designated MBean server.

The setMBeanServer method of a communicator server object enables you to specify the MBean server that fulfills its requests. By chaining one or more MBeanServerForwarder objects between a communicator server and the actual MBean server, the agent application creates a stack of objects that can process the requests before they reach the MBean server.

The MBeanServerChecker is an extension of the forwarder that forces each request to call a –checker method. By extending the MBeanServerChecker class and providing an implementation of the checker methods, you can define a policy for filtering requests before they reach the MBean server. Table 23–1 shows the checker methods that apply to groups of MBeanServer methods.

Table 23–1 Filter Method Granularity for Context Checking

Filter Method 

MBean Server Operations Filtered 

checkAny

Every method of the MBeanServer interface

checkCreate

All forms of the create and registerMBean methods

checkDelete

The unregisterMBean method

checkInstantiate

All forms of the instantiate method

checkInvoke

The invoke method that handles all operation invocations

checkNotification

Both addNotificationListener and removeNotificationListener

checkQuery

Both queryMBeans and queryNames

checkRead

All methods that access but do not change the state of the agent: getAttribute, getAttributes, getObjectInstance, isRegistered, getMBeanCount, getDefaultDomain, getMBeanInfo, and isInstanceOf

checkWrite

The setAttribute and setAttributes methods

As a request passes through a stack of MBean servers, the checker methods are called to determine if the request is allowed. In order to identify the manager that issued a request, the checker can access the operation context of the request.

The operation context, or just context, is an object defined by the manager which seeks access through a context checker. It usually contains some description of the manager's identity. The only restriction on the context object is that it must implement the OperationContext interface. The context object is passed from the connector client to the connector server and is then associated with the execution of a request. Conceptually, this object is stored in the user accessible context of the thread that executes the request.

All methods in the MBeanServerChecker class can access the context object by calling the protected getOperationContext method. The methods of the context checker then implement some policy to filter requests based on the context object, the nature of the request, and the data provided in the request, such as the attribute or operation name.

Figure 23–1 shows the paths of two requests through a stack of MBean server implementations, one of which is stopped by the context checker because it does not provide the correct context.

Figure 23–1 Context Checking in Stackable MBean Servers

Using a context checker in stackable MBean servers

Only connectors fully support the context mechanism. Their connector clients expose the methods that allow the manager to specify the context object. Existing protocol adaptors have no way to specify a context. Their requests can be filtered and checked, but their context object will always be null.

This functionality can still be used to implement a filtering policy, but without a context object, straightforward manager identification is not possible. However, a proprietary protocol adaptor could define some mapping to determine a context object that could be accepted by the filters.

23.2.2 Context Implementation

An agent wanting to implement context checking on a legacy connector first needs to extend the MBeanServerChecker class. This class retrieves the context object and determines whether any given operation is allowed.


Example 23–3 Implementation of the Context Checker

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.QueryExp;

import com.sun.jdmk.MBeanServerChecker;
import com.sun.jdmk.OperationContext;

public class ContextChecker extends MBeanServerChecker {

    // Constructor
    public ContextChecker(MBeanServer mbs) {
        super(mbs);
    }

    // Implementation of the abstract methods of the
    // MBeanServerChecker class: for each of the specific
    // checks, we just print out a trace of being called.
    [...]

    protected void checkWrite( String methodName,
                               ObjectName objectName) {
        System.out.println("checkWrite(\"" + methodName +
                           "\", " + objectName + ")");
    }

    protected void checkQuery( String methodName,
                               ObjectName name,
                               QueryExp query) {
        System.out.println("checkQuery(\"" + methodName +
                           "\", " + name + ", " + query + ")");
    }

    [...]

    /**
     * This is where we implement the check that requires every
     * operation to be called with an OperationContext whose
     * toString() method returns the string "nice".
     */
    protected void checkAny( String methodName,
                             ObjectName objectName ) {

        System.out.println("checkAny(\"" + methodName + "\", " +
                            objectName);
        OperationContext context = getOperationContext();
        System.out.println("  OperationContext: " + context);

        if (context == null || !context.toString().equals("nice")) {
            RuntimeException ex =
                new SecurityException("  Bad context: " + context);
            ex.printStackTrace();
            throw ex;
        }
    }

}

The agent application then instantiates its context checkers and stacks them between the communicator servers and the MBean server. Each communicator server has its own stack, although filters and context checkers can be shared. The agent performs the stacking inside a synchronized block because other threads can try to do stacking simultaneously.


Example 23–4 Stacking MBean Server and Context Checkers

// Create MBeanServer
//
MBeanServer server = MBeanServerFactory.createMBeanServer();

/* Create context checker.  The argument to the constructor is
 * our MBean server to which all requests will be forwarded
 */
ContextChecker contextChecker = new ContextChecker( server );


[...] // Create HTTP connector server

/* Add the context checker to this HTTP connector server.
 * We point it at the context checker which already points
 * to the actual MBean server.
 * It is good policy to check that we are not sidetracking
 * an existing stack of MBean servers before setting ours.
 */
synchronized (http) {
    if (http.getMBeanServer() != server) {
        System.err.println("After registering connector MBean, " +
           "http.getMBeanServer() != " + "our MBeanServer");
        System.exit(1);
    }
    http.setMBeanServer(contextChecker);
}

Finally, the manager operation defines a context object class and then provides a context object instance through its connector client.


Example 23–5 Setting the Context in the Connector Client

/* In this example, the agent checks the OperationContext of
   each operation by examining its toString() method, so we
   define a simple implementation of OperationContext whose
   toString() is a constant string supplied in the constructor
 */
class StringOperationContext
          implements OperationContext, Cloneable {

    private String s;

    StringOperationContext(String s) {
        this.s = s;
    }

    public String toString() {
        return s;
    }

    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

}

// the contextName must be provided on the command line
OperationContext context =
    new StringOperationContext(contextName);

[...]

// The context is set for all requests issued through
// the connector client; it can be changed at any time
connector.setOperationContext(context);

23.2.3 Running the Legacy Security Example With Context Checking

The ContextClient and ContextAgent applications in the examplesDir/legacy/Context directory also demonstrate the use of stackable MBean servers and context checking through the legacy HTTP connector.

If you have not done so already, compile all files in this directory with the javac command. For example, on the Solaris platform with the Korn shell, type:


$ cd examplesDir/legacy/Context/
$ javac -classpath classpath *.java
To Run the Legacy Security Example With Context Checking
  1. If the agent and client applications are not already running from the previous example, type the following commands in separate windows:


    $ java -classpath classpath ContextAgent
    

    $ java -classpath classpath ContextClient
    

    The classpath should include the current directory (.) for both applications because they rely on classes that were compiled in this directory.

  2. Press Enter in the client application to trigger another set of requests.

    The agent window displays the output of the ContextChecker class. We can see that the checkAny method verifies the “nice” context of every request and that the other checkers just print out their names, providing a trace of the request.

  3. Stop both applications by pressing Control-C in each of the windows.

  4. Restart both applications, but specify a different context string for the client:


    $ java -classpath classpath ContextAgent
    

    $ java -classpath classpath ContextClient -context BadToTheBone
    

    This time the context is not recognized. The agent raises a java.lang.SecurityException that is propagated to the client, which then exits.

  5. Press Control-C in the agent window to stop the ContextAgent application.

23.3 Legacy HTTPS Connector

The legacy HTTPS connector provides data encryption and certificate-based security through a Secure Socket Layer (SSL). The Java Secure Socket Extension (JSSE) provides a implementation of secure sockets for the Java 2 platform, Standard Edition 1.4.

The web site for the JSSE is http://java.sun.com/products/jsse. For further information and details regarding the use of the secure sockets, refer to the JSSE documentation.

The legacy HTTPS connector exposes the same interfaces as all other legacy connectors and has exactly the same behavior. The development of a management application that relies on the HTTPS connector is no different from that of any other Java dynamic manager. See 21.2 Legacy Connector Clients for details about programming with the RemoteMBeanServer API.

This section covers the steps that are required to establish a secure connection between your agent and manager applications. These instructions do not guarantee total security. They just explain the programmatic steps needed to ensure data security between two remote Java applications.

Before performing these steps, run each of your manager and agent applications on a separate host, and ensure that each host has its own installation of the Java platform (not a shared network installation).

To Establish a Secure HTTPS Connection
  1. Generate public and private keys.

    Repeat this step on all agent and manager hosts.

    Generate a key pair (a public key and associated private key).

    Wrap the public key into an X.509 v1 self-signed certificate, which is stored as a single-element certificate chain. This certificate chain and the private key are stored in a new keystore entry identified by alias.

    In the following command, the –dname parameters designates the X.500 Distinguished Name for the host where you are generating the certificates. The commonName field must be the host name.


    $ keytool -genkey -alias alias -keyalg RSA -keysize 1024 -sigalg MD5withRSA
              -dname "CN=commonName, OU=orgUnit, O=org, L=location, 
    S=state, C=country"
              -keypass passPhrase -storetype jks -keystore yourHome/.keystore
              -storepass passPhrase
    
  2. Export a local certificate

    Repeat this step on all agent and manager hosts.

    Read the certificate that is associated with your alias from the keystore and store it in a hostCertFile:


    $ keytool -export -alias alias -file hostCertFile -storetype jks
              -keystore yourHome/.keystore -storepass passPhrase -rfc
    

    When you are done with this step, you will have a certificate for each of your hosts.

  3. Import all remote certificates

    Repeat this step on both the agent and manager hosts for all pairs of agent-managers in your management architecture.

    In this step, agent and manager pairs must exchange their certificates. The manager imports the agent's hostCertFile and the agent imports the manager's hostCertFile. If a manager has two agents, it will import two certificates and each agent will import a copy of the manager's certificate.

    Import the certificate into the file containing the trusted Certificate Authorities (CA) certificates. This will add our self-signed certificate as a trusted CA certificate to the cacerts file so that the server and the client will be able to authenticate each other.


    $ keytool -import -alias alias -file hostCertFile -noprompt -trustcacerts
              -storetype jks -keystore JAVAhome/jre/lib/security/cacerts
              -storepass changeit
    

    This command modifies the JAVAhome/jre/lib/security/cacerts that will affect all applications running on that installation. If you do not want to modify this file, you can create a file named jssecacerts and use it instead. The default location of this file is either JAVAhome/lib/security/jssecacerts or if that does not exist, then JAVAhome/lib/security/cacerts.

  4. Run your Java dynamic management agent

    Start your agent applications with the following properties:


    $ java -Djavax.net.ssl.keyStore=yourHome/.keystore
           -Djavax.net.ssl.keyStoreType=jks
           -Djavax.net.ssl.keyStorePassword=passPhrase
           AgentClass
    

    If you are using the notification push mechanism, add the following property definition to the above command line:

    -Djava.protocol.handler.pkgs=com.sun.net.ssl.internal.www.protocol
    
  5. Run your management application

    Start your management applications with the following properties:


    $ java -Djavax.net.ssl.keyStore=yourHome/.keystore
           -Djavax.net.ssl.keyStoreType=jks
           -Djavax.net.ssl.keyStorePassword=passPhrase
           -Djava.protocol.handler.pkgs=com.sun.net.ssl.internal.www.protocol
           ManagerClass
    

Chapter 24 Legacy Proxy Mechanism

As of version 5.1 of Java Dynamic Management Kit (Java DMK), proxies are generated using the java.lang.reflect.Proxy interface defined by the Java 2 Platform, Standard Edition (J2SE). However, if you want to implement proxies with the legacy connectors, you must use the legacy proxy mechanism described in this chapter. This information is still valid, and is provided for backwards compatibility.

In Protocol Adaptors, we saw how to access a remote agent and interact with its MBeans through connectors. Java DMK provides additional functionality that makes the remoteness of an agent and the communication layer even more transparent: proxy objects for registered MBeans.

A proxy is an object instance that represents an MBean, that mirrors the methods of the MBean, and whose methods are called directly by the calling process. The proxy transmits requests to the MBean, through the MBean server, possibly through a connector, and returns any responses to the calling process. Proxy objects can also register listeners for notifications that the MBean might emit.

The advantage of a proxy object is that it enables applications to have an instance of an object that represents an MBean, instead of accessing the MBean's management interface through methods of the MBean server or through a connector client. This can simplify both the conceptual design of a management system and the source code needed to implement that system.

The code samples in this chapter are from the files in the legacy/SimpleClients example directory located in the main examplesDir (see “Directories and Classpath” in the Preface).

This chapter covers the following topics:

24.1 Legacy Proxy Mechanism

Proxy objects simplify the interactions between an application and the MBeans it wants to manage. The purpose of a proxy is to call the methods that access the attributes and operations of an MBean, through its MBean server. These method calls can be rather tedious to construct at every invocation, so the proxy performs this task for the calling process.

For example to call an operation on an MBean, an application must call the invoke method of the MBean server and provide the MBean's object name, the operation name string, an array of parameter objects, and a signature array. The proxy is a class that codes this whole sequence, meaning that the application can call the reset method directly on the proxy instance.

Conceptually, a proxy instance makes the MBean server and a protocol connector completely transparent. Except for MBean registration and connector connection phases, all management requests on MBeans can be fully served through proxies, with identical results. However, all functionality of the Java DMK is available without using proxies, so their usage is never mandatory.

By definition, a proxy object has the same interface as its MBean: the proxy can be manipulated as if it were the MBean instance, except that all requests are transmitted through the MBean server to the actual MBean instance for processing. A standard MBean proxy exposes getters, setters, and operation methods. It can also register listeners for all notifications broadcast by their corresponding MBean. A dynamic MBean proxy, also known as a generic proxy, exposes generic methods that are identical to those of the DynamicMBean interface.

In addition, standard proxies are commonly called proxy MBeans, because they are themselves MBeans. They are generated with an MBean interface, and can therefore be registered as standard MBeans in an MBean server. This feature enables one agent to expose resources whose MBeans are actually located in another agent. An equivalent functionality is described in Chapter 14, Cascading Service.

24.1.1 Legacy Local and Remote Proxies

Proxies can also be bound to objects called handlers that necessarily implement the ProxyHandler interface. The methods of this interface are a subset of MBean server methods, as listed in 21.2.2 RemoteMBeanServer Interface. These are the only methods that a proxy needs to access its corresponding MBean and fulfill all management requests.

In versions of Java DMK prior to 5.1, the RemoteMBeanServer interface extends the ProxyHandler interface, meaning that proxy objects can be bound to any of the legacy connector clients. These are called remote proxies, because they are instantiated in an application that is distant from the agent and its MBean instances.

The implementation of the MBeanServer interface also implements the ProxyHandler interface, so that proxy objects can be bound to the MBean server itself. These are called local proxies because they are located in the same application as their corresponding MBeans. Local proxies help preserve the management architecture by providing the simplicity of performing direct method calls on MBeans, while still routing all operations through the MBean server.

The symmetry of remote and local proxies complements the symmetry that enables management components to execute either in an agent or in a management application. Provided that all proxy classes are available, management components that use MBean proxies can be instantiated in an agent and rely on the MBean server or can be instantiated in a remote manager where they interact with a connector client. Except for communication delays, the results of all operations are identical, and the same notifications are received, whether obtained through a local proxy or a remote proxy (see 22.1.3 Adding a Listener Through the Connector).

Figure 24–1 shows local proxies that are instantiated in an agent and bound to the MBean server, and the same classes instantiated as remote proxies in a management application and bound to the connector client. Management components located in either the agent or the management application can interact with the local or remote proxies, respectively. Management components can also access the MBean server or the connector client directly, regardless of whether proxies are being used.

Figure 24–1 Interacting with Local and Remote Proxies

Diagram showing interactions with local and remote proxies

This diagram shows all possible relations between management components, proxies and their MBeans. Standard proxies can only represent a specific standard MBean, and generic proxies can represent any standard or dynamic MBean. In a typical management scenario, the management components are located in only one application, and for simplicity, they rarely instantiate more than one type of proxy.

Throughout the rest of this chapter, we do not distinguish between local and remote proxies. A proxy object, either standard or generic, is used in exactly the same way regardless of whether it is instantiated locally or remotely.

24.1.2 Legacy Proxy Interface

In addition to the methods for accessing the attributes and operations of its MBean, all proxies implement the methods of the Proxy interface. This interface contains the methods for binding the proxy instance to the proxy handler that can fulfill its requests. The setServer method binds the proxy to the handler object. Setting the server to null effectively unbinds the proxy object. The result of the getServer method can indicate whether or not a proxy is bound and if so, it will return a reference to the handler object.

The following list provides the functionality of the Proxy interface methods. See the Javadoc API of the Proxy interface for details.

ObjectInstance

getMBeanObjectInstance()

ProxyHandler

getServer()

void

setServer(ProxyHandler server)

Standard proxies can also implement the NotificationBroadcasterProxy interface if their corresponding MBean is a notification proxy. This interface contains the same addNotificationListener and removeNotificationListener methods that the MBean implements from the NotificationBroadcaster interface.

Applications that use proxies therefore have two ways to detect notification broadcasters. The first way relies on the implementation of the NotificationBroadcasterProxy interface that can be detected in the proxy's class inheritance. The second and more standard way is to look at the notifications listed in the MBean's metadata obtained by the getMBeanInfo method either from the server or through the proxy.

Generic proxies do not implement the NotificationBroadcasterProxy interface, so calling processes must use the MBean metadata for detecting broadcasters. In addition, generic proxies cannot register notification listeners, calling processes must do this directly through the server.

24.2 Standard MBean Proxies

A standard MBean proxy class is specific to its corresponding MBean class. Furthermore, a proxy instance is always bound to the same MBean instance, as determined by the object name passed to the proxy's constructor. Binding the proxy to its ProxyHandler object can be done through a constructor or set dynamically through the methods of the Proxy interface.

The methods of a standard proxy have exactly the same signature as those of the corresponding standard MBean. Their only task is to construct the complete management request, that necessarily includes the object name, and to transmit it to the MBean server or connector client. They also return any result directly to the caller.

Because the contents of all proxy methods are determined by the management interface of the MBean, the proxy classes can be generated automatically.

24.2.1 Generating Legacy Proxies for Standard MBeans

The deprecated proxygen tool provided with the Java DMK takes the class files of an MBean and its MBean interface and generates the Java source code files of its corresponding proxy object and proxy MBean interface. You then need to compile the two proxy files with the javac command and include the resulting class files in your application's classpath.

The proxygen compiler is fully documented in the Java Dynamic Management Kit 5.1 Tools Reference Guide and in the Javadoc API for the ProxyGen class. Its command—line options enable you to generate read-only proxies where all setter methods are suppressed and to define a package name for your proxies. For the purpose of the examples, we generate the default proxies without any of these options. See the section on 24.2.3 Running the Legacy Standard Proxy Example.

Example 24–1 shows part of the code generated for the SimpleStandard MBean used in the SimpleClients examples.


Example 24–1 Code Generated for the Legacy SimpleStandardProxy Class

  public java.lang.String getState()
    throws InstanceNotFoundException, AttributeNotFoundException,
    ReflectionException, MBeanException {

      return ((java.lang.String)server.getAttribute(
                  objectInstance.getObjectName(), "State"));
    }

  public  void setState(java.lang.String value)
    throws InstanceNotFoundException, ReflectionException,
    AttributeNotFoundException,InvalidAttributeValueException,
    MBeanException {

      server.setAttribute(objectInstance.getObjectName(),
                          new Attribute("State",value));
  }

  public void reset()
    throws InstanceNotFoundException, ReflectionException,
    MBeanException {

    Object result;
    result= server.invoke(objectInstance.getObjectName(), "reset",
                          null, null);
  }

You can modify the generated code if you want to customize the behavior of your proxies. However, customization is not recommended if your MBean interfaces are still evolving, because all modifications will need to be redone every time the proxies are generated.

24.2.2 Using Legacy Standard MBean Proxies

Once the proxies are generated and available in your application's classpath, their usage is straightforward. For each of the proxy objects it wants to use, the application needs to instantiate its proxy class and then bind it to a ProxyHandler object. The application is responsible for creating and binding all of the proxies that it wants to use, and it must unbind and free them when they are no longer needed.


Note –

The binding methods in the Proxy interface in previous releases of Java DMK are deprecated in favor of the new setServer and getServer methods. This change is necessary so that proxies can be bound to any ProxyHandler object, to enable for both local and remote proxies.


All parameters for binding the proxy can be given in its constructor, which makes it very simple to instantiate and bind a proxy in one step.


Example 24–2 Instantiating and Binding a Legacy Proxy in One Step

String mbeanName = "SimpleStandard";

// build the MBean ObjectName instance
ObjectName mbeanObjectName = null;
String domain = connectorClient.getDefaultDomain();
mbeanObjectName = new ObjectName( domain + ":type=" + mbeanName );

// create the MBean in the MBeanServer of the agent
String mbeanClassName = mbeanName;
ObjectInstance mbeanObjectInstance =
    connectorClient.createMBean( mbeanClassName, mbeanObjectName );

// create and bind a proxy MBean on the client side
// that corresponds to the MBean just created in the agent
Proxy mbeanProxy = new SimpleStandardProxy(
                           mbeanObjectInstance, connectorClient );

echo("\tPROXY CLASS NAME  = " +
      mbeanProxy.getClass().getName() );
echo("\tMBEAN OBJECT NAME = " +
      mbeanProxy.getMBeanObjectInstance().getObjectName() );
echo("\tCONNECTOR CLIENT  = " +
      mbeanProxy.getServer().getClass().getName() );

If the class name of your proxy is not known at compile time, you will have to instantiate its class dynamically. In the following code, we obtain the proxy class name that corresponds to an MBean, and we call its first constructor. This must be the constructor that takes an ObjectInstance identifying the MBean, and we must dynamically build the call to this constructor. We then call the setServer method to bind the new proxy instance.


Example 24–3 Instantiating and Binding a Legacy Proxy Class Dynamically

// Get the class name of the MBean's proxy
Class proxyClass = Class.forName(
    connectorClient.getClassForProxyMBean( mbeanObjectInstance ));

// Find the constructor with takes an ObjectInstance parameter
Class[] signature = new Class[1];
signature[0] = Class.forName("javax.management.ObjectInstance");
Constructor proxyConstr = proxyClass.getConstructor( signature );

// Call the constructor to instantiate the proxy object
Object[] initargs = new Object[1];
initargs[0] = mbeanObjectInstance;
Proxy proxy2 = (Proxy) proxyConstr.newInstance( initargs );

// Bind the proxy
proxy2.setServer( connectorClient );

echo("\tPROXY CLASS NAME  = " +
      proxy2.getClass().getName());
echo("\tMBEAN OBJECT NAME = " +
      proxy2.getMBeanObjectInstance().getObjectName());
echo("\tCONNECTOR CLIENT  = " +
      proxy2.getServer().getClass().getName());

// We no longer need proxy2, so we unbind it
proxy2.setServer( null );

Once a proxy is bound, you can access the attributes and operations of its MBean through direct calls to the proxy object, as shown in the following example.


Example 24–4 Accessing a Standard MBean Through Its Legacy Proxy

try {
    // cast mbeanProxy to SimpleStandardProxy, so we can
    // call its MBean specific methods
    SimpleStandardProxy simpleStandardProxy =
        (SimpleStandardProxy) mbeanProxy;

    [...]

    // Change the "State" attribute
    simpleStandardProxy.setState("new state from client");

    // Get and display the new attribute values
    echo("\tState     = \"" + simpleStandardProxy.getState() + "\"");
    echo("\tNbChanges = " + simpleStandardProxy.getNbChanges());

    // Invoke the "reset" operation
    simpleStandardProxy.reset();
    [...]

    // We are done with the MBean, so we
    // unbind the proxy and unregister the MBean
    simpleStandardProxy.setServer( null );
    connectorClient.unregisterMBean( mbeanObjectName );

} catch (Exception e) {
    echo("\t!!! Error accessing proxy for " + 
          mbeanProxy.getMBeanObjectInstance().getObjectName() );
    e.printStackTrace();
}

24.2.3 Running the Legacy Standard Proxy Example

The examplesDir/legacy/SimpleClients directory contains all of the files for the ClientMBeanProxy application that demonstrates the use of standard MBean proxies.

To Run the Legacy Standard Proxy Example
  1. Compile all files in this directory with the javac command, if you have not done so already.

    For example, on the Solaris platform with the Korn shell, type:


    $ cd examplesDir/legacy/SimpleClients/
    $ javac -classpath classpath *.java
    
  2. Generate the proxy MBeans classes and compile them. To do this, from the same directory as above, type the following commands:


    $ installDir/SUNWjdmk/jdmk5.0/proxygen SimpleStandard
    $ javac -classpath classpath SimpleStandardProxy.java
    
  3. Start the base agent in a terminal window with the following command:


    $ java -classpath classpath BaseAgent
    

    The agent creates the RMI connector server to which the client application will establish a connection, and then it waits for management operations.

  4. Wait for the agent to be completely initialized, then, in another window on the same host, start the management application with the following command:


    $ java -classpath classpath ClientMBeanProxy
    
  5. Press Enter in the manager window to step through the example.

    As seen in the code examples, the client application instantiates the proxy objects to access the MBean it has created in the base agent.

  6. Press Enter one last time to exit the manager application, but leave the base agent running for the next example.

24.3 Legacy Generic Proxies

Because dynamic MBeans only expose their management at runtime, it is impossible to generate a specific proxy object for them. Instead, we use the GenericProxy object which can be bound to any dynamic MBean, and whose generic methods take the name of the attribute or operation being accessed. Therefore, to access a dynamic MBean through generic proxy you call exactly the same methods as those of the DynamicMBean interface.

Just as the MBean server's generic methods can access both standard and dynamic MBeans, generic proxies can also be bound to standard MBeans. You lose the specificity and simplicity of a standard proxy, but a generic proxy is always available in any Java dynamic management application, and it never needs regenerating.

The management application in this example shows how generic proxies can be used to access both standard and dynamic MBeans. The application contains the following subroutine that takes the class name of an MBean, creates that MBean in the agent, and instantiates a generic proxy to access the MBean.

In fact, the subroutine instantiates two generic proxies for the MBean, one using the GenericProxy class constructor that also binds the proxy, the other bound in a second, separate call to its setServer method. This demonstrates that it is possible to have two distinct proxy instances coexisting simultaneously for the same MBean.


Example 24–5 Accessing Standard and Dynamic MBeans Using Legacy Generic Proxies

private void doGenericProxyExample( String mbeanName ) {

    try {
        // build the MBean ObjectName instance
        ObjectName mbeanObjectName = null;
        String domain = connectorClient.getDefaultDomain();
        mbeanObjectName = new ObjectName( domain +
                                          ":type=" + mbeanName);

        // create the MBean in the MBeanServer of the agent
        String mbeanClassName = mbeanName;
        ObjectInstance mbeanObjectInstance =
            connectorClient.createMBean( mbeanClassName, mbeanObjectName );

        // create and bind a generic proxy instance for the MBean
        Proxy proxy = new GenericProxy(
                              mbeanObjectInstance, connectorClient );

        echo("\tPROXY CLASS NAME  = " +
              proxy.getClass().getName());
        echo("\tMBEAN OBJECT NAME = " +
              proxy.getMBeanObjectInstance().getObjectName());
        echo("\tCONNECTOR CLIENT  = " +
              proxy.getServer().getClass().getName());

        // An alternate way is to first instantiate the generic proxy,
        // and then to bind it to the connector client:
        Proxy proxy2 = new GenericProxy( mbeanObjectInstance );
        proxy2.setServer( connectorClient );

        echo("\tPROXY CLASS NAME  = " +
              proxy2.getClass().getName());
        echo("\tMBEAN OBJECT NAME = " +
              proxy2.getMBeanObjectInstance().getObjectName());
        echo("\tCONNECTOR CLIENT  = " +
              proxy2.getServer().getClass().getName());

        // we no longer need proxy2, so we unbind it
        proxy2.setServer(null);

        [...] // Accessing the MBean through its generic proxy (see below)

        // When done with the MBean, we unbind the proxy
        // and unregister the MBean
        //
        proxy.setServer(null);
        connectorClient.unregisterMBean( mbeanObjectName );

    } catch (Exception e) {
        echo("\t!!! Error instantiating or binding proxy for " + 
              mbeanName );
        e.printStackTrace();
    }
}

The standard and dynamic MBean classes used in this example have exactly the same management interface, and therefore, we can use the same code to access both of them. The manager application does this by calling the above subroutine twice, once with the class name of the standard MBean, once with that of the dynamic MBean:

manager.doGenericProxyExample("SimpleStandard");
manager.doGenericProxyExample("SimpleDynamic");

Because the two MBeans have the same behavior, they produce the same results when accessed through their proxy. The only difference is that the dynamic MBean can expose a description of its management interface in its MBeanInfo object. As expected, accessing a standard MBean through a generic proxy also produces the same result as when it is accessed through a standard proxy (compare the following with Example 24–5).


Example 24–6 Accessing an MBean Through its Legacy Generic Proxy

try {

    // cast Proxy to GenericProxy
    GenericProxy genericProxy = (GenericProxy) proxy;

    // Get the MBean's metadata through the proxy
    MBeanInfo info = genericProxy.getMBeanInfo();

    // display content of the MBeanInfo object
    echo("\nCLASSNAME: \t"+ info.getClassName() );
    echo("\nDESCRIPTION: \t"+ info.getDescription() );
    [...] // extract all attribute and operation info

    // Change the "State" attribute
    Attribute stateAttr = new Attribute( "State", "new state from client");
    genericProxy.setAttribute( stateAttr );

    // Get and display the new attribute values
    String state =
        (String) genericProxy.getAttribute("State");
    Integer nbChanges =
        (Integer) genericProxy.getAttribute("NbChanges");
    echo("\tState     = \"" + state + "\""); 
    echo("\tNbChanges = " + nbChanges); 

    // Invoke the "reset" operation
    Object[] params = new Object[0];
    String[] signature = new String[0];
    genericProxy.invoke("reset", params, signature );

} catch (Exception e) {
    echo("\t!!! Error accessing proxy for " + 
          proxy.getMBeanObjectInstance().getObjectName() );
    e.printStackTrace();
}

Example 24–6 shows how the generic methods are called with the names of attributes and operations, and how required parameters can be constructed.


24.3.1 Running the Legacy Generic Proxy Example

The ClientGenericProxy application, also located in the examplesDir/legacy/SimpleClients directory, demonstrates the use of generic proxies.

To Run the Generic Proxy Example
  1. Compile all files in this directory with the javac command, if you have not done so already. For example, on the Solaris platform with the Korn shell, type:


    $ cd examplesDir/legacy/SimpleClients/
    $ javac -classpath classpath *.java
    

    Because generic proxies do not need to be generated, this example does not need the proxygen tool. The GenericProxy class is available in the usual classpath for the product's runtime libraries.

  2. If it is not already running on your host, start the base agent in a terminal window with the following command:


    $ java -classpath classpath BaseAgent
    

    The agent creates the RMI connector server to which the client application will establish a connection, and then it waits for management operations.

  3. Wait for the agent to be completely initialized, then start the management application in another window on the same host:


    $ java -classpath classpath ClientGenericProxy
    
  4. Press Enter in the manager window to step through the example.

    As seen in the code examples, the client application instantiates generic proxy objects to access both a standard and dynamic MBean that it creates in the base agent. The only difference between the two is the user-provided information available in the dynamic MBean's metadata.

  5. Press Enter in both windows to exit the base agent and manager applications.

24.4 Legacy Proxies for Java DMK Components

Most components of the Java DMK product are MBeans and can therefore also be managed through local or remote proxies. Nearly all are standard MBeans, so their corresponding standard proxies are provided with the product. The Java source code for all component proxy classes can be found in the /legacy/JdmkProxyMBeans directory located in the main examplesDir (see “Directories and Classpath” in the Preface).


Note –

The HTML protocol adaptor is implemented as a dynamic MBean and therefore cannot have a standard proxy. You must use a generic proxy to access the HTML adaptor through a proxy object.


Of course, all other Java DMK MBean components can also be accessed through generic proxies, although their standard proxies provide more abstraction of the MBean server and a greater simplification of your application's code.

The proxy classes have been generated by the proxygen tool with full read-write access of all attributes. Refer to the chapter on the proxygen tool in the Java Dynamic Management Kit 5.1 Tools Reference Guide.

24.4.1 Legacy Proxy Packages

Like all other classes, proxies can contain a package statement. The package for a component proxy class depends upon the package of the component:

24.4.2 Compiling the LegacyProxy Classes

To use the standard proxies for the product components, you must first compile them with the javac command and then place the classes you need in the classpath of your agent or management application. If you compile the proxy classes individually, be sure to compile the proxy's MBean interface before its corresponding proxy class.

Because of the package statement in proxy classes, use the following commands:


$ cd examplesDir/legacy/JdmkProxyMBeans/
$ javac -d packageRoot -classpath classpath *ProxyMBean.java *Proxy.java

In this command, the classpath must contain the current directory and the classpath of the Java DMK runtime libraries (jdmkrt.jar and legacysnmp.jar). See “Directories and Classpath” in the Preface). The -d option of the javac compiler creates the necessary directories in the given packageRoot for each class's package. The packageRoot is usually the current directory (.) or you can specify a target directory in your application's classpath directly.

Chapter 25 Legacy Cascading Agents

Due to the implementation of Java Management Extensions (JMX) Remote API in Java Dynamic Management Kit (Java DMK) 5.1, the cascading service has been superseded by a new implementation of cascading. The cascading service used with the legacy connectors is retained here for reasons of backwards compatibility.

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

This chapter contains the following topics:

25.1 Legacy 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 25.2 Mirror MBeans in the Legacy Cascading Service 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 legacy RMI connector client that we will connect to the legacy 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 25–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 legacy 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 legacy 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 legacy 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.

25.2 Mirror MBeans in the Legacy Cascading Service Master Agent

Once the legacy 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 25–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 legacy 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.

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

25.2.2 Legacy Cascading Service Issues

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

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

25.2.2.2 MBean Filtering

When the legacy 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.

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

25.3 Running the Legacy Cascading Example

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

To Run the Legacy 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/legacy/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 Legacy 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.