Remote JMX: Connectors and Adapters

One of the things that was most difficult for me to learn when first learning about Remote JMX was the difference between a JMX Connector and a JMX Adapter (also spelled Adaptor in many cases). Part of this confusion was a result from trying to understand the difference based on books written before the Remote JMX specification was written or finalized. Another part of the confusion is in the actual names. In this blog entry, I'll point to a few references and resources that I think best explain the JMX Connector and the JMX Adapter and then I'll go through some source code examples because I believe that using these in practice makes them easier to understand than simply reading the words.

The JMX 1.4 specification includes both "regular" JMX and remote JMX in a single, consolidated specification. Part III of this consolidated JMX specification is focused on Remote JMX and is called "JMX Remote API Specification." The first chapter in this Part III, Chapter 13, covers JMX Connectors in general and is followed by Chapter 14 focusing on the RMI-based JMX Connector and Chapter 15 focusing on the Generic Connector.

I think two sentences in Chapter 13 of the JMX 1.4 specification are particularly important to understanding JMX connectors:

1. "The client end of a connector exports essentially the same interface as the

MBean server."

2. "A connector consists of a connector client and a connector server."

Section 5.3 of the JMX 1.4 specification is called "Protocol Adaptors and Connectors" and covers basics of both adapters and connectors. Related to JMX connectors, this section makes the important observation that "A connector is specific to a given protocol, but the management application can use any connector indifferently because they have the same remote interface."

This section also clearly outlines differentiating characteristics of JMX Protocol Adaptors:

1. "Protocol adaptors provide a management view of the JMX agent through a given protocol. They adapt the operations of MBeans and the MBean server into a representation in the given protocol, and possibly into a different information

model."

2. "Management applications that connect to a protocol adaptor are usually specific to the given protocol."

With the key characteristics of JMX connectors and protocol adaptors highlighted above, there are some quickly identifiable differences between the two. These differences may be most succinctly summarized in Daniel Fuchs's blog entry What is JMX?, where he states that JMX Protocol Connectors represent the MBeans to the remote client the same way they would be represented to a local client and that a JMX Protocol Adaptor adapts the server-side model to what the client expects.

JMX Protocol Connectors and JMX Protocol Adaptors both typically work with a single protocol. The difference between the two is that Adaptors massage the management interface for the client's benefit while the Connectors provide a protocol-independent API that is essentially the same as the local API.

