How to use JSR 160

Introduction

Using JSR 160 is very simple; the API is standard, so it does not matter if you use MX4J's JSR 160 implementation or Sun's JSR 160 reference implementation.
You can checkout the JSR 160 examples shipped with MX4J to understand how to use the JSR 160 API.

JMXServiceURLs

JSR 160 connector servers are identified by a JMXServiceURL, represented by the class javax.management.remote.JMXServiceURL.
A JMXServiceURL is a string of the form:

service:jmx: <protocol>://[[[ <host>]: <port>]/ <path>]

where protocol is a short string that represent the protocol such as "rmi", "iiop", "jmxmp" or "soap", while host, port and path are optional.

A JMXServiceURL can be seen as the "address" of a JMXConnectorServer, and it is the mean by which a JMXConnector can connect to a JMXConnectorServer.

However, a JMXServiceURL is not sufficient to express the many possibile configurations of a JMXConnectorServer (for example, it would be difficult to use a JMXServiceURL to specify - for the RMIConnectorServer - the RMIClientSocketFactory and the RMIServerSocketFactory).
For this reason JMXConnectorServers and JMXConnector make use of java.util.Maps to specify environment properties that a JMXConnectorServer or a JMXConnector may use to setup properly.

Creating a JMXConnectorServer

A JMXConnectorServer is attached to an MBeanServer.
This can be achieved by explicitely passing the MBeanServer to the JMXConnectorServer at the moment of creation, or by registering the JMXConnectorServer - an MBean itself - inside the target MBeanServer.

Once a JMXConnectorServer is attached to an MBeanServer, it is not yet ready to accept incoming calls from clients: it must be started.
After a JMXConnectorServer has been started successfully, it is ready to accept incoming calls from clients.
Symmetrically, a JMXConnectorServer must be stopped in order to stop accepting incoming calls from clients. After a JMXConnectorServer has been stopped, it cannot be restarted, and should be tossed away.

The preferred way to create a JMXConnectorServer is by using the javax.management.remote.JMXConnectorServerFactory class:

Example 3.1. Creating and starting a standalone JMXConnectorServer

               
// The address of the connector server
JMXServiceURL address = new JMXServiceURL("service:jmx:rmi://host");

// The environment map, null in this case
Map environment = null;

// The MBeanServer to which the JMXConnectorServer will be attached to
MBeanServer server = MBeanServerFactory.createMBeanServer();

// Create the JMXCconnectorServer
JMXConnectorServer cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(address, environment, server);

// Start the JMXConnectorServer
cntorServer.start();
               
            

The example above creates a JMXConnectorServer attached to a freshly created MBeanServer.
The JMXConnectorServer - itself an MBean - is however not registered in the MBeanServer.

The following code creates a JMXConnectorServer and registers it in a MBeanServer.

Example 3.2. Creating and starting an MBean JMXConnectorServer

               
// The address of the connector
JMXServiceURL address = new JMXServiceURL("service:jmx:rmi://host");

// The environment map, null in this case
Map environment = null;

JMXConnectorServer cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(address, environment, null);

// The MBeanServer to which the JMXConnectorServer will be registered in
MBeanServer server = MBeanServerFactory.createMBeanServer();

// Register the JMXConnectorServer in the MBeanServer
ObjectName cntorServerName = ObjectName.getInstance("connectors:protocol=rmi");
server.registerMBean(cntorServer, cntorServerName);

// Start the JMXConnectorServer
cntorServer.start();

// An alternative way to start the JMXConnectorServer via the MBeanServer
server.invoke(cntorServerName, "start", null, null);

// Yet another way to start the JMXConnectorServer via the MBeanServer
Object proxy = MBeanServerInvocationHandler.newProxyInstance(server, cntorServerName, JMXConnectorServerMBean.class, true);
JMXConnectorServerMBean cntorServerMBean = (JMXConnectorServerMBean)proxy;
cntorServerMBean.start();
               
            

Once a JMXConnectorServer is connected to an MBeanServer and once it has been started, it is possible to create a JMXConnector on a client host and connect it to the JMXConnectorServer.
We already saw that the mean used by a JMXConnector to connect to a JMXConnectorServer is a JMXServiceURL.

Creating a JMXConnector

If a JMXConnectorServer is the server-side component that allows to interact with a MBeanServer, a JMXConnector is the client-side component that allows client code to contact a remote MBeanServer.
A JMXConnector handles the details of registering notification listeners and receiving notifications from the remote MBeanServer, as well as providing a way to authenticate to the JMXConnectorServer, and eventually execute operations on behalf of a given javax.security.auth.Subject.
Finally the JMXConnector allows client code to obtain an implementation of the javax.management.MBeanServerConnection interface that allows to interact with the remote MBeanServer as if it is local.

The preferred way to create a JMXConnector is to use the javax.management.remote.JMXConnectorFactory class:

