/*
 * Created on 2004-6-1
 *
 * To change the template for this generated file go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */
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.willow.Container;
import org.huihoo.willow.Context;
import org.huihoo.willow.Engine;
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.Provider;
import org.huihoo.willow.core.StandardContext;
import org.huihoo.willow.core.StandardEngine;
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;

import com.zosatapo.commons.util.IntrospectionUtils;

/**
 * @author Administrator
 *
 * To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */
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 Provider that have been deployed in this server.
	 */
	protected Provider providers[] = new Provider[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();
	
	/**
	 * The set of Engines that have been deployed in this server.  Normally
	 * there will only be one.
	 */
	protected Engine engines[] = new Engine[0];

	/**
	 * Descriptive information about this server implementation.
	 */
	protected  final String info = "org.huihoo.willow.startup.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 Provider to the set of defined Providers.  The newly
	 * added Provider 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 addProvider(Provider provider)
	{
		log.debug("Adding Provider (" + provider.getInfo() + ")");

		// Make sure we have a Container to send requests to
		if (engines.length < 1)
		{
			throw new IllegalStateException(sm.getString("embedded.noEngines"));
		}

		// Configure this Provider as needed
		provider.setContainer(engines[engines.length - 1]);

		// Add this Provider to our set of defined Providers
		Provider results[] = new Provider[providers.length + 1];
		for (int i = 0; i < providers.length; i++)
		{
			results[i] = providers[i];
		}
		results[providers.length] = provider;
		providers = results;

		// Start this Provider if necessary
		if (started)
		{
			try
			{
				provider.initialize();
				if (provider instanceof Lifecycle)
				{
					((Lifecycle) provider).start();
				}
			}
			catch (LifecycleException e)
			{
				log.error("Providers.start", e);
			}
		}

	}

	/**
	 * Add a new Engine to the set of defined Engines.
	 *
	 * @param engine The engine to be added
	 */
	public synchronized void addEngine(Engine engine)
	{

		log.debug("Adding engine (" + engine.getInfo() + ")");

		// Add this Engine to our set of defined Engines
		Engine results[] = new Engine[engines.length + 1];
		for (int i = 0; i < engines.length; i++)
		{
			results[i] = engines[i];
		}
		results[engines.length] = engine;
		engines = results;

		// Start this Engine if necessary
		if (started && (engine instanceof Lifecycle))
		{
			try
			{
				((Lifecycle) engine).start();
			}
			catch (LifecycleException e)
			{
				log.error("Engine.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 Provider createProvider(InetAddress address, int port)
	{
		Provider provider = 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 Provider for address='"
					+ ((address == null) ? "ALL" : literal_IP)
					+ "' port='"+ port+ "'");

		try
		{

			Class clazz = Class.forName("org.huihoo.willow.core.StandardNamingprovider");
			provider = (Provider) clazz.newInstance();

			if (literal_IP != null)
			{
				IntrospectionUtils.setProperty(provider, "address", "" + literal_IP);
			}
			IntrospectionUtils.setProperty(provider, "port", "" + port);

		}
		catch (Exception e)
		{
			log.error("Couldn't create connector.");
		}

		return (provider);
	}

	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.setAppBase(appBase);
	
			ContextConfig config = new ContextConfig();
			config.setDebug(debug);
			((Lifecycle) context).addLifecycleListener(config);
	
			return (context);
	}

	/**
	 * Create, configure, and return an Engine that will process all
	 * HTTP requests received from one of the associated Connectors,
	 * based on the specified properties.
	 */
	public Engine createEngine()
	{
		log.debug("Creating engine");
		StandardEngine engine = new StandardEngine();

		engine.setDebug(debug);
		// Default host will be set to the first host added
		engine.setLogger(logger); // Inherited by all children

		return (engine);

	}

	/**
	 * 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 Provider from the set of defined providers.
	 *
	 * @param provider The Provider to be removed
	 */
	public synchronized void removeProvider(Provider provider)
	{
		log.debug("Removing Provider (" + provider.getInfo() + ")");

		// Is the specified Provider actually defined?
		int j = -1;
		for (int i = 0; i < providers.length; i++)
		{
			if (provider == providers[i])
			{
				j = i;
				break;
			}
		}
		if (j < 0)
			return;

		// Stop this Connector if necessary
		if (provider instanceof Lifecycle)
		{
				log.debug(" Stopping this Provider");
			try
			{
				((Lifecycle) provider).stop();
			}
			catch (LifecycleException e)
			{
				log.error("Provider.stop", e);
			}
		}

		// Remove this Provider from our set of defined Providers

			log.debug(" Removing this Provider");
		int k = 0;
		Provider results[] = new Provider[providers.length - 1];
		for (int i = 0; i < providers.length; i++)
		{
			if (i != j)
			{
				results[k++] = providers[i];
			}
		}
		providers = results;

	}

	/**
	 * Remove the specified ParameterContext from the set of defined Contexts for its
	 * associated Host.  If this is the last ParameterContext for this Host, the Host
	 * will also be removed.
	 *
	 * @param context The ParameterContext to be removed
	 */
	public synchronized void removeContext(Context context)
	{
			log.debug("Removing context[" + context.getName() + "]");

		// Is this ParameterContext actually among those that are defined?
		boolean found = false;
		for (int i = 0; i < engines.length; i++)
		{
				Container contexts[] = engines[i].findChildren();
				for (int k = 0; k < contexts.length; k++)
				{
					if (context == (Context) contexts[k])
					{
						found = true;
						break;
					}
				}
			if (found)
				break;
		}
		
		if (!found)
		{
			return;
		}

		// Remove this ParameterContext from the associated Host
			log.debug(" Removing this ParameterContext");
		context.getParent().removeChild(context);

	}

	/**
	 * Remove the specified Engine from the set of defined Engines, along with
	 * all of its related Hosts and Contexts.  All associated Connectors are
	 * also removed.
	 *
	 * @param engine The Engine to be removed
	 */
	public synchronized void removeEngine(Engine engine)
	{

			log.debug("Removing engine (" + engine.getInfo() + ")");

		// Is the specified Engine actually defined?
		int j = -1;
		for (int i = 0; i < engines.length; i++)
		{
			if (engine == engines[i])
			{
				j = i;
				break;
			}
		}
		if (j < 0)
		{
			return;
		}

		// Remove any Connector that is using this Engine
			log.debug(" Removing related Containers");
		while (true)
		{
			int n = -1;
			for (int i = 0; i < providers.length; i++)
			{
				if (providers[i].getContainer() == (Container) engine)
				{
					n = i;
					break;
				}
			}
			if (n < 0)
			{
				break;
			}
			removeProvider(providers[n]);
		}

		// Stop this Engine if necessary
		if (engine instanceof Lifecycle)
		{
				log.debug(" Stopping this Engine");
			try
			{
				((Lifecycle) engine).stop();
			}
			catch (LifecycleException e)
			{
				log.error("Engine.stop", e);
			}
		}

		// Remove this Engine from our set of defined Engines
		log.debug(" Removing this Engine");
		int k = 0;
		Engine results[] = new Engine[engines.length - 1];
		for (int i = 0; i < engines.length; i++)
		{
			if (i != j)
				results[k++] = engines[i];
		}
		engines = results;

	}

	/**
	 * 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);
		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);
		started = false;
	}

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

	protected void initDirs()
	{

		String willowHome = System.getProperty(Globals.PROPS_WILLOW_HOME);
		if (willowHome == null)
		{
			// Backwards compatibility patch for J2EE RI 1.3
			String j2eeHome = System.getProperty("com.sun.enterprise.home");
			if (j2eeHome != null)
			{
				willowHome = System.getProperty("com.sun.enterprise.home");
			}
			else
			{
				if (System.getProperty(Globals.PROPS_WILLOW_HOME) != null)
				{
					willowHome = System.getProperty(Globals.PROPS_WILLOW_HOME);
				}
				else
				{
					// 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 (such as installing special Valves) can
	 * be done prior to calling <code>start()</code>.
	 *
	 * @param context ParameterContext 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);

	}

}
