Java Dynamic Management Kit 5.0 Tutorial

Part III Remote Management Applications

In Part II, we saw how to access and manage a Java dynamic management agent through the HTML protocol adaptor. Protocol adaptors provide a view of an agent through communication protocols. In this part, we present protocol connectors and proxy MBeans for managing agents programmatically.

The Java Dynamic Management Kit goes beyond the scope of the JMX specification to define the APIs needed to develop distributed management applications in the Java programming language. These remote applications establish connections with agents through protocol connectors over RMI, HTTP, or HTTPS. The 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.

Connectors enable you to develop a management application that is both protocol independent and location-independent. Once the connection is established, the communication layer is transparent, and the manager can issue requests as if it were directly calling the MBean server. Using proxy objects that represent MBeans simplifies the design of the management application and reduces development time.

This homogeneity of the API makes it possible to develop portable management applications that can run either in an agent or in a remote management application. This simplifies the development and testing of applications, and it also allows functionality to evolve along with the management solution. As your agent and manager platforms evolve, management policies can be implemented at higher levels of management, and intelligent logic for monitoring and processing can be moved down into agents.

Connectors and proxies provide an abstraction of the communication layer between agent and manager, but they also provide mechanisms for simplifying the management task. 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.

This part contains the following chapters:

Chapter 10 Protocol Connectors

Protocol connectors provide a point-to-point connection between a Java dynamic management agent and a management application. Each connector 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.

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 connectors provided with the Java Dynamic Management Kit (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 SimpleClients and HeartBeat example directories located in the main examplesDir (see Directories and Classpath in the Preface).

This chapter covers the following topics:

Connector Servers

The Java DMK 5.0 introduces 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 connector servers.

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

The connector server on the agent side listens for management requests issued through a corresponding connector client. It transmits these requests to its MBean server and forwards any response back to the management application. The connector server also forwards notifications, when the management application has registered to receive them through its connector client (see Chapter 12, Notification Forwarding for more details).

There is a connector server for each of the protocols supported in the Java DMK: RMI, HTTP and HTTPS. They all inherit from the CommunicatorServer class that is the superclass for all 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 RMI connector server, and the HTTP authentication mechanism is described in Password-Based Authentication.

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.

Instantiating an RMI Connector Server

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

Example 10–1 Instantiating the RMI Connector Server

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

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

} catch(Exception e) {

Other constructors for the 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 RmiConnectorServer class.

Each 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 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 an RMI connector server using the RMI property java.rmi.server.hostname.

Connector States

Like all communicator servers, the 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 10–2 Starting the RMI Connector Server

// Explicitly start the RMI connector server

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

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 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 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 the JDKTM software, 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 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 close 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 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.

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 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 connector client uses the default host name, InetAddress.getLocalHost().getHostName()

Multihome Interfaces

The multihome interfaces introduced in Java DMK 5.0 enable you to select the interfaces for the different network protocols used by 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.

An RMI connector client does not need to specify a local interface, except when a Java DMK 5.0 client needs to connect to a server from Java DMK 4.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 10–1 lists the methods that are defined identically in both the MBeanServer and the RemoteMBeanServer interfaces:

Table 10–1 Shared Methods



* void

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


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)





* MBeanInfo

getMBeanInfo(ObjectName name)


getObjectInstance(ObjectName name)

* java.lang.Object

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


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


isRegistered(ObjectName name)


queryMBeans(ObjectName name, QueryExp query)


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 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. Establishing a Connection shows how to establish a connection and access MBeans directly through the connector client.Heartbeat Mechanism, shows how to monitor a connection and detect when it is lost.

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 Password-Based Authentication). 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 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.

Example 10–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 !!!");

Managing MBeans Remotely

Once the connection to an agent is established, the management application can access that agent's MBeans through the 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 connector client, but this is performed by a security mechanism in the connector server. See Context Checking for more details.

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, or you can use the createMBean method that takes the name of a class loader (see M-Let Loading From a Manager for more details).

Example 10–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 = "+
        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");
            mbeanObjectInstance.getObjectName() );


    } catch (Exception e) {

Example 10–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.

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 10–5 is actually called in between the MBean creation and unregistration shown in Example 10–4.

Example 10–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 );

// display content of MBeanInfo object
echo("\nCLASSNAME: \t"+ info.getClassName());
echo("\nDESCRIPTION: \t"+ info.getDescription());
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 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 10–6 Accessing an MBean Through the 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) {

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

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

Running the SimpleClients Example

The examplesDir/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 an 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/SimpleClients/
    $ javac -classpath classpath *.java

    We do not need all the files for this chapter, but they are used in Chapter 11, MBean Proxies. 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 an 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 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.

Heartbeat Mechanism

The 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 connector clients of the Java DMK implement the HeartBeatClientHandler interface to provide a heartbeat mechanism. This means that agent-manager connections over RMI, HTTP, and HTTPS 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.

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 connector retry until either the connection is reestablished 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 10–7, the management application configures the heartbeat mechanism before the connection to an agent is established.

Example 10–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...");

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

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 reestablished 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 connector server. It automatically responds to the heartbeat messages. These heartbeat messages contain the current heartbeat settings from the 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 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 reestablished. 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 12, Notification Forwarding). 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.