The JMX Reference Implementation (the implementation of JMX included in Sun's Java SE 6 distribution) provides the one Connector required of the specification: the Remote Method Invocation (RMI) Connector. The JMX 1.4 specification only requires a JMX implementation to provide an RMI-based Connector, but the specification outlines an optional JMXMP-based Connector. For this example, I use the JMXMP (JMX Message Protocol) Connector provided by OpenDMK. The third JMX Connector used in this example is the JSR-262 JMX Web Services Connector.

I now delve into some code samples to illustrate JMX Connectors versus JMX Adaptors. For simplicity, I have a single class that runs three types of JMX Connector Servers and also runs an Adapter Agent. This class is called JmxServerMain and its main() method is listed here first.

JmxServerMain.java main() Method

/**
 * Main method to set up various server-side remote JMX constructs.
 * 
 * @param arguments Command-line arguments.
 */
public static void main(final String[] arguments)
{
   final List<JMXConnectorServer> connectorServers
      = new ArrayList<JMXConnectorServer>();
   registerExampleMBean("dustinApp:type=exampleMBean");
   useJmxServiceUrlBasedConnector(
      "service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi",
      "connector:type=standard_rmi",
      connectorServers );
   useJmxServiceUrlBasedConnector(
      "service:jmx:jmxmp://localhost:1098",
      "connector:type=optional_jmxmp",
      connectorServers );
   useJmxServiceUrlBasedConnector(
      "service:jmx:ws://localhost:1097/jmxws",
      "connector:type=jsr262_jmxws",
      connectorServers);
   final HtmlAdaptorServer htmlAdaptor =
      useHtmlAdaptor(1096, "adaptor:type=opendmk_html");
   waitForInput();
   stopConnectorServers(connectorServers);
   stopAdaptors(htmlAdaptor);

I will show the implementations of the methods called above later in this entry, but even this high-level look reveals clear and easily identifiable differences between Connectors and Adapters. Note that all three JMX Connectors can be treated the same and can take advantage of the same method. In other words, all three Connectors can be set up using the useJmxServiceUrlBasedConnector method. The Adapter, on the other hand, can not take advantage of the same method as the Connectors and must be set up with its own useHtmlAdaptor method. Note that this method name, useHtmlAdaptor, is specific to the adaptor involved.

The definition of the useJmxServiceUrlBasedConnector method is shown next.

JmxServerMain and its useJmxServiceUrlBasedConnector Method

/**
 * Use the platform MBean server in conjunction with the JMX connector
 * specified in the provided JMXServiceURL.
 * 
 * @param serviceUrl JMXServiceURL to be used in connector.
 * @param connectorMBeanName MBean registration name for the connector.
 * @param connectorServers Collection to which my instantiated JMX Connector
 *    Server should be added.
 */
public static boolean useJmxServiceUrlBasedConnector(
   final String serviceUrl,
   final String connectorMBeanName,
   final List<JMXConnectorServer> connectorServers)
{
   boolean success = true;
   final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
   printOutputHeader(
      "Setting up connector for JMXServiceURL " + serviceUrl,
      System.out);
   try
   {
      final JMXServiceURL jmxServiceUrl = new JMXServiceURL(serviceUrl);
      final JMXConnectorServer connectorServer =
         JMXConnectorServerFactory.newJMXConnectorServer(
            jmxServiceUrl,
            null, 
            mbs);
      connectorServer.start();
      registerProvidedMBean(connectorServer, connectorMBeanName);
      connectorServers.add(connectorServer);
   }
   catch (MalformedURLException badJmxServiceUrl)
   {
      System.err.print(
           "ERROR trying to create JMX server connector with service URL "
         + serviceUrl + ":\n" + badJmxServiceUrl.getMessage() );
      success = false;
   }
   catch (IOException ioEx)
   {
      System.err.println(
           "ERROR trying to access server connector.\n"
         + ioEx.getMessage() );
      success = false;
   }

   if ( success )
   {
      System.out.println(
           connectorMBeanName
         + " registered in MBean server as connector with JMXServiceURL of "
         + serviceUrl + ".");
   }
   else
   {
      System.out.println("\n\nERROR encountered.");
   }
   System.out.println("\n\n");

   return success;
}

The useJmxServiceUrlBasedConnector method shown above is generic and supports all types of specification-compliant JMX connectors provided to it. In fact, the only thing that differentiates one type of connector from another is the protocol embedded within the string that forms the JMXServiceURL. For connector management purposes, it is a recommended practice to register connectors themselves as MBeans and that is also done in this code.

We have now seen that the same generic code can be used to set up all the Connectors for Remote JMX access. In the example above, the RMI, JMXMP, and JMX Web Services Connector are all used this way. The next code listing shows the code for the useHtmlAdaptor method, which is also part of the JmxServerMain class.

JmxServerMain.java useHtmlAdaptor Method

/**
 * Provide server-side functionality for HTML Adaptor to be used by client
 * web page.
 * 
 * @param htmlAdaptorPort Port on which web browser will see this.
 * @param adaptorMBeanName Name of MBean by which adaptor will be registered.
 * @return Handle to the HTML Adaptor that can be stopped when finished.
 */
public static HtmlAdaptorServer useHtmlAdaptor(
   final int htmlAdaptorPort,
   final String adaptorMBeanName)
{
   final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
   final HtmlAdaptorServer htmlAdaptor = new HtmlAdaptorServer(htmlAdaptorPort);

   printOutputHeader(
      "Setting up HtmlAdaptor for port " + htmlAdaptorPort,
      System.out);

   registerProvidedMBean(htmlAdaptor, adaptorMBeanName);
   htmlAdaptor.start();
   System.out.println("HTML Adaptor started.\n\n");
   return htmlAdaptor;
}

The HTML Adaptor is not acquired with a standardized JMXConnectorServerFactory.newJMXConnectorServer call like the connectors were able to be acquired. Instead, the HtmlAdaptorServer is explicitly instantiated. The HTMLAdaptorServer used here is provided by OpenDMK.

All three JMX Connectors (RMI, JMXMP, and WS-JMX) can be connected to by JConsole or by any other standard JMX client. I'll first show the code for accessing these three connectors from a simple client.

ClientMain.java

package dustin.jmx.client;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Arrays;
import java.util.List;
import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

/**
 * This simple example demonstrates a flexible/generic remote JMX client.
 * 
 * @author Dustin
 */
public class ClientMain
{
   private enum ConnectionProtocol
   {
      RMI ("service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"),
      JMXMP ("service:jmx:jmxmp://localhost:1098"),
      JMXWS ("service:jmx:ws://localhost:1097/jmxws");
      
      String jmxServiceUrl;
      
      ConnectionProtocol(final String jmxServiceUrl)
      {
         this.jmxServiceUrl = jmxServiceUrl;
      }

      public String getJmxServiceUrl() { return this.jmxServiceUrl; }
   };

   /**
    * The functionality here is available directly through the provided
    * MBeanServerConnection with or without a proxy or reflection.  Because it
    * is information only at the MBeanServer level (not at each individually
    * hosted MBean's level), no ObjectName is required.
    * 
    * @param mbsc MBeanServerConnection for connecting to remote JMX agent.
    */
   public static void demonstrateCommonMBeanServerInfo(
      final MBeanServerConnection mbsc)
   {
      try
      {
         System.out.println( "MBean Count: " + mbsc.getMBeanCount() );
         System.out.println( "MBean Default Domain: " + mbsc.getDefaultDomain() );
         final List<String> domains = Arrays.asList(mbsc.getDomains());
         System.out.println("DOMAINS:");
         for ( final String domain : domains )
         {
            System.out.println ("\t- " + domain);
         }
      }
      catch (IOException ioEx)
      {
         System.err.println(  "ERROR encountered trying to get MBeanCount and "
                            + "Default Domain for provided MBeanServer:\n"
                            + ioEx.getMessage() );
      }
   }

   /**
    * Run client that can talk to a JMX Connector Server of one of three types:
    * Remote Method Invocation (RMI), JMX Message Protocol (JMXMP), or JMX-WS
    * (JMX Web Services Connector/JSR-262).
    * 
    * @param aArguments Command-line arguments; one expected to indicate
    *    Remote JMX protocol (RMI, JMXMP, or JMXWS).
    */
   public static void main(String[] aArguments)
   {
      ConnectionProtocol connectionProtocol = null;
      if ( aArguments.length > 0 )
      {
         final String protocolStr = aArguments[0].toUpperCase();
         System.out.println("Protocol String: " + protocolStr);
         connectionProtocol = ConnectionProtocol.valueOf(protocolStr);
      }
      if ( connectionProtocol == null )
      {
         connectionProtocol = ConnectionProtocol.RMI;
      }
      final String jmxServiceUrl = connectionProtocol.getJmxServiceUrl();
      System.out.println("JMXServiceURL: " + jmxServiceUrl);

      try
      {
         final JMXServiceURL jmxUrl = new JMXServiceURL(jmxServiceUrl);
         final JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxUrl);
         final MBeanServerConnection mbsc = jmxConnector.getMBeanServerConnection();

         System.out.println("Using " + connectionProtocol.toString() + "!!!");
         demonstrateCommonMBeanServerInfo(mbsc);
      }
      catch (MalformedURLException badUrl)
      {
         System.err.println(  "ERROR: Problem with JMXServiceURL based on "
                            + jmxServiceUrl + ": " + badUrl.getMessage() );
      }
      catch (IOException ioEx)
      {
         System.err.println(  "ERROR: IOException trying to connect to JMX "
                            + "Connector Server: " + ioEx.getMessage() );
      }
   }
}
1 2 Page 1
Page 1 of 2