jdk-24/jdk/test/javax/management/namespace/VirtualMBeanNotifTest.java
Daniel Fuchs b2e851f920 6746754: jmx namespace: test for leading separator missing
6669137: RFE: InstanceNotFoundException should have a constructor that takes an ObjectName
6746796: jmx namespaces: Several tests are missing an @bug or @run keyword

Note on 6669137: first implementation of 6669137 was actually pushed with 5072476 - here we only have a small update and a test case. Also re-fixes 6737133: Compilation failure of test/javax/management/eventService/LeaseManagerDeadlockTest.java which had failed.

Reviewed-by: emcmanus, yjoan
2008-09-10 16:27:13 +02:00

570 lines
23 KiB
Java

/*
* Copyright 2008 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.
*/
/*
* @test VirtualMBeanNotifTest.java
* @bug 5108776
* @build VirtualMBeanNotifTest Wombat WombatMBean
* @run main VirtualMBeanNotifTest
* @summary Test that Virtual MBeans can be implemented and emit notifs.
* @author Daniel Fuchs
*/
import java.lang.management.ManagementFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import javax.management.Attribute;
import javax.management.DynamicMBean;
import javax.management.InstanceNotFoundException;
import javax.management.JMException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanServer;
import javax.management.Notification;
import javax.management.NotificationBroadcaster;
import javax.management.NotificationEmitter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.RuntimeOperationsException;
import javax.management.StandardMBean;
import javax.management.event.EventSubscriber;
import javax.management.namespace.VirtualEventManager;
import javax.management.namespace.MBeanServerSupport;
public class VirtualMBeanNotifTest {
/**
* An invocation handler that can implement DynamicMBean,
* NotificationBroadcaster, NotificationEmitter.
* The invocation handler works by forwarding all calls received from
* the implemented interfaces through a wrapped MBeanServer object.
*/
public static class DynamicWrapper
implements InvocationHandler {
/**
* Inserts an additional class at the head of a signature array.
* @param first The first class in the signature
* @param rest The other classes in the signature
* @return A signature array, of length rest.length+1.
*/
static Class[] concat(Class first, Class... rest) {
if (rest == null || rest.length == 0) {
return new Class[] { first };
}
final Class[] sig = new Class[rest.length+1];
sig[0] = first;
System.arraycopy(rest, 0, sig, 1, rest.length);
return sig;
}
/**
* Inserts an additional object at the head of a parameters array.
* @param first The first object in the parameter array.
* @param rest The other objects in the parameter array.
* @return A parameter array, of length rest.length+1.
*/
static Object[] concat(Object first, Object... rest) {
if (rest == null || rest.length == 0) {
return new Object[] { first };
}
final Object[] params = new Object[rest.length+1];
params[0] = first;
System.arraycopy(rest, 0, params, 1, rest.length);
return params;
}
/**
* These two sets are used to check that all methods from
* implemented interfaces are mapped.
* unmapped is the set of methods that couldn't be mapped.
* mapped is the set of methods that could be mapped.
*/
final static Set<Method> unmapped = new HashSet<Method>();
final static Set<Method> mapped = new HashSet<Method>();
/**
* For each method define in one of the interfaces intf, tries
* to find a corresponding method in the reference class ref, where
* the method in ref has the same name, and takes an additional
* ObjectName as first parameter.
*
* So for instance, if ref is MBeanServer and intf is {DynamicMBean}
* the result map is:
* DynamicMBean.getAttribute -> MBeanServer.getAttribute
* DynamicMBean.setAttribute -> MBeanServer.setAttribute
* etc...
* If a method was mapped, it is additionally added to 'mapped'
* If a method couldn't be mapped, it is added to 'unmmapped'.
* In our example above, DynamicMBean.getNotificationInfo will end
* up in 'unmapped'.
*
* @param ref The reference class - to which calls will be forwarded
* with an additional ObjectName parameter inserted.
* @param intf The proxy interface classes - for which we must find an
* equivalent in 'ref'
* @return A map mapping the methods from intfs to the method of ref.
*/
static Map<Method,Method> makeMapFor(Class<?> ref, Class<?>... intf) {
final Map<Method,Method> map = new HashMap<Method,Method>();
for (Class<?> clazz : intf) {
for (Method m : clazz.getMethods()) {
try {
final Method m2 =
ref.getMethod(m.getName(),
concat(ObjectName.class,m.getParameterTypes()));
map.put(m,m2);
mapped.add(m);
} catch (Exception x) {
unmapped.add(m);
}
}
}
return map;
}
/**
* Tries to map all methods from DynamicMBean.class and
* NotificationEmitter.class to their equivalent in MBeanServer.
* This should be all the methods except
* DynamicMBean.getNotificationInfo.
*/
static final Map<Method,Method> mbeanmap =
makeMapFor(MBeanServer.class,DynamicMBean.class,
NotificationEmitter.class);
/**
* Tries to map all methods from DynamicMBean.class and
* NotificationEmitter.class to an equivalent in DynamicWrapper.
* This time only DynamicMBean.getNotificationInfo will be mapped.
*/
static final Map<Method,Method> selfmap =
makeMapFor(DynamicWrapper.class,DynamicMBean.class,
NotificationEmitter.class);
/**
* Now check that we have mapped all methods.
*/
static {
unmapped.removeAll(mapped);
if (unmapped.size() > 0)
throw new ExceptionInInitializerError("Couldn't map "+ unmapped);
}
/**
* The wrapped MBeanServer to which everything is delegated.
*/
private final MBeanServer server;
/**
* The name of the MBean we're proxying.
*/
private final ObjectName name;
DynamicWrapper(MBeanServer server, ObjectName name) {
this.server=server;
this.name=name;
}
/**
* Creates a new proxy for the given MBean. Implements
* NotificationEmitter/NotificationBroadcaster if the proxied
* MBean also does.
* @param name the name of the proxied MBean
* @param server the wrapped server
* @return a DynamicMBean proxy
* @throws javax.management.InstanceNotFoundException
*/
public static DynamicMBean newProxy(ObjectName name, MBeanServer server)
throws InstanceNotFoundException {
if (server.isInstanceOf(name,
NotificationEmitter.class.getName())) {
// implements NotificationEmitter
return (DynamicMBean)
Proxy.newProxyInstance(
DynamicWrapper.class.getClassLoader(),
new Class[] {NotificationEmitter.class,
DynamicMBean.class},
new DynamicWrapper(server, name));
}
if (server.isInstanceOf(name,
NotificationBroadcaster.class.getName())) {
// implements NotificationBroadcaster
return (DynamicMBean)
Proxy.newProxyInstance(
DynamicWrapper.class.getClassLoader(),
new Class[] {NotificationBroadcaster.class,
DynamicMBean.class},
new DynamicWrapper(server, name));
}
// Only implements DynamicMBean.
return (DynamicMBean)
Proxy.newProxyInstance(
DynamicWrapper.class.getClassLoader(),
new Class[] {DynamicMBean.class},
new DynamicWrapper(server, name));
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// Look for a method on this class (takes precedence)
final Method self = selfmap.get(method);
if (self != null)
return call(this,self,concat(name,args));
// no method found on this class, look for the same method
// on the wrapped MBeanServer
final Method mbean = mbeanmap.get(method);
if (mbean != null)
return call(server,mbean,concat(name,args));
// This isn't a method that can be forwarded to MBeanServer.
// If it's a method from Object, call it on this.
if (method.getDeclaringClass().equals(Object.class))
return call(this,method,args);
throw new NoSuchMethodException(method.getName());
}
// Call a method using reflection, unwraps invocation target exceptions
public Object call(Object handle, Method m, Object[] args)
throws Throwable {
try {
return m.invoke(handle, args);
} catch (InvocationTargetException x) {
throw x.getCause();
}
}
// this method is called when DynamicMBean.getNotificationInfo() is
// called. This is the method that should be mapped in
// 'selfmap'
public MBeanNotificationInfo[] getNotificationInfo(ObjectName name)
throws JMException {
return server.getMBeanInfo(name).getNotifications();
}
}
/**
* Just so that we can call the same test twice but with two
* different implementations of VirtualMBeanServerSupport.
*/
public static interface MBeanServerWrapperFactory {
public MBeanServer wrapMBeanServer(MBeanServer wrapped);
}
/**
* A VirtualMBeanServerSupport that wrapps an MBeanServer and does not
* use VirtualEventManager.
*/
public static class VirtualMBeanServerTest
extends MBeanServerSupport {
final MBeanServer wrapped;
public VirtualMBeanServerTest(MBeanServer wrapped) {
this.wrapped=wrapped;
}
@Override
public DynamicMBean getDynamicMBeanFor(final ObjectName name)
throws InstanceNotFoundException {
if (wrapped.isRegistered(name))
return DynamicWrapper.newProxy(name,wrapped);
throw new InstanceNotFoundException(String.valueOf(name));
}
@Override
protected Set<ObjectName> getNames() {
return wrapped.queryNames(null, null);
}
public final static MBeanServerWrapperFactory factory =
new MBeanServerWrapperFactory() {
public MBeanServer wrapMBeanServer(MBeanServer wrapped) {
return new VirtualMBeanServerTest(wrapped);
}
@Override
public String toString() {
return VirtualMBeanServerTest.class.getName();
}
};
}
/**
* A VirtualMBeanServerSupport that wrapps an MBeanServer and
* uses a VirtualEventManager.
*/
public static class VirtualMBeanServerTest2
extends VirtualMBeanServerTest {
final EventSubscriber sub;
final NotificationListener nl;
final VirtualEventManager mgr;
/**
* We use an EventSubscriber to subscribe for all notifications from
* the wrapped MBeanServer, and publish them through a
* VirtualEventManager. Not a very efficient way of doing things.
* @param wrapped
*/
public VirtualMBeanServerTest2(MBeanServer wrapped) {
super(wrapped);
this.sub = EventSubscriber.getEventSubscriber(wrapped);
this.mgr = new VirtualEventManager();
this.nl = new NotificationListener() {
public void handleNotification(Notification notification, Object handback) {
mgr.publish((ObjectName)notification.getSource(), notification);
}
};
try {
sub.subscribe(ObjectName.WILDCARD, nl, null, null);
} catch (RuntimeException x) {
throw x;
} catch (Exception x) {
throw new IllegalStateException("can't subscribe for notifications!");
}
}
@Override
public NotificationEmitter
getNotificationEmitterFor(ObjectName name)
throws InstanceNotFoundException {
final DynamicMBean mbean = getDynamicMBeanFor(name);
if (mbean instanceof NotificationEmitter)
return mgr.getNotificationEmitterFor(name);
return null;
}
public final static MBeanServerWrapperFactory factory =
new MBeanServerWrapperFactory() {
public MBeanServer wrapMBeanServer(MBeanServer wrapped) {
return new VirtualMBeanServerTest2(wrapped);
}
@Override
public String toString() {
return VirtualMBeanServerTest2.class.getName();
}
};
}
public static void test(MBeanServerWrapperFactory factory) throws Exception {
final MBeanServer server = ManagementFactory.getPlatformMBeanServer();
// names[] are NotificationEmitters
final ObjectName[] emitters = new ObjectName[2];
// shields[] have been shielded by wrapping them in a StandardMBean,
// so although the resource is an MBean that implements
// NotificationEmitter, the registered MBean (the wrapper) doesn't.
final ObjectName[] shielded = new ObjectName[2];
final List<ObjectName> registered = new ArrayList<ObjectName>(4);
try {
// register two MBeans before wrapping
server.registerMBean(new Wombat(),
emitters[0] = new ObjectName("bush:type=Wombat,name=wom"));
registered.add(emitters[0]);
// we shield the second MBean in a StandardMBean so that it does
// not appear as a NotificationEmitter.
server.registerMBean(
new StandardMBean(new Wombat(), WombatMBean.class),
shielded[0] = new ObjectName("bush:type=Wombat,name=womshield"));
registered.add(shielded[0]);
final MBeanServer vserver = factory.wrapMBeanServer(server);
// register two other MBeans after wrapping
server.registerMBean(new Wombat(),
emitters[1] = new ObjectName("bush:type=Wombat,name=bat"));
registered.add(emitters[1]);
// we shield the second MBean in a StandardMBean so that it does
// not appear as a NotificationEmitter.
server.registerMBean(
new StandardMBean(new Wombat(), WombatMBean.class),
shielded[1] = new ObjectName("bush:type=Wombat,name=batshield"));
registered.add(shielded[1]);
// Call test with this config - we have two wombats who broadcast
// notifs (emitters) and two wombats who don't (shielded).
test(vserver, emitters, shielded);
System.out.println("*** Test passed for: " + factory);
} finally {
// Clean up the platform mbean server for the next test...
for (ObjectName n : registered) {
try {
server.unregisterMBean(n);
} catch (Exception x) {
x.printStackTrace();
}
}
}
}
/**
* Perform the actual test.
* @param vserver A virtual MBeanServerSupport implementation
* @param emitters Names of NotificationBroadcaster MBeans
* @param shielded Names of non NotificationBroadcaster MBeans
* @throws java.lang.Exception
*/
public static void test(MBeanServer vserver, ObjectName[] emitters,
ObjectName[] shielded) throws Exception {
// To catch exception in NotificationListener
final List<Exception> fail = new CopyOnWriteArrayList<Exception>();
// A queue of received notifications
final BlockingQueue<Notification> notifs =
new ArrayBlockingQueue<Notification>(50);
// A notification listener that puts the notification it receives
// in the queue.
final NotificationListener handler = new NotificationListener() {
public void handleNotification(Notification notification,
Object handback) {
try {
notifs.put(notification);
} catch (Exception x) {
fail.add(x);
}
}
};
// A list of attribute names for which we might receive an
// exception. If an exception is received when getting these
// attributes - the test will not fail.
final List<String> exceptions = Arrays.asList( new String[] {
"UsageThresholdCount","UsageThreshold","UsageThresholdExceeded",
"CollectionUsageThresholdCount","CollectionUsageThreshold",
"CollectionUsageThresholdExceeded"
});
// This is just a sanity check. Get all attributes of all MBeans.
for (ObjectName n : vserver.queryNames(null, null)) {
final MBeanInfo m = vserver.getMBeanInfo(n);
for (MBeanAttributeInfo mba : m.getAttributes()) {
// System.out.println(n+":");
Object val;
try {
val = vserver.getAttribute(n, mba.getName());
} catch (Exception x) {
// only accept exception for those attributes that
// have a valid reason to fail...
if (exceptions.contains(mba.getName())) val = x;
else throw new Exception("Failed to get " +
mba.getName() + " from " + n,x);
}
// System.out.println("\t "+mba.getName()+": "+ val);
}
}
// The actual tests. Register for notifications with notif emitters
for (ObjectName n : emitters) {
vserver.addNotificationListener(n, handler, null, n);
}
// Trigger the emission of notifications, check that we received them.
for (ObjectName n : emitters) {
vserver.setAttribute(n,
new Attribute("Caption","I am a new wombat!"));
final Notification notif = notifs.poll(4, TimeUnit.SECONDS);
if (!notif.getSource().equals(n))
throw new Exception("Bad source for "+ notif);
if (fail.size() > 0)
throw new Exception("Failed to handle notif",fail.remove(0));
}
// Check that we didn't get more notifs than expected
if (notifs.size() > 0)
throw new Exception("Extra notifications in queue: "+notifs);
// Check that if the MBean doesn't exist, we get InstanceNotFound.
try {
vserver.addNotificationListener(new ObjectName("toto:toto=toto"),
handler, null, null);
throw new Exception("toto:toto=toto doesn't throw INFE");
} catch (InstanceNotFoundException x) {
System.out.println("Received "+x+" as expected.");
}
// For those MBeans that shouldn't be NotificationEmitters, check that
// we get IllegalArgumentException
for (ObjectName n : shielded) {
try {
vserver.addNotificationListener(n, handler, null, n);
} catch (RuntimeOperationsException x) {
System.out.println("Received "+x+" as expected.");
System.out.println("Cause is: "+x.getCause());
if (!(x.getCause() instanceof IllegalArgumentException))
throw new Exception("was expecting IllegalArgumentException cause. Got "+x.getCause(),x);
}
}
// Sanity check. Remove our listeners.
for (ObjectName n : emitters) {
vserver.removeNotificationListener(n, handler, null, n);
}
// That's it.
// Sanity check: we shouldn't have received any new notif.
if (notifs.size() > 0)
throw new Exception("Extra notifications in queue: "+notifs);
// The NotifListener shouldn't have logged any new exception.
if (fail.size() > 0)
throw new Exception("Failed to handle notif",fail.remove(0));
}
public static void main(String[] args) throws Exception {
// test with a regular MBeanServer (no VirtualMBeanServerSupport)
final MBeanServerWrapperFactory identity =
new MBeanServerWrapperFactory() {
public MBeanServer wrapMBeanServer(MBeanServer wrapped) {
return wrapped;
}
};
test(identity);
// test with no EventManager
test(VirtualMBeanServerTest.factory);
// test with VirtualEventManager
test(VirtualMBeanServerTest2.factory);
}
}