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

import org.jboss.remoting.transport.ClientInvoker;
import org.jboss.system.server.ServerConfig;
import org.jboss.system.server.ServerConfigUtil;

import java.io.Serializable;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;

/**
 * InvokerLocator is an object that indentifies a specific Invoker on the network, via a unique
 * locator URI. The locator URI is in the format: <P>
 * <p/>
 * <tt>protocol://host[:port][/path[?param=value&param2=value2]]</tt>     <P>
 * <p/>
 * For example, a http based locator might be: <P>
 * <p/>
 * <tt>http://192.168.10.1:8081</tt>  <P>
 * <p/>
 * An example Socket based locator might be: <P>
 * <p/>
 * <tt>socket://192.168.10.1:9999</tt>  <P>
 * <p/>
 * An example RMI based locator might be: <P>
 * <p/>
 * <tt>rmi://localhost</tt>  <P>
 * <p/>
 * NOTE: the hostname will automatically be resolved to the outside IP address of the local machine
 * if <tt>localhost</tt> or <tt>127.0.0.1</tt> is used as the hostname in the URI.  If it cannot be
 * determined or resolved, it will use what was passed.
 *
 * @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
 * @author <a href="mailto:telrod@e2technologies.net">Tom Elrod</a>
 * @version $Revision: 1.7.2.2 $
 */
public class InvokerLocator implements Serializable
{
   private static final long serialVersionUID = -2909329895029296248L;
   protected String protocol;
   protected String host;
   protected int port;
   protected String path;
   protected Map parameters;
   private String uri;
   private String originalURL;

   /**
    * Constant to define the param name to be used when defining the data type.
    */
   public static final String DATATYPE = "datatype";
   public static final String DATATYPE_CASED = "dataType";

   /**
    * Constant to define the param name to be used when defining the marshaller fully qualified classname
    */
   public static final String MARSHALLER = "marshaller";
   /**
    * Constant to define the param name to be used when defining the unmarshaller fully qualified classname
    */
   public static final String UNMARSHALLER = "unmarshaller";

   /**
    * Constant to define what port the marshalling loader port resides on.
    */
   public static final String LOADER_PORT = "loaderport";

   /**
    * Constant to define the param name to be used when defining if marshalling should be by value,
    * which means will be remote client invoker instead of using local client invoker.
    */
   public static final String BYVALUE = "byvalue";

   public InvokerLocator(String uri)
   throws MalformedURLException
   {
      originalURL = uri;
      int i = uri.indexOf("://");
      if (i < 0)
      {
         throw new MalformedURLException();
      }
      String tmp = uri.substring(i + 3);
      this.protocol = uri.substring(0, i);
      i = tmp.indexOf("/");
      int p = tmp.indexOf(":");
      if (p != -1)
      {
         host = resolveHost(tmp.substring(0, p).trim());
         if (i > -1)
         {
            port = Integer.parseInt(tmp.substring(p + 1, i));
         }
         else
         {
            port = Integer.parseInt(tmp.substring(p + 1));
         }
      }
      else
      {
         if (i > -1)
         {
            host = resolveHost(tmp.substring(0, i).trim());
         }
         else
         {
            host = resolveHost(tmp.substring(0).trim());
         }
         port = -1;
      }
      p = tmp.indexOf("?");
      if (p != -1)
      {
         path = tmp.substring(i + 1, p);
         String args = tmp.substring(p + 1);
         StringTokenizer tok = new StringTokenizer(args, "&");
         parameters = new HashMap(tok.countTokens());
         while (tok.hasMoreTokens())
         {
            String token = tok.nextToken();
            int eq = token.indexOf("=");
            String name = (eq > -1) ? token.substring(0, eq) : token;
            String value = (eq > -1) ? token.substring(eq + 1) : "";
            parameters.put(name, value);
         }
      }
      else
      {
         path = "";
      }
      // rebuild it, since the host probably got resolved and the port changed
      this.uri = protocol + "://" + this.host + ((port > -1) ? (":" + port) : "") + "/" + path + ((parameters != null) ? "?" : "");
      if (parameters != null)
      {
         Iterator iter = parameters.keySet().iterator();
         while (iter.hasNext())
         {
            String key = (String) iter.next();
            String val = (String) parameters.get(key);
            this.uri += key + "=" + val;
            if (iter.hasNext())
            {
               this.uri += "&";
            }
         }
      }
   }

   /**
    *
    */
   private static final String resolveHost(String host)
   {
      if (host.indexOf("0.0.0.0") != -1)
      {
         if (System.getProperty(ServerConfig.SERVER_BIND_ADDRESS, "0.0.0.0").equals("0.0.0.0"))
         {
            host = ServerConfigUtil.fixRemoteAddress(host);
         }
         else
         {
            host = host.replaceAll("0\\.0\\.0\\.0", System.getProperty(ServerConfig.SERVER_BIND_ADDRESS));
         }
      }
      try
      {
         return InetAddress.getByName(host).getHostAddress();
      }
      catch (Exception ex)
      {
         return host;
      }
   }

   public InvokerLocator(String protocol, String host, int port, String path, Map parameters)
   {
      this.protocol = protocol;
      this.host = resolveHost(host);
      this.port = port;
      this.path = path;
      this.parameters = parameters;

      this.uri = protocol + "://" + this.host + ((port > -1) ? (":" + port) : "") + "/" + path + ((parameters != null) ? "?" : "");
      if (parameters != null)
      {
         Iterator iter = parameters.keySet().iterator();
         while (iter.hasNext())
         {
            String key = (String) iter.next();
            String val = (String) parameters.get(key);
            this.uri += key + "=" + val;
            if (iter.hasNext())
            {
               this.uri += "&";
            }
         }
      }
      originalURL = uri;
   }

   public int hashCode()
   {
      return uri.hashCode();
   }

   public boolean equals(Object obj)
   {
      return obj instanceof InvokerLocator && obj.hashCode() == hashCode();
   }

   /**
    * return the locator URI, in the format: <P>
    * <p/>
    * <tt>protocol://host[:port][/path[?param=value&param2=value2]]</tt>
    *
    * @return
    */
   public String getLocatorURI()
   {
      return uri;
   }

   public String getProtocol()
   {
      return protocol;
   }

   public String getHost()
   {
      return host;
   }

   public int getPort()
   {
      return port;
   }

   public String getPath()
   {
      return path;
   }

   public Map getParameters()
   {
      return parameters;
   }

   public String toString()
   {
      return "InvokerLocator [" + uri + "]";
   }

   public String getOriginalURI()
   {
      return originalURL;
   }

   /**
    * narrow this invoker to a specific RemoteClientInvoker instance
    *
    * @return
    * @throws Exception
    */
   public ClientInvoker narrow() throws Exception
   {
      return InvokerRegistry.createClientInvoker(this);
   }

}