/*
* 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.cache.interop;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.PropertyConfigurator;
import org.jboss.cache.TreeCache;
import org.jboss.cache.TreeCacheMBean;

import java.io.BufferedInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Starts a JBossCache instance and then listens on a socket for
 * a call from {@link Shutdown}.
 * 
 * @author Brian Stansberry
 * @version $Revision$
 */
public class Main
{
   private static final Log log = LogFactory.getLog(Main.class);
   
   private static final String SHUTDOWN = "SHUTDOWN";
   
   private String configFile_;
   private int listenerPort_ = 11111;
   private String bindAddressString_;
   private ServerSocket srvSock_;
   private TreeCacheMBean cache_;
   private Thread listenerThread_;
   private boolean stopped;
   
   /**
    * 
    * @param args
    */
   public static void main(final String[] args)
   {
      try
      {
//         Runnable worker = new Runnable() {
//            public void run()
//            {
//               try
//               {
                  Main main = new Main();
                  main.start(args);
//               }
//               catch (Throwable t)
//               {
//                  log.fatal("Failed to boot JBossCache", t);
//               }
//            }
//   
//         };
//   
//         ThreadGroup threads = new ThreadGroup("jbosscache");
//         new Thread(threads, worker, "main").start();
         
         log.info("Started");
      }
      catch (Throwable t)
      {
         log.fatal("Failed to boot JBossCache", t);
      }
   }

   /**
    * Create a new Main.
    * 
    */
   public Main()
   {
      super();
   }

   public void start(final String[] args) throws Exception
   {
      // First process the command line to pickup custom props/settings
      processCommandLine(args);
      
      Runtime.getRuntime().addShutdownHook(new ShutdownHook());
      
      try
      {
         startCache();
      
         startListener();
      }
      catch (Exception e)
      {
         stopCache();
         throw e;
      }
   }
   
   public void shutdown()
   {
      stopCache();
      
      stopListener();
   }
   
   
   private void processCommandLine(final String[] args)
   {
      if (args == null || args.length == 0 || args[0] == null)
      {
         usage();
         return;
      }
      
      configFile_ = args[0];
      
      if (args.length > 1)
      {
         listenerPort_ = Integer.parseInt(args[1]);
         
         if (args.length > 2)
            bindAddressString_ = args[2];
      }
   }
   
   private void usage()
   {
      System.out.println("Usage:");
      System.out.println("java -cp $CLASSPATH org.jboss.cache.misc.Main configURL [bindAddress] [port]");
      System.out.println();
      System.out.println("Arguments");
      System.out.println("---------");
      System.out.println();
      System.out.println("configFile    Location of a JBossCache configuration descriptor file.");
      System.out.println("              Can be a classpath resource or a location in the file system.");
      System.out.println();
      System.out.println("port          Port on which we should listen for shutdown commands (Optional).");
      System.out.println("              Default is 11111");
      System.out.println();
      System.out.println("bindAddress   IP address on which the socket that listens for shutdown");
      System.out.println("              commands should bind (Optional)");
      System.out.println("              Default is to bind to all addresses");
   }
   
   private void startCache() throws Exception
   {
      if (cache_ == null)
      {
         cache_ = new TreeCache();
         PropertyConfigurator config = new PropertyConfigurator();
         config.configure(cache_, configFile_);
         
         cache_.create();
         cache_.start();
         
         log.info("Cache started");
      }
   }
   
   private void startListener() throws Exception
   {
      if (srvSock_!=null) {
         throw new Exception("Listener already started.");
      }

     if (bindAddressString_!=null) {
         InetAddress bindAddress = InetAddress.getByName(bindAddressString_);
         srvSock_ = new ServerSocket(listenerPort_, 50, bindAddress);  
     }
     else {
         srvSock_ = new ServerSocket(listenerPort_, 50);
     }
     
     listenerThread_ = new Thread(new Runnable() 
        {
           public void run()
           {
              mainLoop();
              stopListener();
           }
           
        }, "JBossCache Shutdown Listener Thread");
     
     listenerThread_.start();
     
     log.info("Listener started");
   }
   
   private void mainLoop()
   {
      Socket sock = null;
      
      while (!stopped)
      {
         ObjectInputStream ois = null;
         try
         {
            // blocks until shutdown request or 'forever'
            sock=srvSock_.accept();
            sock.setSoLinger(true, 500);
   
            if(log.isTraceEnabled()) {
                log.trace("accepted connection from "+sock);
            }
   
            BufferedInputStream bis = new BufferedInputStream(sock.getInputStream());
            
            
            ois = new ObjectInputStream(bis);
            ObjectOutputStream oos = new ObjectOutputStream(sock.getOutputStream());
            
            try
            {
               String cmd = (String) ois.readObject();
               if (SHUTDOWN.equals(cmd))
               {
                  stopped = true;
                  stopCache();
                  oos.writeObject("OK");
               }
               else {
                  cmd = (cmd == null) ? "null" : cmd;
                  oos.writeObject("Invalid command: " + cmd);
               }
            }
            catch (Exception e)
            {
               oos.writeObject("FAILED: " + e.getLocalizedMessage());
            }
            finally
            {
               oos.close();
            }
            
            bis.close();
            sock.close();
        }
        catch(Exception e) {
            if(log.isDebugEnabled()) log.debug("listener thread exception :"+e);
        }
        finally {
            try {
                ois.close();
            }
            catch(Exception e) {
                // OK
            }
        }
         
      }
   }
   
   private void stopListener()
   {
      stopped = true;
      if (Thread.currentThread() != listenerThread_)
      {
         listenerThread_.interrupt();
      }
      
      try 
      {
         srvSock_.close();
      }
      catch(Exception e) 
      {
         log.error("Failed to close server socket: "+e);
      }

      srvSock_ = null;
      log.info("Listener thread stopped");
   }
   
   private void stopCache()
   {
      if (cache_ != null)
      {
         try
         {
            cache_.stop();
            cache_.destroy();
         }
         catch (Exception e)
         {
            log.error("Error stopping cache", e);
         }
         finally
         {
            cache_ = null;
         }
      }
   }

   private class ShutdownHook extends Thread
   {
      public void run()
      {
         try
         {
            stopCache();
         }
         catch (Exception e)
         {
         }
         try
         {
            stopListener();
         }
         catch (Exception e)
         {
         }
      }
   }
}
