//----------------------------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.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.reflect.Constructor;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Vector;

import javax.naming.directory.DirContext;

import org.huihoo.willow.Container;
import org.huihoo.willow.Context;
import org.huihoo.willow.DefaultContext;
import org.huihoo.willow.LifecycleEvent;
import org.huihoo.willow.LifecycleListener;
import org.huihoo.willow.Loader;
import org.huihoo.willow.deploy.ApplicationParameter;
import org.huihoo.willow.loader.WorkflowLoader;
import org.huihoo.willow.util.StringManager;

/**
 * Used to store the default configuration a Host will use
 * when creating a Context.  A Context configured in server.xml
 * can override these defaults by setting the Context attribute
 * <CODE>override="true"</CODE>.
 */

public class StandardDefaultContext implements DefaultContext, LifecycleListener
{

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

	/**
	 * Create the DefaultContext
	 */
	public StandardDefaultContext()
	{

	}

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

	/**
	 * Contexts we are currently associated with.
	 */
	private Hashtable contexts = new Hashtable();

	/**
	 * The set of application listener class names configured for this
	 * application, in the order they were encountered in the web.xml file.
	 */
	private String applicationListeners[] = new String[0];

	/**
	 * The set of application parameters defined for this application.
	 */
	private ApplicationParameter applicationParameters[] = new ApplicationParameter[0];

	/**
	 * The descriptive information string for this implementation.
	 */
	private static final String info = "org.huihoo.willow.core.DefaultContext/1.0";
	
	/**
	 * The context initialization parameters for this web application,
	 * keyed by name.
	 */
	private HashMap parameters = new HashMap();

	/**
	 * The reloadable flag for this web application.
	 */
	private boolean reloadable = false;

	/**
	 * The swallowOutput flag for this web application.
	 */
	private boolean swallowOutput = false;

	/**
	 * The resources DirContext object with which this Container is
	 * associated.
	 *
	 */
	DirContext dirContext = null;

	/**
	 * The human-readable name of this Container.
	 */
	protected String name = "defaultContext";

	/**
	 * The parent Container to which this Container is a child.
	 */
	protected Container parent = null;

	/**
	 * The Context LifecycleListener's
	 */
	protected Vector lifecycle = new Vector();

	/**
	 * The Loader implementation with which this Container is associated.
	 */
	protected Loader loader = null;

	/**
	 * Case sensitivity.
	 */
	protected boolean caseSensitive = true;

	/**
	 * Allow linking.
	 */
	protected boolean allowLinking = false;

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

	/**
	 * The property change support for this component.
	 */
	protected PropertyChangeSupport support = new PropertyChangeSupport(this);

	// ----------------------------------------------------- Context Properties

	/**
	 * Set case sensitivity.
	 */
	public void setCaseSensitive(boolean caseSensitive)
	{
		this.caseSensitive = caseSensitive;
	}

	/**
	 * Is case sensitive ?
	 */
	public boolean isCaseSensitive()
	{
		return caseSensitive;
	}

	/**
	 * Set allow linking.
	 */
	public void setAllowLinking(boolean allowLinking)
	{
		this.allowLinking = allowLinking;
	}

