/***************************************
 *                                     *
 *  JBoss: The OpenSource J2EE WebOS   *
 *                                     *
 *  Distributable under LGPL license.  *
 *  See terms of license at gnu.org.   *
 *                                     *
 ***************************************/
package org.jboss.remoting;


import org.jboss.logging.Logger;
import org.jboss.remoting.transport.ClientInvoker;
import org.jboss.remoting.transport.http.HTTPClientInvoker;
import org.jboss.remoting.transport.http.HTTPServerInvoker;
import org.jboss.remoting.transport.local.LocalClientInvoker;
import org.jboss.remoting.transport.rmi.RMIClientInvoker;
import org.jboss.remoting.transport.rmi.RMIServerInvoker;
import org.jboss.remoting.transport.socket.SocketClientInvoker;
import org.jboss.remoting.transport.socket.SocketServerInvoker;

import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * InvokerRegistry is a simple registery for creating client and server side Invoker implementations,
 * getting information about the invokers and register as a invoker creator for one or more specific
 * transports.
 *
 * @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
 * @author <a href="mailto:telrod@e2technologies.net">Tom Elrod</a>
 * @version $Revision: 1.11.8.2 $
 */
public class InvokerRegistry
{

   private static final Logger log = Logger.getLogger(InvokerRegistry.class);

   private static final Map clientInvokers = new HashMap();
   private static final Map serverInvokers = new HashMap();
   private static final Map clientLocators = new HashMap();
   private static final Map serverLocators = new HashMap();
   private static final Set registeredLocators = new HashSet();
   private static final Object serverLock = new Object();
   private static final Object clientLock = new Object();

   static
   {
      // auto-register some standard invokers
      registerInvoker("socket", SocketClientInvoker.class, SocketServerInvoker.class);
      registerInvoker("rmi", RMIClientInvoker.class, RMIServerInvoker.class);
      registerInvoker("http", HTTPClientInvoker.class, HTTPServerInvoker.class);

   }

   /**
    * return an array of InvokerLocators that are local to this VM (server invokers)
    *
    * @return
    */
   public static synchronized final InvokerLocator[] getRegisteredServerLocators()
   {
      return (InvokerLocator[]) registeredLocators.toArray(new InvokerLocator[registeredLocators.size()]);
   }

   /**
    * return a suitable local server invoker that can service the remote invoker locator based on
    * a compatible transport
    *
    * @param remote
    * @return
    */
   public static synchronized InvokerLocator getSuitableServerLocatorForRemote(InvokerLocator remote)
   {
      Iterator iter = registeredLocators.iterator();
      while(iter.hasNext())
      {
         InvokerLocator l = (InvokerLocator) iter.next();
         if(l.getProtocol().equals(remote.getProtocol()))
         {
            // we found a valid transport match
            return l;
         }
      }
      return null;
   }

   /**
    * return an array of String of the registered transports
    *
    * @return
    */
   public static final String[] getRegisteredInvokerTransports()
   {
      synchronized(clientLock)
      {
         Set set = clientInvokers.keySet();
         String transports[] = new String[set.size()];
         return (String[]) set.toArray(transports);
      }
   }

   /**
    * return an array of ClientInvokers that are connected
    *
    * @return
    */
   public static final ClientInvoker[] getClientInvokers()
   {
      synchronized(clientLock)
      {
         if(clientLocators.isEmpty())
         {
            return new ClientInvoker[0];
         }
         Collection collection = clientLocators.values();
         return (ClientInvoker[]) collection.toArray(new RemoteClientInvoker[collection.size()]);
      }
   }

   /**
    * return an array of ServerInvokers that are connected
    *
    * @return
    */
   public static final ServerInvoker[] getServerInvokers()
   {
      synchronized(serverLock)
      {
         if(serverLocators.isEmpty())
         {
            return new ServerInvoker[0];
         }
         Collection collection = serverLocators.values();
         return (ServerInvoker[]) collection.toArray(new ServerInvoker[collection.size()]);
      }
   }

   /**
    * register a client/server invoker Class pair for a given transport
    *
    * @param transport
    * @param client
    * @param server
    */
   public static synchronized void registerInvoker(String transport, Class client, Class server)
   {
      clientInvokers.put(transport, client);
      serverInvokers.put(transport, server);
   }

   /**
    * unregister a client/server invoker pair for the given transport
    *
    * @param transport
    */
   public static synchronized void unregisterInvoker(String transport)
   {
      clientInvokers.remove(transport);
      serverInvokers.remove(transport);
   }

   public static synchronized void unregisterLocator(InvokerLocator locator)
   {
      serverLocators.remove(locator);
      registeredLocators.remove(locator);
   }

   /**
    * returns true if the client invoker is registered in the local JVM for a given locator
    *
    * @param locator
    * @return
    */
   public static boolean isClientInvokerRegistered(InvokerLocator locator)
   {
      synchronized(clientLock)
      {
         return clientLocators.containsKey(locator);
      }
   }

