| MoveableMBean.java |
/***************************************
* *
* JBoss: The OpenSource J2EE WebOS *
* *
* Distributable under LGPL license. *
* See terms of license at gnu.org. *
* *
***************************************/
package org.jboss.mx.remoting;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.io.Serializable;
import java.io.IOException;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.HashMap;
import javax.management.Attribute;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.ReflectionException;
import org.jboss.remoting.loading.ClassUtil;
import org.jboss.mx.remoting.LocationAware;
import org.jboss.mx.remoting.MBeanLocator;
/**
* MoveableMBean is a Dynamic Proxy to an MBean that exists on a JMX Network. This object
* can be created an cast to the appropriate set of interfaces that the MBean implements and can
* be serialized and past across the network and serialization and remote invocation will be
* handled as if the Object was local to the JVM. <P>
*
* Example usage:
*
* <CODE><PRE>
*
* TestMBean mybean=(TestMBean)MoveableMBean.create(mbeanLocator,TestMBean.class);
*
* // transport method
* mybean.myMethod();
*
* // transport a method against a remote JMX server and pass the TestMBean object
* // it will be serialized and passed to the remote server .. on the other side, the
* // JVM will de-serialize, create a local DynamicProxy and then invocations against the
* // parameter, will be invoked remotely back to the mbeanLocator above
* remoteserver.transport(new ObjectName(":test=MyObject"),"myMethod",new Object[]{mybean},new String[]{TestMBean.class.getName()});
*
* </PRE></CODE>
*
* You can also cache attribute values in the local proxy, in the case where the values are fixed
* and you don't want to remote overhead associated with sending the invocation remotely. In this case,
* you can pass in a Map of attribute/value pairs which will be always returned in any getter method invocation
* against the attribute.
*
* @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
* @version $Revision: 1.1.8.1 $
*/
public class MoveableMBean implements InvocationHandler, Serializable
{
static final long serialVersionUID = -7506487379354274551L;
private transient static Map methodArgs = new WeakHashMap ();
// preloaded Method objects for the methods in java.lang.Object
private static transient Method hashCodeMethod;
private static transient Method equalsMethod;
private static transient Method toStringMethod;
private static transient Method notifyAllMethod;
private static transient Method notifyMethod;
private static transient Method wait0Method;
private static transient Method wait1Method;
private static transient Method wait2Method;
protected MBeanLocator locator;
protected Integer hashCode;
protected Map staticAttributes;
static
{
try
{
// SETUP Method objects from the Object class
hashCodeMethod = Object.class.getMethod ("hashCode", null);
equalsMethod = Object.class.getMethod ("equals", new Class[]{Object.class});
toStringMethod = Object.class.getMethod ("toString", null);
notifyMethod = Object.class.getMethod ("notify", null);
notifyAllMethod = Object.class.getMethod ("notifyAll", null);
notifyMethod = Object.class.getMethod ("notify", null);
wait0Method = Object.class.getMethod ("wait", null);
wait1Method = Object.class.getMethod ("wait", new Class[]{Long.TYPE});
wait2Method = Object.class.getMethod ("wait", new Class[]{Long.TYPE,Integer.TYPE});
}
catch (NoSuchMethodException e)
{
throw new InternalError (e.getMessage ());
}
}
protected MoveableMBean (MBeanLocator locator, Map staticAttributes)
{
this.locator = locator;
this.staticAttributes = staticAttributes;
this.hashCode = new Integer (System.identityHashCode (locator));
}
/**
* return the locator that the mbean references
*
* @return
*/
public final MBeanLocator getMBeanLocator ()
{
return locator;
}
public static Object create (MBeanLocator locator, ClassLoader loader, Class interfaceClass)
{
Class classes[]=ClassUtil.getInterfacesFor(interfaceClass);
return create (locator, loader, classes, null);
}
public static Object create (MBeanLocator locator, ClassLoader loader, Class interfaceClass, Map staticAttributes)
{
Class classes[]=ClassUtil.getInterfacesFor(interfaceClass);
return create (locator, loader, classes, staticAttributes);
}
public static Object create (MBeanLocator locater, Class interfaceClass)
{
Class classes[]=ClassUtil.getInterfacesFor(interfaceClass);
return create (locater, classes);
}
public static Object create (MBeanLocator locator, Class interfaces[])
{
ClassLoader loader = Thread.currentThread ().getContextClassLoader ();
if (loader == null)
{
loader = interfaces[0].getClassLoader ();
}
return create (locator, loader, interfaces, null);
}
public static Object create (MBeanLocator locator, ClassLoader loader, Class interfaces[], Map staticAttributes)
{
Class _inf[]=interfaces;
boolean found = false;
for (int c=0;c<interfaces.length;c++)
{
if (interfaces[c]==LocationAware.class)
{
found=true;
break;
}
}
if (found==false)
{
_inf=new Class[interfaces.length+1];
System.arraycopy(interfaces,0,_inf,0,interfaces.length);
// always add location aware interface so you can cast this mbean to that interface
_inf[interfaces.length]=LocationAware.class;
}
return Proxy.newProxyInstance (loader, _inf, new MoveableMBean (locator,staticAttributes));
}
/**
* add a map of static attributes, with each key being the attributeName and the value being the
* value to return for every invocation to this attribute getter
*
* @param values
*/
public void addStaticAttributes (Map values)
{
if (staticAttributes==null)
{
staticAttributes = values;
}
else
{
staticAttributes.putAll(values);
}
}
/**
* add a static attribute
*
* @param name
* @param value
*/
public void addStaticAttribute (String name, Object value)
{
if (staticAttributes==null)
{
staticAttributes = new HashMap(1);
}
staticAttributes.put(name,value);
}
protected Object handleLocationMethods (Object proxy, Method method, Object[] args)
throws Throwable
{
return locator;
}
/**
* Processes a method invocation on a proxy instance and returns
* the result. This method will be invoked on an invocation handler
* when a method is invoked on a proxy instance that it is
* associated with.
*
* @param proxy the proxy instance that the method was invoked on
*
* @param method the <code>Method</code> instance corresponding to
* the interface method invoked on the proxy instance. The declaring
* class of the <code>Method</code> object will be the interface that
* the method was declared in, which may be a superinterface of the
* proxy interface that the proxy class inherits the method through.
*
* @param args an array of objects containing the values of the
* arguments passed in the method invocation on the proxy instance,
* or <code>null</code> if interface method takes no arguments.
* Arguments of primitive types are wrapped in instances of the
* appropriate primitive wrapper class, such as
* <code>java.lang.Integer</code> or <code>java.lang.Boolean</code>.
*
* @return the value to return from the method invocation on the
* proxy instance. If the declared return type of the interface
* method is a primitive type, then the value returned by
* this method must be an instance of the corresponding primitive
* wrapper class; otherwise, it must be a type assignable to the
* declared return type. If the value returned by this method is
* <code>null</code> and the interface method's return type is
* primitive, then a <code>NullPointerException</code> will be
* thrown by the method invocation on the proxy instance. If the
* value returned by this method is otherwise not compatible with
* the interface method's declared return type as described above,
* a <code>ClassCastException</code> will be thrown by the method
* invocation on the proxy instance.
*
* @throws java.lang.Throwable the exception to throw from the method
* invocation on the proxy instance. The exception's type must be
* assignable either to any of the exception types declared in the
* <code>throws</code> clause of the interface method or to the
* unchecked exception types <code>java.lang.RuntimeException</code>
* or <code>java.lang.Error</code>. If a checked exception is
* thrown by this method that is not assignable to any of the
* exception types declared in the <code>throws</code> clause of
* the interface method, then an
* {@link java.lang.reflect.UndeclaredThrowableException} containing the
* exception that was thrown by this method will be thrown by the
* method invocation on the proxy instance.
*
* @see java.lang.reflect.UndeclaredThrowableException
*/
public Object invoke (Object proxy, Method method, Object[] args)
throws Throwable
{
if (method.getDeclaringClass () == Object.class)
{
return handleObjectMethods (proxy, method, args);
}
if (method.getDeclaringClass() == LocationAware.class)
{
return handleLocationMethods (proxy, method, args);
}
try
{
String name = method.getName();
if (name.startsWith("get") && name.length()>3 && (args==null || args.length<=0))
{
// getter
// example: getListeningPoint "ListeningPoint"
String attributeName = method.getName().substring(3);
if (staticAttributes!=null)
{
// check to see if we want a static attribute value to
// come from the local cache version, rather than from
// remote
if (staticAttributes.containsKey(attributeName))
{
return staticAttributes.get(attributeName);
}
}
return locator.getMBeanServer ().getAttribute (locator.getObjectName (), attributeName);
}
else if (name.startsWith("set") && name.length()>3)
{
// setter
String attributeName = method.getName().substring(3);
// first transport remotely, to make sure that this doesn't fail ..
locator.getMBeanServer().setAttribute(locator.getObjectName(),new Attribute(attributeName,(args==null?null:args[0])));
// if the transport succeeded and we need to update our local cache copy ..
if (staticAttributes!=null)
{
// check to see if we want a static attribute value to
// come from the local cache version, rather than from
// remote, and if so, store it local as well as transport remotely
if (staticAttributes.containsKey(attributeName))
{
return staticAttributes.put(attributeName,(args==null ? null : args[0]));
}
}
return null;
}
else
{
return locator.getMBeanServer ().invoke (locator.getObjectName (), method.getName (), args, makeArgSignature (method, args));
}
}
catch (InstanceNotFoundException inf)
{
throw inf;
}
catch (MBeanException mbe)
{
throw mbe.getTargetException ();
}
catch (ReflectionException re)
{
throw re.getTargetException ();
}
}
protected Boolean proxyEquals (Object proxy, Object other)
{
return (proxy == other ? Boolean.TRUE : Boolean.FALSE);
}
protected String proxyToString (Object proxy)
{
return (locator == null ? ("MoveableMBean [Proxy" + "@" + proxy.hashCode ()+"]") : locator.toString ());
}
protected Integer proxyHashCode (Object proxy)
{
return hashCode;
}
protected Object handleObjectMethods (Object proxy, Method method, Object args[])
throws Exception
{
if (method.equals (hashCodeMethod))
{
return proxyHashCode (proxy);
}
else if (method.equals (equalsMethod))
{
return proxyEquals (proxy, args[0]);
}
else if (method.equals (toStringMethod))
{
return proxyToString (proxy);
}
else if (method.equals (notifyAllMethod))
{
notifyAll();
return null;
}
else if (method.equals(notifyMethod))
{
notify();
return null;
}
else if (method.equals(wait0Method))
{
wait();
return null;
}
else if (method.equals(wait1Method))
{
wait(((Long)args[0]).longValue());
return null;
}
else if (method.equals(wait2Method))
{
wait(((Long)args[0]).longValue(), ((Integer)args[1]).intValue());
return null;
}
else
{
throw new InternalError (
"unexpected Object method dispatched: " + method);
}
}
/**
* convert the method to a String array for the signature of the MBean invocation
*
* @param method
* @param args
* @return
*/
protected synchronized String[] makeArgSignature (Method method, Object args[])
{
if (methodArgs.containsKey (method))
{
return (String[]) methodArgs.get (method);
}
Class pt[] = method.getParameterTypes ();
if (pt == null || pt.length <= 0)
{
return null;
}
String sig[] = new String[args.length];
for (int c = 0; c < pt.length; c++)
{
sig[c] = pt[c].getName ();
}
methodArgs.put (method, sig);
return sig;
}
/**
* resolve the proxy interfaces
*
* @param interfaces
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
protected Class resolveProxyClass(String[] interfaces)
throws IOException, ClassNotFoundException
{
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl==null)
{
cl = MoveableMBean.class.getClassLoader();
}
Class inf[]=new Class[interfaces.length];
for (int c=0;c<interfaces.length;c++)
{
inf[c]=cl.loadClass(interfaces[c]);
}
return java.lang.reflect.Proxy.getProxyClass(cl, inf);
}
}
| MoveableMBean.java |