Chapter 10. JMX Distribution Layer with J2EE Services

This chapter will cover some more connector implementations in detail. You will see example implementations for a SOAP connector and a bare bones implementation of an asynchronous JMS connector. In addition, you will cover some issues with the JMX agent and MBean location transparency and implement a simple mechanism for the management clients to look up the agent or MBean from a naming service.

Location Transparency

When building the management applications, it can be useful to avoid hard coding the location of the MBeans or the MBean servers in the client code. For improved flexibility on the client, both the agent and the MBeans should be accessible using logical names instead of hard-coded addresses. The agent may migrate from one host machine to another during the life span of a management application, and reconfiguring the client to reflect this change can be a nuisance to the user or administrator. More often, an MBean component migrates from one agent to another and the management application must know where the new home of the MBean is, as shown in Figure 10.1.

A management client connecting directly to a JMX agent will not find its target MBean if the MBean has migrated to a different JMX agent.

Figure 10.1. A management client connecting directly to a JMX agent will not find its target MBean if the MBean has migrated to a different JMX agent.

To achieve a basic location transparency, you can add a step of indirection to the process of connecting to a remote JMX agent. Instead of attempting to connect to a host of the agent directly, you can look up a handle to the agent from a well-known naming service. The handle will be bound to a logical name the management application can use to perform the lookup, and if the location of the agent or an MBean changes, these changes can be reflected on the handle to avoid affecting the client application (see Figure 10.2).

When the management client looks up a handle that contains the location information of an MBean, it will continue to operate even if the MBean has migrated to a different JMX agent.

Figure 10.2. When the management client looks up a handle that contains the location information of an MBean, it will continue to operate even if the MBean has migrated to a different JMX agent.

You will next create an export manager service implementation. The export manager is capable of connecting to a naming service via Java Naming and Directory Interface (JNDI) and binding a serializable object, an MBeanHandle, to the service. Later in this chapter, you will implement connectors that are able to look up and use the handle objects to determine the host machine address of an agent or MBean and other additional information required to build a remote connection.

MBean Handle

The MBean handle bound to the naming service can be any serializable Java object. The implementation covered in this section is a basic handle consisting of name-value properties. The handle can be used to export values, such as host name and port number, to the name service.

The ConnectorFactory class you built in Chapter 9, “Connectors and Protocol Adaptors,” can be modified to accept a JNDI name as an argument for the createConnector() method calls. Instead of receiving the connection properties directly from the client, the factory class will attempt to retrieve them from the name service in the form of the MBeanHandle instance.

The MBeanHandle interface is shown in Listing 10.1. It contains one method, getProperties(), that the connector factory can use to retrieve the properties it needs to form a connection. The implementation of the MBeanHandle interface is shown in Listing 10.2.

Example 10.1. MBeanHandle.java

package book.jmx.examples;

import java.util.*;

public interface MBeanHandle extends java.io.Serializable {

  public Properties getProperties();

}

Example 10.2. MBeanHandleImpl.java

package book.jmx.examples;

import java.util.*;

public class MBeanHandleImpl implements MBeanHandle {

  private Properties props = new Properties();

  public void addProperties(Properties props) {
    this.props.putAll(props);
  }

  public Properties getProperties() {
    return (Properties)props.clone();
  }
}

The modification to the ConnectorFactory class is shown in Listing 10.3. The class contains an overloaded createConnector() method that takes a JNDI name as its second argument instead of taking a Properties object directly. The overloaded createConnector() attempts to create a JNDI InitialContext reference to the naming service and look up an MBeanHandle based on the given name. If the handle is successfully retrieved, the implementation will call the getProperties() method of the MBeanHandle interface and call the version of the createConnector() method with the Properties object.

Example 10.3. ConnectorFactory.java

package book.jmx.examples;

import java.util.*;
import java.lang.reflect.*;
import java.rmi.*;
import javax.naming.*;

public class ConnectorFactory  {

  public static RemoteMBeanServer createConnector(String transport,
      String jndiName) throws ConnectorException {

    Context ctx = null;
    MBeanHandle handle = null;

    try {
      ctx = new InitialContext();
      handle = (MBeanHandle)ctx.lookup(jndiName);
    }
    catch (NamingException e) {
      throw new ConnectorException(
        "Unable to find the handle", e
      );
    }

    return createConnector(transport, handle.getProperties());
  }

  public static RemoteMBeanServer createConnector(String transport,
      Properties props)  throws ConnectorException {

    if (transport.equalsIgnoreCase("RMI")) {
      try {
        return (RemoteMBeanServer)Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(),
            new Class[] {  RemoteMBeanServer.class } ,
            new RMIInvocationHandler(props)
        );
      }
      catch (Exception e) {
        throw new ConnectorException(
            "Unable to create proxy.", e
        );
      }
    }

    throw new ConnectorException(
        "Unrecognized connector transport: " + transport
    );
  }
}

Next, you need to write the export manager implementation that takes care of binding the relevant connection properties for the MBeans.

Export Manager

The export manager takes care of binding an MBean handle to the naming service. As was shown in the previous section, the handle contains named properties that the connector factory uses on the client side to determine how to construct the connection.

What needs to be decided next is what properties the handle should contain. The JMX specification does not restrict the distributed services level to a specific RPC implementation, such as RMI, to connect to a remote JMX agent. Instead, the management client can use many different means to connect to the agent. Some possibilities include RMI, SOAP, JMS, and SNMP. So how does the export manager know what kind of properties are required to be exposed in the handle? Several different remote call mechanisms can be used to connect to an MBean server. Furthermore, new connectors can be added to and removed from the server dynamically at runtime. The answer is that the export manager doesn't know. However, because each JMX connector should have a connector server MBean registered to the agent, the manager can query the server for those MBeans for their export properties. Each connector server MBean can be expected to know what information is required to connect to it. For example, an RMI connector server knows the host name and port to which it is listening. Similarly, a SOAP connector server knows the HTTP address that it is using to receive the SOAP requests. A JMS connector server knows what queue or topic the client needs to connect to so it can receive the messages.

To build the handle, the export manager implementation used in this chapter queries each connector server for their individual export properties and adds them as part of the handle. The export manager implementation will assume that the connector servers are registered under the Connector:* object name domain. This restricts all the connector server implementations that want to export their properties as part of the handle to register themselves to this known management domain. As usual, you are free to change the implementation to support a more configurable naming scheme.

In addition, the export manager registers with the MBean server delegate to receive notifications on MBean registration and unregistration events. If new MBeans are added to the Connector domain, the export manager attempts to retrieve the value of the ExportProperties attribute. If the attribute exists, its content is added as part of the properties that are attached to the MBean handle. Similarly, when connector server MBeans are unregistered from the server, their export properties are removed from the export manager.

By registering to listen to the registration events of connector server MBeans, the export manager is able to adapt to the changes in connector configuration of an MBean server. Notice however, that the implementation of the export manager shown here does not keep track of or update existing handles that have already been exported to the name server. When new connector servers are added to a running system, you could rebind the existing handles to reflect the changes. However, the conditions of when and how to make the rebinding varies based on the application, so that part of the implementation is left as an exercise for the reader. Adding a policy descriptor for rebinding behavior might be one option for the implementation.

