//----------------------------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.IOException;
import java.net.URL;
import java.util.ArrayList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.huihoo.willow.Deployer;
import org.huihoo.willow.Service;
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.core.StandardService;
import org.huihoo.willow.util.StringManager;

public final class ServiceConfig implements LifecycleListener
{
	private static Log log = LogFactory.getLog(ServiceConfig.class);

	// ----------------------------------------------------- Instance Variables
	/**
	 * App base.
	 */
	private File appBase = null;

	/**
	 * The Java class name of the Context configuration class we should use.
	 */
	protected String configClass = "org.huihoo.willow.startup.ContextConfig";

	/**
	 * The Java class name of the Context implementation we should use.
	 */
	protected String contextClass = "org.huihoo.willow.core.StandardContext";

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

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

	/**
	 * The Service we are associated with.
	 */
	private Service service = null;

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

	/**
	 * Should we unpack WAR files when auto-deploying applications in the
	 * <code>appBase</code> directory?
	 */
	private boolean unpackWARs = false;

	/**
	 * 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;

	}

	/**
	 * Return the unpack WARs flag.
	 */
	public boolean isUnpackWARs()
	{

		return (this.unpackWARs);

	}

	/**
	 * Set the unpack WARs flag.
	 *
	 * @param unpackWARs The new unpack WARs flag
	 */
	public void setUnpackWARs(boolean unpackWARs)
	{

		this.unpackWARs = unpackWARs;
	}

	/**
	* Set the validation feature of the XML parser used when
	* parsing xml instances.
	* @param xmlValidation true to enable xml instance validation
	*/
	public void setXmlValidation(boolean xmlValidation)
	{
		this.xmlValidation = xmlValidation;
	}

	/**
		* Get the server.xml <host> attribute's xmlValidation.
		* @return true if validation is enabled.
		*
		*/
	public boolean getXmlValidation()
	{
		return xmlValidation;
	}

	/**
		* Get the server.xml <host> attribute's xmlNamespaceAware.
		* @return true if namespace awarenes is enabled.
		*
		*/
	public boolean getXmlNamespaceAware()
	{
		return xmlNamespaceAware;
	}

	/**
		* Set the namespace aware feature of the XML parser used when
		* parsing xml instances.
		* @param xmlNamespaceAware true to enable namespace awareness
		*/
	public void setXmlNamespaceAware(boolean xmlNamespaceAware)
	{
		this.xmlNamespaceAware = xmlNamespaceAware;
	}

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

	/**
	 * Process the START event for an associated Service.
	 *
	 * @param event The lifecycle event that has occurred
	 */
	public void lifecycleEvent(LifecycleEvent event)
	{
		if (event.getType().equals("check"))
		{
			check();
		}
		// Identify the host we are associated with
		try
		{
			service = (Service) event.getLifecycle();
			if (service instanceof StandardService)
			{
				int serviceDebug = ((StandardService) service).getDebug();
				if (serviceDebug > this.debug)
				{
					this.debug = serviceDebug;
				}
				setUnpackWARs(((StandardService) service).isUnpackWARs());
				setXmlNamespaceAware(((StandardService) service).getXmlNamespaceAware());
				setXmlValidation(((StandardService) service).getXmlValidation());
			}
		}
		catch (ClassCastException e)
		{
			log.error(sm.getString("serviceConfig.cce", event.getLifecycle()), e);
			return;
		}

		// Process the event that has occurred
		if (event.getType().equals(Lifecycle.START_EVENT))
		{
			start();
		}
		else if (event.getType().equals(Lifecycle.STOP_EVENT))
		{
			stop();
		}

	}

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

	/**
	 * Return a File object representing the "application root" directory
	 * for our associated Host.
	 */
	protected File appBase()
	{

		if (appBase != null)
		{
			return appBase;
		}

		File file = new File(service.getAppBase());
		if (!file.isAbsolute())
			file = new File(System.getProperty(Globals.PROPS_WILLOW_HOME), service.getAppBase());
		try
		{
			appBase = file.getCanonicalFile();
		}
		catch (IOException e)
		{
			appBase = file;
		}
		return (appBase);

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

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

		Logger logger = null;
		if (service != null)
		{
			logger = service.getLogger();
		}
		if (logger != null)
		{
			logger.log("ServiceConfig: " + message);
		}
		else
		{
			System.out.println("ServiceConfig: " + message);
		}

	}

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

