/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file 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.logging;

import java.net.Socket;
import java.net.ServerSocket;
import java.net.InetAddress;

import org.apache.log4j.LogManager;
import org.apache.log4j.MDC;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.net.SocketNode;

import org.jboss.system.ServiceMBeanSupport;
import org.jboss.system.MissingAttributeException;

import org.jboss.logging.Logger;

/**
 * A Log4j SocketServer service.  Listens for client connections on the
 * specified port and creates a new thread and SocketNode to process the
 * incoming client log messages.
 *
 * <p>
 * The LoggerRepository can be changed based on the clients address
 * by using a custom LoggerRepositoryFactory.  The default factory
 * will simply return the current repository.
 *
 * @jmx:mbean extends="org.jboss.system.ServiceMBean"
 *
 * @version <tt>$Revision: 89152 $</tt>
 * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
 */
public class Log4jSocketServer
   extends ServiceMBeanSupport
   implements Log4jSocketServerMBean
{
   /** The port number where the server listens. */
   protected int port = -1;

   /** The listen backlog count. */
   protected int backlog = 50;

   /** The address to bind to. */
   protected InetAddress bindAddress;
   
   /** True if the socket listener is enabled. */
   protected boolean listenerEnabled = true;

   /** The socket listener thread. */
   protected SocketListenerThread listenerThread;

   /** The server socket which the listener listens on. */
   protected ServerSocket serverSocket;

   /** The factory to create LoggerRepository's for client connections. */
   protected LoggerRepositoryFactory loggerRepositoryFactory;
   
   /**
    * @jmx:managed-constructor
    */
   public Log4jSocketServer()
   {
      super();
   }

   /**
    * @jmx:managed-attribute
    */
   public void setPort(final int port)
   {
      this.port = port;
   }

   /**
    * @jmx:managed-attribute
    */
   public int getPort()
   {
      return port;
   }

   /**
    * @jmx:managed-attribute
    */
   public void setBacklog(final int backlog)
   {
      this.backlog = backlog;
   }

   /**
    * @jmx:managed-attribute
    */
   public int getBacklog()
   {
      return backlog;
   }

   /**
    * @jmx:managed-attribute
    */
   public void setBindAddress(final InetAddress addr)
   {
      this.bindAddress = addr;
   }

   /**
    * @jmx:managed-attribute
    */
   public InetAddress getBindAddress()
   {
      return bindAddress;
   }
   
   /**
    * @jmx:managed-attribute
    */
   public void setListenerEnabled(final boolean enabled)
   {
      listenerEnabled = enabled;
   }

   /**
    * @jmx:managed-attribute
    */
   public boolean setListenerEnabled()
   {
      return listenerEnabled;
   }

   /**
    * @jmx:managed-attribute
    */
   public void setLoggerRepositoryFactoryType(final Class type)
      throws InstantiationException, IllegalAccessException, ClassCastException
   {
      this.loggerRepositoryFactory = (LoggerRepositoryFactory)type.newInstance();
   }

   /**
    * @jmx:managed-attribute
    */
   public Class getLoggerRepositoryFactoryType()
   {
      if (loggerRepositoryFactory == null)
         return null;
      return loggerRepositoryFactory.getClass();
   }

   /**
    * @jmx:managed-operation
    */
   public LoggerRepository getLoggerRepository(final InetAddress addr)
   {
      return loggerRepositoryFactory.create(addr);
   }
   

   ///////////////////////////////////////////////////////////////////////////
   //                             Socket Listener                           //
   ///////////////////////////////////////////////////////////////////////////

   protected class SocketListenerThread
      extends Thread
   {
      protected Logger log = Logger.getLogger(SocketListenerThread.class);
      protected boolean enabled;
      protected boolean shuttingDown;
      protected Object lock = new Object();
      
      public SocketListenerThread(final boolean enabled)
      {
         super("SocketListenerThread");

         this.enabled = enabled;
      }

      public void setEnabled(boolean enabled)
      {
         this.enabled = enabled;

         synchronized (lock)
         {
            lock.notifyAll();
         }
         log.debug("Notified that enabled: " + enabled);
      }

      public void shutdown()
      {
         enabled = false;
         shuttingDown = true;

         synchronized (lock)
         {
            lock.notifyAll();
         }
         log.debug("Notified to shutdown");
      }
      
      public void run()
      {
         boolean intr = false;
         try
         {
            while (!shuttingDown)
            {

               if (!enabled)
               {
                  try
                  {
                     log.debug("Disabled, waiting for notification");
                     synchronized (lock)
                     {
                        lock.wait();
                     }
                  }
                  catch (InterruptedException ignore)
                  {
                     intr = true;
                  }
               }

               try
               {
                  doRun();
               }
               catch (Throwable e)
               {
                  log.error("Exception caught from main loop; ignoring", e);
               }
            }
         }
         finally
         {
            if (intr) Thread.currentThread().interrupt();
         }
      }

      protected void doRun() throws Exception
      {
         while (enabled)
         {
            Socket socket = serverSocket.accept();
            InetAddress addr =  socket.getInetAddress();
            log.debug("Connected to client at " + addr); 

            LoggerRepository repo = getLoggerRepository(addr);
            log.debug("Using repository: " + repo);

            //
            // jason: may want to expose socket node as an MBean for management
            //
            
            log.debug("Starting new socket node");
            SocketNode node = new SocketNode(socket, repo);
            /* Create a thread with and MDC.host value set to the client
            hostname to allow for distiguished output
            */
            String clientHost = addr.getHostName();
            SocketThread thread = new SocketThread(node, clientHost);
            thread.start();
            log.debug("Socket node started");
         }
      }
   }

   static class SocketThread
      extends Thread
   {
      String host;

      SocketThread(Runnable target, String host)
      {
         super(target, host+" LoggingEvent Thread");
         this.host = host;
      }
      public void run()
      {
         MDC.put("host", host);
         super.run();
      }
   }

   ///////////////////////////////////////////////////////////////////////////
   //                         LoggerRepositoryFactory                       //
   ///////////////////////////////////////////////////////////////////////////

   public static interface LoggerRepositoryFactory
   {
      public LoggerRepository create(InetAddress addr);
   }

   /**
    * A simple LoggerRepository factory which simply returns
    * the current repository from the LogManager.
    */
   public static class DefaultLoggerRepositoryFactory
      implements LoggerRepositoryFactory
   {
      private LoggerRepository repo;
      
      public LoggerRepository create(final InetAddress addr)
      {
         if (repo == null)
            repo = LogManager.getLoggerRepository();
         return repo;
      }
   }
   
   ///////////////////////////////////////////////////////////////////////////
   //                            Service Overrides                          //
   ///////////////////////////////////////////////////////////////////////////

   protected void createService() throws Exception
   {
      listenerThread = new SocketListenerThread(false);
      listenerThread.setDaemon(true);
      listenerThread.start();
      log.debug("Socket listener thread started");

      if (loggerRepositoryFactory == null)
      {
         log.debug("Using default logger repository factory");
         loggerRepositoryFactory = new DefaultLoggerRepositoryFactory();
      }
   }
      
   protected void startService() throws Exception
   {
      if (port == -1)
         throw new MissingAttributeException("Port");

      // create a new server socket to handle port number changes
      if (bindAddress == null)
      {
         serverSocket = new ServerSocket(port, backlog);
      }
      else
      {
         serverSocket = new ServerSocket(port, backlog, bindAddress);
      }

      log.info("Listening on " + serverSocket);
      
      listenerThread.setEnabled(listenerEnabled);
   }

   protected void stopService() throws Exception
   {
      listenerThread.setEnabled(false);
   }

   protected void destroyService() throws Exception
   {
      listenerThread.shutdown();
      listenerThread = null;
      serverSocket = null;
   }
}
