//----------------------------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.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.huihoo.commons.util.IntrospectionUtils;
import org.huihoo.willow.Connector;
import org.huihoo.willow.Container;
import org.huihoo.willow.Context;
import org.huihoo.willow.Globals;
import org.huihoo.willow.Lifecycle;
import org.huihoo.willow.LifecycleException;
import org.huihoo.willow.LifecycleListener;
import org.huihoo.willow.Loader;
import org.huihoo.willow.Logger;
import org.huihoo.willow.core.StandardContext;
import org.huihoo.willow.core.StandardService;
import org.huihoo.willow.loader.WorkflowLoader;
import org.huihoo.willow.logger.FileLogger;
import org.huihoo.willow.util.LifecycleSupport;
import org.huihoo.willow.util.StringManager;



public class Embedded extends StandardService implements Lifecycle
{
	private static Log log = LogFactory.getLog(Embedded.class);
	// ----------------------------------------------------------- Constructors
	/**
	 * Construct a new instance of this class with default properties.
	 */
	public Embedded()
	{
		this(null);
	}
	/**
	 * Construct a new instance of this class with specified properties.
	 *
	 * @param logger Logger implementation to be inherited by all components
	 *  (unless overridden further down the container hierarchy)
	 * @param realm Realm implementation to be inherited by all components
	 *  (unless overridden further down the container hierarchy)
	 */
	public Embedded(Logger logger)
	{
		super();
		setLogger(logger);
	}
	// ----------------------------------------------------- Instance Variables
	/**
	 * The set of Connector that have been deployed in this server.
	 */
	protected Connector connectors[] = new Connector[0];
	/**
	 * The debugging detail level for this component.
	 */
	protected int debug = 0;
	/**
	 * The shared extensions class loader for this server.
	 */
	protected ClassLoader parentClassLoader = Willow.class.getClassLoader();
	/**
	 * Descriptive information about this server implementation.
	 */
	protected final String info = "Embedded/1.0";
	/**
	 * The lifecycle event support for this component.
	 */
	protected LifecycleSupport lifecycle = new LifecycleSupport(this);
	/**
	 * The default logger to be used by this component itself.  Unless this
	 * is overridden, log messages will be writted to standard output.
	 */
	protected Logger logger = null;
	/**
	 * The string manager for this package.
	 */
	protected static StringManager sm = StringManager.getManager(Constants.PACKAGE);
	/**
	 * Has this component been started yet?
	 */
	protected boolean started = false;
	/**
	 * Use await.
	 */
	protected boolean await = false;
	/**
	 * The property change support for this component.
	 */
	protected PropertyChangeSupport support = new PropertyChangeSupport(this);
	// ------------------------------------------------------------- Properties
	/**
	 * Return the debugging detail level for this component.
	 */
	public int getDebug()
	{
		return (this.debug);
	}
	/**
	 * Set the debugging detail level for this component.
	 *
	 * @param debug The new debugging detail level
	 */
	public void setDebug(int debug)
	{
		int oldDebug = this.debug;
		this.debug = debug;
		support.firePropertyChange("debug", new Integer(oldDebug), new Integer(this.debug));
	}
	/**
	 * Return the Logger for this component.
	 */
	public Logger getLogger()
	{
		return (this.logger);
	}
	/**
	 * Set the Logger for this component.
	 *
	 * @param logger The new logger
	 */
	public void setLogger(Logger logger)
	{
		Logger oldLogger = this.logger;
		this.logger = logger;
		support.firePropertyChange("logger", oldLogger, this.logger);
	}
	public void setAwait(boolean b)
	{
		await = b;
	}
	public boolean isAwait()
	{
		return await;
	}
	public void setHome(String s)
	{
		System.setProperty(Globals.PROPS_WILLOW_HOME, s);
	}
	public String getHome()
	{
		return System.getProperty(Globals.PROPS_WILLOW_HOME);
	}
	/**
	 * Set the shared extensions class loader.
	 *
	 * @param parentClassLoader The shared extensions class loader.
	 */
	public void setParentClassLoader(ClassLoader parentClassLoader)
	{
		this.parentClassLoader = parentClassLoader;
	}
	// --------------------------------------------------------- Public Methods
	/**
	 * Add a new Connector to the set of defined connectors.  The newly
	 * added Connector will be associated with the most recently added Engine.
	 *
	 * @param Connector The Connector to be added
	 *
	 * @exception IllegalStateException if no engines have been added yet
	 */
	public synchronized void addConnector(Connector Connector)
	{
		log.debug("Adding Connector (" + Connector.getInfo() + ")");
		
		// Make sure we have a Container to send requests to
		// Configure this Connector as needed
		Connector.setService(this);
		
		// Add this Connector to our set of defined connectors
		Connector results[] = new Connector[connectors.length + 1];
		for (int i = 0; i < connectors.length; i++)
		{
			results[i] = connectors[i];
		}
		results[connectors.length] = Connector;
		connectors = results;
		// Start this Connector if necessary
		if (started)
		{
			try
			{
				Connector.initialize();
				if (Connector instanceof Lifecycle)
				{
					((Lifecycle) Connector).start();
				}
			}
			catch (LifecycleException e)
			{
				log.error("connectors.start", e);
			}
		}
	}
	/**
	 * Add a property change listener to this component.
	 *
	 * @param listener The listener to add
	 */
	public void addPropertyChangeListener(PropertyChangeListener listener)
	{
		support.addPropertyChangeListener(listener);
	}
	public Connector createConnector(InetAddress address, int port)
	{
		Connector Connector = null;
		String literal_IP = null;
		if (address != null)
		{
			/*
			 * InetAddress.toString() returns a string of the form
			 * "<hostname>/<literal_IP>". Get the latter part, so that the
			 * address can be parsed (back) into an InetAddress using
			 * InetAddress.getByName().
			 */
			int index = address.toString().indexOf('/');
			if (index != -1)
			{
				literal_IP = address.toString().substring(index + 1);
			}
		}
		log.debug("Creating Connector for address='" + ((address == null) ? "ALL" : literal_IP) + "' port='" + port + "'");
		try
		{
			Class clazz = Class.forName("org.huihoo.willow.core.StandardNamingConnector");
			Connector = (Connector) clazz.newInstance();
			if (literal_IP != null)
			{
				IntrospectionUtils.setProperty(Connector, "address", "" + literal_IP);
			}
			IntrospectionUtils.setProperty(Connector, "port", "" + port);
		}
		catch (Exception e)
		{
			log.error("Couldn't create Connector.");
		}
		return (Connector);
	}
	public Context createContext(String name, String appBase)
	{
		log.debug("Creating context '" + name + "' with appBase '" + appBase + "'");
		StandardContext context = new StandardContext();
		context.setDebug(debug);
		context.setName(name);
		context.setDocBase(appBase);
		ContextConfig config = new ContextConfig();
		config.setDebug(debug);
		((Lifecycle) context).addLifecycleListener(config);
		return (context);
	}