		Logger logger = null;
		if (service != null)
		{
			logger = service.getLogger();
		}
		if (logger != null)
		{
			logger.log("ServiceConfig: " + message, throwable);
		}
		else
		{
			System.out.println("ServiceConfig: " + message);
			System.out.println("" + throwable);
			throwable.printStackTrace(System.out);
		}

	}

	/**
	 * Process a "start" event for this Service.
	 */
	private void start()
	{
		if (debug > 0)
		{
			log(sm.getString("serviceConfig.start"));
		}

		if (service.getDeployOnStartup())
		{
			deployApps();
		}
	}

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

		if (debug > 0)
		{
			log(sm.getString("serviceConfig.stop"));
		}
		undeployApps();

		appBase = null;
	}

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

		String contextNames[] = ((Deployer) service).findDeployedApps();
		for (int i = 0; i < contextNames.length; i++)
		{
			log.debug(sm.getString("serviceConfig.undeploy", contextNames[i]));
			try
			{
				((Deployer) service).remove(contextNames[i]);
			}
			catch (Throwable t)
			{
				log.error(sm.getString("serviceConfig.undeploy.error", contextNames[i]), t);
			}
		}
		deployed.clear();

	}

	/**
	 * Deploy webapps.
	 */
	protected void check()
	{
		if (service.getAutoDeploy())
		{
			// Deploy apps if the Service allows auto deploying
			deployApps();
		}
	}
	/**
	 * Deploy applications for any directories or WAR files that are found
	 * in our "application root" directory.
	 */
	protected void deployApps()
	{
		if (!(service instanceof Deployer))
		{
			return;
		}

		File appBase = appBase();

		if (!appBase.exists() || !appBase.isDirectory())
		{
			return;
		}

		String files[] = appBase.list();
		deployWARs(appBase, files);
		deployDirectories(appBase, files);

	}

	/**
	 * Deploy directories.
	 */
	protected void deployDirectories(File appBase, String[] files)
	{
		for (int i = 0; i < files.length; i++)
		{
			if (files[i].equalsIgnoreCase("META-INF"))
			{
				continue;
			}
			if (files[i].equalsIgnoreCase("WEB-INF"))
			{
				continue;
			}

			if (deployed.contains(files[i]))
			{
				continue;
			}

			File dir = new File(appBase, files[i]);
			if (!dir.exists() || !dir.isDirectory() || !dir.canRead())
			{
				continue;
			}

			deployed.add(files[i]);

			// Make sure there is an application configuration directory
			// This is needed if the Context appBase is the same as the
			// willow server document root to make sure only workflow applications
			// are deployed and not directories for workflow space.
			File xpdlInf = new File(dir, "/WEB-INF");
			if (!xpdlInf.exists() || !xpdlInf.isDirectory() || !xpdlInf.canRead())
			{
				continue;
			}

			// Calculate the context path and make sure it is unique	
			String contextName = files[i];
			if (files[i].equals(Globals.DEFAULT_WORKFLOW))
			{
				contextName = "";
			}

			if (service.findChild(contextName) != null)
			{
				continue;
			}

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

			long t1 = System.currentTimeMillis();
			try
			{
				URL url = new URL("file", null, dir.getCanonicalPath());
				((Deployer) service).install(contextName, url);
			}
			catch (Throwable t)
			{
				log.error(sm.getString("serviceConfig.deployDir.error", files[i]), t);
			}
			long t2 = System.currentTimeMillis();
			if ((t2 - t1) > 200)
			{
				log.debug("Deployed " + files[i] + " " + (t2 - t1));
			}
		}

	}
	protected void deployWARs(File appBase, String[] files)
	{
		for (int i = 0; i < files.length; i++)
		{

			if (files[i].equalsIgnoreCase("META-INF"))
			{
				continue;
			}
			if (files[i].equalsIgnoreCase("WEB-INF"))
			{
				continue;
			}
			if (deployed.contains(files[i]))
			{
				continue;
			}
			File dir = new File(appBase, files[i]);
			if (files[i].toLowerCase().endsWith(".war"))
			{

				deployed.add(files[i]);

				// Calculate the context name and make sure it is unique
				String contextName = files[i];
				int period = contextName.lastIndexOf(".");
				if (period >= 0)
				{
					contextName = contextName.substring(0, period);
				}
				if (contextName.equals(Globals.DEFAULT_WORKFLOW))
				{
					contextName = "";
				}
				if (service.findChild(contextName) != null)
				{
					continue;
				}

				if (isUnpackWARs())
				{

					// Expand and deploy this application as a directory
					log.debug(sm.getString("serviceConfig.expand", files[i]));
					URL url = null;
					String path = null;
					try
					{
						url = new URL("jar:file:" + dir.getCanonicalPath() + "!/");
						path = ExpandWar.expand(service, url);
					}
					catch (IOException e)
					{
						// JAR decompression failure
						log.warn(sm.getString("serviceConfig.expand.error", files[i]));
						continue;
					}
					catch (Throwable t)
					{
						log.error(sm.getString("serviceConfig.expand.error", files[i]), t);
						continue;
					}
					try
					{
						if (path != null)
						{
							url = new URL("file:" + path);
							((Deployer) service).install(contextName, url);
						}
					}
					catch (Throwable t)
					{
						log.error(sm.getString("serviceConfig.expand.error", files[i]), t);
					}

				}
				else
				{

					// Deploy the application in this WAR file
					log.info(sm.getString("serviceConfig.deployJar", files[i]));
					try
					{
						URL url = new URL("file", null, dir.getCanonicalPath());
						url = new URL("jar:" + url.toString() + "!/");
						((Deployer) service).install(contextName, url);
					}
					catch (Throwable t)
					{
						log.error(sm.getString("serviceConfig.deployJar.error", files[i]), t);
					}

				}

			}

		}
	}
}
