/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.remoting;


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;

import org.jboss.logging.Logger;
import org.jboss.remoting.transport.async.AsyncClientInvoker;
import org.jboss.remoting.transport.async.AsyncServerInvoker;
import org.jboss.remoting.transport.rmi.RMIClientInvoker;
import org.jboss.remoting.transport.rmi.RMIServerInvoker;
import org.jboss.remoting.transport.soap.axis.SOAPClientInvoker;
import org.jboss.remoting.transport.soap.axis.SOAPServerInvoker;
import org.jboss.remoting.transport.socket.SocketClientInvoker;
import org.jboss.remoting.transport.socket.SocketServerInvoker;
import org.jboss.remoting.transport.local.LocalClientInvoker;
import org.jboss.remoting.transport.ClientInvoker;

/**
 * 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.2.4 $
 */
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("async",AsyncClientInvoker.class,AsyncServerInvoker.class);

      // attempt to register if AXIS is on the classpath
      try
      {
         registerInvoker("soap",SOAPClientInvoker.class,SOAPServerInvoker.class);
      }
      catch (Throwable cnf)
      {
         log.info("Failed to load soap remoting transport: " + cnf.getMessage());
         // ignore, since we don't want to require it
      }
   }

   /**
    * 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.destroy();
         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;
         }

         // 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)
         {
             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
    *
    * @param locator
    * @return
    * @throws Exception
    */
   public static ServerInvoker createServerInvoker (InvokerLocator locator)
      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()+"'");
         }
         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;
      }
   }
}
