/*
 * 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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.Notification;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.remote.JMXAuthenticator;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXPrincipal;
import javax.management.remote.JMXServiceURL;
import javax.security.auth.Subject;

import test.MX4JTestCase;

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

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

   public abstract JMXServiceURL createJMXConnectorServerAddress() throws MalformedURLException;

   public abstract Map getEnvironment();

   public void testNewJMXConnectorWithNullURL() throws Exception
   {
      try
      {
         JMXConnectorFactory.connect(null);
         fail();
      }
      catch (NullPointerException x)
      {
      }
   }

   public void testConnectionId() throws Exception
   {
      // Format is:
      // protocol:[[host]:port] [clientId] [arbitrary]
      // Spaces are mandatory, brackets indicates optional parts

      JMXConnectorServer cntorServer = null;
      JMXConnector cntor = null;
      try
      {
         JMXServiceURL url = createJMXConnectorServerAddress();
         cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, getEnvironment(), newMBeanServer());
         cntorServer.start();
         sleep(5000);

         cntor = JMXConnectorFactory.connect(cntorServer.getAddress(), getEnvironment());
         String connectionId = cntor.getConnectionId();
         String protocol = connectionId.substring(0, connectionId.indexOf(':'));
         assertEquals(protocol, url.getProtocol());

         // Match first mandatory space
         int space = connectionId.indexOf(' ');
         String remaining = connectionId.substring(space + 1);
         // Match second mandatory space
         space = remaining.indexOf(' ');
         String arbitrary = remaining.substring(space + 1);
         if (arbitrary.length() < 1) fail("Missing MX4J arbitrary test");
      }
      finally
      {
         if (cntor != null) cntor.close();
         if (cntorServer != null) cntorServer.stop();
      }
   }

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

         cntor = JMXConnectorFactory.connect(cntorServer.getAddress(), getEnvironment());
      }
      finally
      {
         if (cntor != null) cntor.close();
         if (cntorServer != null) cntorServer.stop();
      }
   }

   public void testJMXAuthenticator() throws Exception
   {
      final String password = "mx4j";
      JMXAuthenticator authenticator = new JMXAuthenticator()
      {
         public Subject authenticate(Object credentials) throws SecurityException
         {
            if (password.equals(credentials))
            {
               JMXPrincipal principal = new JMXPrincipal("mx4j");
               Subject subject = new Subject();
               subject.getPrincipals().add(principal);
               subject.setReadOnly();
               return subject;
            }
            throw new SecurityException("Authentication Failed");
         }
      };

      JMXConnectorServer cntorServer = null;
      JMXConnector cntor = null;
      try
      {
         JMXServiceURL url = createJMXConnectorServerAddress();
         MBeanServer server = newMBeanServer();
         Map serverEnv = getEnvironment();
         serverEnv.put(JMXConnectorServer.AUTHENTICATOR, authenticator);
         cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, serverEnv, server);
         cntorServer.start();
         sleep(5000);

         // Try to provide wrong password
         Map clientEnv = getEnvironment();
         try
         {
            testJMXAuthenticatorConnect(cntorServer.getAddress(), clientEnv);
            fail();
         }
         catch (SecurityException x)
         {
         }

         // Try now with a correct password
         clientEnv.put(JMXConnector.CREDENTIALS, password);
         testJMXAuthenticatorConnect(cntorServer.getAddress(), clientEnv);
      }
      finally
      {
         if (cntor != null) cntor.close();
         if (cntorServer != null) cntorServer.stop();
      }
   }

   protected void testJMXAuthenticatorConnect(JMXServiceURL url, Map environment) throws SecurityException, IOException
   {
      JMXConnectorFactory.connect(url, environment);
   }

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

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

      cntorServer.stop();

      try
      {
         mbsc.getDefaultDomain();
         fail();
      }
      catch (IOException x)
      {
      }
   }

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

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

      cntor.close();
      cntorServer.stop();

      try
      {
         mbsc.getDefaultDomain();
         fail();
      }
      catch (IOException x)
      {
      }
   }

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

         cntor = JMXConnectorFactory.newJMXConnector(cntorServer.getAddress(), getEnvironment());

         // Serialize it: we want to test serialization does no reset data members
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         ObjectOutputStream oos = new ObjectOutputStream(baos);
         oos.writeObject(cntor);
         oos.close();
         ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
         ObjectInputStream ois = new ObjectInputStream(bais);
         cntor = (JMXConnector)ois.readObject();
         ois.close();

         cntor.connect();
         MBeanServerConnection mbsc = cntor.getMBeanServerConnection();
         mbsc.getDefaultDomain();

         // Again
         cntor = JMXConnectorFactory.connect(cntorServer.getAddress(), getEnvironment());

         // Serialize it: we want to test serialization does no reset data members
         baos = new ByteArrayOutputStream();
         oos = new ObjectOutputStream(baos);
         oos.writeObject(cntor);
         oos.close();
         bais = new ByteArrayInputStream(baos.toByteArray());
         ois = new ObjectInputStream(bais);
         cntor = (JMXConnector)ois.readObject();
         ois.close();

         cntor.connect();
         mbsc = cntor.getMBeanServerConnection();
         mbsc.getDefaultDomain();
      }
      finally
      {
         if (cntor != null) cntor.close();
         if (cntorServer != null) cntorServer.stop();
      }
   }

   public void testDefaultClassLoader() throws Exception
   {
      JMXServiceURL url = createJMXConnectorServerAddress();
      Map environment = new HashMap(getEnvironment());
      environment.put(JMXConnectorFactory.DEFAULT_CLASS_LOADER, new Object());
      try
      {
         JMXConnectorFactory.newJMXConnector(url, environment);
         fail();
      }
      catch (IllegalArgumentException x)
      {
      }

      JMXConnector cntor = JMXConnectorFactory.newJMXConnector(url, getEnvironment());
      try
      {
         cntor.connect(environment);
         fail();
      }
      catch (IllegalArgumentException x)
      {
      }
   }

   public void testListenersAreRemovedOnConnectorClose() 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);

         Emitter emitter = new Emitter();
         ObjectName emitterName = ObjectName.getInstance(":name=emitter");
         server.registerMBean(emitter, emitterName);

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

         NotificationListener listener = new NotificationListener()
         {
            public void handleNotification(Notification notification, Object handback)
            {
            }
         };

         // Add the listener and be sure the mechanism of removal works fine
         mbsc.addNotificationListener(emitterName, listener, null, null);
         assertEquals(emitter.getSize(), 1);
         mbsc.removeNotificationListener(emitterName, listener, null, null);
         assertEquals(emitter.getSize(), 0);

         // Add the listener and close the connector
         mbsc.addNotificationListener(emitterName, listener, null, null);
         assertEquals(emitter.getSize(), 1);
         cntor.close();
         assertEquals(emitter.getSize(), 0);
      }
      finally
      {
         if (cntorServer != null) cntorServer.stop();
      }
   }

   public void testConnectWithProviderClassLoader() throws Exception
   {
      JMXConnectorServer cntorServer = null;
      JMXConnector cntor = null;
      try
      {
         JMXServiceURL url = createJMXConnectorServerAddress();
         MBeanServer server = newMBeanServer();
         Map serverEnv = getEnvironment();
         serverEnv.put(JMXConnectorServerFactory.PROTOCOL_PROVIDER_CLASS_LOADER, getClass().getClassLoader());
         ClassLoader old = Thread.currentThread().getContextClassLoader();
         Thread.currentThread().setContextClassLoader(getClass().getClassLoader().getParent());
         cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, serverEnv, server);
         cntorServer.start();
         Thread.currentThread().setContextClassLoader(old);
         sleep(5000);

         cntor = JMXConnectorFactory.connect(cntorServer.getAddress(), getEnvironment());
         MBeanServerConnection mbsc = cntor.getMBeanServerConnection();
         assertNotNull(mbsc);
      }
      finally
      {
         if (cntor != null) cntor.close();
         if (cntorServer != null) cntorServer.stop();
      }
   }

   public interface EmitterMBean
   {
   }

   private static class Emitter implements NotificationEmitter, EmitterMBean
   {
      private List listeners = new ArrayList();

      public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws IllegalArgumentException
      {
         listeners.add(listener);
      }

      public void removeNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws ListenerNotFoundException
      {
         listeners.remove(listener);
      }

      public MBeanNotificationInfo[] getNotificationInfo()
      {
         return new MBeanNotificationInfo[0];
      }

      public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException
      {
      }

      public int getSize()
      {
         return listeners.size();
      }
   }
}
