/*
 * 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.rmi;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Map;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerFactory;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.loading.MLet;
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 test.MX4JTestCase;
import test.MutableObject;

/**
 * @version $Revision: 1.4 $
 */
public class RMIInteroperabilityTest extends MX4JTestCase
{
   public RMIInteroperabilityTest(String s)
   {
      super(s);
   }

   private Object startJMXRIRMIConnectorServer() throws Exception
   {
      ClassLoader ri = createRemoteJMXRIWithTestsClassLoader();

      Class mbeanServerFactoryClass = ri.loadClass(MBeanServerFactory.class.getName());
      Method method = mbeanServerFactoryClass.getMethod("newMBeanServer", new Class[0]);
      Object mbeanServer = method.invoke(null, new Object[0]);
      if (!mbeanServer.getClass().getName().startsWith("com.sun")) fail();
      Class mbeanServerClass = ri.loadClass(MBeanServer.class.getName());

      Class serviceURLClass = ri.loadClass(JMXServiceURL.class.getName());
      Constructor constructor = serviceURLClass.getConstructor(new Class[]{String.class});
      Object serviceURL = constructor.newInstance(new Object[]{"service:jmx:rmi://localhost"});
      Class cntorServerFactoryClass = ri.loadClass(JMXConnectorServerFactory.class.getName());
      method = cntorServerFactoryClass.getMethod("newJMXConnectorServer", new Class[]{serviceURLClass, Map.class, mbeanServerClass});
      Object cntorServer = method.invoke(null, new Object[]{serviceURL, null, mbeanServer});

      method = cntorServer.getClass().getMethod("start", new Class[0]);
      method.invoke(cntorServer, new Object[0]);

      return cntorServer;
   }

   public void testJMXRIOnServerMX4JOnClientGetMBeanInfo() throws Exception
   {
      Object cntorServer = startJMXRIRMIConnectorServer();
      Method method = cntorServer.getClass().getMethod("getAddress", new Class[0]);
      Object address = method.invoke(cntorServer, new Object[0]);
      String url = address.toString();

      JMXConnector cntor = JMXConnectorFactory.connect(new JMXServiceURL(url));
      MBeanServerConnection cntion = cntor.getMBeanServerConnection();

      MBeanInfo info = cntion.getMBeanInfo(ObjectName.getInstance("JMImplementation", "type", "MBeanServerDelegate"));
      assertNotNull(info);

      cntor.close();

      method = cntorServer.getClass().getMethod("stop", new Class[0]);
      method.invoke(cntorServer, new Object[0]);
      sleep(2000);
   }

   public void testJMXRIOnServerMX4JOnClientNotificationReceiving() throws Exception
   {
      Object cntorServer = startJMXRIRMIConnectorServer();
      Method method = cntorServer.getClass().getMethod("getAddress", new Class[0]);
      Object address = method.invoke(cntorServer, new Object[0]);
      String url = address.toString();

      JMXConnector cntor = JMXConnectorFactory.connect(new JMXServiceURL(url));
      MBeanServerConnection cntion = cntor.getMBeanServerConnection();

      InteroperabilityListener listener = new InteroperabilityListener();
      ObjectName delegateName = ObjectName.getInstance("JMImplementation:type=MBeanServerDelegate");
      cntion.addNotificationListener(delegateName, listener, null, null);
      sleep(1000);

      ObjectName name = ObjectName.getInstance(":mbean=mlet");
      cntion.createMBean(MLet.class.getName(), name, null);
      sleep(1000);
      Notification notification = (Notification)listener.get();
      assertNotNull(notification);
      listener.reset();

      cntion.removeNotificationListener(delegateName, listener);

      cntion.unregisterMBean(name);
      sleep(1000);

      notification = (Notification)listener.get();
      assertNull(notification);

      cntor.close();

      method = cntorServer.getClass().getMethod("stop", new Class[0]);
      method.invoke(cntorServer, new Object[0]);
      sleep(2000);
   }

   private Object connectJMXRIConnector(ClassLoader ri, String url) throws Exception
   {
      Class serviceURLClass = ri.loadClass(JMXServiceURL.class.getName());
      Constructor constructor = serviceURLClass.getConstructor(new Class[]{String.class});
      Object serviceURL = constructor.newInstance(new Object[]{url});
      Class cntorFactoryClass = ri.loadClass(JMXConnectorFactory.class.getName());
      Method method = cntorFactoryClass.getMethod("connect", new Class[]{serviceURLClass});
      return method.invoke(null, new Object[]{serviceURL});
   }

