RMI Adaptors and RMI Connectors

Simone Bordet

Revision History
Revision $Revision: 1.5 $$Date: 2002/10/14 20:46:19 $

Introduction

RMI adaptors allow clients to connect to a JMX Agent from a remote JVM using RMI.

MX4J's features two RMI adaptors:

  • JRMP Adaptor (class mx4j.adaptor.rmi.jrmp.JRMPAdaptor)
  • IIOP Adaptor (class mx4j.adaptor.rmi.iiop.IIOPAdaptor)

The JRMP Adaptor supports also SSL for secure connections, and both the adaptors support remote notification listeners and filters.

Note that RMI is not a protocol, and here must be intended as "Remote Method Invocation using Java". The underlying protocol can be anyone, and in fact two protocols are supported by the MX4J's RMI adaptors: JRMP and IIOP.

JRMP is the Java Remote Method Protocol, and it's the RMI default protocol.
IIOP is the Internet Inter ORB Protocol, and it's the CORBA default protocol.

Remote Method Invocation in Java can have JRMP or IIOP as underlying protocols, and the remote invocation process is usually referred to, respectively, as "RMI" and "RMI over IIOP".
"RMI" is a short for "RMI over JRMP".

JRMP Adaptor

The JRMP adaptor exposes the JMX Agent to RMI over JRMP calls from remote JVMs.
It needs a naming service such as the rmiregistry or the NamingService MBean (see MX4J documentation) to be active before being started, and proper directives to find the naming service (such as a jndi.properties file in the classpath). Below you can see the snippet of the code needed in the server to create, register and start the JRMP adaptor.

Example 3.3. Deploying the JRMP adaptor

		
public class Server
{
   public static void main(String[] args) throws Exception
   {
      // Create a MBeanServer
      MBeanServer server = MBeanServerFactory.createMBeanServer();

      // Create and start the naming service
      ObjectName naming = new ObjectName("Naming:type=rmiregistry");
      server.createMBean("mx4j.tools.naming.NamingService", naming, null);
      server.invoke(naming, "start", null, null);

      // Create the JRMP adaptor
      ObjectName adaptor = new ObjectName("Adaptor:protocol=JRMP");
      m_server.createMBean("mx4j.adaptor.rmi.jrmp.JRMPAdaptor", adaptor, null);
      JRMPAdaptorMBean mbean = (JRMPAdaptorMBean)StandardMBeanProxy.create(JRMPAdaptorMBean.class, server, adaptor);

      // Set the JNDI name with which will be registered
      String jndiName = "jrmp";
      mbean.setJNDIName(jndiName);
/*
      // Optionally, you can specify the JNDI properties, 
      // instead of having in the classpath a jndi.properties file
      mbean.putJNDIProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
      mbean.putJNDIProperty(Context.PROVIDER_URL, "rmi://localhost:1099");
*/

      // Registers the JRMP adaptor in JNDI and starts it
      mbean.start();
   }
}
		
			

Let's look now at the client, and how it can interact with the JRMP Adaptor.
All you need is the corrispondent RMI connector for the registered RMI adaptor, and proper directives to find the naming service.
There are 2 RMI connectors, namely:

  • JRMP Connector (class mx4j.connector.rmi.jrmp.JRMPConnector)
  • IIOP Connector (class mx4j.connector.rmi.iiop.IIOPConnector)
Each connector is able to connect to the corrispondent adaptor.
To find the naming service, you need a jndi.properties file, or a Hashtable with the suitable naming properties to be passed to the RMI connector. Below you can see the code snippet necessary to connect to the adaptor.

Example 3.4. Using the JRMP connector

		
public class Client
{
   public static void main(String[] args) throws Exception
   {
      // Create a JRMPConnector
      JRMPConnector connector = new JRMPConnector();

      // Pass in the adaptor's JNDI name, no properties
      String jndiName = "jrmp";
      connector.connect(jndiName, null);

      // Use the connector directly to retrieve some information
      // about host name and IP address
      String remoteHostName = connector.getRemoteHostName();
      String remoteHostAddress = connector.getRemoteHostAddress();

      // Get the remote MBeanServer from the connector
      // And use it as if it is an MBeanServer
      RemoteMBeanServer server = connector.getRemoteMBeanServer();

      ObjectName objName = new ObjectName("examples:mbean=MyService");
      ObjectInstance instance = server.createMBean("examples.mbeans.rmi.MyRemoteServiceObject", objName, null);

      NotificationListener listener = new NotificationListener()
      {
         public void handleNotification(Notification n, Object handback)
         {
            // Do something
         }
      };
      server.addNotificationListener(objName, listener, null, null);

      // and so on
   }
}
		
			

It is VERY important to use the connectors in the client, and not to try to lookup directly the adaptor in JNDI.
This because the connectors do some trick to handle the listeners to be invoked remotely by the server.
It is definitely simpler to use the RMIConnector class.

JRMP Adaptor over SSL

