/*
 * Copyright (C) The MX4J Contributors.
 * All rights reserved.
 *
 * This software is distributed under the terms of the MX4J License version 1.0.
 * See the terms of the MX4J License in the documentation provided with this software.
 */

package test.javax.management.remote;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Map;

import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerNotification;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationFilter;
import javax.management.NotificationFilterSupport;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.loading.MLet;
import javax.management.relation.MBeanServerNotificationFilter;
import javax.management.remote.JMXConnectionNotification;
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;

import mx4j.remote.MX4JRemoteConstants;
import test.MX4JTestCase;
import test.MutableBoolean;
import test.MutableInteger;
import test.MutableObject;

/**
 * @version $Revision: 1.10 $
 */
public abstract class JMXNotificationsTestCase extends MX4JTestCase
{
   public JMXNotificationsTestCase(String name)
   {
      super(name);
   }

   protected void tearDown() throws Exception
   {
      sleep(5000);
   }

   public abstract JMXServiceURL createJMXConnectorServerAddress() throws MalformedURLException;

   public abstract Map getEnvironment();

   public void testConnectionNotificationOpenedOnServer() throws Exception
   {
      JMXConnectorServer cntorServer = null;
      try
      {
         JMXServiceURL url = createJMXConnectorServerAddress();
         cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, getEnvironment(), newMBeanServer());
         cntorServer.start();
         sleep(5000);

         final MutableObject holder = new MutableObject(null);
         cntorServer.addNotificationListener(new NotificationListener()
         {
            public void handleNotification(Notification notification, Object handback)
            {
               holder.set(notification);
            }
         }, null, null);

         JMXConnectorFactory.connect(cntorServer.getAddress(), getEnvironment());

         Notification notification = (Notification) holder.get();
         if (!(notification instanceof JMXConnectionNotification)) fail();
         assertEquals(notification.getType(), JMXConnectionNotification.OPENED);
      } catch (Exception x)
      {
         x.printStackTrace();
         throw x;
      } finally
      {
         if (cntorServer != null) cntorServer.stop();
      }
   }

   public void testConnectionNotificationClosedOnServer() throws Exception
   {
      JMXConnectorServer cntorServer = null;
      try
      {
         JMXServiceURL url = createJMXConnectorServerAddress();
         cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, getEnvironment(), newMBeanServer());
         cntorServer.start();
         sleep(5000);

         final MutableObject holder = new MutableObject(null);
         cntorServer.addNotificationListener(new NotificationListener()
         {
            public void handleNotification(Notification notification, Object handback)
            {
               holder.set(notification);
            }
         }, null, null);

         JMXConnector cntor = JMXConnectorFactory.connect(cntorServer.getAddress(), getEnvironment());
         cntor.close();

         Notification notification = (Notification) holder.get();
         if (!(notification instanceof JMXConnectionNotification)) fail();
         assertEquals(notification.getType(), JMXConnectionNotification.CLOSED);
      } catch (Exception x)
      {
         x.printStackTrace();
         throw x;
      } finally
      {
         if (cntorServer != null) cntorServer.stop();
      }
   }

   public void testConnectionNotificationOpenedOnClient() throws Exception
   {
      JMXConnectorServer cntorServer = null;
      JMXConnector cntor = null;
      try
      {
         JMXServiceURL url = createJMXConnectorServerAddress();
         MBeanServer server = newMBeanServer();
         cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, getEnvironment(), server);
         cntorServer.start();
         sleep(5000);

         final MutableObject holder = new MutableObject(null);
         cntor = JMXConnectorFactory.newJMXConnector(cntorServer.getAddress(), getEnvironment());
         cntor.addConnectionNotificationListener(new NotificationListener()
         {
            public void handleNotification(Notification notification, Object handback)
            {
               holder.set(notification);
            }
         }, null, null);

         cntor.connect(getEnvironment());

         JMXConnectionNotification notification = (JMXConnectionNotification) holder.get();
         assertEquals(notification.getType(), JMXConnectionNotification.OPENED);
      } catch (Exception x)
      {
         x.printStackTrace();
         throw x;
      } finally
      {
         if (cntor != null) cntor.close();
         if (cntorServer != null) cntorServer.stop();
      }
   }

   public void testConnectionNotificationClosedOnClient() throws Exception
   {
      JMXConnectorServer cntorServer = null;
      JMXConnector cntor = null;
      try
      {
         JMXServiceURL url = createJMXConnectorServerAddress();
         MBeanServer server = newMBeanServer();
         cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, getEnvironment(), server);
         cntorServer.start();
         sleep(5000);

         final MutableObject holder = new MutableObject(null);
         cntor = JMXConnectorFactory.newJMXConnector(cntorServer.getAddress(), getEnvironment());
         cntor.addConnectionNotificationListener(new NotificationListener()
         {
            public void handleNotification(Notification notification, Object handback)
            {
               holder.set(notification);
            }
         }, null, null);

         cntor.connect(getEnvironment());
         cntor.close();

         JMXConnectionNotification notification = (JMXConnectionNotification) holder.get();
         assertEquals(notification.getType(), JMXConnectionNotification.CLOSED);
      } catch (Exception x)
      {
         x.printStackTrace();
         throw x;
      } finally
      {
         if (cntor != null) cntor.close();
         if (cntorServer != null) cntorServer.stop();
      }
   }

   public void testConnectionNotificationFailedOnClient() throws Exception
   {
      JMXServiceURL url = createJMXConnectorServerAddress();
      MBeanServer server = newMBeanServer();
      JMXConnectorServer cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, getEnvironment(), server);

      cntorServer.start();
      sleep(5000);

      final MutableObject holder = new MutableObject(null);
      long period = 1000;
      int retries = 3;
      try
      {
         JMXConnector cntor = JMXConnectorFactory.newJMXConnector(cntorServer.getAddress(), getEnvironment());
         cntor.addConnectionNotificationListener(new NotificationListener()
         {
            public void handleNotification(Notification notification, Object handback)
            {
               holder.set(notification);
            }
         }, null, null);

         Map clientEnv = getEnvironment();
         clientEnv.put(MX4JRemoteConstants.CONNECTION_HEARTBEAT_PERIOD, new Long(period));
         clientEnv.put(MX4JRemoteConstants.CONNECTION_HEARTBEAT_RETRIES, new Integer(retries));
         cntor.connect(clientEnv);

         JMXConnectionNotification notification = (JMXConnectionNotification) holder.get();
         assertEquals(notification.getType(), JMXConnectionNotification.OPENED);
         holder.set(null);
      } catch (Exception x)
      {
         x.printStackTrace();
         throw x;
      } finally
      {
         cntorServer.stop();
         sleep(5000);
      }

      // Wait for the heartbeat to send the failed notification
      sleep((retries * 3) * period);

      JMXConnectionNotification notification = (JMXConnectionNotification) holder.get();
      assertNotNull(notification);
      assertEquals(notification.getType(), JMXConnectionNotification.FAILED);
   }

   public void testRemoteNotificationListener() throws Exception
   {
      JMXConnectorServer cntorServer = null;
      JMXConnector cntor = null;
      try
      {
         MBeanServer server = newMBeanServer();

         JMXServiceURL url = createJMXConnectorServerAddress();
         cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, getEnvironment(), server);
         cntorServer.start();
         sleep(5000);

         cntor = JMXConnectorFactory.connect(cntorServer.getAddress(), getEnvironment());
         MBeanServerConnection mbsc = cntor.getMBeanServerConnection();
         ObjectName delegate = new ObjectName("JMImplementation:type=MBeanServerDelegate");

         final MutableObject holder = new MutableObject(null);
         mbsc.addNotificationListener(delegate, new NotificationListener()
         {
            public void handleNotification(Notification notification, Object handback)
            {
               holder.set(notification);
            }
         }, null, null);

         // Wait for notifications threads to start
         sleep(1000);

         // Register a new MBean, it will generate a notification
         MLet mlet = new MLet();
         ObjectName name = new ObjectName(":mbean=mlet");
         server.registerMBean(mlet, name);

         // Wait for notifications to arrive
         sleep(1000);

         Notification notification = (Notification) holder.get();
         assertEquals(notification.getType(), MBeanServerNotification.REGISTRATION_NOTIFICATION);
         holder.set(null);

         // Unregister the MBean
         server.unregisterMBean(name);

         // Wait for notifications to arrive
         sleep(1000);

         notification = (Notification) holder.get();
         assertEquals(notification.getType(), MBeanServerNotification.UNREGISTRATION_NOTIFICATION);
         holder.set(null);
      } catch (Exception x)
      {
         x.printStackTrace();
         throw x;
      } finally
      {
         if (cntor != null) cntor.close();
         if (cntorServer != null) cntorServer.stop();
      }
   }

   public void testNonSerializableNotifications() throws Exception
   {
      JMXConnectorServer cntorServer = null;
      JMXConnector cntor = null;
      try
      {
         MBeanServer server = newMBeanServer();

         // Register an MBean Emitter
         ObjectName emitterName = ObjectName.getInstance(":mbean=emitter");
         MBeanEmitter emitter = new MBeanEmitter();
         server.registerMBean(emitter, emitterName);

         JMXServiceURL url = createJMXConnectorServerAddress();
         cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, getEnvironment(), null);
         ObjectName cntorName = ObjectName.getInstance("connector:protocol=rmi");
         server.registerMBean(cntorServer, cntorName);
         cntorServer.start();
         sleep(5000);

         cntor = JMXConnectorFactory.connect(cntorServer.getAddress(), getEnvironment());
         MBeanServerConnection mbsc = cntor.getMBeanServerConnection();
         final MutableObject plainNotification = new MutableObject(null);
         mbsc.addNotificationListener(emitterName, new NotificationListener()
         {
            public void handleNotification(Notification notification, Object handback)
            {
               plainNotification.set(notification);
            }
         }, null, null);

         final MutableObject connectionNotification = new MutableObject(null);
         cntor.addConnectionNotificationListener(new NotificationListener()
         {
            public void handleNotification(Notification notification, Object handback)
            {
               connectionNotification.set(notification);
            }
         }, null, null);

         // Wait for notifications threads to start
         sleep(1000);

         String type = "notification.type";
         Notification notification = new Notification(type, this, 0);
         // Make it non-serializable
         notification.setUserData(this);
         emitter.emit(notification);

         // Wait for notifications to arrive
         sleep(1000);

         assertNull(plainNotification.get());
         assertNotNull(connectionNotification.get());
         assertEquals(((Notification) connectionNotification.get()).getType(), JMXConnectionNotification.NOTIFS_LOST);
      } catch (Exception x)
      {
         x.printStackTrace();
         throw x;
      } finally
      {
         if (cntor != null) cntor.close();
         if (cntorServer != null) cntorServer.stop();
      }
   }

   public void testAddRemoveMBeanListener() throws Exception
   {
      JMXConnectorServer cntorServer = null;
      JMXConnector cntor = null;
      try
      {
         MBeanServer server = newMBeanServer();

         // Register an MBean Emitter
         ObjectName emitterName = ObjectName.getInstance(":mbean=emitter");
         MBeanEmitter emitter = new MBeanEmitter();
         server.registerMBean(emitter, emitterName);

         // Register an MBean Listener
         MutableObject notificationHolder = new MutableObject(null);
         MutableObject handbackHolder = new MutableObject(null);
         ObjectName listenerName = ObjectName.getInstance(":mbean=listener");
         MBeanListener listener = new MBeanListener(notificationHolder, handbackHolder);
         server.registerMBean(listener, listenerName);

         JMXServiceURL url = createJMXConnectorServerAddress();
         cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, getEnvironment(), server);
         cntorServer.start();
         sleep(5000);

         cntor = JMXConnectorFactory.connect(cntorServer.getAddress(), getEnvironment());
         MBeanServerConnection mbsc = cntor.getMBeanServerConnection();

         // Non-serializable filter
         try
         {
            mbsc.addNotificationListener(emitterName, listenerName, new NotificationFilter()
            {
               public boolean isNotificationEnabled(Notification notification)
               {
                  return false;
               }
            }, null);
            fail();
         } catch (IOException x)
         {
         }

         // Non-serializable handback
         try
         {
            mbsc.addNotificationListener(emitterName, listenerName, null, new Object());
            fail();
         } catch (IOException x)
         {
         }

         // Non-serializable filter and non serializable handback
         try
         {
            mbsc.addNotificationListener(emitterName, listenerName, new NotificationFilter()
            {
               public boolean isNotificationEnabled(Notification notification)
               {
                  return false;
               }
            }, new Object());
            fail();
         } catch (IOException x)
         {
         }

         // Everything is serializable
         ObjectName name = ObjectName.getInstance(":mbean=dummy");
         MBeanServerNotificationFilter filter = new MBeanServerNotificationFilter();
         filter.disableObjectName(name);
         Object handback = new Integer(13);
         mbsc.addNotificationListener(emitterName, listenerName, filter, handback);

         // Wait for notifications threads to start
         sleep(1000);

         Notification notification = new MBeanServerNotification(MBeanServerNotification.REGISTRATION_NOTIFICATION, this, 0, name);
         emitter.emit(notification);

         // Wait for notification to arrive
         sleep(1000);

         // Be sure the notification has been filtered
         assertNull(notificationHolder.get());
         assertNull(handbackHolder.get());

         // Disable filtering
         filter.enableAllObjectNames();
         // Remove and readd: on server side there is a serialized copy of the filter
         mbsc.removeNotificationListener(emitterName, listenerName);
         mbsc.addNotificationListener(emitterName, listenerName, filter, handback);

         // Wait for notifications threads to start
         sleep(1000);

         emitter.emit(notification);

         // Wait for notification to arrive
         sleep(1000);

         // Be sure we got it
         assertEquals(handbackHolder.get(), handback);
         Notification emitted = (Notification) notificationHolder.get();
         assertNotNull(emitted);
         if (!(notification instanceof MBeanServerNotification)) fail();
         assertEquals(((MBeanServerNotification) emitted).getMBeanName(), name);
         notificationHolder.set(null);
         handbackHolder.set(null);

         mbsc.removeNotificationListener(emitterName, listenerName, filter, handback);

         // Be sure we don't get notifications anymore
         emitter.emit(notification);

         // Wait for notification to arrive
         sleep(1000);

         assertNull(notificationHolder.get());
         assertNull(handbackHolder.get());
      } catch (Exception x)
      {
         x.printStackTrace();
         throw x;
      } finally
      {
         if (cntor != null) cntor.close();
         if (cntorServer != null) cntorServer.stop();
      }
   }

   public void testAddRemoveListenerWithNonSerializableFilter() throws Exception
   {
      JMXConnectorServer cntorServer = null;
      JMXConnector cntor = null;
      try
      {
         MBeanServer server = newMBeanServer();

         // Register an MBean Emitter
         ObjectName emitterName = ObjectName.getInstance(":mbean=emitter");
         MBeanEmitter emitter = new MBeanEmitter();
         server.registerMBean(emitter, emitterName);

         MutableObject notificationHolder = new MutableObject(null);
         MutableObject handbackHolder = new MutableObject(null);
         MBeanListener listener = new MBeanListener(notificationHolder, handbackHolder);

         JMXServiceURL url = createJMXConnectorServerAddress();
         cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, getEnvironment(), server);
         cntorServer.start();
         sleep(5000);

         cntor = JMXConnectorFactory.connect(cntorServer.getAddress(), getEnvironment());
         MBeanServerConnection mbsc = cntor.getMBeanServerConnection();

         // Non-serializable filter, should run on client side
         final MutableBoolean enable = new MutableBoolean(false);
         NotificationFilter filter = new NotificationFilter()
         {
            public boolean isNotificationEnabled(Notification notification)
            {
               return enable.get();
            }
         };
         mbsc.addNotificationListener(emitterName, listener, filter, null);

         // Wait for notification threads to start
         sleep(1000);

         String type = "notification.type";
         Notification notification = new Notification(type, this, 0);
         emitter.emit(notification);

         // Wait for notification to arrive
         sleep(1000);

         // Be sure the notification has been filtered
         assertNull(notificationHolder.get());
         assertNull(handbackHolder.get());

         // Disable the filter
         enable.set(true);

         emitter.emit(notification);

         // Wait for notification to arrive
         sleep(1000);

         // Be sure we got the notification
         assertNull(handbackHolder.get());
         Notification emitted = (Notification) notificationHolder.get();
         assertNotNull(emitted);
         notificationHolder.set(null);
         handbackHolder.set(null);

         mbsc.removeNotificationListener(emitterName, listener, filter, null);

         // Be sure we don't get notifications anymore
         emitter.emit(notification);

         // Wait for notification to arrive
         sleep(1000);

         assertNull(notificationHolder.get());
         assertNull(handbackHolder.get());
      } catch (Exception x)
      {
         x.printStackTrace();
         throw x;
      } finally
      {
         if (cntor != null) cntor.close();
         if (cntorServer != null) cntorServer.stop();
      }
   }

   public void testAddRemoveListenerWithNonSerializableHandback() throws Exception
   {
      JMXConnectorServer cntorServer = null;
      JMXConnector cntor = null;
      try
      {
         MBeanServer server = newMBeanServer();

         // Register an MBean Emitter
         ObjectName emitterName = ObjectName.getInstance(":mbean=emitter");
         MBeanEmitter emitter = new MBeanEmitter();
         server.registerMBean(emitter, emitterName);

         MutableObject notificationHolder = new MutableObject(null);
         MutableObject handbackHolder = new MutableObject(null);
         MBeanListener listener = new MBeanListener(notificationHolder, handbackHolder);

         JMXServiceURL url = createJMXConnectorServerAddress();
         cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, getEnvironment(), server);
         cntorServer.start();
         sleep(5000);

         cntor = JMXConnectorFactory.connect(cntorServer.getAddress(), getEnvironment());
         MBeanServerConnection mbsc = cntor.getMBeanServerConnection();

         // Non-serializable handback, should stay on client side
         Object handback = new Object();
         mbsc.addNotificationListener(emitterName, listener, null, handback);

         // Wait for notification threads to start
         sleep(1000);

         String type = "notification.type";
         Notification notification = new Notification(type, this, 0);
         emitter.emit(notification);

         // Wait for notification to arrive
         sleep(1000);

         // Be sure we got the notification
         assertSame(handbackHolder.get(), handback);
         Notification emitted = (Notification) notificationHolder.get();
         assertNotNull(emitted);
         notificationHolder.set(null);
         handbackHolder.set(null);

         mbsc.removeNotificationListener(emitterName, listener, null, handback);

         // Be sure we don't get notifications anymore
         emitter.emit(notification);

         // Wait for notification to arrive
         sleep(1000);

         assertNull(notificationHolder.get());
         assertNull(handbackHolder.get());
      } catch (Exception x)
      {
         x.printStackTrace();
         throw x;
      } finally
      {
         if (cntor != null) cntor.close();
         if (cntorServer != null) cntorServer.stop();
      }
   }

   public void testAddRemoveSameListenerMultipleTimesWithDifferentFiltersAndHandbacks() throws Exception
   {
      JMXConnectorServer cntorServer = null;
      JMXConnector cntor = null;
      try
      {
         MBeanServer server = newMBeanServer();

         // Register an MBean Emitter
         ObjectName emitterName = ObjectName.getInstance(":mbean=emitter");
         MBeanEmitter emitter = new MBeanEmitter();
         server.registerMBean(emitter, emitterName);

         final MutableInteger counter1 = new MutableInteger(0);
         NotificationListener listener1 = new NotificationListener()
         {
            public void handleNotification(Notification notification, Object handback)
            {
               counter1.set(counter1.get() + 1);
            }
         };
         final MutableInteger counter2 = new MutableInteger(0);
         NotificationListener listener2 = new NotificationListener()
         {
            public void handleNotification(Notification notification, Object handback)
            {
               counter2.set(counter2.get() + 1);
            }
         };

         JMXServiceURL url = createJMXConnectorServerAddress();
         cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, getEnvironment(), server);
         cntorServer.start();
         sleep(5000);

         cntor = JMXConnectorFactory.connect(cntorServer.getAddress(), getEnvironment());
         MBeanServerConnection mbsc = cntor.getMBeanServerConnection();

         String type = "notification.type";

         // First listener
         mbsc.addNotificationListener(emitterName, listener1, null, null);
         // Second listener
         NotificationFilterSupport filter = new NotificationFilterSupport();
         filter.enableType(type);
         mbsc.addNotificationListener(emitterName, listener1, filter, null);
         // Third listener
         Object handback = new Object();
         mbsc.addNotificationListener(emitterName, listener1, null, handback);
         // Fourth listener
         mbsc.addNotificationListener(emitterName, listener2, null, null);

         // Wait for notification threads to start
         sleep(1000);

         Notification notification = new Notification(type, this, 0);
         emitter.emit(notification);
         // Wait for notification to arrive
         sleep(1000);

         // Be sure we got all notifications
         assertEquals(counter1.get(), 3);
         assertEquals(counter2.get(), 1);
         counter1.set(0);
         counter2.set(0);

         // Remove one listener
         mbsc.removeNotificationListener(emitterName, listener1, null, handback);

         emitter.emit(notification);
         // Wait for notification to arrive
         sleep(1000);

         assertEquals(counter1.get(), 2);
         assertEquals(counter2.get(), 1);
         counter1.set(0);
         counter2.set(0);

         // Remove all listeners
         mbsc.removeNotificationListener(emitterName, listener1);

         emitter.emit(notification);
         // Wait for notification to arrive
         sleep(1000);

         assertEquals(counter1.get(), 0);
         assertEquals(counter2.get(), 1);
         counter1.set(0);
         counter2.set(0);

         mbsc.removeNotificationListener(emitterName, listener2);

         emitter.emit(notification);
         // Wait for notification to arrive
         sleep(1000);

         assertEquals(counter1.get(), 0);
         assertEquals(counter2.get(), 0);
      } catch (Exception x)
      {
         x.printStackTrace();
         throw x;
      } finally
      {
         if (cntor != null) cntor.close();
         if (cntorServer != null) cntorServer.stop();
      }
   }

   public void testTwoMBeanServerConnectionsHaveSameListener() throws Exception
   {
      JMXConnectorServer cntorServer = null;
      JMXConnector cntor = null;
      try
      {
         MBeanServer server = newMBeanServer();

         // Register an MBean Emitter
         ObjectName emitterName = ObjectName.getInstance(":mbean=emitter");
         MBeanEmitter emitter = new MBeanEmitter();
         server.registerMBean(emitter, emitterName);

         final MutableInteger counter = new MutableInteger(0);
         NotificationListener listener = new NotificationListener()
         {
            public void handleNotification(Notification notification, Object handback)
            {
               counter.set(counter.get() + 1);
            }
         };

         JMXServiceURL url = createJMXConnectorServerAddress();
         cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, getEnvironment(), server);
         cntorServer.start();
         sleep(5000);

         cntor = JMXConnectorFactory.connect(cntorServer.getAddress(), getEnvironment());
         MBeanServerConnection mbsc1 = cntor.getMBeanServerConnection();

         mbsc1.addNotificationListener(emitterName, listener, null, null);
         // Wait for notification threads to start
         sleep(1000);

         Notification notification = new Notification("type", emitter, 0);
         emitter.emit(notification);

         // Wait for the notification to arrive
         sleep(1000);

         // Be sure it's received
         assertEquals(counter.get(), 1);

         // Be sure I can remove the same listener from another MBeanServerConnection
         MBeanServerConnection mbsc2 = cntor.getMBeanServerConnection();
         mbsc2.removeNotificationListener(emitterName, listener, null, null);

         emitter.emit(notification);

         // Be sure no listeners anymore
         assertEquals(counter.get(), 1);
      } catch (Exception x)
      {
         x.printStackTrace();
         throw x;
      } finally
      {
         if (cntor != null) cntor.close();
         if (cntorServer != null) cntorServer.stop();
      }
   }

   public interface MBeanListenerMBean
   {
   }

   public static class MBeanListener implements NotificationListener, MBeanListenerMBean
   {
      private MutableObject notificationHolder;
      private MutableObject handbackHolder;

      public MBeanListener(MutableObject notificationHolder, MutableObject handbackHolder)
      {
         this.notificationHolder = notificationHolder;
         this.handbackHolder = handbackHolder;
      }

      public void handleNotification(Notification notification, Object handback)
      {
         notificationHolder.set(notification);
         handbackHolder.set(handback);
      }
   }

   public interface MBeanEmitterMBean
   {
      public void emit(Notification notification);
   }

   public static class MBeanEmitter extends NotificationBroadcasterSupport implements MBeanEmitterMBean
   {
      public void emit(Notification notification)
      {
         sendNotification(notification);
      }
   }
}
