//----------------------------BEGIN LICENSE----------------------------
/* 
 * Willow : the Open Source WorkFlow Project
 * Distributable under GNU LGPL license by gun.org
 *
 * Copyright (C) 2004-2010  huihoo.org 
 * Copyright (C) 2004-2010  ZosaTapo <dertyang@hotmail.com> 
 * 
 * ====================================================================
 * Project Homepage : http://www.huihoo.org/willow
 * Source Forge     : http://sourceforge.net/projects/huihoo
 * Mailing list     : willow@lists.sourceforge.net 
 */
//----------------------------END  LICENSE-----------------------------
package org.huihoo.willow.startup;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

import org.apache.commons.digester.Digester;
import org.apache.commons.digester.Rule;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.huihoo.commons.logging.SystemLogHandler;
import org.huihoo.willow.Container;
import org.huihoo.willow.Globals;
import org.huihoo.willow.Lifecycle;
import org.huihoo.willow.LifecycleException;
import org.huihoo.willow.Server;
import org.huihoo.willow.core.StandardServer;
import org.huihoo.willow.deploy.PluginRepository;
import org.huihoo.willow.xmlrules.ServiceRuleSet;
import org.huihoo.willow.xmlrules.setAllProperitesExcludingClassNameRule;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;

public class Willow extends Embedded
{
	private static Log log = LogFactory.getLog(Willow.class);

	// ----------------------------------------------------- Instance Variables
	/**
	 * Pathname to the server configuration file.
	 */
	protected String configFile = "conf/server.xml";

	/**
	 * The server component we are starting or stopping
	 */
	protected Server server = null;

	/**
	 * Are we starting a new server?
	 */
	protected boolean starting = false;

	/**
	 * Are we stopping an existing server?
	 */
	protected boolean stopping = false;

	/**
	 * Shutdown hook.
	 */
	protected Thread shutdownHook = new WillowShutdownHook();

	// ------------------------------------------------------------- Properties

	public void setConfigFile(String file)
	{
		configFile = file;
	}

	public String getConfigFile()
	{
		return configFile;
	}

	/**
	 * Set the server instance we are configuring.
	 *
	 * @param server The new server
	 */
	public void setServer(Server server)
	{

		this.server = server;

	}

	// ----------------------------------------------------------- Main Program

	/**
	 * The application main program.
	 *
	 * @param args Command line arguments
	 */
	public static void main(String args[])
	{
		(new Willow()).process(args);
	}

	/**
	 * The instance main program.
	 *
	 * @param args Command line arguments
	 */
	public void process(String args[])
	{

		setAwait(true);
		setHome();
		try
		{
			if (arguments(args))
			{
				if (starting)
				{
					load(args);
					start();
				}
				else if (stopping)
				{
					stopServer();
				}
			}
		}
		catch (Exception e)
		{
			e.printStackTrace(System.out);
		}
	}

	// ------------------------------------------------------ Protected Methods

	/**
	 * Process the specified command line arguments, and return
	 * <code>true</code> if we should continue processing; otherwise
	 * return <code>false</code>.
	 *
	 * @param args Command line arguments to process
	 */
	protected boolean arguments(String args[])
	{

		boolean isConfig = false;

		if (args.length < 1)
		{
			usage();
			return (false);
		}

		for (int i = 0; i < args.length; i++)
		{
			if (isConfig)
			{
				configFile = args[i];
				isConfig = false;
			}
			else if (args[i].equals("-config"))
			{
				isConfig = true;
			}
			else if (args[i].equals("-help"))
			{
				usage();
				return (false);
			}
			else if (args[i].equals("start"))
			{
				starting = true;
				stopping = false;
			}
			else if (args[i].equals("stop"))
			{
				starting = false;
				stopping = true;
			}
			else
			{
				usage();
				return (false);
			}
		}

		return (true);

	}

	/**
	 * Return a File object representing our configuration file.
	 */
	protected File configFile()
	{

		File file = new File(configFile);
		if (!file.isAbsolute())
		{
			file = new File(System.getProperty(Globals.PROPS_WILLOW_HOME), configFile);
		}
		return (file);

	}

