/*
 * Created on 2004-6-8
 *
 * To change the template for this generated file go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */
package org.huihoo.willow.core;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;

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.EngineDeployer;
import org.huihoo.willow.Globals;
import org.huihoo.willow.Lifecycle;
import org.huihoo.willow.LifecycleException;
import org.huihoo.willow.Service;
import org.huihoo.willow.naming.WillowContext;
import org.huihoo.willow.naming.ContextFactory;
import org.huihoo.willow.session.SessionManager;
import org.huihoo.willow.store.UserDatabaseRealm;
import org.huihoo.willow.util.ServerInfo;

import workflow.store.RealmDatabase;

/**
 * @author reic
 *
 * To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */
public class StandardEngine extends ContainerBase implements EngineDeployer, Engine
{

	private static Log log = LogFactory.getLog(StandardEngine.class);

	// ----------------------------------------------------------- Constructors

	/**
	 * Create a new StandardEngine component with the default basic Valve.
	 */
	public StandardEngine()
	{

		super();

		// By default, the engine will hold the reloading thread
		backgroundProcessorDelay = 10;

	}

	// ----------------------------------------------------- Instance Variables

	/**
	 * The descriptive information string for this implementation.
	 */
	private static final String info = "org.huihoo.willow.core.StandardEngine/1.0";

	/**
	 * The <code>Service</code> that owns this Engine, if any.
	 */
	private Service service = null;

	/**
		* The application root for this Engine.
		*/
	private String engineBase = ".";

	/**
		* The auto deploy flag for this Engine.
		*/
	private boolean autoDeploy = true;

	/**
	 * The deploy on startup flag for this Engine.
	 */
	private boolean deployOnStartup = true;

	/**
	 * The Java class name of the default context configuration class
	 * for deployed web applications.
	 */
	private String configClass = "org.huihoo.willow.startup.ContextConfig";

	/**
	 * The Java class name of the default ParameterContext implementation class for
	 * deployed web applications.
	 */
	private String contextClass = "org.huihoo.willow.core.StandardContext";

	/**
	 * The <code>EngineDeployer</code> to whom we delegate application
	 * deployment requests.
	 */
	private EngineDeployer deployer = null;

	/**
	 * The session timeout (in minutes) for this web application.
	 */
	private int sessionTimeout = 30;

	/**
	 * Frequency of the session expiration, and related manager operations.
	 * SessionManager operations will be done once for the specified amount of
	 * backgrondProcess calls (ie, the lower the amount, the most often the
	 * checks will occur).
	 */
	private int managerChecksFrequency = 6;

	/**
	 * Iteration count for background processing.
	 */
	private int count = 0;

	/**
	 * Work Directory base for applications.
	 */
	private String workDir = null;

	/**
	 * The Realm with which this Container is associated.
	 */
	private RealmDatabase realmDatabase = null;

	/**
	 * The SessionManager implementation with which this Engine is associated.
	 */
	protected SessionManager manager = null;

	// ------------------------------------------------------------- Properties
	public RealmDatabase getRealmDatabase()
	{
		return this.realmDatabase;
	}

	public void setRealmDatabase(RealmDatabase realmDatabase)
	{
		//	Change components if necessary
		RealmDatabase oldRealm = this.realmDatabase;
		if (oldRealm == realmDatabase)
			return;
		this.realmDatabase = realmDatabase;

		// Stop the old component if necessary
		if (started && (oldRealm != null) && (oldRealm instanceof Lifecycle))
		{
			try
			{
				((Lifecycle) oldRealm).stop();
			}
			catch (LifecycleException e)
			{
				log.error("StandardEngine.setRealmDatabase: stop: ", e);
			}
		}

		// Start the new component if necessary
		if ((realmDatabase != null) && (realmDatabase instanceof UserDatabaseRealm))
		{
			((UserDatabaseRealm) realmDatabase).setContainer(this);
		}

		if (started && (realmDatabase != null) && (realmDatabase instanceof Lifecycle))
		{
			try
			{
				((Lifecycle) realmDatabase).start();
			}
			catch (LifecycleException e)
			{
				log.error("StandardEngine.setRealmDatabase: start: ", e);
			}
		}

		support.firePropertyChange("realmDatabase", oldRealm, this.realmDatabase);
	}