   /**
    * called to destroy any cached RemoteClientInvoker copies inside the registry. this method
    * must be called when it is determined that a remote server (via the locator) is no
    * longer available.
    *
    * @param locator
    */
   public static void destroyClientInvoker(InvokerLocator locator)
   {
      ClientInvoker invoker = null;

      synchronized(clientLock)
      {
         invoker = (ClientInvoker) clientLocators.remove(locator);
      }

      if(invoker != null)
      {
         if(log.isDebugEnabled())
         {
            log.debug("destroying client for locator: " + locator + ", invoker:" + invoker + ", remaining list:" + clientLocators);
         }
         invoker.disconnect();
         invoker = null;
      }

   }

   /**
    * create a ClientInvoker instance, using the specific InvokerLocator, which is just a client-side
    * invoker to a remote server
    *
    * @param locator
    * @return
    * @throws Exception
    */
   public static ClientInvoker createClientInvoker(InvokerLocator locator)
         throws Exception
   {
      if(locator == null)
      {
         throw new NullPointerException("locator cannot be null");
      }
      synchronized(clientLock)
      {
         ClientInvoker invoker = (ClientInvoker) clientLocators.get(locator);
         if(invoker != null)
         {
            return invoker;
         }

         boolean isPassByValue = false;
         Map parameters = locator.getParameters();
         if(parameters != null)
         {
            String value = (String) parameters.get(InvokerLocator.BYVALUE);
            if(value != null && Boolean.valueOf(value).booleanValue())
            {
               isPassByValue = true;
            }
         }

         // Check to see if server invoker is local
         // If in server locators map, then created locally by this class
         ServerInvoker svrInvoker = (ServerInvoker) serverLocators.get(locator);
         if(svrInvoker != null && !isPassByValue)
         {
            LocalClientInvoker localInvoker = new LocalClientInvoker(locator);
            // have to set reference to local server invoker so client in invoke directly
            localInvoker.setServerInvoker(svrInvoker);
            invoker = localInvoker;
            InvokerLocator l = invoker.getLocator();
            clientLocators.put(l, invoker);
         }
         else //not local
         {
            String protocol = locator.getProtocol();
            if(protocol == null)
            {
               throw new NullPointerException("protocol cannot be null for the locator");
            }
            Class cl = (Class) clientInvokers.get(protocol);
            if(cl == null)
            {
               throw new RuntimeException("Couldn't find valid client invoker class for transport '" + protocol + "'");
            }
            Constructor ctor = cl.getConstructor(new Class[]{InvokerLocator.class});
            invoker = (ClientInvoker) ctor.newInstance(new Object[]{locator});
            InvokerLocator l = invoker.getLocator();
            clientLocators.put(l, invoker);
         }
         return invoker;
      }
   }

   /**
    * returns true if the server invoker is registered in the local JVM for a given locator/handler pair
    *
    * @param locator
    * @return
    */
   public static boolean isServerInvokerRegistered(InvokerLocator locator)
   {
      synchronized(serverLock)
      {
         return serverLocators.containsKey(locator);
      }
   }

   /**
    * create a ServerInvoker instance, using the specific Invoker locator data and an implementation of the
    * ServerInvocationHandler interface.  Will use the default configuration values for the transport.
    *
    * @param locator
    * @return
    * @throws Exception
    */
   public static ServerInvoker createServerInvoker(InvokerLocator locator)
         throws Exception
   {
      return createServerInvoker(locator, null);
   }

      /**
       * create a ServerInvoker instance, using the specific Invoker locator data and an implementation of the
       * ServerInvocationHandler interface along with
       *
       * @param locator
       * @return
       * @throws Exception
       */
      public static ServerInvoker createServerInvoker(InvokerLocator locator, Map configuration)
            throws Exception
      {

      ServerInvoker invoker = null;
      synchronized(serverLock)
      {
         invoker = (ServerInvoker) serverLocators.get(locator);
         if(invoker != null)
         {
            return invoker;
         }
         Class cl = (Class) serverInvokers.get(locator.getProtocol());
         if(cl == null)
         {
            throw new RuntimeException("Couldn't find valid server invoker class for transport '" + locator.getProtocol() + "'");
         }
         if(configuration != null)
         {
            Constructor ctor = cl.getConstructor(new Class[]{InvokerLocator.class, Map.class});
            invoker = (ServerInvoker) ctor.newInstance(new Object[]{locator, configuration});
         }
         else
         {
            Constructor ctor = cl.getConstructor(new Class[]{InvokerLocator.class});
            invoker = (ServerInvoker) ctor.newInstance(new Object[]{locator});
         }
         serverLocators.put(locator, invoker);
         registeredLocators.add(invoker.getLocator());
      }
      try
      {
         return invoker;
      }
      catch(Exception ex)
      {
         serverLocators.remove(locator);
         registeredLocators.remove(locator);
         throw ex;
      }
   }
}