//----------------------------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.core;

import java.io.File;
import java.io.IOException;
import java.net.URL;

import org.apache.commons.digester.Digester;
import org.huihoo.willow.Container;
import org.huihoo.willow.Context;
import org.huihoo.willow.Deployer;
import org.huihoo.willow.Globals;
import org.huihoo.willow.Lifecycle;
import org.huihoo.willow.LifecycleException;
import org.huihoo.willow.LifecycleListener;
import org.huihoo.willow.Service;
import org.huihoo.willow.startup.ExpandWar;
import org.huihoo.willow.startup.WillowDigester;
import org.huihoo.willow.util.StringManager;
import org.huihoo.willow.xmlrules.ContextRuleSet;

public class StandardServiceDeployer extends ContainerBase implements Deployer
{

	private static org.apache.commons.logging.Log log =
		org.apache.commons.logging.LogFactory.getLog(StandardServiceDeployer.class);

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

	public StandardServiceDeployer()
	{
	}

	/**
	 * Create a new StandardServiceDeployer associated with the specified
	 * StandardService.
	 *
	 * @param service The StandardService we are associated with
	 */
	public StandardServiceDeployer(StandardService service)
	{

		super();
		this.service = service;

	}

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

	/**
	 * The <code>ContextRuleSet</code> associated with our
	 * <code>digester</code> instance.
	 */
	private ContextRuleSet contextRuleSet = null;

	/**
	* The <code>Digester</code> instance to use for deploying org.huihoo.workflow applications
	* to this <code>Host</code>.  <strong>WARNING</strong> - Usage of this
	* instance must be appropriately synchronized to prevent simultaneous
	* access by multiple threads.
	*/
	private Digester digester = null;

	/**
	 * The <code>StandardService</code> instance we are associated with.
	 */
	protected StandardService service = null;

	/**
	 * The string manager for this package.
	 */
	protected static StringManager sm = StringManager.getManager(Constants.PACKAGE);

	// -------------------------------------------------------- Depoyer Methods

	public Service getService()
	{
		return service;
	}

	public void setService(Service service)
	{
		this.service = (StandardService) service;
	}

	/**
	 * Return the name of the Container with which this ServiceDeployer is associated.
	 */
	public String getName()
	{

		return (service.getName());

	}

	/**
	 * Install a new  application, whose org.huihoo.workflow application archive is at the
	 * specified URL, into this container with the specified app name.
	 * <p>
	 * If this application is successfully installed, a ContainerEvent of type
	 * <code>PRE_INSTALL_EVENT</code> will be sent to registered listeners
	 * before the associated Context is started, and a ContainerEvent of type
	 * <code>INSTALL_EVENT</code> will be sent to all registered listeners
	 * after the associated Context is started, with the newly created
	 * <code>Context</code> as an argument.
	 *
	 * @param contextName The app name to which this application should
	 *  be installed (must be unique)
	 * @param warURL A URL of  type "file:" that points to an unpacked 
	 *  directory structure containing  the org.huihoo.workflow application to be installed
	 *
	 * @exception IllegalArgumentException if the specified app name  is malformed 
	 * @exception IllegalStateException if the specified app name
	 *  is already attached to an existing org.huihoo.workflow application
	 * @exception IOException if an input/output error was encountered
	 *  during installation
	 */
	public synchronized void install(String contextName, URL warURL) throws IOException
	{
		// Validate the format and state of our arguments
		if (contextName == null)
		{
			throw new IllegalArgumentException(sm.getString("standardService.nameRequired"));
		}

		if (findDeployedApp(contextName) != null)
		{
			throw new IllegalStateException(sm.getString("standardService.nameUsed", contextName));
		}
		if (warURL == null)
		{
			throw new IllegalArgumentException(sm.getString("standardService.dirRequired"));
		}

		// Calculate the document base for the new org.huihoo.workflow application
		log.info(sm.getString("standardService.installing", contextName, warURL.toString()));
		String url = warURL.toString();
		String docBase = null;
		boolean isWAR = false;

		if (url.startsWith("jar:"))
		{
			url = url.substring(4, url.length() - 2);
			if (!url.toLowerCase().endsWith(".war"))
			{
				throw new IllegalArgumentException(sm.getString("standardService.warURL", url));
			}
			isWAR = true;
		}
		if (url.startsWith("file://"))
		{
			docBase = url.substring(7);
		}
		else if (url.startsWith("file:"))
		{
			docBase = url.substring(5);
		}
		else
		{
			throw new IllegalArgumentException(sm.getString("standardService.warURL", url));
		}

		// Determine if directory to install is in the service appBase
		boolean isAppBase = false;

		File appBase = new File(service.getAppBase());
		if (!appBase.isAbsolute())
		{
			appBase = new File(System.getProperty(Globals.PROPS_WILLOW_HOME), service.getAppBase());
		}

		File contextFile = new File(docBase);
		File baseDir = contextFile.getParentFile();
		if (appBase.getCanonicalPath().equals(baseDir.getCanonicalPath()))
		{
			isAppBase = true;
		}

		// Make sure contextName and directory names match when
		// installing from the service engineBase
		if (isAppBase && service.getAutoDeploy())
		{
			String filename = contextFile.getName();
			if (isWAR)
			{
				filename = filename.substring(0, filename.length() - 4);
			}

			if (contextName.length() == 0)
			{
				if (!filename.equals(Globals.DEFAULT_WORKFLOW))
				{
					throw new IllegalArgumentException(sm.getString("standardService.nameMatch", "", Globals.DEFAULT_WORKFLOW));
				}
			}
			else if (!filename.equals(contextName))
			{
				throw new IllegalArgumentException(sm.getString("standardService.nameMatch", contextName, filename));
			}
		}

		// Expand war file if service wants wars unpacked
		if (isWAR && service.isUnpackWARs())
		{
			docBase = ExpandWar.expand(service, warURL, contextName);
		}

		// Install the new workflow application
		try
		{
			Class clazz = Class.forName(service.getContextClass());
			Context context = (Context) clazz.newInstance();
			context.setName(contextName);
			context.setDocBase(docBase);
			if (context instanceof Lifecycle)
			{
				clazz = Class.forName(service.getConfigClass());
				LifecycleListener listener = (LifecycleListener) clazz.newInstance();
				((Lifecycle) context).addLifecycleListener(listener);
			}

			service.addChild(context);
		}
		catch (ClassNotFoundException e)
		{
			log.info("", e);
		}
		catch (Exception e)
		{
			log.info("Error installing", e);
			throw new IOException(e.toString());
		}

	}

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