	/**
	 * Return the application root for this Engine.  This can be an absolute
	 * pathname, a relative pathname
	 */
	public String getEngineBase()
	{
		return (this.engineBase);
	}

	/**
	 * Return the application root for this Engine.
	 */
	public File getAbsoluteEngineBase()
	{
		String absolute_engineBase = getEngineBase();

		File absolute_engineBaseFile = new File(absolute_engineBase);

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

	/**
	 * Set the application root for this Engine.  This can be an absolute
	 * pathname, a relative pathname
	 *
	 * @param appBase The new application root
	 */
	public void setEngineBase(String engineBase)
	{

		String oldEngineBase = this.engineBase;
		this.engineBase = engineBase;
		support.firePropertyChange("engineBase", oldEngineBase, this.engineBase);

	}

	/**
	 * Return the value of the auto deploy flag.  If true, it indicates that 
	 * this Engine's child webapps will be dynamically deployed.
	 */
	public boolean getAutoDeploy()
	{

		return (this.autoDeploy);

	}

	/**
	 * Set the auto deploy flag value for this Engine.
	 * 
	 * @param autoDeploy The new auto deploy flag
	 */
	public void setAutoDeploy(boolean autoDeploy)
	{

		boolean oldAutoDeploy = this.autoDeploy;
		this.autoDeploy = autoDeploy;
		support.firePropertyChange("autoDeploy", oldAutoDeploy, this.autoDeploy);

	}

	/**
	 * Return the Java class name of the context configuration class
	 * for new web applications.
	 */
	public String getConfigClass()
	{

		return (this.configClass);

	}

	/**
	 * Set the Java class name of the context configuration class
	 * for new web applications.
	 *
	 * @param configClass The new context configuration class
	 */
	public void setConfigClass(String configClass)
	{

		String oldConfigClass = this.configClass;
		this.configClass = configClass;
		support.firePropertyChange("configClass", oldConfigClass, this.configClass);

	}

	/**
	 * Return the Java class name of the ParameterContext implementation class
	 * for new web applications.
	 */
	public String getContextClass()
	{

		return (this.contextClass);

	}

	/**
	 * Set the Java class name of the ParameterContext implementation class
	 * for new web applications.
	 *
	 * @param contextClass The new context implementation class
	 */
	public void setContextClass(String contextClass)
	{

		String oldContextClass = this.contextClass;
		this.contextClass = contextClass;
		support.firePropertyChange("contextClass", oldContextClass, this.contextClass);

	}

	/**
	 * Return the default session timeout (in minutes) for this
	 * workflow client
	 */
	public int getSessionTimeout()
	{

		return (this.sessionTimeout);

	}

	/**
	 * Set the default session timeout (in minutes) for this
	 * workflow client
	 *
	 * @param timeout The new default session timeout
	 */
	public void setSessionTimeout(int timeout)
	{

		int oldSessionTimeout = this.sessionTimeout;

		this.sessionTimeout = (timeout == 0) ? -1 : timeout;
		support.firePropertyChange(
			"sessionTimeout",
			new Integer(oldSessionTimeout),
			new Integer(this.sessionTimeout));

	}

	/**
	 * Return the <code>Service</code> with which we are associated (if any).
	 */
	public Service getService()
	{

		return (this.service);

	}

	/**
	 * Set the <code>Service</code> with which we are associated (if any).
	 *
	 * @param service The service that owns this Engine
	 */
	public void setService(Service service)
	{
		this.service = service;
	}

	/**
	 * Return the value of the deploy on startup flag.  If true, it indicates 
	 * that this Engine's child webapps should be discovred and automatically 
	 * deployed at startup time.
	 */
	public boolean getDeployOnStartup()
	{

		return (this.deployOnStartup);

	}

	/**
	 * Set the deploy on startup flag value for this Engine.
	 * 
	 * @param autoDeploy The new deploy on startup flag
	 */
	public void setDeployOnStartup(boolean deployOnStartup)
	{

		boolean oldDeployOnStartup = this.deployOnStartup;
		this.deployOnStartup = deployOnStartup;
		support.firePropertyChange("deployOnStartup", oldDeployOnStartup, this.deployOnStartup);

	}

	/**
	 * Return the SessionManager with which this Engine is associated. 
	 */
	public SessionManager getSessionManager()
	{
		return (manager);
	}

	/**
	 * Set the SessionManager with which this Engine is associated.
	 *
	 * @param manager The newly associated SessionManager
	 */
	public synchronized void setSessionManager(SessionManager manager)
	{

		// Change components if necessary
		SessionManager oldManager = this.manager;
		if (oldManager == manager)
		{
			return;
		}
		this.manager = manager;

		// Stop the old component if necessary
		if (started && (oldManager != null) && (oldManager instanceof Lifecycle))
		{
			try
			{
				((Lifecycle) oldManager).stop();
			}
			catch (LifecycleException e)
			{
				log.error("StandardEngine.setSessionManager: stop: ", e);
			}
		}

		// Start the new component if necessary
		if (manager != null)
			manager.setContainer(this);
		if (started && (manager != null) && (manager instanceof Lifecycle))
		{
			try
			{
				((Lifecycle) manager).start();
			}
			catch (LifecycleException e)
			{
				log.error("StandardEngine.setSessionManager: start: ", e);
			}
		}

		// Report this property change to interested listeners
		support.firePropertyChange("sessionManager", oldManager, this.manager);

	}

	/**
	 * Return the canonical, fully qualified, name of the engine
	 * this Container represents.
	 */
	public String getName()
	{

		return (name);

	}

	/**
	 * Set the canonical, fully qualified, name of the virtual Engine
	 * this Container represents.
	 *
	 * @param name Virtual Engine name
	 *
	 * @exception IllegalArgumentException if name is null
	 */
	public void setName(String name)
	{

		if (name == null)
		{
			throw new IllegalArgumentException(sm.getString("standardEngine.nullName"));
		}

		name = name.toLowerCase(); // Internally all names are lower case

		String oldName = this.name;
		this.name = name;
		support.firePropertyChange("name", oldName, this.name);

	}
	
	/**
	 * Engine work directory base.
	 */
	public String getWorkDir()
	{
		if (workDir != null)
		{
			return workDir;
		}

		workDir = org.huihoo.willow.startup.Constants.WORK_DIRECTORY;

		return (workDir);
	}

	public File getAbsoluteWorkDir()
	{
		String absolute_workDir = getWorkDir();
		File absolute_workDirFile = new File(absolute_workDir);

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

	/**
	 * Engine work directory base.
	 */
	public void setWorkDir(String workDir)
	{

		this.workDir = workDir;
	}

	// --------------------------------------------------------- Public Methods
	/**
	 * Add a child Container, only if the proposed child is an implementation
	 * of Engine.
	 *
	 * @param child Child container to be added
	 */
	public void addChild(Container child)
	{

		if (!(child instanceof Context))
		{
			throw new IllegalArgumentException(sm.getString("standardEngine.notContext"));
		}
		super.addChild(child);

	}

	/**
	 * Return descriptive information about this Container implementation and
	 * the corresponding version number, in the format
	 * <code>&lt;description&gt;/&lt;version&gt;</code>.
	 */
	public String getInfo()
	{

		return (info);

	}

	/**
	 * Disallow any attempt to set a parent for this Container, since an
	 * Engine is supposed to be at the top of the Container hierarchy.
	 *
	 * @param container Proposed parent Container
	 */
	public void setParent(Container container)
	{
		throw new IllegalArgumentException(sm.getString("standardEngine.notParent"));
	}

	/**
	 * Start this Engine component.
	 *
	 * @exception LifecycleException if a startup error occurs
	 */
	public void start() throws LifecycleException
	{
		if (started)
		{
			return;
		}

		// Log our server identification information
		log.info("Starting WorkflowService Engine: " + ServerInfo.getServerInfo());

		if ((realmDatabase != null) && (realmDatabase instanceof Lifecycle))
		{
			try
			{
				((Lifecycle) realmDatabase).start();
			}
			catch (LifecycleException e)
			{
				log.error("StandardEngine.setRealmDatabase: start: ", e);
			}
		}

		if (manager == null)
		{
			manager = new SessionManager();
			manager.setContainer(this);
		}

		if ((manager != null) && (manager instanceof Lifecycle))
		{
			try
			{
				((Lifecycle) manager).start();
			}
			catch (LifecycleException e)
			{
				log.error("StandardEngine.setSessionManager: start: ", e);
			}
		}
		
		WillowContext willowContext=ContextFactory.getWillowContext();	
		willowContext.setRealmDatabase(getRealmDatabase());
		willowContext.setGlobalSessionManager(getSessionManager());
		
		// Standard container startup
		super.start();

	}

	public void stop() throws LifecycleException
	{
		if ((realmDatabase != null) && (realmDatabase instanceof Lifecycle))
		{
			try
			{
				((Lifecycle) realmDatabase).stop();
			}
			catch (LifecycleException e)
			{
				log.error("StandardEngine.setRealmDatabase: stop: ", e);
			}
		}

		if ((manager != null) && (manager instanceof Lifecycle))
		{
			try
			{
				((Lifecycle) manager).stop();
			}
			catch (LifecycleException e)
			{
				log.error("StandardEngine.setSessionManager: stop: ", e);
			}
		}
		super.stop();
	}

	/**
	 * Return a String representation of this component.
	 */
	public String toString()
	{

		StringBuffer sb = new StringBuffer("StandardEngine[");
		sb.append(getName());
		sb.append("]");
		return (sb.toString());

	}

	// ------------------------------------------------------- EngineDeployer Methods

	/**
	 * Install a new web application, whose web application archive is at the
	 * specified URL, into this container with the specified context path.
	 * A context path of "" (the empty string) should be used for the root
	 * application for this container.  Otherwise, the context path must
	 * start with a slash.
	 * <p>
	 * If this application is successfully installed, a ContainerEvent of type
	 * <code>INSTALL_EVENT</code> will be sent to all registered listeners,
	 * with the newly created <code>ParameterContext</code> as an argument.
	 *
	 * @param contextName The context path to which this application should
	 *  be installed (must be unique)
	 * @param war A URL of type "jar:" that points to a WAR file, or type
	 *  "file:" that points to an unpacked directory structure containing
	 *  the web application to be installed
	 *
	 * @exception IllegalArgumentException if the specified context path
	 *  is malformed (it must be "" or start with a slash)
	 * @exception IllegalStateException if the specified context path
	 *  is already attached to an existing web application
	 * @exception IOException if an input/output error was encountered
	 *  during installation
	 */
	public void install(String contextName, URL war) throws IOException
	{
		getDeployer().install(contextName, war);
	}

	/**
	 * Return the Context for the deployed application that is associated
	 * with the specified context path (if any); otherwise return
	 * <code>null</code>.
	 *
	 * @param contextName The context path of the requested web application
	 */
	public Context findDeployedApp(String contextName)
	{

		return (getDeployer().findDeployedApp(contextName));

	}

	/**
	 * Return the context paths of all deployed web applications in this
	 * Container.  If there are no deployed applications, a zero-length
	 * array is returned.
	 */
	public String[] findDeployedApps()
	{

		return (getDeployer().findDeployedApps());

	}

	/**
	 * Remove an existing web application, attached to the specified context
	 * path.  If this application is successfully removed, a
	 * ContainerEvent of type <code>REMOVE_EVENT</code> will be sent to all
	 * registered listeners, with the removed <code>Context</code> as
	 * an argument.
	 *
	 * @param contextName The context path of the application to be removed
	 *
	 * @exception IllegalArgumentException if the specified context path
	 *  is malformed (it must be "" or start with a slash)
	 * @exception IllegalArgumentException if the specified context path does
	 *  not identify a currently installed web application
	 * @exception IOException if an input/output error occurs during
	 *  removal
	 */
	public void remove(String contextName) throws IOException
	{

		getDeployer().remove(contextName);

	}

	/**
	 * Remove an existing web application, attached to the specified context
	 * path.  If this application is successfully removed, a
	 * ContainerEvent of type <code>REMOVE_EVENT</code> will be sent to all
	 * registered listeners, with the removed <code>Context</code> as
	 * an argument. Deletes the web application war file and/or directory
	 * if they exist in the Engine's appBase.
	 *
	 * @param contextName The context path of the application to be removed
	 * @param undeploy boolean flag to remove web application from server
	 *
	 * @exception IllegalArgumentException if the specified context path
	 *  is malformed (it must be "" or start with a slash)
	 * @exception IllegalArgumentException if the specified context path does
	 *  not identify a currently installed web application
	 * @exception IOException if an input/output error occurs during
	 *  removal
	 */
	public void remove(String contextName, boolean undeploy) throws IOException
	{

		getDeployer().remove(contextName, undeploy);

	}

	/**
	 * Start an existing web application, attached to the specified context
	 * path.  Only starts a web application if it is not running.
	 *
	 * @param contextName The context path of the application to be started
	 *
	 * @exception IllegalArgumentException if the specified context path
	 *  is malformed (it must be "" or start with a slash)
	 * @exception IllegalArgumentException if the specified context path does
	 *  not identify a currently installed web application
	 * @exception IOException if an input/output error occurs during
	 *  startup
	 */
	public void start(String contextName) throws IOException
	{

		getDeployer().start(contextName);

	}

	/**
	 * Stop an existing web application, attached to the specified context
	 * path.  Only stops a web application if it is running.
	 *
	 * @param contextName The context path of the application to be stopped
	 *
	 * @exception IllegalArgumentException if the specified context path
	 *  is malformed (it must be "" or start with a slash)
	 * @exception IllegalArgumentException if the specified context path does
	 *  not identify a currently installed web application
	 * @exception IOException if an input/output error occurs while stopping
	 *  the web application
	 */
	public void stop(String contextName) throws IOException
	{

		getDeployer().stop(contextName);

	}

	/**
	 * Execute a periodic task, such as reloading, etc. This method will be
	 * invoked inside the classloading context of this container. Unexpected
	 * throwables will be caught and logged.
	 */
	public void backgroundProcess()
	{
		lifecycle.fireLifecycleEvent("check", null);

		count = (count + 1) % managerChecksFrequency;

		if ((getSessionManager() != null) && (count == 0))
		{
			getSessionManager().processExpires();
		}
	}
	// ------------------------------------------------------ Protected Methods

	static String STANDARD_ENGINE_DEPLOYER = "org.huihoo.willow.core.StandardEngineDeployer";

	public EngineDeployer getDeployer()
	{
		if (deployer != null)
		{
			return deployer;
		}

		log.info("Create Engine deployer for direct deployment ");

		try
		{
			Class c = Class.forName(STANDARD_ENGINE_DEPLOYER);
			deployer = (EngineDeployer) c.newInstance();
			Method m = c.getMethod("setEngine", new Class[] { Engine.class });
			m.invoke(deployer, new Object[] { this });
		}
		catch (Throwable t)
		{
			log.error("Error creating deployer ", t);
		}
		return deployer;
	}

	public void setDeployer(EngineDeployer d)
	{
		this.deployer = d;
	}
}