The implementation of the ExportManager is shown in Listing 10.4. The connectorMap field holds a map with MBean object names as keys and a list of properties as its value. The start() method creates the initial naming context reference and registers a listener to the MBean server delegate. It also queries the MBean server for already existing MBeans in the Connector domain and adds them to the map if necessary. The export() method can be used by the agent or an individual MBean to bind a logical name to the naming service. The MBeanHandleImpl object contains the properties that the connectors can use to create a connection to the agent where the MBean resides.

Example 10.4. ExportManager.java

package book.jmx.examples;

import java.util.*;
import javax.naming.*;
import javax.management.*;


public class ExportManager {

  final static String SERVER_DELEGATE =
      "JMImplementation:type=MBeanServerDelegate";

  // maps connector object name to export properties
  private HashMap connectorMap = new HashMap();
  // stores the naming context
  private Context ctx          = null;

  // reference to the mbean server
  private MBeanServer server   = null;


  // default constructor
  public ExportManager() {  }

  public void start(String agentID) throws NamingException {

    ctx = new InitialContext();
    ObjectName serverDelegate = null;

    try {
      server = (MBeanServer)MBeanServerFactory.
          findMBeanServer(agentID).get(0);

      // listen for MBean registration notifications
      serverDelegate = new ObjectName(SERVER_DELEGATE);

      server.addNotificationListener(
        serverDelegate,
        new RegistrationListener(),
        null,
        null
      );

      // query for existing connector servers
      ObjectName connectorQuery = new ObjectName("Connector:*");
      Set connectors = server.queryNames(connectorQuery, null);
      Iterator it = connectors.iterator();

      while (it.hasNext()) {
        ObjectName name = null;

        try {
          name = (ObjectName)it.next();

          // try to retrieve export properties
          connectorMap.put(name,
              server.getAttribute(name, "ExportProperties"));
        }
        catch (JMException e) {
          System.out.println(
              name + " does not have ExportProperties attribute."
          );
        }
      }
    }
    catch (JMException e) {
      e.printStackTrace();
    }
  }

  public void export(String exportName) throws NamingException {

    MBeanHandleImpl handle = new MBeanHandleImpl();
    Iterator it = connectorMap.values().iterator();

    // add the known export properties to the handle
    while (it.hasNext())
      handle.addProperties((Properties)it.next());

    // bind to naming service
    ctx.rebind(exportName, handle);
  }


  // This notification listener updates the export properties based
  // on connector server registration and unregistration events
  class RegistrationListener implements NotificationListener {

