package org.jboss.mq.il.uil2;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Properties;
import javax.naming.InitialContext;
import javax.net.ServerSocketFactory;
import org.jboss.mq.il.Invoker;
import org.jboss.mq.il.ServerIL;
import org.jboss.mq.il.ServerILJMXService;
import org.jboss.mq.il.uil2.msgs.MsgTypes;
import org.jboss.mq.il.uil2.msgs.BaseMsg;
import org.jboss.security.SecurityDomain;
import org.jboss.system.server.ServerConfigUtil;
public class UILServerILService extends ServerILJMXService
      implements MsgTypes, Runnable, UILServerILServiceMBean
{
   final static int SO_TIMEOUT = 5000;
   private Invoker server;
   
   private String securityDomain;
   
   private String clientSocketFactoryName;
   
   private ServerSocketFactory serverSocketFactory;
   
   private ServerSocket serverSocket;
   private UILServerIL serverIL;
   private boolean running;
   
   private int serverBindPort = 0;
   
   private InetAddress bindAddress = null;
   
   private Thread acceptThread;
   
   private InetAddress clientAddress;
   
   private boolean enableTcpNoDelay = false;
   
   private int readTimeout = 0;
   
   private int bufferSize = 1;
   
   private int chunkSize = 0x40000000;
   
   private Properties connectionProperties;
   
   public Properties getClientConnectionProperties()
   {
      return connectionProperties;
   }
   
   public ServerIL getServerIL()
   {
      return serverIL;
   }
   
   public void run()
   {
      boolean trace = log.isTraceEnabled();
      while (running)
      {
         Socket socket = null;
         SocketManager socketMgr = null;
         try
         {
            socket = serverSocket.accept();
            if( trace )
               log.trace("Accepted connection: "+socket);
            socket.setSoTimeout(readTimeout);
            socket.setTcpNoDelay(enableTcpNoDelay);
            socketMgr = new SocketManager(socket);
            ServerSocketManagerHandler handler = new ServerSocketManagerHandler(server, socketMgr);
            socketMgr.setHandler(handler);
            socketMgr.setBufferSize(bufferSize);
            socketMgr.setChunkSize(chunkSize);
            socketMgr.start(server.getThreadGroup());
         }
         catch (IOException e)
         {
            if (running)
               log.warn("Failed to setup client connection", e);
         }
         catch(Throwable e)
         {
            if (running || trace)
               log.warn("Unexpected error in setup of client connection", e);            
         }
      }
   }
   
   public void startService() throws Exception
   {
      super.startService();
      running = true;
      this.server = lookupJMSServer();
            if (serverSocketFactory == null)
         serverSocketFactory = ServerSocketFactory.getDefault();
      
      if (securityDomain != null)
      {
         try
         {
            InitialContext ctx = new InitialContext();
            Class ssfClass = serverSocketFactory.getClass();
            SecurityDomain domain = (SecurityDomain) ctx.lookup(securityDomain);
            Class[] parameterTypes = {SecurityDomain.class};
            Method m = ssfClass.getMethod("setSecurityDomain", parameterTypes);
            Object[] args = {domain};
            m.invoke(serverSocketFactory, args);
         }
         catch (NoSuchMethodException e)
         {
            log.error("Socket factory does not support setSecurityDomain(SecurityDomain)");
         }
         catch (Exception e)
         {
            log.error("Failed to setSecurityDomain=" + securityDomain + " on socket factory");
         }
      }
            serverSocket = serverSocketFactory.createServerSocket(serverBindPort, 50, bindAddress);
      InetAddress socketAddress = serverSocket.getInetAddress();
      log.info("JBossMQ UIL service available at : " + socketAddress + ":" + serverSocket.getLocalPort());
      acceptThread = new Thread(server.getThreadGroup(), this, "UILServerILService Accept Thread");
      acceptThread.start();
      
      socketAddress = ServerConfigUtil.fixRemoteAddress(socketAddress);
            if( clientAddress != null )
         socketAddress = clientAddress;
      serverIL = new UILServerIL(socketAddress, serverSocket.getLocalPort(),
            clientSocketFactoryName, enableTcpNoDelay, bufferSize, chunkSize);
            connectionProperties = super.getClientConnectionProperties();
      connectionProperties.setProperty(UILServerILFactory.CLIENT_IL_SERVICE_KEY, UILClientILService.class.getName());
      connectionProperties.setProperty(UILServerILFactory.UIL_PORT_KEY, "" + serverSocket.getLocalPort());
      connectionProperties.setProperty(UILServerILFactory.UIL_ADDRESS_KEY, "" + socketAddress.getHostAddress());
      connectionProperties.setProperty(UILServerILFactory.UIL_TCPNODELAY_KEY, enableTcpNoDelay ? "yes" : "no");
      connectionProperties.setProperty(UILServerILFactory.UIL_BUFFERSIZE_KEY, "" + bufferSize);
      connectionProperties.setProperty(UILServerILFactory.UIL_CHUNKSIZE_KEY, "" + chunkSize);
      connectionProperties.setProperty(UILServerILFactory.UIL_RECEIVE_REPLIES_KEY, "No");
      bindJNDIReferences();
      BaseMsg.setUseJMSServerMsgIDs(true);
   }
   
   public void stopService()
   {
      try
      {
         running = false;
         unbindJNDIReferences();
                  if (serverSocket != null)
         {
            serverSocket.close();
         }
      }
      catch (Exception e)
      {
         log.error("Exception occured when trying to stop UIL Service: ", e);
      }
   }
   
   public int getServerBindPort()
   {
      return serverBindPort;
   }
   
   public void setServerBindPort(int serverBindPort)
   {
      this.serverBindPort = serverBindPort;
   }
   
   public String getBindAddress()
   {
      String addr = "0.0.0.0";
      if (bindAddress != null)
         addr = bindAddress.getHostName();
      return addr;
   }
   
   public void setBindAddress(String host) throws UnknownHostException
   {
            if (host == null || host.length() == 0)
         bindAddress = null;
      else
         bindAddress = InetAddress.getByName(host);
   }
   
   public InetAddress getClientAddress()
   {
      return clientAddress;
   }
   
   public void setClientAddress(InetAddress addr)
   {
      clientAddress = addr;
   }
   
   public boolean getEnableTcpNoDelay()
   {
      return enableTcpNoDelay;
   }
   
   public void setEnableTcpNoDelay(boolean enableTcpNoDelay)
   {
      this.enableTcpNoDelay = enableTcpNoDelay;
   }
   
   public int getBufferSize()
   {
      return bufferSize;
   }
   
   public void setBufferSize(int size)
   {
      this.bufferSize = size;
   }
   
   public int getChunkSize()
   {
      return chunkSize;
   }
   
   public void setChunkSize(int size)
   {
      this.chunkSize = size;
   }
   
   public int getReadTimeout()
   {
      return readTimeout;
   }
   
   public void setReadTimeout(int timeout)
   {
      this.readTimeout = timeout;
   }
   
   public String getClientSocketFactory()
   {
      return clientSocketFactoryName;
   }
   
   public void setClientSocketFactory(String name)
   {
      this.clientSocketFactoryName = name;
   }
   
   public void setServerSocketFactory(String name) throws Exception
   {
      ClassLoader loader = Thread.currentThread().getContextClassLoader();
      Class ssfClass = loader.loadClass(name);
      serverSocketFactory = (ServerSocketFactory) ssfClass.newInstance();
   }
   
   public String getServerSocketFactory()
   {
      String name = null;
      if (serverSocketFactory != null)
         name = serverSocketFactory.getClass().getName();
      return name;
   }
   
   public void setSecurityDomain(String domainName)
   {
      this.securityDomain = domainName;
   }
   
   public String getSecurityDomain()
   {
      return this.securityDomain;
   }
}