   public void testMX4JOnServerJMXRIOnClientGetMBeanInfo() throws Exception
   {
      MBeanServer server = newMBeanServer();
      JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://localhost");
      JMXConnectorServer cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
      cntorServer.start();
      url = cntorServer.getAddress();

      ClassLoader ri = createRemoteJMXRIWithTestsClassLoader();
      ClassLoader tccl = Thread.currentThread().getContextClassLoader();
      Thread.currentThread().setContextClassLoader(ri);
      Object cntor = connectJMXRIConnector(ri, url.toString());

      Method method = cntor.getClass().getMethod("getMBeanServerConnection", new Class[0]);
      Object cntion = method.invoke(cntor, new Object[0]);
      Class cntionClass = ri.loadClass(MBeanServerConnection.class.getName());

      Class objectNameClass = ri.loadClass(ObjectName.class.getName());
      method = objectNameClass.getMethod("getInstance", new Class[]{String.class});
      Object objectName = method.invoke(null, new Object[]{"JMImplementation:type=MBeanServerDelegate"});
      method = cntionClass.getMethod("getMBeanInfo", new Class[]{objectNameClass});
      Object info = method.invoke(cntion, new Object[]{objectName});
      assertNotNull(info);

      method = cntor.getClass().getMethod("close", new Class[0]);
      method.invoke(cntor, new Object[0]);

      Thread.currentThread().setContextClassLoader(tccl);
      cntorServer.stop();
      sleep(2000);
   }

   public void testMX4JOnServerJMXRIOnClientNotificationReceiving() throws Exception
   {
      MBeanServer server = newMBeanServer();
      JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://localhost");
      JMXConnectorServer cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
      cntorServer.start();
      url = cntorServer.getAddress();

      ClassLoader ri = createRemoteJMXRIWithTestsClassLoader();
      ClassLoader tccl = Thread.currentThread().getContextClassLoader();
      Thread.currentThread().setContextClassLoader(ri);
      Object cntor = connectJMXRIConnector(ri, url.toString());

      Method method = cntor.getClass().getMethod("getMBeanServerConnection", new Class[0]);
      Object cntion = method.invoke(cntor, new Object[0]);
      Class cntionClass = ri.loadClass(MBeanServerConnection.class.getName());

      Class objectNameClass = ri.loadClass(ObjectName.class.getName());
      method = objectNameClass.getMethod("getInstance", new Class[]{String.class});
      Object delegateName = method.invoke(null, new Object[]{"JMImplementation:type=MBeanServerDelegate"});
      Class notificationListenerClass = ri.loadClass(NotificationListener.class.getName());
      Class notificationFilterClass = ri.loadClass(NotificationFilter.class.getName());
      Object listener = ri.loadClass(InteroperabilityListener.class.getName()).newInstance();
      method = cntionClass.getMethod("addNotificationListener", new Class[]{objectNameClass, notificationListenerClass, notificationFilterClass, Object.class});
      method.invoke(cntion, new Object[]{delegateName, listener, null, null});
      sleep(1000);

      method = objectNameClass.getMethod("getInstance", new Class[]{String.class});
      Object mletName = method.invoke(null, new Object[]{":mbean=mlet"});

      method = cntionClass.getMethod("createMBean", new Class[]{String.class, objectNameClass, objectNameClass});
      method.invoke(cntion, new Object[]{MLet.class.getName(), mletName, null});
      sleep(1000);

      method = listener.getClass().getMethod("get", new Class[0]);
      Object notification = method.invoke(listener, new Object[0]);
      assertNotNull(notification);

      method = listener.getClass().getMethod("reset", new Class[0]);
      method.invoke(listener, new Object[0]);

      method = cntionClass.getMethod("removeNotificationListener", new Class[]{objectNameClass, notificationListenerClass});
      method.invoke(cntion, new Object[]{delegateName, listener});

      method = cntionClass.getMethod("unregisterMBean", new Class[]{objectNameClass});
      method.invoke(cntion, new Object[]{mletName});
      sleep(1000);

      method = listener.getClass().getMethod("get", new Class[0]);
      notification = method.invoke(listener, new Object[0]);
      assertNull(notification);

      method = cntor.getClass().getMethod("close", new Class[0]);
      method.invoke(cntor, new Object[0]);

      Thread.currentThread().setContextClassLoader(tccl);
      cntorServer.stop();
      sleep(2000);
   }

   public static class InteroperabilityListener implements NotificationListener
   {
      private final MutableObject holder;

      public InteroperabilityListener()
      {
         this.holder = new MutableObject(null);
      }

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

      public void reset()
      {
         holder.set(null);
      }

      public Object get()
      {
         return holder.get();
      }
   }
}
