JBoss.org Community Documentation
In the section on connecting to the JMX server we mentioned that there was a service that allows one to access the javax.management.MBeanServer
via any protocol using an invoker service. In this section we present the org.jboss.jmx.connector.invoker.InvokerAdaptorService
and its configuration for access via RMI/JRMP as an example of the steps required to provide remote access to an MBean service.
The InvokerAdaptorService
is a simple MBean service that only exists to fulfill the target MBean role in the detached invoker pattern.
package org.jboss.jmx.connector.invoker; public interface InvokerAdaptorServiceMBean extends org.jboss.system.ServiceMBean { Class getExportedInterface(); void setExportedInterface(Class exportedInterface); Object invoke(org.jboss.invocation.Invocation invocation) throws Exception; } package org.jboss.jmx.connector.invoker; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.UndeclaredThrowableException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import javax.management.MBeanServer; import javax.management.ObjectName; import org.jboss.invocation.Invocation; import org.jboss.invocation.MarshalledInvocation; import org.jboss.mx.server.ServerConstants; import org.jboss.system.ServiceMBeanSupport; import org.jboss.system.Registry; public class InvokerAdaptorService extends ServiceMBeanSupport implements InvokerAdaptorServiceMBean, ServerConstants { private static ObjectName mbeanRegistry; static { try { mbeanRegistry = new ObjectName(MBEAN_REGISTRY); } catch (Exception e) { throw new RuntimeException(e.toString()); } } private Map marshalledInvocationMapping = new HashMap(); private Class exportedInterface; public Class getExportedInterface() { return exportedInterface; } public void setExportedInterface(Class exportedInterface) { this.exportedInterface = exportedInterface; } protected void startService() throws Exception { // Build the interface method map Method[] methods = exportedInterface.getMethods(); HashMap tmpMap = new HashMap(methods.length); for (int m = 0; m < methods.length; m ++) { Method method = methods[m]; Long hash = new Long(MarshalledInvocation.calculateHash(method)); tmpMap.put(hash, method); } marshalledInvocationMapping = Collections.unmodifiableMap(tmpMap); // Place our ObjectName hash into the Registry so invokers can // resolve it Registry.bind(new Integer(serviceName.hashCode()), serviceName); } protected void stopService() throws Exception { Registry.unbind(new Integer(serviceName.hashCode())); } public Object invoke(Invocation invocation) throws Exception { // Make sure we have the correct classloader before unmarshalling Thread thread = Thread.currentThread(); ClassLoader oldCL = thread.getContextClassLoader(); // Get the MBean this operation applies to ClassLoader newCL = null; ObjectName objectName = (ObjectName) invocation.getValue("JMX_OBJECT_NAME"); if (objectName != null) { // Obtain the ClassLoader associated with the MBean deployment newCL = (ClassLoader) server.invoke(mbeanRegistry, "getValue", new Object[] { objectName, CLASSLOADER }, new String[] { ObjectName.class.getName(), "java.lang.String" }); } if (newCL != null && newCL != oldCL) { thread.setContextClassLoader(newCL); } try { // Set the method hash to Method mapping if (invocation instanceof MarshalledInvocation) { MarshalledInvocation mi = (MarshalledInvocation) invocation; mi.setMethodMap(marshalledInvocationMapping); } // Invoke the MBeanServer method via reflection Method method = invocation.getMethod(); Object[] args = invocation.getArguments(); Object value = null; try { String name = method.getName(); Class[] sig = method.getParameterTypes(); Method mbeanServerMethod = MBeanServer.class.getMethod(name, sig); value = mbeanServerMethod.invoke(server, args); } catch(InvocationTargetException e) { Throwable t = e.getTargetException(); if (t instanceof Exception) { throw (Exception) t; } else { throw new UndeclaredThrowableException(t, method.toString()); } } return value; } finally { if (newCL != null && newCL != oldCL) { thread.setContextClassLoader(oldCL); } } } }
Example 3.16. The InvokerAdaptorService MBean
Let's go through the key details of this service. The InvokerAdaptorServiceMBean
Standard MBean interface of the InvokerAdaptorService
has a single ExportedInterface
attribute and a single invoke(Invocation)
operation. The ExportedInterface
attribute allows customization of the type of interface the service exposes to clients. This has to be compatible with the MBeanServer
class in terms of method name and signature. The invoke(Invocation)
operation is the required entry point that target MBean services must expose to participate in the detached invoker pattern. This operation is invoked by the detached invoker services that have been configured to provide access to the InvokerAdaptorService
.
Lines 54-64 of the InvokerAdaptorService build the HashMap<Long, Method> of the ExportedInterface Class using the org.jboss.invocation.MarshalledInvocation.calculateHash(Method)
utility method. Because java.lang.reflect.Method
instances are not serializable, a MarshalledInvocation
version of the non-serializable Invocation
class is used to marshall the invocation between the client and server. The MarshalledInvocation
replaces the Method instances with their corresponding hash representation. On the server side, the MarshalledInvocation
must be told what the hash to Method mapping is.
Line 64 creates a mapping between the InvokerAdaptorService
service name and its hash code representation. This is used by detached invokers to determine what the target MBean ObjectName
of an Invocation
is. When the target MBean name is store in the Invocation
, its store as its hashCode because ObjectName
s are relatively expensive objects to create. The org.jboss.system.Registry
is a global map like construct that invokers use to store the hash code to ObjectName
mappings in.
Lines 77-93 obtain the name of the MBean on which the MBeanServer operation is being performed and lookup the class loader associated with the MBean's SAR deployment. This information is available via the org.jboss.mx.server.registry.BasicMBeanRegistry
, a JBoss JMX implementation specific class. It is generally necessary for an MBean to establish the correct class loading context because the detached invoker protocol layer may not have access to the class loaders needed to unmarshall the types associated with an invocation.
Lines 101-105 install the ExposedInterface
class method hash to method mapping if the invocation argument is of type MarshalledInvocation
. The method mapping calculated previously at lines 54-62 is used here.
Lines 107-114 perform a second mapping from the ExposedInterface
Method to the matching method of the MBeanServer class. The InvokerServiceAdaptor
decouples the ExposedInterface
from the MBeanServer class in that it allows an arbitrary interface. This is needed on one hand because the standard java.lang.reflect.Proxy
class can only proxy interfaces. It also allows one to only expose a subset of the MBeanServer methods and add transport specific exceptions like java.rmi.RemoteException
to the ExposedInterface
method signatures.
Line 115 dispatches the MBeanServer method invocation to the MBeanServer instance to which the InvokerAdaptorService
was deployed. The server instance variable is inherited from the ServiceMBeanSupport
superclass.
Lines 117-124 handle any exceptions coming from the reflective invocation including the unwrapping of any declared exception thrown by the invocation.
Line 126 is the return of the successful MBeanServer method invocation result.
Note that the InvokerAdaptorService
MBean does not deal directly with any transport specific details. There is the calculation of the method hash to Method mapping, but this is a transport independent detail.
Now let's take a look at how the InvokerAdaptorService
may be used to expose the same org.jboss.jmx.adaptor.rmi.RMIAdaptor
interface via RMI/JRMP as seen in Connecting to JMX Using RMI. We will start by presenting the proxy factory and InvokerAdaptorService
configurations found in the default setup in the jmx-invoker-adaptor-service.sar
deployment. Example 3.17, “The default jmx-invoker-adaptor-server.sar jboss-service.xml deployment descriptor” shows the jboss-service.xml
descriptor for this deployment.
<server> <!-- The JRMP invoker proxy configuration for the InvokerAdaptorService --> <mbean code="org.jboss.invocation.jrmp.server.JRMPProxyFactory" name="jboss.jmx:type=adaptor,name=Invoker,protocol=jrmp,service=proxyFactory"> <!-- Use the standard JRMPInvoker from conf/jboss-service.xml --> <attribute name="InvokerName">jboss:service=invoker,type=jrmp</attribute> <!-- The target MBean is the InvokerAdaptorService configured below --> <attribute name="TargetName">jboss.jmx:type=adaptor,name=Invoker</attribute> <!-- Where to bind the RMIAdaptor proxy --> <attribute name="JndiName">jmx/invoker/RMIAdaptor</attribute> <!-- The RMI compabitle MBeanServer interface --> <attribute name="ExportedInterface">org.jboss.jmx.adaptor.rmi.RMIAdaptor</attribute> <attribute name="ClientInterceptors"> <iterceptors> <interceptor>org.jboss.proxy.ClientMethodInterceptor</interceptor> <interceptor> org.jboss.jmx.connector.invoker.client.InvokerAdaptorClientInterceptor </interceptor> <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor> </iterceptors> </attribute> <depends>jboss:service=invoker,type=jrmp</depends> </mbean> <!-- This is the service that handles the RMIAdaptor invocations by routing them to the MBeanServer the service is deployed under. --> <mbean code="org.jboss.jmx.connector.invoker.InvokerAdaptorService" name="jboss.jmx:type=adaptor,name=Invoker"> <attribute name="ExportedInterface">org.jboss.jmx.adaptor.rmi.RMIAdaptor</attribute> </mbean> </server>
Example 3.17. The default jmx-invoker-adaptor-server.sar jboss-service.xml deployment descriptor
The first MBean, org.jboss.invocation.jrmp.server.JRMPProxyFactory
, is the proxy factory MBean service that creates proxies for the RMI/JRMP protocol. The configuration of this service as shown in Example 3.17, “The default jmx-invoker-adaptor-server.sar jboss-service.xml deployment descriptor” states that the JRMPInvoker will be used as the detached invoker, the InvokerAdaptorService
is the target mbean to which requests will be forwarded, that the proxy will expose the RMIAdaptor
interface, the proxy will be bound into JNDI under the name jmx/invoker/RMIAdaptor
, and the proxy will contain 3 interceptors: ClientMethodInterceptor
, InvokerAdaptorClientInterceptor
, InvokerInterceptor
. The configuration of the InvokerAdaptorService
simply sets the RMIAdaptor interface that the service is exposing.
The last piece of the configuration for exposing the InvokerAdaptorService
via RMI/JRMP is the detached invoker. The detached invoker we will use is the standard RMI/JRMP invoker used by the EJB containers for home and remote invocations, and this is the org.jboss.invocation.jrmp.server.JRMPInvoker
MBean service configured in the conf/jboss-service.xml
descriptor. That we can use the same service instance emphasizes the detached nature of the invokers. The JRMPInvoker simply acts as the RMI/JRMP endpoint for all RMI/JRMP proxies regardless of the interface(s) the proxies expose or the service the proxies utilize.