/*
 * Created on 2004-6-11
 *
 * 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.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;

import org.apache.commons.digester.Digester;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.huihoo.willow.Container;
import org.huihoo.willow.ContextDeployer;
import org.huihoo.willow.Globals;
import org.huihoo.willow.Lifecycle;
import org.huihoo.willow.LifecycleEvent;
import org.huihoo.willow.LifecycleListener;
import org.huihoo.willow.Logger;
import org.huihoo.willow.client.LiveServiceImpl;
import org.huihoo.willow.client.NamingImpl;
import org.huihoo.willow.core.StandardContext;
import org.huihoo.willow.core.StandardEngine;
import org.huihoo.willow.naming.WillowContext;
import org.huihoo.willow.naming.ContextFactory;
import org.huihoo.willow.util.StringManager;
import org.xml.sax.InputSource;
import org.xml.sax.SAXParseException;

import workflow.script.core.ScriptContext;
import workflow.script.interpretor.JavaInterpretor;
import workflow.script.interpretor.ScriptInterpretor;
import workflow.store.CaseDatabase;
import workflow.store.SchemaContext;
import workflow.store.UserDatabase;
import workflow.store.WorkflowDispatcher;
import workflow.store.factory.StoreFactory;
import workflow.store.spi.SpiUserDatabase;

import com.zosatapo.commons.store.Store;
import com.zosatapo.workflow.store.UserDatabaseImpl;

/**
 * @author reic
 *
 * To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */
public final class ContextConfig implements LifecycleListener
{

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

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

	/**
	 * The Context we are associated with.
	 */
	private StandardContext context = null;

	/**
	 * The debugging detail level for this component.
	 */
	private int debug = 0;

	/**
	 * Track any fatal errors during startup configuration processing.
	 */
	private boolean ok = false;

	/**
	 * The names of applications that we have auto-deployed (to avoid
	 * double deployment attempts).
	 */
	protected ArrayList deployed = new ArrayList();

	/**
	 * Last modified dates of the XPDL's xml files of the XPDLs, keyed by
	 * xpdl file name.
	 */
	private HashMap xpdlXmlLastModified = new HashMap();

	/**
	 * The string resources for this package.
	 */
	private static final StringManager sm = StringManager.getManager(Constants.PACKAGE);

	/**
	 * The <code>Digester</code> we will use to process workflow application
	 * deployment descriptor files.
	 */
	private Digester workflowDigester = null;

	/**
	 * The <code>Rule</code> used to parse the workflow.xml
	 */
	private WorkflowRuleSet workflowRuleSet = new WorkflowRuleSet();

	/**
	 * Attribute value used to turn on/off XML validation
	 */
	private boolean xmlValidation = false;

	/**
	 * Attribute value used to turn on/off XML namespace awarenes.
	 */
	private boolean xmlNamespaceAware = false;

	// ------------------------------------------------------------- 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)
	{

		this.debug = debug;

	}

	// --------------------------------------------------------- Public Methods

	/**
	 * Process the START event for an associated Context.
	 *
	 * @param event The lifecycle event that has occurred
	 */
	public void lifecycleEvent(LifecycleEvent event)
	{
		if (event.getType().equals("check"))
		{
			check();
		}

		// Identify the context we are associated with
		try
		{
			context = (StandardContext) event.getLifecycle();
			if (context instanceof StandardContext)
			{
				int contextDebug = ((StandardContext) context).getDebug();
				if (contextDebug > this.debug)
				{
					this.debug = contextDebug;
				}
			}
		}
		catch (ClassCastException e)
		{
			log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
			return;
		}

		// Called from ContainerBase.addChild() -> StandardContext.start()
		// Process the event that has occurred
		if (event.getType().equals(Lifecycle.START_EVENT))
		{
			start();
		}
		else if (event.getType().equals(Lifecycle.STOP_EVENT))
		{
			stop();
		}

	}

	// -------------------------------------------------------- Private Methods

