Remote JMX: Connectors and Adapters

1 2 Page 2
Page 2 of 2

The simple client whose code is shown above allows the user to specify which protocol connector to use (RMI, JMXMP, JMXWS) as a command-line argument and RMI is used if none is specified. What this simple client demonstrates is that the JMX Connector Servers are accessed in the identical manner regardless of the underlying protocol. Only the JMXServiceURL needs to be different.

The three JMX Connector Servers can also be accessed from JConsole. As with the simple client, the only thing that needs to be different is the JMXServiceURL provided to JConsole in the remote field.

The HTML Adaptor is not accessed via the simple client shown above or via JConsole. Rather, as an HTML adapter, it has adapted the model for HTML presentation and one accesses its exposed management interfaces via web browser with URL of http://localhost:1096 (1096 in this example because we established this as the port for the HtmlAdaptorServer).

I am not going to show the simple client output, the JConsole output, or the HTML web page output here, because they are nothing different from what one would see using these tools for other JMX uses. For convenience, I am including the entire JmxServerMain class next. I included significant portions of it above, but this listing includes the entire class with all of its convenience methods.

Entire JmxServerMain.java Class

package dustin.jmx.server;

import dustin.jmx.ApplicationState;

import com.sun.jdmk.comm.HtmlAdaptorServer;

import java.io.Console;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.management.ManagementFactory;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;

import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;

/**
 * Main executable to showcase various commonly used JMX connectors and adapters
 * (or adaptors).
 * 
 * @author Dustin
 */
public class JmxServerMain
{
   /**
    * Artificial pause until ENTER button is pressed.
    */
   public static void waitForInput()
   {
      final Console console = System.console();
      if ( console == null )
      {
         System.err.println(
            "ERROR.  Please run this application on a machine with a console.");
         return;
      }
      console.printf("Press <ENTER> to exit.");
      final String unusedInput = console.readLine();
   }

   /**
    * Print out header to provided output stream.
    * 
    * @param headerText Text to be displayed in output header/separator.
    * @param os OutputStream to which header should be written (such as
    *    System.out).
    */
   public static void printOutputHeader(
      final String headerText,
      final OutputStream os )
   {
      final String newLine = System.getProperty("line.separator");
      final String headerLine = "-- " + headerText + newLine;
      final String separator =
           "------------------------------------------------------------------"
         + newLine;

      try
      {
         os.write(separator.getBytes());
         os.write(headerLine.getBytes());
         os.write(separator.getBytes());
      }
      catch (IOException ioEx)
      {
         System.err.println(
            "ERROR trying to write header '" + headerText + "' out to the "
            + " provided OutputStream:\n" + ioEx.getMessage() );
      }
   }

   /**
    * Stop all opened and started JMXConnectorServer instances.
    * 
    * @param connectors JMXConnectorServer instances.
    * @return Number of connector servers stopped.
    */
   public static int stopConnectorServers(final List<JMXConnectorServer> connectors)
   {
      System.out.println("Stopping Connector Servers ...");
      int numberOfConnectorsClosed = 0;
      for ( final JMXConnectorServer connector : connectors )
      {
         numberOfConnectorsClosed++;
         try
         {
            connector.stop();
         }
         catch (IOException ioEx)
         {
            System.err.println(
               "ERROR trying to close JMXConnectorServer:\n" + ioEx.getMessage());
         }
      }
      System.out.println(
         "Stopped " + numberOfConnectorsClosed + " connector servers.");
      return numberOfConnectorsClosed;
   }

   /**
    * Stop the adaptors.
    * 
    * @param htmlAdaptor Handle to HTML Adaptor to be stopped.
    * @return Number of adaptors stopped.
    */
   public static int stopAdaptors(HtmlAdaptorServer htmlAdaptor)
   {
      System.out.println("Stopping Adaptors ...");
      htmlAdaptor.stop();
      System.out.println("Stopped HtmlAdaptorServer.");
      return 1;
   }