The JRMP adaptor can be setup to use SSL to allow secure RMI connections.
Using SSL with RMI over JRMP requires the use of custom RMI socket factories, and requires the server to setup a keystore where a private key and the corresponding public key wrapped in a X509 self-signed certificate are stored.
The certificate is then sent to the client when the connection is established, and the client must trust the server's certificate by looking it up in a truststore (in the client JVM). If the certificate found in the truststore matches the one sent by the server, the SSL connection is complete and data exchange may begin.

The operations needed to setup the JRMP adaptor over SSL are the following:

  • Generate a keystore file using JDK's keytool
  • Export the X509 certificate in a truststore file using JDK's keytool
  • Deploy a SSL ServerSocket factory MBean with the information about the keystore you just generated
  • Deploy the JRMP adaptor MBean passing it the information about the just deployed SSL ServerSocket factory MBean
  • Start the JRMP adaptor

The operations needed to setup the client are the following:

  • Copy the generated trust.store file containing the server certificate to the client
  • Invoke the java interpreter with the following system property: javax.net.ssl.trustStore=<path>/trust.store

Below you can find the code snippets for the server; the client is exactly equal as before.

Example 3.5. Deploying the JRMP adaptor over SSL

		
// Generate the keystore
keytool -genkey -v -keystore key.store -storepass storepwd -keypass keypwd -dname "CN=Simone Bordet, OU=Project Administrator, O=MX4J, L=Torino, S=TO, C=IT" -validity 365

// Export the X509 certificate
keytool -export -v -storepass storepwd -keystore key.store | keytool -import -v -storepass storepwd -keystore trust.store -noprompt

// Deploy the MBeans
public class Server
{
   public static void main(String[] args) throws Exception
   {
      // Create a MBeanServer
      MBeanServer server = MBeanServerFactory.createMBeanServer();

      // Create the SSL ServerSocket factory
      ObjectName ssl = new ObjectName("Adaptor:service=SSLServerSocketFactory");
      server.createMBean("mx4j.adaptor.ssl.SSLAdaptorServerSocketFactory", ssl, null);
      SSLAdaptorServerSocketFactoryMBean factory = (SSLAdaptorServerSocketFactoryMBean)StandardMBeanProxy.create(SSLAdaptorServerSocketFactoryMBean.class, server, ssl);
      factory.setKeyStoreName("key.store");
      factory.setKeyStorePassword("storepwd");
      factory.setKeyManagerPassword("keypwd");

      // Create and start the naming service
      ObjectName naming = new ObjectName("Naming:type=rmiregistry");
      server.createMBean("mx4j.tools.naming.NamingService", naming, null);
      server.invoke(naming, "start", null, null);

      // Create the JRMP adaptor
      ObjectName adaptor = new ObjectName("Adaptor:protocol=JRMP");
      m_server.createMBean("mx4j.adaptor.rmi.jrmp.JRMPAdaptor", adaptor, null);
      JRMPAdaptorMBean mbean = (JRMPAdaptorMBean)StandardMBeanProxy.create(JRMPAdaptorMBean.class, server, adaptor);
      // Set the JNDI name with which will be registered
      String jndiName = "jrmp";
      mbean.setJNDIName(jndiName);
      // Set the SSL ServerSocket Factory
      mbean.setSSLFactory(ssl.toString());
      // Register the JRMP adaptor in JNDI and start it
      mbean.start();
   }
}
		
			

IIOP Adaptor

The IIOP adaptor exposes the JMX Agent to RMI over IIOP calls from remote JVMs.
It needs a naming service such as tnameserv to be active before being started, and proper directives to find the naming service (such as a jndi.properties file in the classpath). Below you can see the snippet of the code needed in the server to create, register and start the IIOP adaptor.

Example 3.6. Deploying the IIOP adaptor

		
public class Server
{
   public static void main(String[] args) throws Exception
   {
      // Create a MBeanServer
      MBeanServer server = MBeanServerFactory.createMBeanServer();

      // Create and start tnameserv
      ObjectName naming = new ObjectName("Naming:type=tnameserv");
      server.createMBean("mx4j.tools.naming.CosNamingService", naming, null);
      server.setAttribute(naming, new Attribute("Delay", new Integer(5000)));
      server.invoke(naming, "start", null, null);

      // Create the IIOP adaptor
      ObjectName adaptor = new ObjectName("Adaptor:protocol=IIOP");
      m_server.createMBean("mx4j.adaptor.rmi.iiop.IIOPAdaptor", adaptor, null);
      IIOPAdaptorMBean mbean = (IIOPAdaptorMBean)StandardMBeanProxy.create(IIOPAdaptorMBean.class, server, adaptor);

      // Set the JNDI name and properties with which will be registered
      String jndiName = "jrmp";
      mbean.setJNDIName(jndiName);
      mbean.putJNDIProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.cosnaming.CNCtxFactory");
      mbean.putJNDIProperty(Context.PROVIDER_URL, "iiop://localhost:900");

      // Register the IIOP adaptor in JNDI and start it
      mbean.start();
   }
}
		
			

The client is exactly equal as before, just replace JRMPConnector with IIOPConnector and remember to give to the client the proper directives to find the naming service (such a jndi.properties file in the classpath, but this time with the CosNaming values).