5.6. Remote Access to Services, Detached Invokers

Figure 5.21. The main components in the detached invoker architecture
Invoker interface illustrates the generic invoke operation.
package org.jboss.invocation;
import java.rmi.Remote;
import org.jboss.proxy.Interceptor;
import org.jboss.util.id.GUID;
public interface Invoker
extends Remote
{
GUID ID = new GUID();
String getServerHostName() throws Exception;
Object invoke(Invocation invocation) throws Exception;
}
Remote to be compatible with RMI, but this does not mean that an invoker must expose an RMI service stub. The detached invoker service simply acts as a transport gateway that accepts invocations represented as the org.jboss.invocation.Invocation object over its specific transport, unmarshalls the invocation, forwards the invocation onto the destination MBean service, represented by the Target MBean in Figure 5.21, “The main components in the detached invoker architecture”, and marshalls the return value or exception resulting from the forwarded call back to the client.
Invocation object is just a representation of a method invocation context. This includes the target MBean name, the method, the method arguments, a context of information associated with the proxy by the proxy factory, and an arbitrary map of data associated with the invocation by the client proxy interceptors.
- Create a dynamic proxy that implements the interface the target MBean wishes to expose.
- Associate the client proxy interceptors with the dynamic proxy handler.
- Associate the invocation context with the dynamic proxy. This includes the target MBean, detached invoker stub and the proxy JNDI name.
- Make the proxy available to clients by binding the proxy into JNDI.
- Define a JMX operation matching the signature:
public Object invoke(org.jboss.invocation.Invocation) throws Exception - Create a
HashMap<Long, Method>mapping from the exposed interfacejava.lang.reflect.Methods to the long hash representation using theorg.jboss.invocation.MarshalledInvocation.calculateHashmethod. - Implement the
invoke(Invocation)JMX operation and use the interface method hash mapping to transform from the long hash representation of the invoked method to thejava.lang.reflect.Methodof the exposed interface. Reflection is used to perform the actual invocation on the object associated with the MBean service that actually implements the exposed interface.
5.6.1. A Detached Invoker Example, the MBeanServer Invoker Adaptor Service
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.
InvokerAdaptorService is a simple MBean service that only exists to fulfill the target MBean role in the detached invoker pattern.
Example 5.16. The InvokerAdaptorService MBean
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);
}
}
}
}
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.
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.
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 ObjectNames 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.
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.
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.
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.
InvokerAdaptorService was deployed. The server instance variable is inherited from the ServiceMBeanSupport superclass.
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.
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 5.17, “The default jmx-invoker-adaptor-server.sar jboss-service.xml deployment descriptor” shows the jboss-service.xml descriptor for this deployment.
Example 5.17. The default jmx-invoker-adaptor-server.sar jboss-service.xml deployment descriptor
<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>
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 5.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.
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.

Where did the comment section go?
Red Hat's documentation publication system recently went through an upgrade to enable speedier, more mobile-friendly content. We decided to re-evaluate our commenting platform to ensure that it meets your expectations and serves as an optimal feedback mechanism. During this redesign, we invite your input on providing feedback on Red Hat documentation via the discussion platform.