Receiving Heartbeat Notifications

The 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 Registering Manager-Side Listeners).

Example 10–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-reestablished 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 10–1 Sequencing of Heartbeat Notifications

Diagram showing sequencing of heartbeat notifications

Example 10–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 10–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 =

        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.

Running the Heartbeat Example

The examplesDir/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/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 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 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 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 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 11 MBean Proxies

In Protocol Adaptors, we saw how to access a remote agent and interact with its MBeans through connectors. The 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 SimpleClients example directory located in the main examplesDir (see “Directories and Classpath” in the Preface).

This chapter covers the following topics:

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. And because the code of a proxy class is deterministic, it can be generated automatically using the proxygen tool.

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 16, Cascading Agents.

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 Table 10–1 (see RemoteMBeanServer Interface). These are the only methods that a proxy needs to access its corresponding MBean and fulfill all management requests.

In the Java DMK the RemoteMBeanServer interface extends the ProxyHandler interface, meaning that proxy objects can be bound to any of the 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 Adding a Listener Through the Connector).

Figure 11–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 11–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.

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.






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.

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.

Generating Proxies for Standard MBeans

The 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.0 Tools Reference 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 Running the Standard Proxy Example.

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

Example 11–1 Code Generated for the 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,
    MBeanException {

                          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.

Using 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 –

In previous versions of the Java DMK, the connector client handled the creation of proxy instances and ensured that only one proxy object could exist for each MBean. Since version 4.2 of the product, connector clients no longer instantiate nor control proxy objects. The corresponding methods of the RemoteMBeanServer interface are now deprecated.

Similarly, the previous binding methods in the Proxy interface 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 11–2 Instantiating and Binding a 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 11–3 Instantiating and Binding a 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("");
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  = " +
echo("\tMBEAN OBJECT NAME = " +
echo("\tCONNECTOR CLIENT  = " +

// 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 11–4 Accessing a Standard MBean Through Its 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

    // 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() );

Running the Standard Proxy Example

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

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

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 11–5 Accessing Standard and Dynamic MBeans Using 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  = " +
        echo("\tMBEAN OBJECT NAME = " +
        echo("\tCONNECTOR CLIENT  = " +

        // 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  = " +
        echo("\tMBEAN OBJECT NAME = " +
        echo("\tCONNECTOR CLIENT  = " +

        // we no longer need proxy2, so we unbind it

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

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

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

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:


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 11–4).

Example 11–6 Accessing an MBean Through its 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() );

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

Running the Generic Proxy Example

The ClientGenericProxy application, also located in the examplesDir/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/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.

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 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.0 Tools Reference guide.

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:

Compiling the Proxy 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/JdmkProxyMBeans/
$ javac -d packageRoot -classpath classpath * *

In this command, the classpath must contain the current directory and the classpath of the Java DMK runtime libraries (jdmkrt.jar and jsnmpapi.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 12 Notification Forwarding

In this chapter, we expand the notification mechanism to the manager side, looking at how remote applications receive notifications. Notifications are forwarded to a manager through existing structures of the Java dynamic management architecture: MBeans, the MBean server, and the connectors. Notably, this implies that the notification mechanism is designed to forward only notifications from registered MBeans on the agent side to proper listeners on the manager side.

As with the other management operations, listening for notifications is nearly as simple to do in a management application as it is to do locally in an agent. The interface of the connector client hides all communication issues, so that listeners can be registered through the connector or directly with existing proxy MBeans.

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

This chapter covers the following topics:

Registering Manager-Side Listeners

Like the other structures of the Java DMK, the 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.

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 12–1 Agent-Side Broadcaster MBean


public class NotificationEmitter
    extends NotificationBroadcasterSupport
    implements NotificationEmitterMBean {

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

    // 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,
            "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 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.

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 12–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 Overview, 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.

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 12–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);

// Wait 1 second for connecting

// 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 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. When generating proxy classes with the proxygen tool, MBeans that implement the NotificationBroadcaster interface will have proxy classes that implement the NotificationBroadcasterProxy interface.

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

Push Mode

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 connector client instructs the 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 12–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 12–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: 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 12–4 Switching to the Notification Push Mode

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

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

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.

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.

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 12–5 Pulling Notifications Automatically

System.out.println(">>> Set notification forward mode to PULL.");

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

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");

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

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 12–6 Pulling Notifications by Request

System.out.println(">>> Use pull mode with period set to zero.");

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.");;

// Wait for the handler to process all notifications

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.

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 12–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.setCacheSize(10, true); // see "Buffering Specifics"

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.");;

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

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

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 10 and there are 30 notifications. In this mode, the first 10 sequence numbers remain in the cache to be forwarded when the manager pulls them from the agent, and 20 more will overflow.

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.

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

Running the Notification Forwarding Example

The examplesDir/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 notification forwarding example, we use the BaseAgent application that contains an RMI connector server.

To Run the 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/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 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:

    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 13 Access Control and Security

Whenever considering a distributed architecture, security issues are often an added factor in the complexity of the design. This is not the case with the Java Dynamic Management Kit (DMK), whose security features are built into the modularity of the components.

Management solutions can evolve from basic password-based protection all the way to secure connections using cryptography simply by switching protocol connectors or by adding filter components. The rest of the architecture is unchanged because it relies on the interface that is common to all connectors.

There are two categories of access-control: 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 HTTPS connector. Using other components of the Java platform, connectors can effectively make all open communication undecipherable.

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

This chapter covers the following topics:

Password-Based Authentication

The simplest form of agent security 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.

In the , only the HTTP–based connectors support password–based authentication. The SNMP protocol adaptor also supports access control, but it is based on a different mechanism (see IP-Based Access Control Lists).

By default, no authentication is enabled in the HTTP-based 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 13–1 Implementing Password Authentication in the 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++)

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

Example 13–2 Specifying the Login and Password in the 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.

Running the Example With Authentication

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

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

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.

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 13–1 shows the checker methods that apply to groups of MBeanServer methods.

Table 13–1 Filter Method Granularity for Context Checking

Filter Method 

MBean Server Operations Filtered 


Every method of the MBeanServer interface


All forms of the create and registerMBean methods


The unregisterMBean method


All forms of the instantiate method


The invoke method that handles all operation invocations


Both addNotificationListener and removeNotificationListener


Both queryMBeans and queryNames


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


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

Context Implementation

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

Example 13–3 Implementation of the Context Checker


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

public class ContextChecker extends MBeanServerChecker {

    // Constructor
    public ContextChecker(MBeanServer 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 + "\", " +
        OperationContext context = getOperationContext();
        System.out.println("  OperationContext: " + context);

        if (context == null || !context.toString().equals("nice")) {
            RuntimeException ex =
                new SecurityException("  Bad context: " + context);
            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 13–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");

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

Example 13–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

Running the Example With Context Checking

The ContextClient and ContextAgent applications in the examplesDir/Context directory also demonstrate the use of stackable MBean servers and context checking through the 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/Context/
$ javac -classpath classpath *.java
To Run the 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.

HTTPS Connector

The HTTPS connector provides data encryption and certificate-based security through a Secure Socket Layer (SSL). An implementation of secure sockets is only available for the Java 2 platform. However, secure sockets are not part of the Java 2 Software Development Kit (SDK), and their libraries must be installed separately.

The Java Secure Socket Extension (JSSE) provides a compatible implementation of secure sockets for the Java 2 platform.

The web site for the JSSE is This site provides links for downloading the software and the documentation. For further information and details regarding the use of the secure sockets, refer to the JSSE documentation.

The HTTPS connector exposes the same interfaces as all other 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 Connector Clients for details about programming with the RemoteMBeanServer API.

Where the HTTPS connector differs is that it relies on the security mechanisms built into the Java language and extended by JSSE. In order to use these libraries and communicate securely, you must configure your application environment to meet all security requirements. The cost of security is establishing all of the structures that guarantee the trust between two communicating parties.

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 SDK (not a shared network installation).

To Establish a Secure HTTPS Connection
  1. Install the software.

    You should install both the Java 2 SDK, Standard Edition, v1.2.2, and the JSSE 1.01 products on all hosts that will use the HTTPS connector client or connector server components.

    In this procedure, the directories where you have installed these products are named JAVAhome and JSSEhome, respectively. These names are used on all hosts where the products are installed, even though their values are specific to each host.

  2. Extend your Java runtime libraries.

    For each of your SDK/JSSE installations, copy the three JAR files (jsse.jar, jcert.jar, and jnet.jar) of the JSSE reference implementation into the extensions directory of your Java runtime environment.

    • For example, on the Solaris platform, type:

      $ cp JSSEhome/lib/jsse.jar JAVAhome/jre/lib/ext/
      $ cp JSSEhome/lib/jcert.jar JAVAhome/jre/lib/ext/
      $ cp JSSEhome/lib/jnet.jar JAVAhome/jre/lib/ext/
    • Or add these JAR files to your environment's classpath, as follows (for the Korn shell):

      $ export CLASSPATH=${CLASSPATH}:JSSEhome/lib/jsse.jar:\ 
  3. Designate your security provider

    The JSSE follows the same provider architecture found in the Java Cryptography Architecture (JCA) which is provided in the Java 2 platform as the Java Cryptography Extension (JCE). In order to use JSSE, you must install this provider either statically or dynamically. Again, you must do this for all of your SDK/JSSE installations.

    • To install the provider statically, edit the security properties file (JAVAhome/lib/security/ The boldface text is the part you must add:

      The first line of this file depends upon your SDK platform and should not be changed.

    • To install the provider dynamically in your Java application, call the addProvider method of the class as follows:

      Security.addProvider( new );

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

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

  7. Run your Java dynamic management agent

    Start your agent applications with the following properties:

    $ java

    If you are using the notification push mechanism, add the following property definition to the above command line:
  8. Run your management application

    Start your management applications with the following properties:

    $ java