	/**
	 * Create and configure the Digester we will be using for startup.
	 */
	protected Digester createStartDigester()
	{
		long t1 = System.currentTimeMillis();
		// Initialize the digester
		Digester digester = new WillowDigester();
		if (debug > 0)
		{
			digester.setDebug(debug);
		}
		digester.setValidating(false);
		digester.setClassLoader(StandardServer.class.getClassLoader());

		// Configure the actions we will be using
		digester.addObjectCreate("Server", "org.huihoo.willow.core.StandardServer", "className");
		digester.addRule("Server", new setAllProperitesExcludingClassNameRule());
		digester.addSetNext("Server", "setServer", "org.huihoo.willow.Server");

		digester.addObjectCreate("Server/Listener", null, // MUST be specified in the element
		"className");
		digester.addRule("Server/Listener", new setAllProperitesExcludingClassNameRule());
		digester.addSetNext("Server/Listener", "addLifecycleListener", "org.huihoo.willow.LifecycleListener");

		// Add RuleSets for nested elements		
		digester.addRuleSet(new ServiceRuleSet("Server/"));

		// When the 'service' is found, set the parentClassLoader.
		digester.addRule("Server/Service", new SetParentClassLoaderRule(digester, parentClassLoader));

		long t2 = System.currentTimeMillis();
		log.debug("Digester for server.xml created " + (t2 - t1));
		return (digester);

	}

	/**
	 * Create and configure the Digester we will be using for shutdown.
	 */
	protected Digester createStopDigester()
	{

		// Initialize the digester
		Digester digester = new Digester();
		if (debug > 0)
		{
			digester.setDebug(debug);
		}

		// Configure the rules we need for shutting down
		digester.addObjectCreate("Server", "org.huihoo.willow.core.StandardServer", "className");
		digester.addSetProperties("Server");
		digester.addSetNext("Server", "setServer", "org.huihoo.willow.Server");

		return (digester);

	}

	public void stopServer()
	{

		if (server == null)
		{
			// Create and execute our Digester
			Digester digester = createStopDigester();
			digester.setClassLoader(Thread.currentThread().getContextClassLoader());
			File file = configFile();
			try
			{
				InputSource is = new InputSource("file://" + file.getAbsolutePath());
				FileInputStream fis = new FileInputStream(file);
				is.setByteStream(fis);
				digester.push(this);
				digester.parse(is);
				fis.close();
			}
			catch (Exception e)
			{
				System.out.println("Willow.stop: " + e);
				e.printStackTrace(System.out);
				System.exit(1);
			}
		}

		// Stop the existing server
		try
		{
			Socket socket = new Socket("127.0.0.1", server.getPort());
			OutputStream stream = socket.getOutputStream();
			String shutdown = server.getShutdown();
			for (int i = 0; i < shutdown.length(); i++)
				stream.write(shutdown.charAt(i));
			stream.flush();
			stream.close();
			socket.close();
		}
		catch (IOException e)
		{
			System.out.println("Willow.stop: " + e);
			e.printStackTrace(System.out);
			System.exit(1);
		}

	}

	/**
	 * Set the <code>catalina.home</code> System property to the current
	 * working directory if it has not been set.
	 * @deprecated Use initDirs()
	 */
	public void setHome()
	{
		initDirs();
	}