	/**
	 * Is linking allowed.
	 */
	public boolean isAllowLinking()
	{
		return allowLinking;
	}

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

	}

	/**
	 * Return the reloadable flag for this web application.
	 */
	public boolean getReloadable()
	{

		return (this.reloadable);

	}

	/**
	 * Set the reloadable flag for this web application.
	 *
	 * @param reloadable The new reloadable flag
	 */
	public void setReloadable(boolean reloadable)
	{
		boolean oldReloadable = this.reloadable;
		this.reloadable = reloadable;

	}

	/**
	 * Return the swallowOutput flag for this web application.
	 */
	public boolean getSwallowOutput()
	{

		return (this.swallowOutput);

	}

	/**
	 * Set the swallowOutput flag for this web application.
	 *
	 * @param swallowOutput The new swallowOutput flag
	 */
	public void setSwallowOutput(boolean swallowOutput)
	{
		boolean oldSwallowOutput = this.swallowOutput;
		this.swallowOutput = swallowOutput;

	}

	/**
	 * Set the resources DirContext object with which this Container is
	 * associated.
	 *
	 * @param resources The newly associated DirContext
	 */
	public void setResources(DirContext resources)
	{
		this.dirContext = resources;

	}

	/**
	 * Get the resources DirContext object with which this Container is
	 * associated.
	 *
	 * @param resources The new associated DirContext
	 */
	public DirContext getResources()
	{

		return this.dirContext;

	}

	/**
	 * Return the Loader with which this Container is associated.  If there is
	 * no associated Loader return <code>null</code>.
	 */
	public Loader getLoader()
	{

		return loader;

	}

	/**
	 * Set the Loader with which this Context is associated.
	 *
	 * @param loader The newly associated loader
	 */
	public void setLoader(Loader loader)
	{
		Loader oldLoader = this.loader;
		this.loader = loader;

		// Report this property change to interested listeners
		support.firePropertyChange("loader", oldLoader, this.loader);
	}

	// ------------------------------------------------------ Lifecycle Methods

	/**
	 * Add a lifecycle event listener to this component.
	 *
	 * @param listener The listener to add
	 */
	public void addLifecycleListener(LifecycleListener listener)
	{
		lifecycle.add(listener);
	}

	// ------------------------------------------------------ Public Properties

	/**
	 * The name of this DefaultContext
	 */

	public String getName()
	{
		return (this.name);
	}

	public void setName(String name)
	{
		this.name = name;
	}

	/**
	 * Return the Container for which this Container is a child, if there is
	 * one.  If there is no defined parent, return <code>null</code>.
	 */
	public Container getParent()
	{

		return (parent);

	}

	/**
	 * Set the parent Container to which this Container is being added as a
	 * child.  This Container may refuse to become attached to the specified
	 * Container by throwing an exception.
	 *
	 * @param container Container to which this Container is being added
	 *  as a child
	 *
	 * @exception IllegalArgumentException if this Container refuses to become
	 *  attached to the specified Container
	 */
	public void setParent(Container container)
	{
		Container oldParent = this.parent;
		this.parent = container;
		support.firePropertyChange("parent", oldParent, this.parent);

	}

	// -------------------------------------------------------- Context Methods

	/**
	 * Add a new Listener class name to the set of Listeners
	 * configured for this application.
	 *
	 * @param listener Java class name of a listener class
	 */
	public void addApplicationListener(String listener)
	{

		synchronized (applicationListeners)
		{
			String results[] = new String[applicationListeners.length + 1];
			for (int i = 0; i < applicationListeners.length; i++)
				results[i] = applicationListeners[i];
			results[applicationListeners.length] = listener;
			applicationListeners = results;
		}

	}

	/**
	 * Add a new application parameter for this application.
	 *
	 * @param parameter The new application parameter
	 */
	public void addApplicationParameter(ApplicationParameter parameter)
	{

		synchronized (applicationParameters)
		{
			ApplicationParameter results[] = new ApplicationParameter[applicationParameters.length + 1];
			System.arraycopy(applicationParameters, 0, results, 0, applicationParameters.length);
			results[applicationParameters.length] = parameter;
			applicationParameters = results;
		}

	}
	
	
	/**
	 * Add a new context initialization parameter, replacing any existing
	 * value for the specified name.
	 *
	 * @param name Name of the new parameter
	 * @param value Value of the new  parameter
	 *
	 * @exception IllegalArgumentException if the name or value is missing,
	 *  or if this context initialization parameter has already been
	 *  registered
	 */
	public void addInitParameter(String name, String value)
	{
		// Validate the proposed context initialization parameter
		if ((name == null) || (value == null))
			throw new IllegalArgumentException(sm.getString("standardContext.parameter.required"));
		if (parameters.get(name) != null)
			throw new IllegalArgumentException(sm.getString("standardContext.parameter.duplicate", name));

		// Add this parameter to our defined set
		synchronized (parameters)
		{
			parameters.put(name, value);
		}

	}

	/**
	 * Add a property change listener to this component.
	 *
	 * @param listener The listener to add
	 */
	public void addPropertyChangeListener(PropertyChangeListener listener)
	{

		support.addPropertyChangeListener(listener);

	}

	/**
	 * Return the set of application listener class names configured
	 * for this application.
	 */
	public String[] findApplicationListeners()
	{

		return (applicationListeners);

	}

	/**
	 * Return the set of application parameters for this application.
	 */
	public ApplicationParameter[] findApplicationParameters()
	{

		return (applicationParameters);

	}

	/**
	 * Return the value for the specified context initialization
	 * parameter name, if any; otherwise return <code>null</code>.
	 *
	 * @param name Name of the parameter to return
	 */
	public String findInitParameter(String name)
	{

		synchronized (parameters)
		{
			return ((String) parameters.get(name));
		}

	}

	/**
	 * Return the names of all defined context initialization parameters
	 * for this Context.  If no parameters are defined, a zero-length
	 * array is returned.
	 */
	public String[] findInitParameters()
	{

		synchronized (parameters)
		{
			String results[] = new String[parameters.size()];
			return ((String[]) parameters.keySet().toArray(results));
		}

	}

	/**
	 * Remove the specified application listener class from the set of
	 * listeners for this application.
	 *
	 * @param listener Java class name of the listener to be removed
	 */
	public void removeApplicationListener(String listener)
	{

		synchronized (applicationListeners)
		{

			// Make sure this application listener is currently present
			int n = -1;
			for (int i = 0; i < applicationListeners.length; i++)
			{
				if (applicationListeners[i].equals(listener))
				{
					n = i;
					break;
				}
			}
			if (n < 0)
				return;

			// Remove the specified application listener
			int j = 0;
			String results[] = new String[applicationListeners.length - 1];
			for (int i = 0; i < applicationListeners.length; i++)
			{
				if (i != n)
					results[j++] = applicationListeners[i];
			}
			applicationListeners = results;

		}

	}

	/**
	 * Remove the application parameter with the specified name from
	 * the set for this application.
	 *
	 * @param name Name of the application parameter to remove
	 */
	public void removeApplicationParameter(String name)
	{

		synchronized (applicationParameters)
		{

			// Make sure this parameter is currently present
			int n = -1;
			for (int i = 0; i < applicationParameters.length; i++)
			{
				if (name.equals(applicationParameters[i].getName()))
				{
					n = i;
					break;
				}
			}
			if (n < 0)
				return;

			// Remove the specified parameter
			int j = 0;
			ApplicationParameter results[] = new ApplicationParameter[applicationParameters.length - 1];
			for (int i = 0; i < applicationParameters.length; i++)
			{
				if (i != n)
					results[j++] = applicationParameters[i];
			}
			applicationParameters = results;

		}

	}
	
	/**
	 * Remove the context initialization parameter with the specified
	 * name, if it exists; otherwise, no action is taken.
	 *
	 * @param name Name of the parameter to remove
	 */
	public void removeInitParameter(String name)
	{

		synchronized (parameters)
		{
			parameters.remove(name);
		}

	}

	/**
	 * Remove a property change listener from this component.
	 *
	 * @param listener The listener to remove
	 */
	public void removePropertyChangeListener(PropertyChangeListener listener)
	{

		support.removePropertyChangeListener(listener);

	}

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

	/**
	 * Process the START event for an associated Context.
	 *
	 * @param event The lifecycle event that has occurred
	 */
	public void lifecycleEvent(LifecycleEvent event)
	{

	}

	/**
	 * Install the StandardContext portion of the DefaultContext
	 * configuration into current Context.
	 *
	 * @param context current web application context
	 */
	public void installDefaultContext(Context context)
	{

		if (context instanceof StandardContext)
		{

			((StandardContext) context).setSwallowOutput(getSwallowOutput());
			((StandardContext) context).setAllowLinking(isAllowLinking());
			((StandardContext) context).setCaseSensitive(isCaseSensitive());
			if (!contexts.containsKey(context))
			{
				((StandardContext) context).addLifecycleListener(this);
			}
			Enumeration lifecycleListeners = lifecycle.elements();
			while (lifecycleListeners.hasMoreElements())
			{
				((StandardContext) context).addLifecycleListener((LifecycleListener) lifecycleListeners.nextElement());
			}
		}

		if (!context.getPrivileged() && loader != null)
		{
			ClassLoader parentClassLoader = context.getParent().getParentClassLoader();
			Class clazz = loader.getClass();
			Class types[] = { ClassLoader.class };
			Object args[] = { parentClassLoader };
			try
			{
				Constructor constructor = clazz.getDeclaredConstructor(types);
				Loader context_loader = (Loader) constructor.newInstance(args);
				context_loader.setDelegate(loader.getDelegate());
				context_loader.setReloadable(loader.getReloadable());
				if (loader instanceof WorkflowLoader)
				{
					((WorkflowLoader) context_loader).setDebug(((WorkflowLoader) loader).getDebug());
					((WorkflowLoader) context_loader).setLoaderClass(((WorkflowLoader) loader).getLoaderClass());
				}
				context.setLoader(context_loader);
			}
			catch (Exception e)
			{
				throw new IllegalArgumentException("DefaultContext custom Loader install failed, Exception: " + e.getMessage());
			}
		}
	}

	/**
	 * Import the configuration from the DefaultContext into
	 * current Context.
	 *
	 * @param context current web application context
	 */
	public void importDefaultContext(Context context)
	{

		context.setReloadable(getReloadable());

		String[] listeners = findApplicationListeners();
		for (int i = 0; i < listeners.length; i++)
		{
			context.addApplicationListener(listeners[i]);
		}
		
		String[] parameters = findInitParameters();
		for (int i = 0; i < parameters.length; i++)
		{
			context.addInitParameter(parameters[i], findInitParameter(parameters[i]));
		}
		
		ApplicationParameter[] appParams = findApplicationParameters();
		for (int i = 0; i < appParams.length; i++)
		{
			context.addApplicationParameter(appParams[i]);
		}

	}

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

		StringBuffer sb = new StringBuffer();
		if (getParent() != null)
		{
			sb.append(getParent().toString());
			sb.append(".");
		}
		sb.append("DefaultContext[");
		sb.append("]");
		return (sb.toString());

	}
}
