jdk/test/javax/management/namespace/VirtualMBeanNotifTest.java
changeset 4159 9e3aae7675f1
parent 4158 0b4d21bc8b5c
parent 4156 acaa49a2768a
child 4160 bda0a85afcb7
equal deleted inserted replaced
4158:0b4d21bc8b5c 4159:9e3aae7675f1
     1 /*
       
     2  * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.
       
     8  *
       
     9  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    12  * version 2 for more details (a copy is included in the LICENSE file that
       
    13  * accompanied this code).
       
    14  *
       
    15  * You should have received a copy of the GNU General Public License version
       
    16  * 2 along with this work; if not, write to the Free Software Foundation,
       
    17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18  *
       
    19  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
       
    20  * CA 95054 USA or visit www.sun.com if you need additional information or
       
    21  * have any questions.
       
    22  */
       
    23 
       
    24 /*
       
    25  * @test VirtualMBeanNotifTest.java
       
    26  * @bug 5108776
       
    27  * @build VirtualMBeanNotifTest Wombat WombatMBean
       
    28  * @run main VirtualMBeanNotifTest
       
    29  * @summary Test that Virtual MBeans can be implemented and emit notifs.
       
    30  * @author  Daniel Fuchs
       
    31  */
       
    32 import java.lang.management.ManagementFactory;
       
    33 import java.lang.reflect.InvocationHandler;
       
    34 import java.lang.reflect.InvocationTargetException;
       
    35 import java.lang.reflect.Method;
       
    36 import java.lang.reflect.Proxy;
       
    37 import java.util.ArrayList;
       
    38 import java.util.Arrays;
       
    39 import java.util.HashMap;
       
    40 import java.util.HashSet;
       
    41 import java.util.List;
       
    42 import java.util.Map;
       
    43 import java.util.Set;
       
    44 import java.util.concurrent.ArrayBlockingQueue;
       
    45 import java.util.concurrent.BlockingQueue;
       
    46 import java.util.concurrent.CopyOnWriteArrayList;
       
    47 import java.util.concurrent.TimeUnit;
       
    48 import javax.management.Attribute;
       
    49 import javax.management.DynamicMBean;
       
    50 import javax.management.InstanceNotFoundException;
       
    51 import javax.management.JMException;
       
    52 import javax.management.MBeanAttributeInfo;
       
    53 import javax.management.MBeanInfo;
       
    54 import javax.management.MBeanNotificationInfo;
       
    55 import javax.management.MBeanServer;
       
    56 import javax.management.Notification;
       
    57 import javax.management.NotificationBroadcaster;
       
    58 import javax.management.NotificationEmitter;
       
    59 import javax.management.NotificationListener;
       
    60 import javax.management.ObjectName;
       
    61 import javax.management.RuntimeOperationsException;
       
    62 import javax.management.StandardMBean;
       
    63 import javax.management.event.EventSubscriber;
       
    64 import javax.management.namespace.VirtualEventManager;
       
    65 import javax.management.namespace.MBeanServerSupport;
       
    66 
       
    67 public class VirtualMBeanNotifTest {
       
    68 
       
    69     /**
       
    70      * An invocation handler that can implement DynamicMBean,
       
    71      * NotificationBroadcaster, NotificationEmitter.
       
    72      * The invocation handler works by forwarding all calls received from
       
    73      * the implemented interfaces through a wrapped MBeanServer object.
       
    74      */
       
    75     public static class DynamicWrapper
       
    76             implements InvocationHandler {
       
    77 
       
    78         /**
       
    79          * Inserts an additional class at the head of a signature array.
       
    80          * @param first The first class in the signature
       
    81          * @param rest  The other classes in the signature
       
    82          * @return A signature array, of length rest.length+1.
       
    83          */
       
    84         static Class[] concat(Class first, Class... rest) {
       
    85             if (rest == null || rest.length == 0) {
       
    86                 return new Class[] { first };
       
    87             }
       
    88             final Class[] sig = new Class[rest.length+1];
       
    89             sig[0] = first;
       
    90             System.arraycopy(rest, 0, sig, 1, rest.length);
       
    91             return sig;
       
    92         }
       
    93 
       
    94         /**
       
    95          * Inserts an additional object at the head of a parameters array.
       
    96          * @param first The first object in the parameter array.
       
    97          * @param rest  The other objects in the parameter array.
       
    98          * @return A parameter array, of length rest.length+1.
       
    99          */
       
   100         static Object[] concat(Object first, Object... rest) {
       
   101             if (rest == null || rest.length == 0) {
       
   102                 return new Object[] { first };
       
   103             }
       
   104             final Object[] params = new Object[rest.length+1];
       
   105             params[0] = first;
       
   106             System.arraycopy(rest, 0, params, 1, rest.length);
       
   107             return params;
       
   108         }
       
   109 
       
   110         /**
       
   111          * These two sets are used to check that all methods from
       
   112          * implemented interfaces are mapped.
       
   113          * unmapped is the set of methods that couldn't be mapped.
       
   114          * mapped is the set of methods that could be mapped.
       
   115          */
       
   116         final static Set<Method> unmapped = new HashSet<Method>();
       
   117         final static Set<Method> mapped = new HashSet<Method>();
       
   118 
       
   119         /**
       
   120          * For each method define in one of the interfaces intf, tries
       
   121          * to find a corresponding method in the reference class ref, where
       
   122          * the method in ref has the same name, and takes an additional
       
   123          * ObjectName as first parameter.
       
   124          *
       
   125          * So for instance, if ref is MBeanServer and intf is {DynamicMBean}
       
   126          * the result map is:
       
   127          *     DynamicMBean.getAttribute -> MBeanServer.getAttribute
       
   128          *     DynamicMBean.setAttribute -> MBeanServer.setAttribute
       
   129          *     etc...
       
   130          * If a method was mapped, it is additionally added to 'mapped'
       
   131          * If a method couldn't be mapped, it is added to 'unmmapped'.
       
   132          * In our example above, DynamicMBean.getNotificationInfo will end
       
   133          * up in 'unmapped'.
       
   134          *
       
   135          * @param ref   The reference class - to which calls will be forwarded
       
   136          *              with an additional ObjectName parameter inserted.
       
   137          * @param intf  The proxy interface classes - for which we must find an
       
   138          *              equivalent in 'ref'
       
   139          * @return A map mapping the methods from intfs to the method of ref.
       
   140          */
       
   141         static Map<Method,Method> makeMapFor(Class<?> ref, Class<?>... intf) {
       
   142             final Map<Method,Method> map = new HashMap<Method,Method>();
       
   143             for (Class<?> clazz : intf) {
       
   144                 for (Method m : clazz.getMethods()) {
       
   145                     try {
       
   146                         final Method m2 =
       
   147                             ref.getMethod(m.getName(),
       
   148                             concat(ObjectName.class,m.getParameterTypes()));
       
   149                         map.put(m,m2);
       
   150                         mapped.add(m);
       
   151                     } catch (Exception x) {
       
   152                         unmapped.add(m);
       
   153                     }
       
   154                 }
       
   155             }
       
   156             return map;
       
   157         }
       
   158 
       
   159         /**
       
   160          * Tries to map all methods from DynamicMBean.class and
       
   161          * NotificationEmitter.class to their equivalent in MBeanServer.
       
   162          * This should be all the methods except
       
   163          * DynamicMBean.getNotificationInfo.
       
   164          */
       
   165         static final Map<Method,Method> mbeanmap =
       
   166                 makeMapFor(MBeanServer.class,DynamicMBean.class,
       
   167                 NotificationEmitter.class);
       
   168         /**
       
   169          * Tries to map all methods from DynamicMBean.class and
       
   170          * NotificationEmitter.class to an equivalent in DynamicWrapper.
       
   171          * This time only DynamicMBean.getNotificationInfo will be mapped.
       
   172          */
       
   173         static final Map<Method,Method> selfmap =
       
   174                 makeMapFor(DynamicWrapper.class,DynamicMBean.class,
       
   175                 NotificationEmitter.class);
       
   176 
       
   177         /**
       
   178          * Now check that we have mapped all methods.
       
   179          */
       
   180         static {
       
   181             unmapped.removeAll(mapped);
       
   182             if (unmapped.size() > 0)
       
   183                 throw new ExceptionInInitializerError("Couldn't map "+ unmapped);
       
   184         }
       
   185 
       
   186         /**
       
   187          * The wrapped MBeanServer to which everything is delegated.
       
   188          */
       
   189         private final MBeanServer server;
       
   190 
       
   191         /**
       
   192          * The name of the MBean we're proxying.
       
   193          */
       
   194         private final ObjectName name;
       
   195         DynamicWrapper(MBeanServer server, ObjectName name) {
       
   196             this.server=server;
       
   197             this.name=name;
       
   198         }
       
   199 
       
   200         /**
       
   201          * Creates a new proxy for the given MBean. Implements
       
   202          * NotificationEmitter/NotificationBroadcaster if the proxied
       
   203          * MBean also does.
       
   204          * @param name    the name of the proxied MBean
       
   205          * @param server  the wrapped server
       
   206          * @return a DynamicMBean proxy
       
   207          * @throws javax.management.InstanceNotFoundException
       
   208          */
       
   209         public static DynamicMBean newProxy(ObjectName name, MBeanServer server)
       
   210             throws InstanceNotFoundException {
       
   211             if (server.isInstanceOf(name,
       
   212                     NotificationEmitter.class.getName())) {
       
   213                 // implements NotificationEmitter
       
   214                 return (DynamicMBean)
       
   215                         Proxy.newProxyInstance(
       
   216                         DynamicWrapper.class.getClassLoader(),
       
   217                         new Class[] {NotificationEmitter.class,
       
   218                         DynamicMBean.class},
       
   219                         new DynamicWrapper(server, name));
       
   220             }
       
   221             if (server.isInstanceOf(name,
       
   222                     NotificationBroadcaster.class.getName())) {
       
   223                 // implements NotificationBroadcaster
       
   224                 return (DynamicMBean)
       
   225                         Proxy.newProxyInstance(
       
   226                         DynamicWrapper.class.getClassLoader(),
       
   227                         new Class[] {NotificationBroadcaster.class,
       
   228                         DynamicMBean.class},
       
   229                         new DynamicWrapper(server, name));
       
   230             }
       
   231             // Only implements DynamicMBean.
       
   232             return (DynamicMBean)
       
   233                         Proxy.newProxyInstance(
       
   234                         DynamicWrapper.class.getClassLoader(),
       
   235                         new Class[] {DynamicMBean.class},
       
   236                         new DynamicWrapper(server, name));
       
   237         }
       
   238 
       
   239         public Object invoke(Object proxy, Method method, Object[] args)
       
   240                 throws Throwable {
       
   241             // Look for a method on this class (takes precedence)
       
   242             final Method self = selfmap.get(method);
       
   243             if (self != null)
       
   244                 return call(this,self,concat(name,args));
       
   245 
       
   246             // no method found on this class, look for the same method
       
   247             // on the wrapped MBeanServer
       
   248             final Method mbean = mbeanmap.get(method);
       
   249             if (mbean != null)
       
   250                 return call(server,mbean,concat(name,args));
       
   251 
       
   252             // This isn't a method that can be forwarded to MBeanServer.
       
   253             // If it's a method from Object, call it on this.
       
   254             if (method.getDeclaringClass().equals(Object.class))
       
   255                 return call(this,method,args);
       
   256             throw new NoSuchMethodException(method.getName());
       
   257         }
       
   258 
       
   259         // Call a method using reflection, unwraps invocation target exceptions
       
   260         public Object call(Object handle, Method m, Object[] args)
       
   261                 throws Throwable {
       
   262             try {
       
   263                 return m.invoke(handle, args);
       
   264             } catch (InvocationTargetException x) {
       
   265                throw x.getCause();
       
   266             }
       
   267         }
       
   268 
       
   269         // this method is called when DynamicMBean.getNotificationInfo() is
       
   270         // called. This is the method that should be mapped in
       
   271         // 'selfmap'
       
   272         public MBeanNotificationInfo[] getNotificationInfo(ObjectName name)
       
   273             throws JMException {
       
   274             return server.getMBeanInfo(name).getNotifications();
       
   275         }
       
   276     }
       
   277 
       
   278     /**
       
   279      * Just so that we can call the same test twice but with two
       
   280      * different implementations of VirtualMBeanServerSupport.
       
   281      */
       
   282     public static interface MBeanServerWrapperFactory {
       
   283         public MBeanServer wrapMBeanServer(MBeanServer wrapped);
       
   284     }
       
   285 
       
   286     /**
       
   287      * A VirtualMBeanServerSupport that wrapps an MBeanServer and does not
       
   288      * use VirtualEventManager.
       
   289      */
       
   290     public static class VirtualMBeanServerTest
       
   291             extends MBeanServerSupport {
       
   292 
       
   293         final MBeanServer wrapped;
       
   294 
       
   295         public VirtualMBeanServerTest(MBeanServer wrapped) {
       
   296             this.wrapped=wrapped;
       
   297         }
       
   298 
       
   299         @Override
       
   300         public DynamicMBean getDynamicMBeanFor(final ObjectName name)
       
   301                 throws InstanceNotFoundException {
       
   302             if (wrapped.isRegistered(name))
       
   303                 return DynamicWrapper.newProxy(name,wrapped);
       
   304             throw new InstanceNotFoundException(String.valueOf(name));
       
   305         }
       
   306 
       
   307         @Override
       
   308         protected Set<ObjectName> getNames() {
       
   309             return wrapped.queryNames(null, null);
       
   310         }
       
   311 
       
   312         public final static MBeanServerWrapperFactory factory =
       
   313                 new MBeanServerWrapperFactory() {
       
   314 
       
   315             public MBeanServer wrapMBeanServer(MBeanServer wrapped) {
       
   316                 return new VirtualMBeanServerTest(wrapped);
       
   317             }
       
   318             @Override
       
   319             public String toString() {
       
   320                 return VirtualMBeanServerTest.class.getName();
       
   321             }
       
   322         };
       
   323     }
       
   324 
       
   325      /**
       
   326      * A VirtualMBeanServerSupport that wrapps an MBeanServer and
       
   327      * uses a VirtualEventManager.
       
   328      */
       
   329     public static class VirtualMBeanServerTest2
       
   330             extends VirtualMBeanServerTest {
       
   331 
       
   332         final EventSubscriber sub;
       
   333         final NotificationListener nl;
       
   334         final VirtualEventManager  mgr;
       
   335 
       
   336         /**
       
   337          * We use an EventSubscriber to subscribe for all notifications from
       
   338          * the wrapped MBeanServer, and publish them through a
       
   339          * VirtualEventManager. Not a very efficient way of doing things.
       
   340          * @param wrapped
       
   341          */
       
   342         public VirtualMBeanServerTest2(MBeanServer wrapped) {
       
   343             super(wrapped);
       
   344             this.sub = EventSubscriber.getEventSubscriber(wrapped);
       
   345             this.mgr = new VirtualEventManager();
       
   346             this.nl = new NotificationListener() {
       
   347                 public void handleNotification(Notification notification, Object handback) {
       
   348                     mgr.publish((ObjectName)notification.getSource(), notification);
       
   349                 }
       
   350             };
       
   351             try {
       
   352                 sub.subscribe(ObjectName.WILDCARD, nl, null, null);
       
   353             } catch (RuntimeException x) {
       
   354                 throw x;
       
   355             } catch (Exception x) {
       
   356                 throw new IllegalStateException("can't subscribe for notifications!");
       
   357             }
       
   358         }
       
   359 
       
   360         @Override
       
   361         public NotificationEmitter
       
   362                 getNotificationEmitterFor(ObjectName name)
       
   363                 throws InstanceNotFoundException {
       
   364             final DynamicMBean mbean = getDynamicMBeanFor(name);
       
   365             if (mbean instanceof NotificationEmitter)
       
   366                 return mgr.getNotificationEmitterFor(name);
       
   367             return null;
       
   368         }
       
   369 
       
   370         public final static MBeanServerWrapperFactory factory =
       
   371                 new MBeanServerWrapperFactory() {
       
   372 
       
   373             public MBeanServer wrapMBeanServer(MBeanServer wrapped) {
       
   374                 return new VirtualMBeanServerTest2(wrapped);
       
   375             }
       
   376             @Override
       
   377             public String toString() {
       
   378                 return VirtualMBeanServerTest2.class.getName();
       
   379             }
       
   380         };
       
   381     }
       
   382 
       
   383 
       
   384     public static void test(MBeanServerWrapperFactory factory) throws Exception {
       
   385         final MBeanServer server = ManagementFactory.getPlatformMBeanServer();
       
   386 
       
   387         // names[] are NotificationEmitters
       
   388         final ObjectName[] emitters = new ObjectName[2];
       
   389         // shields[] have been shielded by wrapping them in a StandardMBean,
       
   390         // so although the resource is an MBean that implements
       
   391         // NotificationEmitter, the registered MBean (the wrapper) doesn't.
       
   392         final ObjectName[] shielded = new ObjectName[2];
       
   393 
       
   394         final List<ObjectName> registered = new ArrayList<ObjectName>(4);
       
   395 
       
   396         try {
       
   397             // register two MBeans before wrapping
       
   398             server.registerMBean(new Wombat(),
       
   399                     emitters[0] = new ObjectName("bush:type=Wombat,name=wom"));
       
   400             registered.add(emitters[0]);
       
   401 
       
   402             // we shield the second MBean in a StandardMBean so that it does
       
   403             // not appear as a NotificationEmitter.
       
   404             server.registerMBean(
       
   405                     new StandardMBean(new Wombat(), WombatMBean.class),
       
   406                     shielded[0] = new ObjectName("bush:type=Wombat,name=womshield"));
       
   407             registered.add(shielded[0]);
       
   408 
       
   409             final MBeanServer vserver = factory.wrapMBeanServer(server);
       
   410 
       
   411             // register two other MBeans after wrapping
       
   412             server.registerMBean(new Wombat(),
       
   413                     emitters[1] = new ObjectName("bush:type=Wombat,name=bat"));
       
   414             registered.add(emitters[1]);
       
   415 
       
   416             // we shield the second MBean in a StandardMBean so that it does
       
   417             // not appear as a NotificationEmitter.
       
   418             server.registerMBean(
       
   419                     new StandardMBean(new Wombat(), WombatMBean.class),
       
   420                     shielded[1] = new ObjectName("bush:type=Wombat,name=batshield"));
       
   421             registered.add(shielded[1]);
       
   422 
       
   423             // Call test with this config - we have two wombats who broadcast
       
   424             // notifs (emitters) and two wombats who don't (shielded).
       
   425             test(vserver, emitters, shielded);
       
   426 
       
   427             System.out.println("*** Test passed for: " + factory);
       
   428         } finally {
       
   429             // Clean up the platform mbean server for the next test...
       
   430             for (ObjectName n : registered) {
       
   431                 try {
       
   432                     server.unregisterMBean(n);
       
   433                 } catch (Exception x) {
       
   434                     x.printStackTrace();
       
   435                 }
       
   436             }
       
   437         }
       
   438     }
       
   439 
       
   440     /**
       
   441      * Perform the actual test.
       
   442      * @param vserver    A virtual MBeanServerSupport implementation
       
   443      * @param emitters   Names of NotificationBroadcaster MBeans
       
   444      * @param shielded   Names of non NotificationBroadcaster MBeans
       
   445      * @throws java.lang.Exception
       
   446      */
       
   447     public static void test(MBeanServer vserver, ObjectName[] emitters,
       
   448             ObjectName[] shielded) throws Exception {
       
   449 
       
   450         // To catch exception in NotificationListener
       
   451         final List<Exception> fail = new CopyOnWriteArrayList<Exception>();
       
   452 
       
   453         // A queue of received notifications
       
   454         final BlockingQueue<Notification> notifs =
       
   455                 new ArrayBlockingQueue<Notification>(50);
       
   456 
       
   457         // A notification listener that puts the notification it receives
       
   458         // in the queue.
       
   459         final NotificationListener handler = new NotificationListener() {
       
   460 
       
   461             public void handleNotification(Notification notification,
       
   462                     Object handback) {
       
   463                 try {
       
   464                     notifs.put(notification);
       
   465                 } catch (Exception x) {
       
   466                     fail.add(x);
       
   467                 }
       
   468             }
       
   469         };
       
   470 
       
   471         // A list of attribute names for which we might receive an
       
   472         // exception. If an exception is received when getting these
       
   473         // attributes - the test will not fail.
       
   474         final List<String> exceptions = Arrays.asList( new String[] {
       
   475            "UsageThresholdCount","UsageThreshold","UsageThresholdExceeded",
       
   476            "CollectionUsageThresholdCount","CollectionUsageThreshold",
       
   477            "CollectionUsageThresholdExceeded"
       
   478         });
       
   479 
       
   480         // This is just a sanity check. Get all attributes of all MBeans.
       
   481         for (ObjectName n : vserver.queryNames(null, null)) {
       
   482             final MBeanInfo m = vserver.getMBeanInfo(n);
       
   483             for (MBeanAttributeInfo mba : m.getAttributes()) {
       
   484                 // System.out.println(n+":");
       
   485                 Object val;
       
   486                 try {
       
   487                     val = vserver.getAttribute(n, mba.getName());
       
   488                 } catch (Exception x) {
       
   489                     // only accept exception for those attributes that
       
   490                     // have a valid reason to fail...
       
   491                     if (exceptions.contains(mba.getName())) val = x;
       
   492                     else throw new Exception("Failed to get " +
       
   493                             mba.getName() + " from " + n,x);
       
   494                 }
       
   495                 // System.out.println("\t "+mba.getName()+": "+ val);
       
   496             }
       
   497         }
       
   498 
       
   499         // The actual tests. Register for notifications with notif emitters
       
   500         for (ObjectName n : emitters) {
       
   501             vserver.addNotificationListener(n, handler, null, n);
       
   502         }
       
   503 
       
   504         // Trigger the emission of notifications, check that we received them.
       
   505         for (ObjectName n : emitters) {
       
   506             vserver.setAttribute(n,
       
   507                     new Attribute("Caption","I am a new wombat!"));
       
   508             final Notification notif = notifs.poll(4, TimeUnit.SECONDS);
       
   509             if (!notif.getSource().equals(n))
       
   510                 throw new Exception("Bad source for "+ notif);
       
   511             if (fail.size() > 0)
       
   512                 throw new Exception("Failed to handle notif",fail.remove(0));
       
   513         }
       
   514 
       
   515         // Check that we didn't get more notifs than expected
       
   516         if (notifs.size() > 0)
       
   517             throw new Exception("Extra notifications in queue: "+notifs);
       
   518 
       
   519         // Check that if the MBean doesn't exist, we get InstanceNotFound.
       
   520         try {
       
   521             vserver.addNotificationListener(new ObjectName("toto:toto=toto"),
       
   522                     handler, null, null);
       
   523             throw new Exception("toto:toto=toto doesn't throw INFE");
       
   524         } catch (InstanceNotFoundException x) {
       
   525             System.out.println("Received "+x+" as expected.");
       
   526         }
       
   527 
       
   528         // For those MBeans that shouldn't be NotificationEmitters, check that
       
   529         // we get IllegalArgumentException
       
   530         for (ObjectName n : shielded) {
       
   531             try {
       
   532                 vserver.addNotificationListener(n, handler, null, n);
       
   533             } catch (RuntimeOperationsException x) {
       
   534                 System.out.println("Received "+x+" as expected.");
       
   535                 System.out.println("Cause is: "+x.getCause());
       
   536                 if (!(x.getCause() instanceof IllegalArgumentException))
       
   537                     throw new Exception("was expecting IllegalArgumentException cause. Got "+x.getCause(),x);
       
   538             }
       
   539         }
       
   540 
       
   541         // Sanity check. Remove our listeners.
       
   542         for (ObjectName n : emitters) {
       
   543             vserver.removeNotificationListener(n, handler, null, n);
       
   544         }
       
   545 
       
   546         // That's it.
       
   547         // Sanity check: we shouldn't have received any new notif.
       
   548         if (notifs.size() > 0)
       
   549             throw new Exception("Extra notifications in queue: "+notifs);
       
   550         // The NotifListener shouldn't have logged any new exception.
       
   551         if (fail.size() > 0)
       
   552                 throw new Exception("Failed to handle notif",fail.remove(0));
       
   553     }
       
   554 
       
   555     public static void main(String[] args) throws Exception {
       
   556         // test with a regular MBeanServer (no VirtualMBeanServerSupport)
       
   557         final MBeanServerWrapperFactory identity =
       
   558                 new MBeanServerWrapperFactory() {
       
   559             public MBeanServer wrapMBeanServer(MBeanServer wrapped) {
       
   560                 return wrapped;
       
   561             }
       
   562         };
       
   563         test(identity);
       
   564         // test with no EventManager
       
   565         test(VirtualMBeanServerTest.factory);
       
   566         // test with VirtualEventManager
       
   567         test(VirtualMBeanServerTest2.factory);
       
   568     }
       
   569 }