   /**
    * Register the provided object as an MBean with the provided ObjectName.
    * 
    * @param objectToBeRegisteredAsMBean Object to be registered with MBean
    *    Server.
    * @param nameForMBean Name to be used for ObjectName of registered MBean.
    */
   public static void registerProvidedMBean(
      final Object objectToBeRegisteredAsMBean,
      final String nameForMBean)
   {
      final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
      try
      {
         mbs.registerMBean(
            objectToBeRegisteredAsMBean,
            new ObjectName(nameForMBean) );
      }
      catch (MalformedObjectNameException badMBeanNameEx)
      {
         System.err.println(
            "ERROR trying to create ObjectName with " + nameForMBean + ":\n"
            + badMBeanNameEx.getMessage() );
      }
      catch (MBeanRegistrationException badMBeanRegistrationEx)
      {
         System.err.println(
              "ERROR trying to register MBean " + nameForMBean + ":\n"
            + badMBeanRegistrationEx.getMessage() );
      }
      catch (NotCompliantMBeanException nonCompliantEx)
      {
         System.err.println(
              "ERROR: " + nameForMBean + " is not a compliant MBean.:\n"
            + nonCompliantEx.getMessage() );
      }
      catch (InstanceAlreadyExistsException redundantMBeanEx)
      {
         System.err.println(
              "ERROR: MBean instance " + nameForMBean + " already exists:\n"
            + redundantMBeanEx.getMessage() );
      }
   }

   /**
    * Register an example MBean with the Platform MBean server to be looked up
    * by clients connected via a connector or adapter.  Note that this is not
    * absolutely necessary in Java SE 6, but it is interesting.
    * 
    * @param nameForMBean Name for MBean to be registered with MBeanServer.
    */
   public static void registerExampleMBean(
      final String nameForMBean )
   {
      registerProvidedMBean(new ApplicationState(), nameForMBean);
   }

   /**
    * 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;
   }

   /**
    * 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;
   }

   /**
    * 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);
   }
}

Because the RMI connector is included with the Reference Implementation of JMX that is included with Java SE 6, I did not need to explicitly include anything on the classpath to use it. However, to use the JSR-262 WS-JMX and to use OpenDMK for both the HTMLAdaptor and for the JMXMP Connector, I did need to add relevant JARs to the classpaths.

I ran JmxServerMain as follows (thankfully, Java SE 6 supports wildcards for including JAR files in the classpath or else the WS-JMX entries would be numerous):

java -cp C:\OpenDMK-bin\lib\jdmkrt.jar;C:\OpenDMK-bin\lib\jmxremote_optional.jar;dist\RemoteJMX.jar;C:\jmx-ws-262\jsr262-ri\lib\* dustin.jmx.server.JmxServerMain

To run JConsole to "see" the OpenDMK and WS-JMX stuff, I used this command:

jconsole -J-Djava.class.path=C:\jmx-ws-262\jsr262-ri\lib\*;"C:\Program Files\Java\jdk1.6.0_07"\lib\jconsole.jar;"C:\Program Files\Java\jdk1.6.0_07"\lib\tools.jar;C:\OpenDMK-bin\lib\jmxremote_optional.jar

Finally, to run the simple programmatic client, I used this command:

java -cp dist\RemoteJMX.jar;C:\jmx-ws-262\jsr262-ri\lib\*;C:\OpenDMK-bin\lib\* dustin.jxm.client.ClientMain

If you don't include the appropriate JAR files for WS-JMX or for OpenDMK's JMXMP Connector, you will see errors like those shown in the following two images ("Unsupported protocol: jmxmp" and "Unsupported protocol: ws"):

Failure to Include OpenDMK JARs on ClassPath

Failure to Include JSR-262 JARs on ClassPath

Failure to include the OpenDMK JARs for HTMLAdaptor will be revealed at compile time instead of runtime because of the direct instantiation of the HtmlAdaptorServer class described above. Of course, the runtime classpath will need them as well, but failure to do that is marked by the well-known NoClassDefFoundError.

In this blog entry, I've attempted to demonstrate key differences between JMX Protocol Adaptors and JMX Protocol Connectors. In general, I prefer Connectors because of the standardized approach that can be used to work with them both on the Connector Client and Connector Server side. I also generally like having the same interface both locally and remotely. However, there are times when adapters have their advantages. For example, it is nice not to have to include OpenDMK or WS-JMX libraries on the client machine when using a web browser with the HTML Adaptor. Perhaps at least partially for this reason (simplicity on the client side), early JMX books often favored the HTML Adaptor as the view into managed applications. The fact that JConsole was not yet available probably had something to do with the prevalent use of HTML Adaptor as well. With JConsole, VisualVM, and the ability to easily write JMX Connector Clients, I find myself generally favoring Connectors over Adaptors unless there is something specific about the client format (such as HTML or SNMP) that is required.

Other resources describing JMX Connectors and Adaptors include the JMX Tutorial (focus on connectors), the JMX Accelerated How-to, and the Remote Management Applications section of the Sun Java Dynamic Management Kit Tutorial.

1 2 Page 2
Page 2 of 2