	/**
	 * Process the application configuration file, if it exists.
	 */
	private void applicationConfig()
	{
		// Open the application workflow.xml file, if it exists
		ScriptContext servletContext = context.getScriptContext();
		InputStream stream = null;
		InputStream stream_config = null;
		
		if(servletContext!=null)
		{
			stream = servletContext.getResourceAsStream(Constants.ApplicationWorkflowXml);
			stream_config = servletContext.getResourceAsStream(Constants.ApplicationWorkflowXml);
			if (stream == null)
			{
				log.info(sm.getString("contextConfig.applicationMissing") + " " + context);
				return;
			}
		}
		

		if (workflowDigester == null)
		{
			workflowDigester = createWorkflowDigester();
		}

		// Process the application workflow.xml file
		synchronized (workflowDigester)
		{
			try
			{
				URL workflowURL = servletContext.getResource(Constants.ApplicationWorkflowXml);
				if (workflowURL != null)
				{
					//--------------------entire workflow application-----------------
					InputSource is = new InputSource(workflowURL.toExternalForm());
					is.setByteStream(stream);
					workflowDigester.clear();
					workflowDigester.setDebug(getDebug());
					workflowDigester.setUseContextClassLoader(false);
					workflowDigester.push(context);
					workflowDigester.parse(is);
					
					//--------------------script config of  workflow application -----------------	
					Digester configDigester=createConfigDigester();
					is = new InputSource(workflowURL.toExternalForm());
					is.setByteStream(stream_config);				
					configDigester.setDebug(getDebug());
					configDigester.setUseContextClassLoader(false);
					configDigester.addCallMethod("workflow-app/script-config/init-param","addInitParameter", 2);
					configDigester.addCallParam("workflow-app/script-config/init-param/param-name",0);
					configDigester.addCallParam("workflow-app/script-config/init-param/param-value",1);
					configDigester.push(servletContext.getScriptConfig());
					configDigester.parse(is);	
					
					//--------------------create database and dispatacher
					UserDatabase userDatabase = null;
					CaseDatabase caseDatabase = null;
					WorkflowDispatcher dispatcher = null;

					String schemaConextBase=context.findInitParameter(workflow.store.factory.Constants.WORKFLOW_SCHEMA_CONTEXT);
					if (schemaConextBase == null)
					{
						schemaConextBase=WorkflowProperties.getSchemaContextFile();
					}
					
					Hashtable env=new Hashtable();
					String storeFactoryClass =
						context.findFactoryClass(workflow.store.factory.Constants.WORKFLOW_FACTORY_STORE);
					if (storeFactoryClass != null)
					{
						env.put(workflow.store.factory.Constants.WORKFLOW_FACTORY_STORE, storeFactoryClass);
					}	
					StoreFactory dbFactory = StoreFactory.newInstance(env);
					env.clear();
					
					WillowContext willowContext = ContextFactory.getWillowContext();
					willowContext.bind(context.getName(),context);
					
					NamingImpl namingImpl = willowContext.getGlobalNamingImpl();
					if (namingImpl != null)
					{
						try
						{
							namingImpl.addLiveService(new LiveServiceImpl(context));
						}
						catch (RemoteException e)
						{
							e.printStackTrace();
						}
					}
										
					//	组织模型是否需要统一处理?
					Store  userStore=context.findStore(workflow.store.factory.Constants.WORKFLOW_STORE_USER);									
					SpiUserDatabase spiUserDatabase = willowContext.getSpiUserDatabase();
					if (userStore==null && spiUserDatabase != null)
					{
						userDatabase = new UserDatabaseImpl(spiUserDatabase);
					}
					else
					{			
						if (userStore == null)
						{
							userStore=WorkflowProperties.getUserStore();
						}
						env.put(workflow.store.factory.Constants.WORKFLOW_STORE_USER,userStore);
						userDatabase = dbFactory.newUserDatabase(env);
						env.clear();
					}
					userDatabase.setWorkflowService(context);

					Store  caseStore=context.findStore(workflow.store.factory.Constants.WORKFLOW_STORE_CASE);							
					if (caseStore == null)
					{
						caseStore=WorkflowProperties.getCaseStore();
					}

					env.put(workflow.store.factory.Constants.WORKFLOW_STORE_CASE,caseStore);
					caseDatabase = dbFactory.newCaseDatabase(env);
					dispatcher =dbFactory.newWorkDispatcher(env);					
					caseDatabase.setWorkflowService(context);
					dispatcher.setWorkflowService(context);

					SchemaContext schemaContext=new SchemaContext(schemaConextBase);
					if(userDatabase.getSchemaContext()==null)
					{
						userDatabase.setSchemaContext(schemaContext);
					}

					if(caseDatabase.getSchemaContext()==null)
					{
						caseDatabase.setSchemaContext(schemaContext);
					}
					
					ScriptInterpretor scriptInterpretor = new JavaInterpretor();
					scriptInterpretor.initialize(context.getScriptContext());
					
					context.setCaseDatabase(caseDatabase);
					context.setUserDatabase(userDatabase);
					context.setWorkDispatcher(dispatcher);
					context.setScriptInterpretor(scriptInterpretor);				
				}
				else
				{
					//log.error("No workflow.xml, using defaults " + context);
				}
			}
			catch (SAXParseException e)
			{
				log(sm.getString("contextConfig.applicationParse"), e);
				log(
					sm.getString(
						"contextConfig.applicationPosition",
						"" + e.getLineNumber(),
						"" + e.getColumnNumber()));
				ok = false;
			}
			catch (Exception e)
			{
				log(sm.getString("contextConfig.applicationParse"), e);
				ok = false;
			}
			finally
			{
				try
				{
					if (stream != null)
					{
						stream.close();
					}
					
					if (stream_config != null)
					{
						stream_config.close();
					}
				}
				catch (IOException e)
				{
					log.error(sm.getString("contextConfig.applicationClose"), e);
				}
				workflowDigester.push(null);
			}
		}
	}