	/**
	 * Create and return a class loader manager that can be customized, and
	 * then attached to a Container, before it is started.
	 *
	 * @param parent ClassLoader that will be the parent of the one
	 *  created by this Loader
	 */
	public Loader createLoader(ClassLoader parent)
	{
		log.debug("Creating Loader with parent class loader '" + parent + "'");
		WorkflowLoader loader = new WorkflowLoader(parent);
		return (loader);
	}
	/**
	 * Return descriptive information about this NamingServer implementation and
	 * the corresponding version number, in the format
	 * <code>&lt;description&gt;/&lt;version&gt;</code>.
	 */
	public String getInfo()
	{
		return (this.info);
	}
	/**
	 * Remove the specified Connector from the set of defined connectors.
	 *
	 * @param Connector The Connector to be removed
	 */
	public synchronized void removeConnector(Connector Connector)
	{
		log.debug("Removing Connector (" + Connector.getInfo() + ")");
		// Is the specified Connector actually defined?
		int j = -1;
		for (int i = 0; i < connectors.length; i++)
		{
			if (Connector == connectors[i])
			{
				j = i;
				break;
			}
		}
		if (j < 0)
			return;
		// Stop this Connector if necessary
		if (Connector instanceof Lifecycle)
		{
			log.debug(" Stopping this Connector");
			try
			{
				((Lifecycle) Connector).stop();
			}
			catch (LifecycleException e)
			{
				log.error("Connector.stop", e);
			}
		}
		// Remove this Connector from our set of defined connectors
		log.debug(" Removing this Connector");
		int k = 0;
		Connector results[] = new Connector[connectors.length - 1];
		for (int i = 0; i < connectors.length; i++)
		{
			if (i != j)
			{
				results[k++] = connectors[i];
			}
		}
		connectors = results;
	}
	/**
	 * Remove the specified Context from the set of defined Contexts for its
	 * associated Host.  If this is the last Context for this Host, the Host
	 * will also be removed.
	 *
	 * @param context The Context to be removed
	 */
	public synchronized void removeContext(Context context)
	{
		log.debug("Removing context[" + context.getName() + "]");
		// Is this Context actually among those that are defined?
		boolean found = false;
		Container contexts[] = findChildren();
		for (int k = 0; k < contexts.length; k++)
		{
			if (context == (Context) contexts[k])
			{
				found = true;
				break;
			}
		}
		if (!found)
		{
			return;
		}
		// Remove this Context from the associated Host
		log.debug(" Removing this Context");
		context.getParent().removeChild(context);
	}
	/**
	 * Remove a property change listener from this component.
	 *
	 * @param listener The listener to remove
	 */
	public void removePropertyChangeListener(PropertyChangeListener listener)
	{
		support.removePropertyChangeListener(listener);
	}
	// ------------------------------------------------------ Lifecycle Methods
	/**
	 * Add a lifecycle event listener to this component.
	 *
	 * @param listener The listener to add
	 */
	public void addLifecycleListener(LifecycleListener listener)
	{
		lifecycle.addLifecycleListener(listener);
	}
	/**
	 * Get the lifecycle listeners associated with this lifecycle. If this 
	 * Lifecycle has no listeners registered, a zero-length array is returned.
	 */
	public LifecycleListener[] findLifecycleListeners()
	{
		return lifecycle.findLifecycleListeners();
	}
	/**
	 * Remove a lifecycle event listener from this component.
	 *
	 * @param listener The listener to remove
	 */
	public void removeLifecycleListener(LifecycleListener listener)
	{
		lifecycle.removeLifecycleListener(listener);
	}
	/**
	 * Prepare for the beginning of active use of the public methods of this
	 * component.  This method should be called after <code>configure()</code>,
	 * and before any of the public methods of the component are utilized.
	 *
	 * @exception LifecycleException if this component detects a fatal error
	 *  that prevents this component from being used
	 */
	public void start() throws LifecycleException
	{
		log.info("Starting willow server");
		// Validate the setup of our required system properties
		initDirs();
		// Validate and update our current component state
		if (started)
		{
			throw new LifecycleException(sm.getString("embedded.alreadyStarted"));
		}
		lifecycle.fireLifecycleEvent(START_EVENT, null);
		
		super.start();
		
		started = true;
	}
	/**
	 * Gracefully terminate the active use of the public methods of this
	 * component.  This method should be the last one called on a given
	 * instance of this component.
	 *
	 * @exception LifecycleException if this component detects a fatal error
	 *  that needs to be reported
	 */
	public void stop() throws LifecycleException
	{
		log.debug("Stopping embedded server");
		// Validate and update our current component state
		if (!started)
		{
			throw new LifecycleException(sm.getString("embedded.notStarted"));
		}
		lifecycle.fireLifecycleEvent(STOP_EVENT, null);
		
		super.stop();
		
		started = false;
	}
	// ------------------------------------------------------ Protected Methods
	protected void initDirs()
	{
		String willowHome = System.getProperty(Globals.PROPS_WILLOW_HOME);
		if (willowHome == null)
		{
			// Use IntrospectionUtils and guess the dir
			willowHome = IntrospectionUtils.guessHome(Globals.PROPS_WILLOW_HOME, Globals.PROPS_WILLOW_JAR);
		}
		if (willowHome != null)
		{
			File home = new File(willowHome);
			if (!home.isAbsolute())
			{
				try
				{
					willowHome = home.getCanonicalPath();
				}
				catch (IOException e)
				{
					willowHome = home.getAbsolutePath();
				}
			}
			System.setProperty(Globals.PROPS_WILLOW_HOME, willowHome);
		}
	}
	// -------------------------------------------------------- Private Methods
	/**
	 * Customize the specified context to have its own log file instead of
	 * inheriting the default one.  This is just an example of what you can
	 * do; pretty much anything can
	 * be done prior to calling <code>start()</code>.
	 *
	 * @param context Context to receive a specialized logger
	 */
	private static void customize(Context context)
	{
		// Create a customized file logger for this context
		String basename = context.getName();
		FileLogger special = new FileLogger();
		special.setPrefix(basename + "_log.");
		special.setSuffix(".txt");
		special.setTimestamp(true);
		// Override the default logger for this context
		context.setLogger(special);
	}
}