	/**
	 * Start a new server instance.
	 */
	public void load()
	{
		initDirs();

		// Create and execute our Digester
		Digester digester = createStartDigester();
		long t1 = System.currentTimeMillis();

		InputSource inputSource = null;
		InputStream inputStream = null;
		try
		{
			File file = configFile();
			inputStream = new FileInputStream(file);
			inputSource = new InputSource("file://" + file.getAbsolutePath());
			log.info("Server configuration :" + file.getAbsolutePath());
		}
		catch (Exception e)
		{
			;
		}
		if (inputStream == null)
		{
			try
			{
				inputStream = getClass().getClassLoader().getResourceAsStream(getConfigFile());
				inputSource = new InputSource(getClass().getClassLoader().getResource(getConfigFile()).toString());
			}
			catch (Exception e)
			{
				;
			}
		}

		if (inputStream == null)
		{
			System.out.println("Willow : Can't load server.xml");
			return;
		}

		try
		{
			inputSource.setByteStream(inputStream);
			digester.push(this);
			digester.parse(inputSource);
			inputStream.close();
		}
		catch (Exception e)
		{
			System.out.println("Willow.start: " + e);
			e.printStackTrace(System.out);
			return;
		}

		// Replace System.out and System.err with a custom PrintStream
		SystemLogHandler systemlog = new SystemLogHandler(System.out);
		System.setOut(systemlog);
		System.setErr(systemlog);

		PluginRepository.loadOnStartup(org.huihoo.willow.Globals.PluginXml);

		// Start the new server
		if (server instanceof Lifecycle)
		{
			try
			{
				server.initialize();
			}
			catch (LifecycleException e)
			{
				log.error("Willow.start: ", e);
			}
		}

		long t2 = System.currentTimeMillis();
		log.info("Initialization processed in " + (t2 - t1) + " ms");

	}

	/* 
	 * Load using arguments
	 */
	public void load(String args[])
	{

		setHome();

		try
		{
			if (arguments(args))
			{
				load();
			}
		}
		catch (Exception e)
		{
			e.printStackTrace(System.out);
		}
	}

	/**
	 * Start a new server instance.
	 */
	public void start()
	{
		if (server == null)
		{
			load();
		}

		long t1 = System.currentTimeMillis();

		// Start the new server
		if (server instanceof Lifecycle)
		{
			try
			{
				((Lifecycle) server).start();
			}
			catch (LifecycleException e)
			{
				log.error("Willow.start: ", e);
			}
		}
		//			
		long t2 = System.currentTimeMillis();
		log.info("Server startup in " + (t2 - t1) + " ms");

		try
		{
			// Register shutdown hook
			Runtime.getRuntime().addShutdownHook(shutdownHook);
		}
		catch (Throwable t)
		{
			// This will fail on JDK 1.2. Ignoring, as Willow can run
			// fine without the shutdown hook.
		}

		if (await)
		{
			await();
			stop();
		}
	}

	/**
	 * Stop an existing server instance.
	 */
	public void stop()
	{
		try
		{
			// Remove the ShutdownHook first so that server.stop() 
			// doesn't get invoked twice
			Runtime.getRuntime().removeShutdownHook(shutdownHook);
		}
		catch (Throwable t)
		{
			// This will fail on JDK 1.2. Ignoring, as Willow can run
			// fine without the shutdown hook.
		}

		// Shut down the server
		if (server instanceof Lifecycle)
		{
			try
			{
				((Lifecycle) server).stop();
			}
			catch (LifecycleException e)
			{
				log.error("Willow.stop", e);
			}
		}
	}

	/**
	 * Await and shutdown.
	 */
	public void await()
	{
		server.await();
	}

	/**
	 * Print usage information for this application.
	 */
	protected void usage()
	{

		System.out.println(
			"usage: java org.huihoo.willow.startup.Willow" + " [ -config {pathname} ] [ -debug ]" + " { start | stop }");

	}

	// --------------------------------------- WillowShutdownHook Inner Class
	/**
	 * Shutdown hook which will perform a clean shutdown of Willow if needed.
	 */
	protected class WillowShutdownHook extends Thread
	{

		public void run()
		{
			Willow.this.stop();
		}

	}

	// ------------------------------------------------------------ Private Classes
	/**
	 * Rule that sets the parent class loader for the top object on the stack,
	 * which must be a <code>Container</code>.
	 */

	final class SetParentClassLoaderRule extends Rule
	{

		public SetParentClassLoaderRule(Digester digester, ClassLoader parentClassLoader)
		{

			super(digester);
			this.parentClassLoader = parentClassLoader;

		}

		ClassLoader parentClassLoader = null;

		public void begin(Attributes attributes) throws Exception
		{

			if (digester.getDebug() >= 1)
			{
				digester.log("Setting parent class loader");
			}

			Container top = (Container) digester.peek();
			top.setParentClassLoader(parentClassLoader);

		}
	}
}