    public void handleNotification(Notification n, Object hb) {

      if (!(n instanceof MBeanServerNotification))
        return;

      MBeanServerNotification notif = (MBeanServerNotification)n;
      ObjectName name  = notif.getMBeanName();
      String notifType = notif.getType();
      String domain    = name.getDomain();

      if (!domain.equalsIgnoreCase("Connector"))
        return;
      if (notifType.equals(
          MBeanServerNotification.REGISTRATION_NOTIFICATION)) {

        try {
          connectorMap.put(name,
              server.getAttribute(name, "ExportProperties"));
        }
        catch (JMException e) {
          System.out.println(
              name + " does not have ExportProperties attribute."
          );
        }
      }

      else if (notifType.equals(
          MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {

        connectorMap.remove(name);
      }
    }
  }
}

Also, to be able to register the export manager as an MBean to the server, it needs a management interface. To use the XMBean Model MBean implementation, you will need the management interface declaration in XML. One such suggestion for the management interface is shown in Listing 10.5.

Example 10.5. ExportManager.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mbean SYSTEM "file:/C:/Examples/xmbean.dtd">

<mbean>
  <operation>
    <name>export</name>
    <parameter>
      <name>ExportName</name>
      <type>java.lang.String</type>
    </parameter>
    <impact>ACTION</impact>
  </operation>
  <operation>
    <name>start</name>
    <parameter>
      <name>AgentID</name>
      <type>java.lang.String</type>
    </parameter>
    <impact>ACTION</impact>
  </operation>
</mbean>

Notice the location of the DTD file. You may need to modify it to reflect the location of the xmbean.dtd on your system.

Next, you need to create a connector implementation that can work with the export manager MBean to relay its connection properties to the handle.

SOAP Connector

In Chapter 9, you built a generic connector framework that used the Java RMI as the remote call mechanism. It is quite easy to modify that basic approach to building a connector to include other means of transporting the method invocation to the remote JMX agent. In this chapter, you will see how to add a SOAP-based JMX connector to your collection of distributed services.

Recall from Chapter 9 that to build a new connector with different transport mechanism requires you to implement an invocation handler for the client-side proxy and a connector server MBean to the server side. You will next implement a SOAP invocation handler and a SOAP connector server MBean, as shown in Figure 10.3.

SOAP connector needs to replace the proxy implementation with a SOAPInvocationHandler on the client and add a SOAP connector server to the agent.

Figure 10.3. SOAP connector needs to replace the proxy implementation with a SOAPInvocationHandler on the client and add a SOAP connector server to the agent.

For the SOAP implementation, the GLUE framework is used, available from http://www.themindelectric.com (see Appendix A, “Environment Setup,” for installation instructions). The SOAPInvocationHandler class (see Listing 10.6) implements the InvocationHandler interface and sends the MethodInvocation instance through a SOAP envelope.

The SOAP connector also uses the MBeanServerInvoker interface as its remote interface to the connector server. The client-side implementation of the proxy is quite simple. In the SOAPInvocationHandler constructor, you retrieve the reference to the invoker, and you set the MethodInvocation as the payload of the SOAP message in the invoke() method. The GLUE library handles the details of sending the invocation to the receiving HTTP server. The relevant two lines of code are highlighted in Listing 10.6.

Example 10.6. SOAPInvocationHandler.java

package book.jmx.examples;

import java.util.*;
import java.net.*;
import java.lang.reflect.*;
import java.rmi.*;
import electric.registry.*;


public class SOAPInvocationHandler
    implements InvocationHandler, SOAPConnectorConstants {

  // the remote interface
  private MBeanServerInvoker invoker = null;

  public SOAPInvocationHandler(Properties props)
      throws RegistryException {

    System.setProperty("electric.xml.io.serialize", "yes");

    // retrieve the connection properties
    String host = props.getProperty(HOST, "localhost");
    String port = props.getProperty(PORT, "8085");
    String name = props.getProperty(NAME);
    String path = props.getProperty(PATH, "");

    // retrieve the stub
    String url = "http://" + host + ":" + port + "/" + path + "/" + name;
    invoker = (MBeanServerInvoker)Registry.bind(url, MBeanServerInvoker.class);
  }
  public Object invoke(Object proxy, Method method,
                       Object[] args) throws Throwable {

    String methodName = method.getName();

    MethodInvocation mi = new MethodInvocation(method);
    mi.setParams(args);

    return invoker.invoke(mi);
  }
}

The connector server is as easy to implement as the client proxy (see Listing 10.7). The start() method again makes use of the GLUE framework to start an HTTP server that receives the SOAP invocations. The invoke() method of the SOAPConnector is called whenever a SOAP request arrives to the HTTP server for the given path and WSDL (Web Service Definition Language) file. In addition, the SOAPConnector class implements a getExportProperties() method that the export manager can use to retrieve the connection properties. For the SOAP connector, you include the host name, port number, and the WSDL filename and path. The property names are declared in the SOAPConnectorConstants interface, as shown in Listing 10.8.

Example 10.7. SOAPConnector.java

package book.jmx.examples;

import java.rmi.RemoteException;
import java.util.*;
import java.io.*;
import java.net.*;
import javax.management.*;
import javax.management.modelmbean.*;

import electric.server.http.*;
import electric.registry.*;

public class SOAPConnector
    implements MBeanServerInvoker, SOAPConnectorConstants {

  private int port = 8085;
  private String path = "jmx";
  private String name = "urn:soapconnector";
  private MBeanServer server = null;

  public SOAPConnector() {
    System.setProperty("electric.xml.io.serialize", "yes");
  }

  public void start(String agentID)
      throws IOException, RegistryException {

    HTTP.startup("http://localhost:" + port + "/" + path);
    Registry.publish(name, this, MBeanServerInvoker.class);

    // find the mbean server reference
    server = (MBeanServer)MBeanServerFactory
        .findMBeanServer(agentID).get(0);
  }

  public int getPort() {
    return port;
  }

  public void setPort(int port) {
    this.port = port;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getPath() {
    return path;
  }

  public void setPath() {
    this.path = path;
  }

  public Object invoke(MethodInvocation mi) throws Exception {

      mi.setMBeanServer(server);
      mi.invoke();
      if (mi.getStatus() == MethodInvocation.ERROR) {
        Object val = mi.getReturnValue();

        if (val instanceof Exception)
          throw (Exception)val;
        else throw new RemoteException(
            "Error at the server.", (Throwable)val
        );
      }

      return mi.getReturnValue();
  }


  /* Export Manager */
  public Properties getExportProperties() throws UnknownHostException {
    Properties props = new Properties();
    props.put(HOST, InetAddress.getLocalHost().getHostAddress());
    props.put(PORT, "" + getPort());
    props.put(PATH, path);
    props.put(NAME, getName() + ".wsdl");
    return props;
  }
}

Example 10.8. SOAPConnectorConstants.java

package book.jmx.examples;

public interface SOAPConnectorConstants {

    final static String NAME = "urn.name";
    final static String PATH = "http.path";
    final static String HOST = "http.host";
    final static String PORT = "soap.port";

}

To make the new connector accessible to the clients, you need to also add a new proxy instance creation to the ConnectorFactory class, as shown in Listing 10.9.

Example 10.9. ConnectorFactory.java (SOAP Connector)

// Modify the existing ConnectorFactory.java with the
// the highlighted changes below...


public static RemoteMBeanServer createConnector(String transport,
    Properties props)  throws ConnectorException {

  if (transport.equalsIgnoreCase("RMI")) {
    try {
      return (RemoteMBeanServer)Proxy.newProxyInstance(
          Thread.currentThread().getContextClassLoader(),
          new Class[] {  RemoteMBeanServer.class } ,
          new RMIInvocationHandler(props)
      );
    }
    catch (Exception e) {
      throw new ConnectorException(
          "Unable to create proxy.", e
      );
    }
  }

  else if (transport.equalsIgnoreCase("SOAP")) {
    try {
      return (RemoteMBeanServer)Proxy.newProxyInstance(
          Thread.currentThread().getContextClassLoader(),
          new Class[] {  RemoteMBeanServer.class } ,
          new SOAPInvocationHandler(props)
      );
    }
    catch (Exception e) {
      throw new ConnectorException(
          "Unable to create proxy.", e
      );
    }
  }

  throw new ConnectorException(
      "Unrecognized connector transport: " + transport
  );
}

Also, to register the SOAP connector as an MBean, you will need to declare a management interface for it. An XML declaration for the XMBean is shown in the SOAPConnector.xml file in Listing 10.10. Again, notice that the DTD is pointing to a file in the local file system. You may need to modify the second line to point to where you have placed your DTD.

Example 10.10. SOAPConnector.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mbean SYSTEM "file:/C:/Examples/xmbean.dtd">

<mbean>

  <attribute getMethod="getExportProperties"
             currencyTimeLimit="-1">
    <name>ExportProperties</name>
    <type>java.util.Properties</type>
    <access>read-only</access>
  </attribute>

  <operation>
    <name>start</name>
    <parameter>
      <name>AgentID</name>
      <type>java.lang.String</type>
    </parameter>
  </operation>

  <operation>
    <name>getExportProperties</name>
    <return-type>java.util.Properties</return-type>
  </operation>

</mbean>

To compile the SOAP connector classes, execute the following command:

C: Examples> javac -d . -classpath .;electric lib GLUE-STD.jar; jmx-1_0_1-ri_bin jmx
SOAPConnector.xml lib jmxri.jaru-4064SOAPInvocationHandler.java SOAPConnector.java SOAPConnectorConstants
SOAPConnector.xml.javaMethodInvocation.java MBeanServerInvoker.java ConnectorFactory.java ConnectorException
SOAPConnector.xml.java

Next, you will see how to use the SOAP connector with the export manager to look up and invoke a remote JMX agent.

SOAP Example

The following example will show you how to use the SOAP connector from an external JVM or from a remote host to find the location of an agent and invoke the methods of the MBean server.

The example consists of three parts:

  • An agent implementation

  • A client implementation

  • A name server.

The agent implementation will contain two MBeans—one for the export manager implementation and another for the SOAP connector server. The client will look up the agent from the name server by using a logical name with the help of the modified ConnectorFactory you saw in Listing 10.9. The name server implementation will be provided by the JBoss server. The components of the example are shown in Figure 10.4.

Components of the SOAP example.

Figure 10.4. Components of the SOAP example.

The agent registers two Model MBeans—one for the SOAP connector and another for the export manager. The management interfaces for each are described in Listings 10.5 and 10.10. When the start() operation is invoked on the SOAP connector, it will start the HTTP server and register a WSDL description of the MBeanServerInvoker interface to the Web server. After that happens, the start() operation on the export manager will discover the SOAP connector registered at the Connector domain and query it for the ExportProperties attribute. With the export() operation, the agent binds a handle under name MyAgent to the naming service that contains the required connection properties for the client-side connection factory to build a SOAP connection to the server.

The source for the ExportAgent class is shown in Listing 10.11.

Example 10.11. ExportAgent.java

package book.jmx.examples;

import javax.management.*;
import javax.management.modelmbean.*;
import java.rmi.*;
import java.rmi.registry.*;

public class ExportAgent {

  final static String SERVER_DELEGATE =
      "JMImplementation:type=MBeanServerDelegate";
  final static String AGENT_ID =
      "MBeanServerId";
  final static String XMBEAN =
      "book.jmx.examples.XMBean";
  final static String[] XMBEAN_CONSTRUCTOR_SIGNATURE =
      {  String.class.getName(), String.class.getName() } ;

  public static void main(String[] args) {

    try {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      String agentID = (String)server.getAttribute(
          new ObjectName(SERVER_DELEGATE), AGENT_ID
      );

      ObjectName exportManager =
          new ObjectName("Service:name=ExportManager");
      ObjectName soapConnector =
          new ObjectName("Connector:transport=SOAP");

      server.createMBean(XMBEAN, soapConnector,
          new Object[] {
              "file:/C:/Examples/SOAPConnector.xml",
              "book.jmx.examples.SOAPConnector" } ,
          XMBEAN_CONSTRUCTOR_SIGNATURE
      );

      server.createMBean(XMBEAN, exportManager,
          new Object[] {
              "file:/C:/Examples/ExportManager.xml",
              "book.jmx.examples.ExportManager" } ,
          XMBEAN_CONSTRUCTOR_SIGNATURE
      );
      server.invoke(soapConnector, "start",
          new Object[] {  agentID } ,
          new String[] {  String.class.getName() }
      );

      server.invoke(exportManager, "start",
          new Object[] {  agentID } ,
          new String[] {  String.class.getName() }
      );

      server.invoke(exportManager, "export",
          new Object[] {  "MyAgent" } ,
          new String[] {  String.class.getName() }
      );
    }
    catch (RuntimeMBeanException e) {
      e.getTargetException().printStackTrace();
    }
    catch (MBeanRegistrationException e) {
      e.getTargetException().printStackTrace();
    }
    catch (RuntimeErrorException e) {
      e.getTargetError().printStackTrace();
    }
    catch (ReflectionException e) {
      e.getTargetException().printStackTrace();
    }
    catch (MBeanException e) {
      e.getTargetException().printStackTrace();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

The client code is shown in Listing 10.12. It requests the connection factory for a connector implementation with the "SOAP" identifier and requests a handle bound to the "MyAgent" name. It then executes a simple query for all MBean names in the agent.

Example 10.12. SOAPClient.java

package book.jmx.examples;

import java.util.*;

public class SOAPClient {

  public static void main(String[] args) {

    RemoteMBeanServer server = null;

    try {
      // Lookup and connect to "MyAgent"
      server = ConnectorFactory.createConnector("SOAP", "MyAgent");

      // invoke queryNames on the remote mbean server
      Iterator it = server.queryNames(null, null).iterator();

      while(it.hasNext())
        System.out.println(it.next());
    }
    catch (ConnectorException e) {
      e.printStackTrace();
    }
  }
}

To compile and run the example, first start the JBoss server. This is required for the naming service. The JBoss server can be started with the run.bat or run.sh scripts in its bin directory (JBoss-2.4.1 bin). See Appendix A for more detailed instructions for setup.

C: Examples> cd JBoss-2.4.1 bin

C: Examples JBoss-2.4.1 bin> run

When the server starts, you will see a long list of services being started. You will later make use of some of them, mostly the Java Message Service, but for now, all you need is the naming service.

When the JBoss server has started, you will see a line similar to the following printed on the console.

[Default] JBoss 2.4.1 Started in 0m:33s

Next, in a different command shell, compile and start the ExportAgent application. It will start the GLUE HTTP server and bind a "MyAgent" reference to the name server. For the initial naming context lookup to work, a file called jndi.properties must be located in the classpath (shown in Listing 10.13)

Example 10.13. jndi.properties

java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.provider.url=localhost:1099

Save the jndi.properties to your working directory (C: Examples in the context of this example) and execute the following two commands:

C: Examples> javac -d . -classpath jmx-1_0_1-ri_bin jmx lib jmxri.jar ExportAgent
jndi.properties.javaExportManager.java MBeanHandleImpl.java MBeanHandle.java

C: Examples> java -classpath .;jmx-1_0_1-ri_bin jmx lib jmxri.jar; electric lib
jndi.properties GLUE-STD.jar;electric lib servlet.jar; electric lib jnet.jar;xmbean.jar; jdom-b7 lib
jndi.properties xerces.jar; jdom-b7 build jdom.jar;JBoss-2.4.1 client jnp-client.jar book.jmx.examples
jndi.properties.ExportAgent

The libraries GLUE-STD.jar, servlet.jar, and jnet.jar under the electric directory are required by the GLUE SOAP implementation. The xmbean.jar is the packaged XMBean implementation from Chapter 8, “XMBean: Model MBean Implementation,” and requires the jdom.jar (JDOM API) and the xerces.jar for XML parsing. Finally, the jnp-client.jar is required from the JBoss client directory to interact with the JBoss naming service.

After you add all of the previously mentioned libraries successfully and get your export agent application started, you should see the following line printed on your console window:

GLUE 1.2 (c) 2001 The Mind Electric
startup server on http://192.168.0.3:8085/jmx

You can also confirm that the handle can be found from the JBoss naming service. Point your browser to http://localhost:8082 and you will see the HTTP adaptor view of the JBoss server. Find a JNDIView MBean (see Figure 10.5) and execute its list() operation by clicking the corresponding button. You should then be able to find the "MyAgent" reference bound to the global naming space (see Figure 10.6).

The JNDIView MBean displays the contents of the JBoss Name Service.

Figure 10.5. The JNDIView MBean displays the contents of the JBoss Name Service.

The global namespace should list the MBean handle for “MyAgent”.

Figure 10.6. The global namespace should list the MBean handle for “MyAgent”.

The last step is to compile and run the client code that executes the query to the MBean server. Again, to run the client the GLUE libraries are required (GLUE-STD.jar, servlet.jar, and jnet.jar) and the JBoss naming client library (jnp-client.jar and the jndi.properties file).

C: Examples> javac -d . -classpath .;jmx-1_0_1-ri_bin jmx lib jmxri.jar; electric lib
The global namespace should list the MBean handle for “MyAgent”. GLUE-STD.jar SOAPClient.javaC: Examples> java -classpath .; jmx-1_0_1-ri_bin jmx lib
The global namespace should list the MBean handle for “MyAgent”. jmxri.jar;electric lib GLUESTD.jar; electric lib servlet.jar;electric lib jnet
The global namespace should list the MBean handle for “MyAgent”..jar;JBoss-2.4.1 client jnp-client.jar book.jmx.examples.SOAPClient

When you successfully run the client, it should print out the object names of the agent to which it connected:

Connector:transport=SOAP
JMImplementation:type=MBeanServerDelegate
Service:name=ExportManager

This output means you have successfully executed a method on the MBean server using the SOAP connector.

Next, you will look at a slightly different kind of connector that sends its MBean invocations asynchronously.

Asynchronous JMS Connector

One of the common features with both the connectors you have seen so far has been the fact that the invocation semantics have been synchronous. This means that when a method is being invoked from the client side to the MBean server, the client-side thread initiating the call sequence will block until the response from the MBean is returned by the MBean server back to the client.

Next you will see an implementation of an asynchronous connector. An asynchronous connector allows the client thread initiating the call sequence to continue to work, regardless of the network latency and execution time between the client and the MBean server. The synchronous invocation can be slowed down by the network traffic or, in the worst case, its inability to reach the remote host. The client-side thread initiating the invocation is blocked until the invocation is returned, or the invocation times out because either the host was unreachable, or the return value did not arrive in time. It may also be the case that the invocation starts a resource-intensive task on the server side and can take a long time to finish before any return value can be delivered back to the client.

The asynchronous connector resolves the aforementioned issues by not requiring the client to block on the method invocation. The timeout threshold for an asynchronous invocation can be set higher because the client is free to continue to work and there is no resource waste on blocked threads. It may not be relevant for the management application to know when the invocation has been received by the remote host, so there is increased tolerance for system downtime and network problems.

Messaging services are often used to implement asynchronous invocation semantics. The invocation is abstracted as a message and left to be delivered by a message service that implements asynchronous delivery. The receiver of the message can optionally send another message back to the caller to indicate it has received the message or to deliver possible return values. If the client side implements a callback mechanism, the message service can deliver the receipt or a return value when it is available to the caller.

The J2EE platform provides a Java Message Service (JMS) that you can use for asynchronous messaging. The next connector implementation, to implement an asynchronous connector, is based on the JMS.

Design of the JMS Connector

The asynchronous JMS connector consists of the following classes and interfaces:

  • JMSInvocationHandler class

  • JMSConnector class

  • AsyncMBeanServer interface

  • Callback interface

The JMSConnector and JMSInvocationHandler classes are again the corresponding connector server and client proxy implementation classes shown in Figure 10.7. The AsyncMBeanServer interface is a replacement for the RemoteMBeanServer interface used as a façade for the proxies. The asynchronous call semantics require a slightly modified remote MBean server interface. The return values are now replaced with Callback instances instead of the actual return types. Changing the interface exposed to the client is quite easy because all you need to do is to provide a different class instance to the java.reflect.Proxy instance. Other than the changed return values, the AsyncMBeanServer is an exact copy of the RemoteMBeanServer interface.

The JMS connector requires a JMSInvocationHandler proxy implementation, JMS connector server, and a modified interface façade for the client.

Figure 10.7. The JMS connector requires a JMSInvocationHandler proxy implementation, JMS connector server, and a modified interface façade for the client.

You will also modify the ConnectorFactory class to add factory methods for a JMS connector. Additionally, you will define the management interface of the JMSConnector in an XML file conforming to the XMBean DTD.

Asynchronous MBean Server Interface

The asynchronous MBean server interface shown in Listing 10.13 replaces all the method return types with a Callback interface. The returned Callback instances from all the methods can be queried for the return values at any time. Consequently, the client thread is not required to block on the MBean server invocation to receive the return value.

Example 10.13a. AsyncMBeanServer.java

package book.jmx.examples;

import javax.management.*;

public interface AsyncMBeanServer {

  /* returns ObjectInstance */
  public Callback createMBean(String className,
                              ObjectName name);

  /* returns ObjectInstance */
  public Callback createMBean(String className,
                              ObjectName name,
                              ObjectName loaderName);

  /* return ObjectInstance */
  public Callback createMBean(String className,
                              ObjectName name,
                              Object[] params,
                              String[] signature);

  /* returns ObjectInstance */
  public Callback createMBean(String className,
                              ObjectName name,
                              ObjectName loaderName,
                              Object[] params,
                              String[] signature);

  public void unregisterMBean(ObjectName name);

  /* returns ObjectInstance */
  public Callback getObjectInstance(ObjectName name);
  /* returns boolean */
  public Callback isRegistered(ObjectName name);

  /* returns Integer */
  public Callback getMBeanCount();

  /* returns Object */
  public Callback getAttribute(ObjectName name,
                               String attribute);

  /* returns AttributeList */
  public Callback getAttributes(ObjectName name,
                                String[] attributes);

  public void setAttribute(ObjectName name,
                           Attribute attribute);

  /* returns AttributeList */
  public Callback setAttributes(ObjectName name,
                                AttributeList attributes);

  /* returns Object */
  public Callback invoke(ObjectName name,
                         String operationName,
                         Object[] params,
                         String[] signature);

  /* returns String */
  public Callback getDefaultDomain();

  public void addNotificationListener(
                         ObjectName name,
                         NotificationListener listener,
                         NotificationFilter filter,
                         Object handback);

  public void addNotificationListener(
                         ObjectName name,
                         ObjectName listener,
                         NotificationFilter filter,
                         Object handback);

  public void removeNotificationListener(
                         ObjectName name,
                         NotificationListener listener);
  public void removeNotificationListener(
                         ObjectName name,
                         ObjectName listener);

  /* returns MBeanInfo */
  public Callback getMBeanInfo(ObjectName name);

  /* returns boolean */
  public Callback isInstanceOf(ObjectName name,
                               String className);

  /* returns Set */
  public Callback queryMBeans(ObjectName name, QueryExp query);

  /* returns Set */
  public Callback queryNames(ObjectName name, QueryExp query);

  public void close();

}

Callback Interface

The Callback implementation is created on the client-side proxy of the connector when the message that contains the invocation has been left for the JMS to deliver. The Callback is returned to the client immediately and the client is free to handle the Callback instances as it sees fit. The client may not use them at all or it can store them for later use.

Because you cannot determine beforehand when the return value from the asynchronous invocation is received, the Callback interface declares two methods that allows you to investigate the state of the invocation—peek() and get().

The peek() method allows you to check the Callback object to see whether the return value has been received. The important thing to notice with the peek() method implementation is that it returns the state of the invocation immediately and does not block to wait for a return value in case one has not arrived yet. Therefore, the peek() method should be used when the client wants to store the Callback instance, and periodically check whether or not the JMX invocation has been executed and if the return value has been received.

The get() method allows you to retrieve the return value when it has arrived. The get() method does block if the return value has not been received yet. This behavior has two implications to the client using the connector. First, to continue the execution of the thread without blocking, the client should always check the Callback with peek() before invoking get() to retrieve the value. Second, if the client thread needs to retrieve the value of the invocation before it can continue its work, it may safely block on the get() method to guarantee the thread execution will not proceed until the remote invocation has returned. In essence, calling the get() method immediately on the Callback object after each invocation will give you synchronous invocation semantics at the client.

The Callback interface declaration is shown in Listing 10.14.

Example 10.14. Callback.java

package book.jmx.examples;

import java.rmi.*;
import java.io.*;

public interface Callback extends Serializable {

  int peek();

  Object get() throws RemoteException;

}

The concrete implementations of the Callback interface are returned by the JMS connector proxy.

ConnectorFactory

You will next modify the existing ConnectorFactory to support the new connector type. Add the factory methods to create the JMS connector proxies based on an explicitly passed properties and a factory method that accepts the JNDI reference to an MBeanHandle object.

The modified implementation of the ConnectorFactory class is shown in Listing 10.15.

Example 10.15. ConnectorFactory.java

package book.jmx.examples;

import java.util.*;
import java.lang.reflect.*;
import java.rmi.*;

import javax.naming.*;
import javax.jms.*;
public class ConnectorFactory  {

  public static RemoteMBeanServer createConnector(String transport,
      String jndiName) throws ConnectorException {

    Context ctx = null;
    MBeanHandle handle = null;

    try {
      ctx = new InitialContext();

      handle = (MBeanHandle)ctx.lookup(jndiName);
    }
    catch (NamingException e) {
      throw new ConnectorException(
        "Unable to find the handle", e
      );
    }

    return createConnector(transport, handle.getProperties());
  }

  public static RemoteMBeanServer createConnector(String transport,
      Properties props)  throws ConnectorException {

    if (transport.equalsIgnoreCase("RMI")) {
      try {
        return (RemoteMBeanServer)Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(),
            new Class[] {  RemoteMBeanServer.class } ,
            new RMIInvocationHandler(props)
        );
      }
      catch (Exception e) {
        throw new ConnectorException(
            "Unable to create proxy.", e
        );
      }
    }

    else if (transport.equalsIgnoreCase("SOAP")) {
      try {
        return (RemoteMBeanServer)Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(),
            new Class[] {  RemoteMBeanServer.class } ,
            new SOAPInvocationHandler(props)
        );
      }
      catch (Exception e) {
        throw new ConnectorException(
            "Unable to create proxy.", e
        );
      }
    }

    throw new ConnectorException(
        "Unrecognized connector transport: " + transport
    );
  }

  public static AsyncMBeanServer createAsyncConnector(
      String transport, String jndiName) throws ConnectorException {

    try {
      Context ctx = new InitialContext();

      MBeanHandle handle = (MBeanHandle)ctx.lookup(jndiName);

      return createAsyncConnector(transport, handle.getProperties());
    }
    catch (NamingException e) {
      throw new ConnectorException(
          "Error connecting to Naming Service.", e
      );
    }

  }

  public static AsyncMBeanServer createAsyncConnector(
      String transport, Properties props) throws ConnectorException {

    if (transport.equalsIgnoreCase("jms")) {
      try {
        return (AsyncMBeanServer)Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(),
            new Class[] {  AsyncMBeanServer.class } ,
            new JMSInvocationHandler(props)
        );
      }
      catch (JMSException e) {
        throw new ConnectorException(
            "Error connecting to Java Message Service", e
        );
      }
      catch (NamingException e) {
        throw new ConnectorException(
            "Error connecting to Naming Service", e
        );
      }
    }

    throw new ConnectorException("Unknown transport " +
        transport);

  }
}

The JMS connector proxy requires its own InvocationHandler implementation. The JMSInvocationHandler class will transform all invocations to JMS messages and then publish them to the given message topic. It will also subscribe to the topic to receive the messages containing the return values that are sent back from the remote agent. The JMSInvocationHandler implementation is shown in Listing 10.16.

In the JMSInvocationHandler constructor, the connection to a JMS topic is created using the connection properties from the MBean handle. The JMSInvocationHandler will act as a publisher of messages that contain the invocation and as a subscriber to messages that contain the return values of the invocations. The invocation messages are matched to the return values with a messageID counter.

Just as with the RemoteMBeanServer interface, all the invocations that are made to the AsyncMBeanServer interface are delegated to the invoke() method of the JMSInvocationHandler instance. The change of the interface does not require any other changes to the way you implement the connector. Just remember to return the correct runtime types from the invoke() operation. In the invoke() implementation, the MethodInvocation is attached as the payload of the message that is published to the topic. The MethodInvocation object contains all the required information for the receiving connector MBean on the remote agent to invoke the corresponding management operation on the target MBean server.

With each published invocation a JMSCallback instance is created. This Callback object is matched with the messageID of the published message. When the onMessage() method of the JMSInvocationHandler receives a reply from the remote agent, it looks up the corresponding Callback object from callbackBuffer and sets its return value. At the same time, the Callback object notifies all threads that may be waiting on its get() method.

Example 10.16. JMSInvocationHandler.java

package book.jmx.examples;

import java.util.*;
import java.lang.reflect.*;
import java.rmi.*;
import javax.jms.*;
import javax.naming.*;
import javax.management.*;

public class JMSInvocationHandler
    implements MessageListener, InvocationHandler, JMSConnectorConstants {

  // JMS Topic
  private Topic topic                = null;
  private TopicConnection con        = null;
  private TopicSession session       = null;
  private TopicPublisher publisher   = null;
  private TopicSubscriber subscriber = null;
  private Context ctx                = null;

  // keeps track of the message IDs
  static long messageID = 1;

  // Stores the references to the callback objects. When a reply
  // matching to the ID is received the return value is inserted
  // to the callback instance and any threads waiting on it will
  // be notified.
  private Map callbackBuffer =
      Collections.synchronizedMap(new HashMap());


  // constructor
  public JMSInvocationHandler(Properties props)
      throws NamingException, JMSException {

    // retrieve connection properties
    String conFactory =
        props.getProperty(JMS_TOPIC_CONNECTION_FACTORY);
    String topicName  =
        props.getProperty(JMS_TOPIC);

    // lookup connection factory and create topic connection
    // and topic session
    ctx = new InitialContext();
    TopicConnectionFactory factory =
        (TopicConnectionFactory)ctx.lookup(conFactory);

    con = factory.createTopicConnection();

    session = con.createTopicSession(
        false,      /* not a transacted session */
        Session.AUTO_ACKNOWLEDGE
    );

    // Proxy acts as a publisher of invocations and subscribes
    // for reply messages
    topic = (Topic)ctx.lookup(topicName);
    publisher   = session.createPublisher(topic);
    subscriber  = session.createSubscriber(topic, "JMSType='REPLY'", true);

    // topic is non persistent.
    publisher.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
    publisher.setDisableMessageTimestamp(true);
    subscriber.setMessageListener(this);

    con.start();
  }


  public Object invoke(Object proxy, Method method,
                       Object[] args) throws Throwable {

    // initialize MethodInvocation
    String methodName   = method.getName();
    MethodInvocation mi = new MethodInvocation(method);
    mi.setParams(args);

    // create message ID and callback object.
    // Map ID to callback in callbackBuffer.
    String ID           = "" + messageID++;
    Callback cb         = new JMSCallback(ID, mi);
    callbackBuffer.put(ID, cb);
    // wrap MethodInvocation in JMS Object message
    ObjectMessage msg   = session.createObjectMessage(mi);
    msg.setJMSType("SEND");
    msg.setStringProperty("ID", ID);

    if (methodName.equals("close")) {
      close();
      return null;
    }

    // send
    publisher.publish(msg);
    return cb;
  }

  private void close() {
    try {
      con.close();
    }
    catch (JMSException e) {
      e.printStackTrace();
    }
  }


  // subscriber for reply messages

  public void onMessage(Message message) {

    try {
      // extract the MethodInvocation and message ID
      ObjectMessage msg   = (ObjectMessage)message;
      MethodInvocation mi = (MethodInvocation)msg.getObject();
      String ID           = msg.getStringProperty("ID");

      // find the corresponding callback object (ID is the key)
      JMSCallback cb      = (JMSCallback)callbackBuffer.get(ID);
// JPL: REMOVE FROM QUEUE!

      // setMI() implementation in JMS Callback will set the
      // return value and notify all waiting threads
      cb.setMethodInvocation(mi);
    }
    catch (JMSException e) {
      e.printStackTrace();
    }
  }

  // JMS callback implementation

  class JMSCallback implements Callback {

    private int status          = UNKNOWN;
    private String ID           = null;
    private MethodInvocation mi = null;

    JMSCallback(String ID, MethodInvocation mi) {

      status = SENDING;

      this.ID = ID;
      this.mi = mi;
    }

    // notifies all threads that the answer has arrived
    protected void setMethodInvocation(MethodInvocation mi) {
      synchronized (this) {
        this.mi = mi;
        status = FINISHED;

        notifyAll();
      }
    }

    // returns status, won't block
    public int peek() {
      return status;
    }

    // blocks on status -- status set to FINISHED after
    // return value has been set and before threads are
    // notified.
    public Object get() throws RemoteException {
      synchronized (this) {
        while (status != FINISHED) {
          try {
              wait();
          }
          catch (InterruptedException e) { }
        }
      }

      // if an exception was thrown, wrap it in RemoteExc.
      // and throw to the client
      if (mi.getStatus() == MethodInvocation.ERROR) {
        Throwable t = (Throwable)mi.getReturnValue();

        if (t instanceof RuntimeMBeanException) {
          RuntimeMBeanException e = (RuntimeMBeanException)t;
          throw new RemoteException(e.getMessage(), e.getTargetException());
        }

        throw new RemoteException("", (Throwable)mi.getReturnValue());
      }

      // return value
      return mi.getReturnValue();
    }
  }

}

Example 10.17. JMSConnectorConstants.java

package book.jmx.examples;

public interface JMSConnectorConstants {

  final static String JMS_TOPIC  =
      "jms.topic";
  final static String JMS_TOPIC_CONNECTION_FACTORY =
      "jms.topic.connection.factory";


  final static int UNKNOWN   = -999;
  final static int SENDING   = 1000;
  final static int FINISHED  = 1;
}

JMSConnector

The JMSConnector is both a subscriber and publisher to the JMS topic, just like the JMSInvocationHandler is on the client side. The onMessage() method receives the messages from the JMS topic and extracts the MethodInvocation object. The JMSConnector then sets the agent reference to the MethodInvocation object before calling its invoke() method. The MethodInvocation handles the invocation and stores the return value or a possible exception.

The source for the JMSConnector is shown in Listing 10.18. The start() method looks up the JMS connection factory and topic and creates the subscriber listener before starting the connection. The getExportProperties() method returns the connection factory and topic JNDI names to the export manager when it queries for the connection properties.

The ConnectorListener inner class creates a publisher for the return messages for JMS invocations. The return messages have a matching ID value that the JMSInvocationHandler on the client side has set and uses to find the corresponding Callback object. Also, the returned message contains the complete MethodInvocation instance this time. Although this adds to the message size somewhat, it can be a useful mechanism for sending additional information about the invocation back to the client. If you recall from Chapter 9, the MethodInvocation object can carry any context information with it. Some of the context information, such as measurement data or an invocation trace, can be useful to be sent back to the client. This can be easily achieved by returning the MethodInvocation object itself. Asynchronous calls are convenient for gathering such data and the additional overhead in message size is less of a factor compared to synchronous calls.

Example 10.18. JMSConnector.java

package book.jmx.examples;

import java.io.*;
import java.util.*;
import javax.management.*;
import javax.jms.*;
import javax.naming.*;


public class JMSConnector implements JMSConnectorConstants {

  // JMS topic
  private TopicConnection connection = null;
  private TopicSubscriber subscriber = null;
  private Topic topic                = null;
  // JNDI names for connection factory and topic
  private String connFactory = "TopicConnectionFactory";
  private String topicName   = "topic/JMSConnector";

  // reference to the target MBean server
  private MBeanServer server = null;


  // constructor
  public JMSConnector() {  }


  public void start(String agentID) throws NamingException, JMSException {

    server = (MBeanServer)MBeanServerFactory
        .findMBeanServer(agentID).get(0);

    // lookup topic and create topic connection
    InitialContext ctx = new InitialContext();
    TopicConnectionFactory fact =
        (TopicConnectionFactory)ctx.lookup(connFactory);
    topic = (Topic)ctx.lookup(topicName);
    connection = fact.createTopicConnection();

    // subscribe to invocation messages
    TopicSession session = connection.createTopicSession(
        false, Session.AUTO_ACKNOWLEDGE);
    subscriber  = session.createSubscriber(topic, "JMSType='SEND'", true);
    subscriber.setMessageListener(new ConnectorListener());

    connection.start();
  }

  /* Export Manager */
  public Properties getExportProperties() {
    Properties props = new Properties();
    props.put(JMS_TOPIC_CONNECTION_FACTORY, connFactory);
    props.put(JMS_TOPIC, topicName);

    return props;
  }


  // message listener for invocations
  class ConnectorListener implements MessageListener {

    TopicSession session     = null;
    TopicPublisher publisher = null;


    // constructor

    ConnectorListener() throws JMSException {
      // create a publisher session for return values
      session   = connection.createTopicSession(false,
Session.AUTO_ACKNOWLEDGE);
      publisher = session.createPublisher(topic);

      publisher.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
      publisher.setDisableMessageTimestamp(true);
    }

    // onMessage invoke when a message arrives
    public void onMessage(Message message) {

      try {

        // extract MethodInvocation and message ID
        ObjectMessage msg   = (ObjectMessage)message;
        MethodInvocation mi = (MethodInvocation)msg.getObject();
        String ID           = msg.getStringProperty("ID");

        // set the target mbean server reference and invoke the method
        mi.setMBeanServer(server);
        mi.invoke();

        // Wrap the invoked MI as JMS object message and send it back.
        // The MI will contain the return value. Attach the same ID
        // to the return message.
        ObjectMessage reply = session.createObjectMessage(mi);
        reply.setJMSType("REPLY");
        reply.setStringProperty("ID", ID);

        // send message
        publisher.publish(reply);
      }  catch (JMSException e) {
          e.printStackTrace();
      }
    }
  }
}

The JMSConnector requires a management interface as well so it can be registered to the MBean server. Listing 10.19 shows a declaration of a management interface using the XMBean DTD.

Example 10.19. JMSConnector.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mbean SYSTEM "file:/C:/Examples/xmbean.dtd">

<mbean>

  <attribute getMethod="getExportProperties"
             currencyTimeLimit="-1">
    <name>ExportProperties</name>
    <type>java.util.Properties</type>
    <access>read-only</access>
  </attribute>

  <operation>
    <name>start</name>
    <parameter>
      <name>AgentID</name>
      <type>java.lang.String</type>
    </parameter>
  </operation>

  <operation>
    <name>getExportProperties</name>
    <return-type>java.util.Properties</return-type>
  </operation>

</mbean>

JMS Connector Example

To build a test application for the JMS connector, you can reuse the ExportAgent class you wrote for the SOAP connector earlier in this chapter. Register an additional connector server MBean, JMSConnector, to the agent and start the connector server. The modified application code is shown in Listing 10.20.

Example 10.20. ExportAgent.java

package book.jmx.examples;

import javax.management.*;
import javax.management.modelmbean.*;
import java.rmi.*;
import java.rmi.registry.*;

public class ExportAgent {

  final static String SERVER_DELEGATE =
      "JMImplementation:type=MBeanServerDelegate";
  final static String AGENT_ID =
      "MBeanServerId";
  final static String XMBEAN =
      "book.jmx.examples.XMBean";
  final static String[] XMBEAN_CONSTRUCTOR_SIGNATURE =
      {  String.class.getName(), String.class.getName() } ;

  public static void main(String[] args) {

    try {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      String agentID = (String)server.getAttribute(
          new ObjectName(SERVER_DELEGATE), AGENT_ID
      );

      ObjectName exportManager =
          new ObjectName("Service:name=ExportManager");
      ObjectName soapConnector =
          new ObjectName("Connector:transport=SOAP");
      ObjectName jmsConnector =
          new ObjectName("Connector:transport=JMS");

      server.createMBean(XMBEAN, soapConnector,
          new Object[] {
              "file:/C:/Examples/SOAPConnector.xml",
              "book.jmx.examples.SOAPConnector" } ,
          XMBEAN_CONSTRUCTOR_SIGNATURE
      );

      server.createMBean(XMBEAN, jmsConnector,
          new Object[] {
              "file:/C:/Examples/JMSConnector.xml",
               "book.jmx.examples.JMSConnector" } ,
          XMBEAN_CONSTRUCTOR_SIGNATURE
      );

      server.createMBean(XMBEAN, exportManager,
          new Object[] {
              "file:/C:/Examples/ExportManager.xml",
              "book.jmx.examples.ExportManager" } ,
          XMBEAN_CONSTRUCTOR_SIGNATURE
      );

      server.invoke(soapConnector, "start",
          new Object[] {  agentID } ,
          new String[] {  String.class.getName() }
      );

      server.invoke(jmsConnector, "start",
          new Object[] {  agentID } ,
          new String[] {  String.class.getName() }
      );

      server.invoke(exportManager, "start",
          new Object[] {  agentID } ,
          new String[] {  String.class.getName() }
      );

      server.invoke(exportManager, "export",
          new Object[] {  "MyAgent" } ,
          new String[] {  String.class.getName() }
      );
    }
    catch (RuntimeMBeanException e) {
      e.getTargetException().printStackTrace();
    }
    catch (MBeanRegistrationException e) {
      e.getTargetException().printStackTrace();
    }
    catch (RuntimeErrorException e) {
      e.getTargetError().printStackTrace();
    }
    catch (ReflectionException e) {
      e.getTargetException().printStackTrace();
    }
    catch (MBeanException e) {
      e.getTargetException().printStackTrace();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

You can then run a little example to see if the JMS connector works. First, make sure that the JBoss server has a topic JMSConnector configured in the JBoss-2.4.1/conf/default/ jboss.jcml configuration file. For details where to find this entry and how to set up the JMS topic, see Appendix A.

Compile the JMS connector classes:

C: Examples> javac -d . -classpath .;jmx-1_0_1-ri_bin jmx lib jmxri.jar; JBoss-2.4.1
ExportAgent.java client jboss-j2ee.jar;electric lib GLUE-STD.jar JMSConnector.java JMSConnectorConstants
ExportAgent.java.java JMSInvocationHandler.java ConnectorFactory.java ConnectorException.java
ExportAgent.java AsyncMBeanServer.java Callback.java ExportAgent.java

Notice that to run the ExportAgent application that uses JBossMQ (JBoss JMS implementation), you will need to add the classes from the JBoss-2.4.1 client directory to your classpath. Libraries jboss-j2ee.jar, jbossmq-client.jar, log4j.jar, and jta-spec1_0_1.jar are required in addition to the classes shown in the SOAP example.

Listing 10.21 shows how to create a client with both a synchronous SOAP connector and an asynchronous JMS connector. It creates a User MBean (from Chapter 3, “Standard MBeans”) and invokes a setAttribute() method in a loop of 5,000 times for each connector. The asynchronous JMS connector thread should finish quite a bit before the synchronous SOAP connector thread. The JMS connector is finished as soon as it has published the messages to the JMS topic. After that, the workload is moved to the JMS implementation to handle the delivery of the messages.

Example 10.21. ConnectorClient.java

package book.jmx.examples;

import java.util.*;
import javax.management.*;

public class ConnectorClient {

  public static void main(String[] args) {


    try {
      final ObjectName user = new ObjectName("test:name=user");

      final AsyncMBeanServer aServer =
          ConnectorFactory.createAsyncConnector("JMS", "MyAgent");
      final RemoteMBeanServer rServer =
          ConnectorFactory.createConnector("SOAP", "MyAgent");

      rServer.createMBean("book.jmx.examples.User", user);

      Thread jms = new Thread(new Runnable() {

        Attribute attr = new Attribute("Name", "Testing...");

        public void run() {
          try {
            System.out.println("JMS Started.");

            for (int i = 0; i < 5000; ++i)
              aServer.setAttribute(user, attr);

            System.out.println("JMS Ended.");
          }
          catch (Exception e) {
            e.printStackTrace();
          }
        }
      } );

      Thread soap = new Thread(new Runnable() {

        Attribute attr = new Attribute("Name", "Testing...");
        public void run() {
          try {
            System.out.println("SOAP Started.");

            for (int i = 0; i < 5000; ++i)
              rServer.setAttribute(user, attr);

            System.out.println("SOAP Ended.");
          }
          catch (Exception e) {
            e.printStackTrace();
          }
        }
      });

      soap.start();
      jms.start();
    }
    catch (ConnectorException e) {
      e.getTargetException().printStackTrace();
    }
    catch (JMException e) {
      e.printStackTrace();
    }
  }
}

Summary

This chapter covered some slightly more advanced implementations of how to connect and communicate with remote JMX agents. It provided examples of bare-bones implementations of two new connectors—a synchronous SOAP connector and an asynchronous JMS connector. You also saw how to use the Java Naming service to locate your JMX agents and MBeans. These implementations can be used as a base for more robust and advanced connectors. However, you can take a little breather from all the coding now as you move on to the next chapters that discuss how the future of JMX and J2EE management looks. Also discussed are the JBoss server and how JMX has been used in a state-of-the-art application server.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.189.251