/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.mx.remoting; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; import javax.management.Attribute; import javax.management.InstanceNotFoundException; import javax.management.MBeanException; import javax.management.ReflectionException; import org.jboss.remoting.loading.ClassUtil; /** * 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> * <p/> * Example usage: * <p/> * <CODE><PRE> * <p/> * TestMBean mybean=(TestMBean)MoveableMBean.create(mbeanLocator,TestMBean.class); * <p/> * // transport method * mybean.myMethod(); * <p/> * // 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()}); * <p/> * </PRE></CODE> * <p/> * 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: 81023 $ */ 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); } }