	/**
	 * Create (if necessary) and return a Digester configured to process the
	 * workflow application deployment descriptor (workflow.xml).
	 */
	private Digester createWorkflowDigester()
	{
		Digester workflowDigester =
			DigesterFactory.newDigester(xmlValidation, xmlNamespaceAware, workflowRuleSet);
		return workflowDigester;
	}

	/**
	 * Create (if necessary) and return a Digester configured to process the
	 * workflow application deployment descriptor (workflow.xml).
	 */
	private Digester createConfigDigester()
	{
		Digester workflowDigester =DigesterFactory.newDigester(xmlValidation, xmlNamespaceAware,null);
		return workflowDigester;		
	}

	private String getEngineBase()
	{
		Container engineC = context.getParent();
		if (engineC instanceof StandardEngine)
		{
			return ((StandardEngine) engineC).getEngineBase();
		}
		return System.getProperty(Globals.PROPS_WILLOW_HOME);
	}

	/**
	 * Log a message on the Logger associated with our ServletContext (if any)
	 *
	 * @param message Message to be logged
	 */
	private void log(String message)
	{

		Logger logger = null;
		if (context != null)
		{
			logger = context.getLogger();
		}

		if (logger != null)
		{
			logger.log("ContextConfig[" + context.getName() + "]: " + message);
		}
		else
		{
			log.info(message);
		}
	}

	/**
	 * Log a message on the Logger associated with our ServletContext (if any)
	 *
	 * @param message Message to be logged
	 * @param throwable Associated exception
	 */
	private void log(String message, Throwable throwable)
	{

		Logger logger = null;
		if (context != null)
		{
			logger = context.getLogger();
		}
		if (logger != null)
		{
			logger.log("ContextConfig[" + context.getName() + "] " + message, throwable);
		}
		else
		{
			log.error(message, throwable);
		}

	}

