/* * Copyright 2007 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.management.ManagementFactory; import java.lang.ref.WeakReference; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.AttributeNotFoundException; import javax.management.DynamicMBean; import javax.management.InstanceAlreadyExistsException; import javax.management.InstanceNotFoundException; import javax.management.IntrospectionException; import javax.management.InvalidAttributeValueException; import javax.management.ListenerNotFoundException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanConstructorInfo; import javax.management.MBeanException; import javax.management.MBeanInfo; import javax.management.MBeanNotificationInfo; import javax.management.MBeanOperationInfo; import javax.management.MBeanRegistration; import javax.management.MBeanRegistrationException; import javax.management.MBeanServer; import javax.management.MBeanServerBuilder; import javax.management.MBeanServerConnection; import javax.management.MBeanServerDelegate; import javax.management.MBeanServerFactory; import javax.management.MBeanServerNotification; import javax.management.MalformedObjectNameException; import javax.management.NotCompliantMBeanException; import javax.management.Notification; import javax.management.NotificationBroadcaster; import javax.management.NotificationBroadcasterSupport; import javax.management.NotificationEmitter; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.ObjectInstance; import javax.management.ObjectName; import javax.management.OperationsException; import javax.management.QueryEval; import javax.management.QueryExp; import javax.management.ReflectionException; import javax.management.RuntimeErrorException; import javax.management.RuntimeMBeanException; import javax.management.StandardMBean; import javax.management.loading.ClassLoaderRepository; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; /* * @test OldMBeanServerTest.java * @bug 5072268 * @summary Test that nothing assumes a post-1.2 MBeanServer * @author Eamonn McManus * @run main/othervm -ea OldMBeanServerTest */ /* * We defined the MBeanServerBuilder class and the associated system * property javax.management.builder.initial in version 1.2 of the JMX * spec. That amounts to a guarantee that someone can set the property * to an MBeanServer that only knows about JMX 1.2 semantics, and if they * only do JMX 1.2 operations, everything should work. This test is a * sanity check that ensures we don't inadvertently make any API changes * that stop that from being true. It includes a complete (if slow) * MBeanServer implementation. That implementation doesn't replicate the * mandated exception behaviour everywhere, though, since there's lots of * arbitrary cruft in that. Also, the behaviour of concurrent unregisterMBean * calls is incorrect in detail. */ public class OldMBeanServerTest { private static MBeanServerConnection mbsc; private static String failure; public static void main(String[] args) throws Exception { if (!OldMBeanServerTest.class.desiredAssertionStatus()) throw new Exception("Test must be run with -ea"); System.setProperty("javax.management.builder.initial", OldMBeanServerBuilder.class.getName()); assert MBeanServerFactory.newMBeanServer() instanceof OldMBeanServer; System.out.println("=== RUNNING TESTS WITH LOCAL MBEANSERVER ==="); runTests(new Callable() { public MBeanServerConnection call() { return MBeanServerFactory.newMBeanServer(); } }, null); System.out.println("=== RUNNING TESTS THROUGH CONNECTOR ==="); ConnectionBuilder builder = new ConnectionBuilder(); runTests(builder, builder); if (failure == null) System.out.println("TEST PASSED"); else throw new Exception("TEST FAILED: " + failure); } private static class ConnectionBuilder implements Callable, Runnable { private JMXConnector connector; public MBeanServerConnection call() { MBeanServer mbs = MBeanServerFactory.newMBeanServer(); try { JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://"); JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer( url, null, mbs); cs.start(); JMXServiceURL addr = cs.getAddress(); connector = JMXConnectorFactory.connect(addr); return connector.getMBeanServerConnection(); } catch (IOException e) { throw new RuntimeException(e); } } public void run() { if (connector != null) { try { connector.close(); } catch (IOException e) { throw new RuntimeException(e); } } } } private static void runTests( Callable maker, Runnable breaker) throws Exception { for (Method m : OldMBeanServerTest.class.getDeclaredMethods()) { if (Modifier.isStatic(m.getModifiers()) && m.getName().startsWith("test") && m.getParameterTypes().length == 0) { ExpectException expexc = m.getAnnotation(ExpectException.class); mbsc = maker.call(); try { m.invoke(null); if (expexc != null) { failure = m.getName() + " did not got expected exception " + expexc.value().getName(); System.out.println(failure); } else System.out.println(m.getName() + " OK"); } catch (InvocationTargetException ite) { Throwable t = ite.getCause(); String prob = null; if (expexc != null) { if (expexc.value().isInstance(t)) { System.out.println(m.getName() + " OK (got expected " + expexc.value().getName() + ")"); } else prob = "got wrong exception"; } else prob = "got exception"; if (prob != null) { failure = m.getName() + ": " + prob + " " + t.getClass().getName(); System.out.println(failure); t.printStackTrace(System.out); } } finally { if (breaker != null) breaker.run(); } } } } @Retention(RetentionPolicy.RUNTIME) private static @interface ExpectException { Class value(); } public static interface BoringMBean { public String getName(); public int add(int x, int y); } // This class is Serializable so we can createMBean a StandardMBean // that contains it. Not recommended practice in general -- // should we have a StandardMBean constructor that takes a class // name and constructor parameters? public static class Boring implements BoringMBean, Serializable { public String getName() { return "Jessica"; } public int add(int x, int y) { return x + y; } } public static interface BoringNotifierMBean extends BoringMBean { public void send(); } public static class BoringNotifier extends Boring implements BoringNotifierMBean, NotificationBroadcaster { private final NotificationBroadcasterSupport nbs = new NotificationBroadcasterSupport(); public void addNotificationListener( NotificationListener listener, NotificationFilter filter, Object handback) throws IllegalArgumentException { nbs.addNotificationListener(listener, filter, handback); } public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException { nbs.removeNotificationListener(listener); } public MBeanNotificationInfo[] getNotificationInfo() { return null; } public void send() { Notification n = new Notification("type.type", this, 0L); nbs.sendNotification(n); } } private static class CountListener implements NotificationListener { volatile int count; public void handleNotification(Notification n, Object h) { if (h == null) h = 1; count += (Integer) h; } void waitForCount(int expect) throws InterruptedException { long deadline = System.currentTimeMillis() + 2000L; while (count < expect && System.currentTimeMillis() < deadline) Thread.sleep(1); assert count == expect; } } private static void testBasic() throws Exception { CountListener countListener = new CountListener(); mbsc.addNotificationListener( MBeanServerDelegate.DELEGATE_NAME, countListener, null, null); assert countListener.count == 0; ObjectName name = new ObjectName("a:b=c"); if (mbsc instanceof MBeanServer) ((MBeanServer) mbsc).registerMBean(new Boring(), name); else mbsc.createMBean(Boring.class.getName(), name); countListener.waitForCount(1); assert mbsc.isRegistered(name); assert mbsc.queryNames(null, null).contains(name); assert mbsc.getAttribute(name, "Name").equals("Jessica"); assert mbsc.invoke( name, "add", new Object[] {2, 3}, new String[] {"int", "int"}) .equals(5); mbsc.unregisterMBean(name); countListener.waitForCount(2); assert !mbsc.isRegistered(name); assert !mbsc.queryNames(null, null).contains(name); mbsc.createMBean(BoringNotifier.class.getName(), name); countListener.waitForCount(3); CountListener boringListener = new CountListener(); class AlwaysNotificationFilter implements NotificationFilter { public boolean isNotificationEnabled(Notification notification) { return true; } } mbsc.addNotificationListener( name, boringListener, new AlwaysNotificationFilter(), 5); mbsc.invoke(name, "send", null, null); boringListener.waitForCount(5); } private static void testPrintAttrs() throws Exception { printAttrs(mbsc, null); } private static void testPlatformMBeanServer() throws Exception { MBeanServer pmbs = ManagementFactory.getPlatformMBeanServer(); assert pmbs instanceof OldMBeanServer; // Preceding assertion could be violated if at some stage we wrap // the Platform MBeanServer. In that case we can still check that // it is ultimately an OldMBeanServer for example by adding a // counter to getAttribute and checking that it is incremented // when we call pmbs.getAttribute. printAttrs(pmbs, UnsupportedOperationException.class); ObjectName memoryMXBeanName = new ObjectName(ManagementFactory.MEMORY_MXBEAN_NAME); pmbs.invoke(memoryMXBeanName, "gc", null, null); } private static void printAttrs( MBeanServerConnection mbsc1, Class expectX) throws Exception { Set names = mbsc1.queryNames(null, null); for (ObjectName name : names) { System.out.println(name + ":"); MBeanInfo mbi = mbsc1.getMBeanInfo(name); MBeanAttributeInfo[] mbais = mbi.getAttributes(); for (MBeanAttributeInfo mbai : mbais) { String attr = mbai.getName(); Object value; try { value = mbsc1.getAttribute(name, attr); } catch (Exception e) { if (expectX != null && expectX.isInstance(e)) value = "<" + e + ">"; else throw e; } String s = " " + attr + " = " + value; if (s.length() > 80) s = s.substring(0, 77) + "..."; System.out.println(s); } } } private static void testJavaxManagementStandardMBean() throws Exception { ObjectName name = new ObjectName("a:b=c"); Object mbean = new StandardMBean(new Boring(), BoringMBean.class); mbsc.createMBean( StandardMBean.class.getName(), name, new Object[] {new Boring(), BoringMBean.class}, new String[] {Object.class.getName(), Class.class.getName()}); assert mbsc.getAttribute(name, "Name").equals("Jessica"); assert mbsc.invoke( name, "add", new Object[] {2, 3}, new String[] {"int", "int"}) .equals(5); mbsc.unregisterMBean(name); } private static void testConnector() throws Exception { } public static class OldMBeanServerBuilder extends MBeanServerBuilder { public MBeanServer newMBeanServer( String defaultDomain, MBeanServer outer, MBeanServerDelegate delegate) { return new OldMBeanServer(defaultDomain, delegate); } } public static class OldMBeanServer implements MBeanServer { // We pretend there's a ClassLoader MBean representing the Class Loader // Repository and intercept references to it where necessary to keep up // the pretence. This allows us to fake the right behaviour for // the omitted-ClassLoader versions of createMBean and instantiate // (which are not the same as passing a null for the ClassLoader parameter // of the versions that have one). private static final ObjectName clrName; static { try { clrName = new ObjectName("JMImplementation:type=ClassLoaderRepository"); } catch (MalformedObjectNameException e) { throw new RuntimeException(e); } } private final ConcurrentMap mbeans = new ConcurrentHashMap(); private final ConcurrentMap listenerMap = new ConcurrentHashMap(); private final String defaultDomain; private final MBeanServerDelegate delegate; private final ClassLoaderRepositoryImpl clr = new ClassLoaderRepositoryImpl(); OldMBeanServer(String defaultDomain, MBeanServerDelegate delegate) { this.defaultDomain = defaultDomain; this.delegate = delegate; try { registerMBean(delegate, MBeanServerDelegate.DELEGATE_NAME); } catch (Exception e) { throw new RuntimeException(e); } } public ObjectInstance createMBean(String className, ObjectName name) throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException { return createMBean(className, name, null, null); } public ObjectInstance createMBean( String className, ObjectName name, ObjectName loaderName) throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, InstanceNotFoundException { return createMBean(className, name, loaderName, null, null); } public ObjectInstance createMBean( String className, ObjectName name, Object[] params, String[] signature) throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException { try { return createMBean(className, name, clrName, params, signature); } catch (InstanceNotFoundException ex) { throw new RuntimeException(ex); // can't happen } } public ObjectInstance createMBean( String className, ObjectName name, ObjectName loaderName, Object[] params, String[] signature) throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, InstanceNotFoundException { Object mbean = instantiate(className, loaderName, params, signature); return registerMBean(mbean, name); } private void forbidJMImpl(ObjectName name) { if (name.getDomain().equals("JMImplementation") && mbeans.containsKey(MBeanServerDelegate.DELEGATE_NAME)) throw new IllegalArgumentException("JMImplementation reserved"); } public ObjectInstance registerMBean(Object object, ObjectName name) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { forbidJMImpl(name); if (name.isPattern()) throw new IllegalArgumentException(name.toString()); // This is the only place we check for wildcards. Since you // can't register a wildcard name, other operations that supply // one will get InstanceNotFoundException when they look it up. DynamicMBean mbean; if (object instanceof DynamicMBean) mbean = (DynamicMBean) object; else mbean = standardToDynamic(object); MBeanRegistration reg = mbeanRegistration(object); try { name = reg.preRegister(this, name); } catch (Exception e) { throw new MBeanRegistrationException(e); } DynamicMBean put = mbeans.putIfAbsent(name, mbean); if (put != null) { reg.postRegister(false); throw new InstanceAlreadyExistsException(name.toString()); } reg.postRegister(true); if (object instanceof ClassLoader) clr.addLoader((ClassLoader) object); Notification n = new MBeanServerNotification( MBeanServerNotification.REGISTRATION_NOTIFICATION, MBeanServerDelegate.DELEGATE_NAME, 0, name); delegate.sendNotification(n); String className = mbean.getMBeanInfo().getClassName(); return new ObjectInstance(name, className); } public void unregisterMBean(ObjectName name) throws InstanceNotFoundException, MBeanRegistrationException { forbidJMImpl(name); DynamicMBean mbean = getMBean(name); if (mbean == null) throw new InstanceNotFoundException(name.toString()); MBeanRegistration reg = mbeanRegistration(mbean); try { reg.preDeregister(); } catch (Exception e) { throw new MBeanRegistrationException(e); } if (!mbeans.remove(name, mbean)) throw new InstanceNotFoundException(name.toString()); // This is incorrect because we've invoked preDeregister Object userMBean = getUserMBean(mbean); if (userMBean instanceof ClassLoader) clr.removeLoader((ClassLoader) userMBean); Notification n = new MBeanServerNotification( MBeanServerNotification.REGISTRATION_NOTIFICATION, MBeanServerDelegate.DELEGATE_NAME, 0, name); delegate.sendNotification(n); reg.postDeregister(); } public ObjectInstance getObjectInstance(ObjectName name) throws InstanceNotFoundException { DynamicMBean mbean = getMBean(name); return new ObjectInstance(name, mbean.getMBeanInfo().getClassName()); } private static class TrueQueryExp implements QueryExp { public boolean apply(ObjectName name) { return true; } public void setMBeanServer(MBeanServer s) {} } private static final QueryExp trueQuery = new TrueQueryExp(); public Set queryMBeans(ObjectName name, QueryExp query) { Set instances = newSet(); if (name == null) name = ObjectName.WILDCARD; if (query == null) query = trueQuery; MBeanServer oldMBS = QueryEval.getMBeanServer(); try { query.setMBeanServer(this); for (ObjectName n : mbeans.keySet()) { if (name.apply(n)) { try { if (query.apply(n)) instances.add(getObjectInstance(n)); } catch (Exception e) { // OK: Ignore this MBean in the result } } } } finally { query.setMBeanServer(oldMBS); } return instances; } public Set queryNames(ObjectName name, QueryExp query) { Set instances = queryMBeans(name, query); Set names = newSet(); for (ObjectInstance instance : instances) names.add(instance.getObjectName()); return names; } public boolean isRegistered(ObjectName name) { return mbeans.containsKey(name); } public Integer getMBeanCount() { return mbeans.size(); } public Object getAttribute(ObjectName name, String attribute) throws MBeanException, AttributeNotFoundException, InstanceNotFoundException, ReflectionException { return getMBean(name).getAttribute(attribute); } public AttributeList getAttributes(ObjectName name, String[] attributes) throws InstanceNotFoundException, ReflectionException { return getMBean(name).getAttributes(attributes); } public void setAttribute(ObjectName name, Attribute attribute) throws InstanceNotFoundException, AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { getMBean(name).setAttribute(attribute); } public AttributeList setAttributes( ObjectName name, AttributeList attributes) throws InstanceNotFoundException, ReflectionException { return getMBean(name).setAttributes(attributes); } public Object invoke( ObjectName name, String operationName, Object[] params, String[] signature) throws InstanceNotFoundException, MBeanException, ReflectionException { return getMBean(name).invoke(operationName, params, signature); } public String getDefaultDomain() { return defaultDomain; } public String[] getDomains() { Set domains = newSet(); for (ObjectName name : mbeans.keySet()) domains.add(name.getDomain()); return domains.toArray(new String[0]); } // ClassCastException if MBean is not a NotificationBroadcaster public void addNotificationListener( ObjectName name, NotificationListener listener, NotificationFilter filter, Object handback) throws InstanceNotFoundException { NotificationBroadcaster userMBean = (NotificationBroadcaster) getUserMBean(name); NotificationListener wrappedListener = wrappedListener(name, userMBean, listener); userMBean.addNotificationListener(wrappedListener, filter, handback); } public void addNotificationListener( ObjectName name, ObjectName listener, NotificationFilter filter, Object handback) throws InstanceNotFoundException { NotificationListener nl = (NotificationListener) getUserMBean(listener); addNotificationListener(name, nl, filter, handback); } public void removeNotificationListener( ObjectName name, ObjectName listener) throws InstanceNotFoundException, ListenerNotFoundException { NotificationListener nl = (NotificationListener) getUserMBean(listener); removeNotificationListener(name, nl); } public void removeNotificationListener( ObjectName name, ObjectName listener, NotificationFilter filter, Object handback) throws InstanceNotFoundException, ListenerNotFoundException { NotificationListener nl = (NotificationListener) getUserMBean(listener); removeNotificationListener(name, nl, filter, handback); } public void removeNotificationListener( ObjectName name, NotificationListener listener) throws InstanceNotFoundException, ListenerNotFoundException { NotificationBroadcaster userMBean = (NotificationBroadcaster) getUserMBean(name); NotificationListener wrappedListener = wrappedListener(name, userMBean, listener); userMBean.removeNotificationListener(wrappedListener); } public void removeNotificationListener( ObjectName name, NotificationListener listener, NotificationFilter filter, Object handback) throws InstanceNotFoundException, ListenerNotFoundException { NotificationEmitter userMBean = (NotificationEmitter) getMBean(name); NotificationListener wrappedListener = wrappedListener(name, userMBean, listener); userMBean.removeNotificationListener(wrappedListener, filter, handback); } public MBeanInfo getMBeanInfo(ObjectName name) throws InstanceNotFoundException, IntrospectionException, ReflectionException { return getMBean(name).getMBeanInfo(); } public boolean isInstanceOf(ObjectName name, String className) throws InstanceNotFoundException { DynamicMBean mbean = getMBean(name); String mbeanClassName = mbean.getMBeanInfo().getClassName(); if (className.equals(mbeanClassName)) return true; ClassLoader loader = getUserMBean(mbean).getClass().getClassLoader(); try { Class mbeanClass = Class.forName(mbeanClassName, false, loader); Class isInstClass = Class.forName(className, false, loader); return isInstClass.isAssignableFrom(mbeanClass); } catch (ClassNotFoundException e) { return false; } } public Object instantiate(String className) throws ReflectionException, MBeanException { return instantiate(className, null, null); } public Object instantiate(String className, ObjectName loaderName) throws ReflectionException, MBeanException, InstanceNotFoundException { return instantiate(className, loaderName, null, null); } public Object instantiate( String className, Object[] params, String[] signature) throws ReflectionException, MBeanException { try { return instantiate(className, clrName, params, signature); } catch (InstanceNotFoundException e) { throw new RuntimeException(e); // can't happen } } public Object instantiate( String className, ObjectName loaderName, Object[] params, String[] signature) throws ReflectionException, MBeanException, InstanceNotFoundException { if (params == null) params = new Object[0]; if (signature == null) signature = new String[0]; ClassLoader loader; if (loaderName == null) loader = this.getClass().getClassLoader(); else if (loaderName.equals(clrName)) loader = clr; else loader = (ClassLoader) getMBean(loaderName); Class c; try { c = Class.forName(className, false, loader); } catch (ClassNotFoundException e) { throw new ReflectionException(e); } Constructor[] constrs = c.getConstructors(); Constructor found = null; findconstr: for (Constructor constr : constrs) { Class[] cTypes = constr.getParameterTypes(); if (cTypes.length == signature.length) { for (int i = 0; i < cTypes.length; i++) { if (!cTypes[i].getName().equals(signature[i])) continue findconstr; } found = constr; break findconstr; } } if (found == null) { Exception x = new NoSuchMethodException( className + Arrays.toString(signature)); throw new ReflectionException(x); } return invokeSomething(found, null, params); } @Deprecated public ObjectInputStream deserialize(ObjectName name, byte[] data) throws InstanceNotFoundException, OperationsException { throw new UnsupportedOperationException(); } @Deprecated public ObjectInputStream deserialize(String className, byte[] data) throws OperationsException, ReflectionException { throw new UnsupportedOperationException(); } @Deprecated public ObjectInputStream deserialize( String className, ObjectName loaderName, byte[] data) throws InstanceNotFoundException, OperationsException, ReflectionException { throw new UnsupportedOperationException(); } public ClassLoader getClassLoaderFor(ObjectName mbeanName) throws InstanceNotFoundException { DynamicMBean mbean = getMBean(mbeanName); Object userMBean = getUserMBean(mbean); return userMBean.getClass().getClassLoader(); } public ClassLoader getClassLoader(ObjectName loaderName) throws InstanceNotFoundException { return (ClassLoader) getMBean(loaderName); } public ClassLoaderRepository getClassLoaderRepository() { return new ClassLoaderRepository() { public Class loadClass(String className) throws ClassNotFoundException { return clr.loadClass(className); } public Class loadClassWithout( ClassLoader exclude, String className) throws ClassNotFoundException { return clr.loadClassWithout(exclude, className); } public Class loadClassBefore( ClassLoader stop, String className) throws ClassNotFoundException { return clr.loadClassBefore(stop, className); } }; } private static class ClassLoaderRepositoryImpl extends ClassLoader implements ClassLoaderRepository { private List loaders = newList(); { loaders.add(this.getClass().getClassLoader()); // We also behave as if the system class loader were in // the repository, since we do nothing to stop delegation // to the parent, which is the system class loader, and // that delegation happens before our findClass is called. } void addLoader(ClassLoader loader) { loaders.add(loader); } void removeLoader(ClassLoader loader) { if (!loaders.remove(loader)) throw new RuntimeException("Loader was not in CLR!"); } public Class loadClassWithout( ClassLoader exclude, String className) throws ClassNotFoundException { return loadClassWithoutBefore(exclude, null, className); } public Class loadClassBefore(ClassLoader stop, String className) throws ClassNotFoundException { return loadClassWithoutBefore(null, stop, className); } private Class loadClassWithoutBefore( ClassLoader exclude, ClassLoader stop, String className) throws ClassNotFoundException { for (ClassLoader loader : loaders) { if (loader == exclude) continue; if (loader == stop) break; try { return Class.forName(className, false, loader); } catch (ClassNotFoundException e) { // OK: try others } } throw new ClassNotFoundException(className); } @Override protected Class findClass(String className) throws ClassNotFoundException { return loadClassWithout(null, className); } } /* There is zero or one ListenerTable per MBean. * The ListenerTable stuff is complicated. We want to rewrite the * source of notifications so that if the source of a notification * from the MBean X is a reference to X itself, it gets replaced * by X's ObjectName. To do this, we wrap the user's listener in * a RewriteListener. But if the same listener is added a second * time (perhaps with a different filter or handback) we must * reuse the same RewriteListener so that the two-argument * removeNotificationListener(ObjectName,NotificationListener) will * correctly remove both listeners. This means we must remember the * mapping from listener to WrappedListener. But if the MBean * discards its listeners (as a result of removeNL or spontaneously) * then we don't want to keep a reference to the WrappedListener. * So we have tons of WeakReferences. The key in the ListenerTable * is an IdentityListener, which wraps the user's listener to ensure * that identity and not equality is used during the lookup, even if * the user's listener has an equals method. The value in the * ListenerTable is a WeakReference wrapping a RewriteListener wrapping * the same IdentityListener. Since the RewriteListener is what is * added to the user's MBean, the WeakReference won't disappear as long * as the MBean still has this listener. And since it references the * IdentityListener, that won't disappear either. But once the * RewriteListener is no longer referenced by the user's MBean, * there's nothing to stop its WeakReference from being cleared, * and then corresponding IdentityListener that is now only weakly * referenced from the key in the table. */ private static class ListenerTable extends WeakHashMap> { } private static class IdentityListener implements NotificationListener { private final NotificationListener userListener; IdentityListener(NotificationListener userListener) { this.userListener = userListener; } public void handleNotification( Notification notification, Object handback) { userListener.handleNotification(notification, handback); } @Override public boolean equals(Object o) { return (this == o); } @Override public int hashCode() { return System.identityHashCode(this); } } private static class RewriteListener implements NotificationListener { private final ObjectName name; private final Object userMBean; private final NotificationListener userListener; RewriteListener( ObjectName name, Object userMBean, NotificationListener userListener) { this.name = name; this.userMBean = userMBean; this.userListener = userListener; } public void handleNotification( Notification notification, Object handback) { if (notification.getSource() == userMBean) notification.setSource(name); userListener.handleNotification(notification, handback); } } private NotificationListener wrappedListener( ObjectName name, Object userMBean, NotificationListener userListener) throws InstanceNotFoundException { ListenerTable table = new ListenerTable(); ListenerTable oldTable = listenerMap.putIfAbsent(name, table); if (oldTable != null) table = oldTable; NotificationListener identityListener = new IdentityListener(userListener); synchronized (table) { NotificationListener rewriteListener = null; WeakReference wr = table.get(identityListener); if (wr != null) rewriteListener = wr.get(); if (rewriteListener == null) { rewriteListener = new RewriteListener( name, userMBean, identityListener); wr = new WeakReference(rewriteListener); table.put(identityListener, wr); } return rewriteListener; } } private DynamicMBean getMBean(ObjectName name) throws InstanceNotFoundException { DynamicMBean mbean = mbeans.get(name); if (mbean == null) throw new InstanceNotFoundException(name.toString()); return mbean; } private static interface WrapDynamicMBean extends DynamicMBean { public Object getWrappedMBean(); } private static class StandardWrapper implements WrapDynamicMBean, MBeanRegistration { private final Map attrMap = newMap(); private final Map> opMap = newMap(); private static class AttrMethods { Method getter, setter; } private final Object std; StandardWrapper(Object std) throws NotCompliantMBeanException { this.std = std; Class intf = mbeanInterface(std.getClass()); try { initMaps(intf); } catch (NotCompliantMBeanException e) { throw e; } catch (Exception e) { NotCompliantMBeanException x = new NotCompliantMBeanException(e.getMessage()); x.initCause(e); throw x; } } private static Class mbeanInterface(Class c) throws NotCompliantMBeanException { do { Class[] intfs = c.getInterfaces(); String intfName = c.getName() + "MBean"; for (Class intf : intfs) { if (intf.getName().equals(intfName)) return intf; } c = c.getSuperclass(); } while (c != null); throw new NotCompliantMBeanException( "Does not match Standard or Dynamic MBean patterns: " + c.getName()); } private void initMaps(Class intf) throws NotCompliantMBeanException { Method[] methods = intf.getMethods(); for (Method m : methods) { final String name = m.getName(); final int nParams = m.getParameterTypes().length; String attrName = ""; if (name.startsWith("get")) attrName = name.substring(3); else if (name.startsWith("is") && m.getReturnType() == boolean.class) attrName = name.substring(2); if (attrName.length() != 0 && m.getParameterTypes().length == 0 && m.getReturnType() != void.class) { // It's a getter // Check we don't have both isX and getX AttrMethods am = attrMap.get(attrName); if (am == null) am = new AttrMethods(); else { if (am.getter != null) { final String msg = "Attribute " + attrName + " has more than one getter"; throw new NotCompliantMBeanException(msg); } } am.getter = m; attrMap.put(attrName, am); } else if (name.startsWith("set") && name.length() > 3 && m.getParameterTypes().length == 1 && m.getReturnType() == void.class) { // It's a setter attrName = name.substring(3); AttrMethods am = attrMap.get(attrName); if (am == null) am = new AttrMethods(); else if (am.setter != null) { final String msg = "Attribute " + attrName + " has more than one setter"; throw new NotCompliantMBeanException(msg); } am.setter = m; attrMap.put(attrName, am); } else { // It's an operation List ops = opMap.get(name); if (ops == null) ops = newList(); ops.add(m); opMap.put(name, ops); } } /* Check that getters and setters are consistent. */ for (Map.Entry entry : attrMap.entrySet()) { AttrMethods am = entry.getValue(); if (am.getter != null && am.setter != null && am.getter.getReturnType() != am.setter.getParameterTypes()[0]) { final String msg = "Getter and setter for " + entry.getKey() + " have inconsistent types"; throw new NotCompliantMBeanException(msg); } } } public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException { AttrMethods am = attrMap.get(attribute); if (am == null || am.getter == null) throw new AttributeNotFoundException(attribute); return invokeMethod(am.getter); } public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { String name = attribute.getName(); AttrMethods am = attrMap.get(name); if (am == null || am.setter == null) throw new AttributeNotFoundException(name); invokeMethod(am.setter, attribute.getValue()); } public AttributeList getAttributes(String[] attributes) { AttributeList list = new AttributeList(); for (String attr : attributes) { try { list.add(new Attribute(attr, getAttribute(attr))); } catch (Exception e) { // OK: ignore per spec } } return list; } public AttributeList setAttributes(AttributeList attributes) { AttributeList list = new AttributeList(); // We carefully avoid using any new stuff from AttributeList here! for (Iterator it = attributes.iterator(); it.hasNext(); ) { Attribute attr = (Attribute) it.next(); try { setAttribute(attr); list.add(attr); } catch (Exception e) { // OK: ignore per spec } } return list; } public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException { if (params == null) params = new Object[0]; if (signature == null) signature = new String[0]; List methods = opMap.get(actionName); if (methods == null) { Exception x = new NoSuchMethodException(actionName); throw new MBeanException(x); } Method found = null; methodloop: for (Method m : methods) { Class[] msig = m.getParameterTypes(); if (msig.length != signature.length) continue methodloop; for (int i = 0; i < msig.length; i++) { if (!msig[i].getName().equals(signature[i])) continue methodloop; } found = m; break methodloop; } if (found == null) { Exception x = new NoSuchMethodException( actionName + Arrays.toString(signature)); throw new MBeanException(x); } return invokeMethod(found, params); } public MBeanInfo getMBeanInfo() { // Attributes List attrs = newList(); for (Map.Entry attr : attrMap.entrySet()) { String name = attr.getKey(); AttrMethods am = attr.getValue(); try { attrs.add(new MBeanAttributeInfo( name, name, am.getter, am.setter)); } catch (IntrospectionException e) { // grrr throw new RuntimeException(e); } } // Operations List ops = newList(); for (Map.Entry> op : opMap.entrySet()) { String name = op.getKey(); List methods = op.getValue(); for (Method m : methods) ops.add(new MBeanOperationInfo(name, m)); } // Constructors List constrs = newList(); for (Constructor constr : std.getClass().getConstructors()) constrs.add(new MBeanConstructorInfo("Constructor", constr)); // Notifications MBeanNotificationInfo[] notifs; if (std instanceof NotificationBroadcaster) notifs = ((NotificationBroadcaster) std).getNotificationInfo(); else notifs = null; String className = std.getClass().getName(); return new MBeanInfo( className, className, attrs.toArray(new MBeanAttributeInfo[0]), constrs.toArray(new MBeanConstructorInfo[0]), ops.toArray(new MBeanOperationInfo[0]), notifs); } private Object invokeMethod(Method m, Object... args) throws MBeanException, ReflectionException { return invokeSomething(m, std,args); } public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception { return mbeanRegistration(std).preRegister(server, name); } public void postRegister(Boolean registrationDone) { mbeanRegistration(std).postRegister(registrationDone); } public void preDeregister() throws Exception { mbeanRegistration(std).preDeregister(); } public void postDeregister() { mbeanRegistration(std).postDeregister(); } public Object getWrappedMBean() { return std; } } private DynamicMBean standardToDynamic(Object std) throws NotCompliantMBeanException { return new StandardWrapper(std); } // private static class NotifWrapper // implements WrapDynamicMBean, NotificationEmitter { // private final DynamicMBean mbean; // // NotifWrapper(DynamicMBean mbean) { // this.mbean = mbean; // } // // public Object getAttribute(String attribute) // throws AttributeNotFoundException, MBeanException, ReflectionException { // return mbean.getAttribute(attribute); // } // // public void setAttribute(Attribute attribute) // throws AttributeNotFoundException, InvalidAttributeValueException, // MBeanException, ReflectionException { // mbean.setAttribute(attribute); // } // // public AttributeList getAttributes(String[] attributes) { // return mbean.getAttributes(attributes); // } // // public AttributeList setAttributes(AttributeList attributes) { // return mbean.setAttributes(attributes); // } // // public Object invoke( // String actionName, Object[] params, String[] signature) // throws MBeanException, ReflectionException { // return mbean.invoke(actionName, params, signature); // } // // public MBeanInfo getMBeanInfo() { // return mbean.getMBeanInfo(); // } // // public void removeNotificationListener( // NotificationListener listener, NotificationFilter filter, Object handback) // throws ListenerNotFoundException { // ((NotificationEmitter) mbean).removeNotificationListener( // listener, filter, handback); // // ClassCastException if MBean is not an emitter // } // // public void addNotificationListener( // NotificationListener listener, NotificationFilter filter, Object handback) // throws IllegalArgumentException { // ((NotificationBroadcaster) mbean).addNotificationListener( // listener, filter, handback); // } // // public void removeNotificationListener(NotificationListener listener) // throws ListenerNotFoundException { // ((NotificationBroadcaster) mbean).removeNotificationListener(listener); // } // // public MBeanNotificationInfo[] getNotificationInfo() { // return ((NotificationBroadcaster) mbean).getNotificationInfo(); // } // // public Object getWrappedMBean() { // return getUserMBean(mbean); // } // } private static Object invokeSomething( AccessibleObject ao, Object target, Object[] args) throws MBeanException, ReflectionException { try { if (ao instanceof Method) return ((Method) ao).invoke(target, args); else return ((Constructor) ao).newInstance(args); } catch (InvocationTargetException e) { try { throw e.getCause(); } catch (RuntimeException x) { throw new RuntimeMBeanException(x); } catch (Error x) { throw new RuntimeErrorException(x); } catch (Exception x) { throw new MBeanException(x); } catch (Throwable x) { throw new RuntimeException(x); // neither Error nor Exception! } } catch (Exception e) { throw new ReflectionException(e); } } private static Object getUserMBean(DynamicMBean mbean) { if (mbean instanceof WrapDynamicMBean) return ((WrapDynamicMBean) mbean).getWrappedMBean(); return mbean; } private Object getUserMBean(ObjectName name) throws InstanceNotFoundException { return getUserMBean(getMBean(name)); } private static final MBeanRegistration noRegistration = new MBeanRegistration() { public ObjectName preRegister(MBeanServer server, ObjectName name) { return name; } public void postRegister(Boolean registrationDone) { } public void preDeregister() throws Exception { } public void postDeregister() { } }; private static MBeanRegistration mbeanRegistration(Object object) { if (object instanceof MBeanRegistration) return (MBeanRegistration) object; else return noRegistration; } private static List newList() { return new ArrayList(); } private static Map newMap() { return new HashMap(); } private static Set newSet() { return new HashSet(); } } }