/*
 * 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.mx4j.tools.remote.proxy;

import java.io.IOException;
import java.net.URL;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerDelegateMBean;
import javax.management.MBeanServerInvocationHandler;
import javax.management.Notification;
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 javax.management.timer.Timer;

import mx4j.tools.remote.proxy.RemoteMBeanProxy;
import mx4j.util.Utils;
import test.MX4JTestCase;
import test.MutableObject;

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

   public void testNotifications() throws Exception
   {
      // The remote MBeanServer
      MBeanServer remoteServer = newMBeanServer();

      JMXServiceURL address1 = new JMXServiceURL("rmi", "localhost", 0);
      JMXConnectorServer connectorServer1 = JMXConnectorServerFactory.newJMXConnectorServer(address1, null, null);
      ObjectName connectorServerName1 = ObjectName.getInstance(":type=connector,protocol=" + address1.getProtocol());
      remoteServer.registerMBean(connectorServer1, connectorServerName1);
      connectorServer1.start();
      address1 = connectorServer1.getAddress();

      ObjectName remoteDelegateName = ObjectName.getInstance("JMImplementation:type=MBeanServerDelegate");

      // The local MBeanServer
      MBeanServer localServer = newMBeanServer();

      // The MBean proxy for the remote delegate
      JMXConnector cntor = JMXConnectorFactory.newJMXConnector(address1, null);
      RemoteMBeanProxy proxy = new RemoteMBeanProxy(remoteDelegateName, cntor, null, null);
      ObjectName proxyName = ObjectName.getInstance(":proxy=" + ObjectName.quote(remoteDelegateName.getCanonicalName()));
      localServer.registerMBean(proxy, proxyName);

      // Register a listener to the MBean proxy for the remote delegate
      final MutableObject holder = new MutableObject(null);
      NotificationListener listener = new NotificationListener()
      {
         public void handleNotification(Notification notification, Object handback)
         {
            synchronized (holder)
            {
               holder.set(notification);
            }
         }
      };
      localServer.addNotificationListener(proxyName, listener, null, null);

      // Sleep to make sure the remote notifications threads started before we emit the notification
      sleep(1000);

      // Add an MBean to the remote MBeanServer: this will trigger a notification from the remote delegate,
      // that should be dispatched transparently the the listener above
      MLet remoteMLet = new MLet();
      ObjectName remoteMLetName = ObjectName.getInstance(":type=mlet");
      remoteServer.registerMBean(remoteMLet, remoteMLetName);

      synchronized (holder)
      {
         while (holder.get() == null) holder.wait(10);
         assertNotNull(holder.get());
         holder.set(null);
      }

      // Remove the listener
      localServer.removeNotificationListener(proxyName, listener);

      // Unregister the MLet: this will trigger a notification
      remoteServer.unregisterMBean(remoteMLetName);

      assertNull(holder.get());
   }

   public void testMultipleRemoteMBeanServers() throws Exception
   {
      // The 1st remote server
      MBeanServer remoteServer1 = newMBeanServer();
      MLet mlet = new MLet(new URL[]{new URL("http", "host", 80, "/path")});
      ObjectName remoteMLetName = ObjectName.getInstance(":type=mlet");
      remoteServer1.registerMBean(mlet, remoteMLetName);
      JMXServiceURL address1 = new JMXServiceURL("rmi", "localhost", 0);
      JMXConnectorServer connectorServer1 = JMXConnectorServerFactory.newJMXConnectorServer(address1, null, null);
      ObjectName connectorServerName1 = ObjectName.getInstance(":type=connector,protocol=" + address1.getProtocol());
      remoteServer1.registerMBean(connectorServer1, connectorServerName1);
      connectorServer1.start();
      address1 = connectorServer1.getAddress();

      // The 2nd remote server
      MBeanServer remoteServer2 = newMBeanServer();
      Timer timer = new Timer();
      ObjectName remoteTimerName = ObjectName.getInstance(":type=timer");
      remoteServer2.registerMBean(timer, remoteTimerName);
      timer.start();
      JMXServiceURL address2 = new JMXServiceURL("rmi", "localhost", 0);
      JMXConnectorServer connectorServer2 = JMXConnectorServerFactory.newJMXConnectorServer(address2, null, remoteServer2);
      connectorServer2.start();
      address2 = connectorServer2.getAddress();

      // The local server
      MBeanServer localServer = newMBeanServer();
      RemoteMBeanProxy proxy1 = new RemoteMBeanProxy(remoteMLetName, address1, null, null);
      JMXConnector cntor = JMXConnectorFactory.connect(address2);
      RemoteMBeanProxy proxy2 = new RemoteMBeanProxy(remoteTimerName, cntor.getMBeanServerConnection());
      ObjectName proxyName1 = ObjectName.getInstance(":proxy=" + ObjectName.quote(remoteMLetName.getCanonicalName()));
      ObjectName proxyName2 = ObjectName.getInstance(":proxy=" + ObjectName.quote(remoteTimerName.getCanonicalName()));
      localServer.registerMBean(proxy1, proxyName1);
      localServer.registerMBean(proxy2, proxyName2);
      JMXServiceURL address3 = new JMXServiceURL("local", "localhost", 0);
      JMXConnectorServer connectorServer3 = JMXConnectorServerFactory.newJMXConnectorServer(address3, null, localServer);
      connectorServer3.start();
      address3 = connectorServer3.getAddress();

      // The client
      JMXConnector connector = JMXConnectorFactory.connect(address3);
      MBeanServerConnection mbsc = connector.getMBeanServerConnection();

      // Tests
      URL[] urls = (URL[])mbsc.getAttribute(proxyName1, "URLs");
      if (!Utils.arrayEquals(urls, mlet.getURLs())) fail();

      if (!timer.isActive()) fail();
      mbsc.invoke(proxyName2, "stop", null, null);
      if (timer.isActive()) fail();
   }

   public void testJMXConnectorCloseOnDeregistration() throws Exception
   {
      // The remote MBeanServer
      MBeanServer remoteServer = newMBeanServer();

      JMXServiceURL address = new JMXServiceURL("rmi", "localhost", 0);
      JMXConnectorServer connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(address, null, null);
      ObjectName connectorServerName = ObjectName.getInstance(":type=connector,protocol=" + address.getProtocol());
      remoteServer.registerMBean(connectorServer, connectorServerName);
      connectorServer.start();
      address = connectorServer.getAddress();

      ObjectName remoteDelegateName = ObjectName.getInstance("JMImplementation:type=MBeanServerDelegate");

      // The local MBeanServer
      MBeanServer localServer = newMBeanServer();

      // The MBean proxy for the remote delegate
      JMXConnector cntor = JMXConnectorFactory.newJMXConnector(address, null);
      RemoteMBeanProxy proxy = new RemoteMBeanProxy(remoteDelegateName, cntor, null, null);
      ObjectName proxyName = ObjectName.getInstance(":proxy=" + ObjectName.quote(remoteDelegateName.getCanonicalName()));
      localServer.registerMBean(proxy, proxyName);

      // Be sure it works
      MBeanServerDelegateMBean mbean = (MBeanServerDelegateMBean)MBeanServerInvocationHandler.newProxyInstance(localServer, proxyName, MBeanServerDelegateMBean.class, true);
      String vendor = mbean.getImplementationVendor();
      assertNotNull(vendor);

      // Unregister and be sure the connector is closed
      localServer.unregisterMBean(proxyName);
      try
      {
         cntor.getMBeanServerConnection();
         fail();
      }
      catch (IOException x)
      {
      }
   }
}