Example 3.3. Connecting a JMXConnector

               
// The address of the connector server
JMXServiceURL address = ...;

// The environment map, null in this case
Map environment = null;

// Create the JMXCconnectorServer
JMXConnector cntor = JMXConnectorFactory.connect(address, environment);

// Obtain a "stub" for the remote MBeanServer
MBeanServerConnection mbsc = cntor.getMBeanServerConnection();

// Call the remote MBeanServer
String domain = mbsc.getDefaultDomain();
               
            

JMXConnectors can be instantiated, but connected at a later time.
Below is a code snippet that shows how to instantiate a JMXConnector and then connect it to the JMXConnectorServer.
Note the use of two different environment Maps: one is used to specify creation parameters, the other to specify connection parameters.

Example 3.4. Creating and connecting a JMXConnector

               
// The address of the connector server
JMXServiceURL address = ...;

// The creation environment map, null in this case
Map creationEnvironment = null;

// Create the JMXCconnectorServer
JMXConnector cntor = JMXConnectorFactory.newJMXConnector(address, creationEnvironment);

// The connection environment map, null in this case
// May contain - for example - user's credentials
Map connectionEnvironment = null;

// Connect
cntor.connect(connectionEnvironment);

// Obtain a "stub" for the remote MBeanServer
MBeanServerConnection mbsc = cntor.getMBeanServerConnection();

// Call the remote MBeanServer
String domain = mbsc.getDefaultDomain();
               
            

Remote Notifications

JSR 160 connectors are able to receive notifications emitted by a remote MBean.
The details of the mechanism of how remote notifications are delivered depends on the protocol used by the connector; however, few general principles are explained below.

To receive notifications, a client must register a listener by means of the javax.management.MBeanServerConnection.addNotificationListener(...) method.
There are two overloaded versions of this method: one that takes an ObjectName as listener, and one that takes a NotificationListener as listener.

In the first case, the listener is remote (an MBean in the remote MBeanServer) and thus both the filter and the handback object are sent over the wire to the server (and of course both must be serializable).

The more interesting case is the second, where the listener is local to the client code.
In this case the listener that receives notifications emitted by a remote MBean always remains local to the client code that registered it. The NotificationListener object is never sent across the wire.
NotificationListener objects are usually implemented with anonymous inner classes (that most of the times are not serializables), and client code should not make any particular attention on how to implement NotificationListeners that receive remote Notifications: anonymous inner classes are a good choice.

On the other end, if the remote MBean sends custom subclasses of the Notification class, it must ensure that the custom Notification objects are serializable.
The meaning of serializable depends on the protocol used; in case of RMI, it is the usual meaning of "Java serializable" (that is, it can be written to a java.io.ObjectOutputStream).

NotificationFilters may or may not be sent over the wire, depending on the protocol used by the JMXConnector.
It is a good choice to use the standard JMX NotificationFilters such as javax.management.NotificationFilterSupport, javax.management.AttributeChangeNotificationFilter and javax.management.relation.MBeanServerNotificationFilter to perform filtering of Notifications: these classes are serializable, and known to the server side.
If you want to write your custom NotificationFilter, write it in such a way that will work no matter if it is run on client side or on server side, and be sure its class is known to the server side (for more details about server side classloading, refer to the JSR 160 specification).

The handback object always remains on client side.

In the MX4J implementation, notifications are requested by the client to the server, and use a notification buffer as explained in the JSR 160 specification.
Since it's the client that initiates the notification request, the mechanism can be seen as a polling mechanism. However, if the server does not have notifications to send to the client, it does not return an empty result, but instead holds the call for a configurable timeout until a notification is emitted or the timeout elapses.
This allows to reduce the network traffic (since there is no continuous polling from the client to the server) and still have a fast reactivity to notification emitted by the server.
Refer to the javadocs of the mx4j.remote.RemoteNotificationClientHandler and of the mx4j.remote.RemoteNotificationServerHandler for further details.

Take also a look at the examples bundled with the MX4J distribution for code snippets on registering listeners to remote MBeans.
Below, a quick example of how to register a listener to a remote MBean:

Example 3.5. Registering a NotificationListener to a remote MBean

               
// The address of the connector server
JMXServiceURL address = ...;

// The JMXConnector
JMXConnector connector = JMXConnectorFactory.connect(address);

// The MBeanServerConnection "stub"
MBeanServerConnection mbsc = connector.getMBeanServerConnection();

// The MBeanServerDelegate emits notifications about registration/unregistration of MBeans
ObjectName delegateName = ObjectName.getInstance("JMImplementation:type=MBeanServerDelegate");

NotificationListener listener = new NotificationListener()
{
   public void handleNotification(Notification notification, Object handback)
   {
      // Do something
   }
};

mbsc.addNotificationListener(delegateName, listener, null, null);