		return ((Context) service.findChild(contextName));

	}

	/**
	 * Return the app name s of all deployed org.huihoo.workflow applications in this
	 * Container.  If there are no deployed applications, a zero-length
	 * array is returned.
	 */
	public String[] findDeployedApps()
	{

		Container children[] = service.findChildren();
		String results[] = new String[children.length];
		for (int i = 0; i < children.length; i++)
		{
			results[i] = children[i].getName();
		}
		return (results);

	}

	/**
	 * Remove an existing org.huihoo.workflow application, attached to the specified app
	 * name.  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 app name of the application to be removed
	 *
	 * @exception IllegalArgumentException if the specified app name is malformed 
	 * @exception IllegalArgumentException if the specified app name does
	 *  not identify a currently installed org.huihoo.workflow application
	 * @exception IOException if an input/output error occurs during
	 *  removal
	 */
	public void remove(String contextName) throws IOException
	{

		// Validate the format and state of our arguments
		if (contextName == null)
		{
			throw new IllegalArgumentException(sm.getString("standardService.nameRequired"));
		}
		// Locate the context and associated work directory
		Context context = findDeployedApp(contextName);
		if (context == null)
			throw new IllegalArgumentException(sm.getString("standardService.nameMissing", contextName));

		// Remove this org.huihoo.workflow application
		log.info(sm.getString("standardService.removing", contextName));
		try
		{
			service.removeChild(context);
		}
		catch (Exception e)
		{
			log.error(sm.getString("standardService.removeError", contextName), e);
			throw new IOException(e.toString());
		}

	}

	/**
	 * Remove an existing org.huihoo.workflow application, attached to the specified app
	 * name.  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 org.huihoo.workflow application war file and/or directory
	 * if they exist in the Host's appBase.
	 *
	 * @param contextName The application name of the application to be removed
	 * @param undeploy boolean flag to remove org.huihoo.workflow application from server
	 *
	 * @exception IllegalArgumentException if the specified app name
	 * @exception IllegalArgumentException if the specified app name does
	 *  not identify a currently installed org.huihoo.workflow application
	 * @exception IOException if an input/output error occurs during
	 *  removal
	 */
	public void remove(String contextName, boolean undeploy) throws IOException
	{

		// Validate the format and state of our arguments
		if (contextName == null)
		{
			throw new IllegalArgumentException(sm.getString("standardService.nameRequired"));
		}

		// Locate the context and associated work directory
		Context context = findDeployedApp(contextName);
		if (context == null)
		{
			throw new IllegalArgumentException(sm.getString("standardService.nameMissing", contextName));
		}

		// Remove this org.huihoo.workflow application
		service.log(sm.getString("standardService.removing", contextName));
		try
		{
			// Get the work directory for the Context
			File workDir = new File(((StandardContext) context).getWorkPath());
			service.removeChild(context);

			if (undeploy)
			{
				// Remove the org.huihoo.workflow application directory  file if it
				// exists in the Service's engineBase directory.

				// Determine if directoryr to remove is in the service engineBase
				boolean isAppBase = false;
				File appBase = new File(service.getAppBase());
				if (!appBase.isAbsolute())
				{
					appBase = new File(System.getProperty(Globals.PROPS_WILLOW_HOME), service.getAppBase());
				}
				File contextFile = new File(context.getDocBase());
				File baseDir = contextFile.getParentFile();
				if ((baseDir == null) || (appBase.getCanonicalPath().equals(baseDir.getCanonicalPath())))
				{
					isAppBase = true;
				}

				// Only remove directory and/or war if they are located in the
				// Host's appBase autoDeploy is true
				if (isAppBase && service.getAutoDeploy())
				{
					String filename = contextFile.getName();

					if (filename.equals(contextName))
					{
						if (contextFile.isDirectory())
						{
							deleteDir(contextFile);
						}
					}
				}

				// Remove the work directory for the Context
				if (workDir == null && context instanceof StandardContext && ((StandardContext) context).getWorkDir() != null)
				{
					workDir = new File(((StandardContext) context).getWorkPath());
				}
				if (workDir != null && workDir.exists())
				{
					deleteDir(workDir);
				}
			}
		}
		catch (Exception e)
		{
			service.log(sm.getString("standardService.removeError", contextName), e);
			throw new IOException(e.toString());
		}

	}

	/**
	 * Start an existing org.huihoo.workflow application, attached to the specified app
	 * name.  Only starts a org.huihoo.workflow application if it is not running.
	 *
	 * @param contextName The app name of the application to be started
	 *
	 * @exception IllegalArgumentException if the specified app name
	 * @exception IllegalArgumentException if the specified app name does
	 *  not identify a currently installed org.huihoo.workflow application
	 * @exception IOException if an input/output error occurs during
	 *  startup
	 */
	public void start(String contextName) throws IOException
	{

		// Validate the format and state of our arguments
		if (contextName == null)
		{
			throw new IllegalArgumentException(sm.getString("standardService.nameRequired"));
		}

		Context context = findDeployedApp(contextName);
		if (context == null)
		{
			throw new IllegalArgumentException(sm.getString("standardService.nameMissing", contextName));
		}
		log.info("standardService.start " + contextName);
		try
		{
			((Lifecycle) context).start();
		}
		catch (LifecycleException e)
		{
			log.info("standardService.start " + contextName + ": ", e);
			throw new IllegalStateException("standardService.start " + contextName + ": " + e);
		}
	}

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

		// Validate the format and state of our arguments
		if (contextName == null)
		{
			throw new IllegalArgumentException(sm.getString("standardService.nameRequired"));
		}

		Context context = findDeployedApp(contextName);
		if (context == null)
		{
			throw new IllegalArgumentException(sm.getString("standardService.nameMissing", contextName));
		}
		log.info("standardService.stop " + contextName);
		try
		{
			((Lifecycle) context).stop();
		}
		catch (LifecycleException e)
		{
			log.error("standardService.stop " + contextName + ": ", e);
			throw new IllegalStateException("standardService.stop " + contextName + ": " + e);
		}

	}

	// ------------------------------------------------------ Delegated Methods

	/**
	 * Delegate a request to add a child Context to our associated Host.
	 *
	 * @param child The child Context to be added
	 */
	public void addChild(Context child)
	{
		String contextName = child.getName();
		if (contextName == null)
		{
			throw new IllegalArgumentException(sm.getString("standardService.nameRequired"));
		}

		if (service.findChild(contextName) != null)
		{
			throw new IllegalStateException(sm.getString("standardService.nameUsed", contextName));
		}

		service.addChild(child);

	}

	/**
	 * Delegate a request for the parent class loader to our associated Host.
	 */
	public ClassLoader getParentClassLoader()
	{

		return (service.getParentClassLoader());

	}

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

	/**
	 * Create (if necessary) and return a Digester configured to process the
	 * context configuration descriptor for an application.
	 */
	protected Digester createDigester()
	{
		if (digester == null)
		{
			digester = new WillowDigester();
			if (service.getDebug() > 0)
			{
				digester.setDebug(3);
			}
			digester.setValidating(false);
			contextRuleSet = new ContextRuleSet("");
			digester.addRuleSet(contextRuleSet);
		}
		return (digester);

	}

	/**
	 * Delete the specified directory, including all of its contents and
	 * subdirectories recursively.
	 *
	 * @param dir File object representing the directory to be deleted
	 */
	protected void deleteDir(File dir)
	{

		String files[] = dir.list();
		if (files == null)
		{
			files = new String[0];
		}
		for (int i = 0; i < files.length; i++)
		{
			File file = new File(dir, files[i]);
			if (file.isDirectory())
			{
				deleteDir(file);
			}
			else
			{
				file.delete();
			}
		}
		dir.delete();

	}

}