	private void startNew()
	{	
		try
		{
			if (context.getUserDatabase() != null)
			{
				context.getUserDatabase().start();
			}

			if (context.getCaseDatabase() != null)
			{
				context.getCaseDatabase().start();
			}
			if (context.getScriptInterpretor() != null)
			{
				context.getScriptInterpretor().start();
			}		
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}
	private void stopNew()
	{
		try
		{
			if (context.getUserDatabase() != null)
			{
				context.getUserDatabase().stop();
			}

			if (context.getCaseDatabase() != null)
			{
				context.getCaseDatabase().stop();
			}
			if (context.getScriptInterpretor() != null)
			{
				context.getScriptInterpretor().stop();
			}		
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}
	
	/**
	 * Process a "start" event for this ServletContext - in background
	 */
	private synchronized void start()
	{
		// Called from StandardContext.start()
		log.debug(sm.getString("contextConfig.start"));
		// Process the application workflow.xml files
		applicationConfig();
		startNew();
		
		deployApps();
		ok = true;
	}

	/**
	 * Process a "stop" event for this Context.
	 */
	private synchronized void stop()
	{

		log.debug(sm.getString("contextConfig.stop"));

		try
		{
			WillowContext willowContext = ContextFactory.getWillowContext();
			willowContext.unbind(context.getName());
			
			NamingImpl namingImpl = willowContext.getGlobalNamingImpl();
			if (namingImpl != null)
			{
				namingImpl.removeLiveService(context.getName());
			}
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}
		
		int i;

		//Removing parameters
		String[] parameters = context.findInitParameters();
		for (i = 0; i < parameters.length; i++)
		{
			context.removeInitParameter(parameters[i]);
		}

		//Removing Store
		String[] stores = context.findStores();
		for (i = 0; i < stores.length; i++)
		{
			context.removeStore(stores[i]);
		}

		//Removing Factory
		String[] factories = context.findFactoryClasses();
		for (i = 0; i < factories.length; i++)
		{
			context.removeFactoryClass(factories[i]);
		}
		
		stopNew();
		undeployApps();
	}

	/**
	 * Undeploy all deployed applications.
	 */
	protected void undeployApps()
	{
		if (!(context instanceof ContextDeployer))
		{
			return;
		}
		log.debug(sm.getString("contextConfig.undeploying"));

		ContextDeployer deployer = (ContextDeployer) context;
		String xpdlFiles[] = deployer.findDeployedPackages();
		for (int i = 0; i < xpdlFiles.length; i++)
		{
			log.debug(sm.getString("contextConfig.undeploy", xpdlFiles[i]));
			try
			{
				((ContextDeployer) context).remove(xpdlFiles[i]);
			}
			catch (Throwable t)
			{
				log.error(sm.getString("contextConfig.undeploy.error", xpdlFiles[i]), t);
			}
		}

		xpdlXmlLastModified.clear();
		deployed.clear();

	}

	/**
		 * Deploy applications for any directories or WAR files that are found
		 * in our "application root" directory.
		 */
	protected void deployApps()
	{
		if (!(context instanceof ContextDeployer))
		{
			return;
		}

		File contextBase = context.getAbsoluteAppBase();
		if (!contextBase.exists() || !contextBase.isDirectory())
		{
			return;
		}

		File xpdlBase = new File(contextBase, "WEB-INF/xpdl");
		String files[] = xpdlBase.list();
		deployXpdls(xpdlBase, files);

	}

	/**
	 * Deploy directories.
	 */
	protected void deployXpdls(File xpdlBase, String[] files)
	{
		ContextDeployer deployer = (ContextDeployer) context;

		for (int i = 0; i < files.length; i++)
		{
			if (deployed.contains(files[i]))
			{
				continue;
			}

			File xpdl = new File(xpdlBase, files[i]);
			if (xpdl.isFile() && xpdl.getName().endsWith(".xpdl"))
			{

				deployed.add(files[i]);

				// Deploy the application in this directory
				log.debug(sm.getString("contextConfig.deployXPDL", files[i]));

				long t1 = System.currentTimeMillis();
				try
				{
					URL url = new URL("file", null, xpdl.getCanonicalPath());
					deployer.install(xpdl.getName(), url);
				}
				catch (Throwable t)
				{
					log(sm.getString("contextConfig.deployXPDL.error", files[i]), t);
				}
				long t2 = System.currentTimeMillis();
				if ((t2 - t1) > 200)
				{
					log.debug("Deployed " + files[i] + " " + (t2 - t1));
				}
			}
		}
	}

	/**
			* Check deployment descriptors last modified date.
			*/
	protected void checkXpdlLastModified()
	{
		if (!(context instanceof ContextDeployer))
		{
			return;
		}

		File contextBase = context.getAbsoluteAppBase();
		if (!contextBase.exists() || !contextBase.isDirectory())
		{
			return;
		}

		ContextDeployer deployer = (ContextDeployer) context;

		String xpdlFiles[] = deployer.findDeployedPackages();
		for (int i = 0; i < xpdlFiles.length; i++)
		{
			String xpdlFileName = xpdlFiles[i];

			File xpdlBase = new File(contextBase, "WEB-INF/xpdl");
			File xpdlFile = new File(xpdlBase, xpdlFileName);
			long newLastModified = xpdlFile.lastModified();

			Long lastModified = (Long) xpdlXmlLastModified.get(xpdlFileName);
			if (lastModified == null)
			{
				xpdlXmlLastModified.put(xpdlFileName, new Long(newLastModified));
			}
			else
			{
				if (lastModified.longValue() != newLastModified)
				{
					if (newLastModified > (lastModified.longValue() + 5000))
					{						
						xpdlXmlLastModified.remove(xpdlFileName);
						try
						{
							URL url = new URL("file", null, xpdlFile.getCanonicalPath());
							deployer.remove(xpdlFileName);
							deployer.install(xpdlFileName, url);
							
						}
						catch (Throwable t)
						{
							log.error(sm.getString("contextConfig.deployXPDL.error", xpdlFile.getName()), t);
						}
					}
					else
					{
						xpdlXmlLastModified.put(xpdlFileName, new Long(newLastModified));
					}
				}
			}
		}
	}

	/**
	 * Deploy workflows.
	 */
	protected void check()
	{
		if (context.getAutoDeploy())
		{
			deployApps();
			checkXpdlLastModified();
		}
	}
}