/*
 * Created on 2004-6-12
 *
 * 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.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;

import org.huihoo.willow.Logger;
import org.huihoo.willow.util.Enumerator;
import org.huihoo.willow.util.StringManager;

import workflow.script.core.ScriptConfig;
import workflow.script.core.ScriptContext;
import workflow.services.WorkflowContext;
import workflow.services.WorkflowService;

import com.zosatapo.commons.util.FileUtils;

/**
 * @author reic
 *
 * To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */

public class ApplicationContext implements ScriptContext,WorkflowContext
{
	// ----------------------------------------------------------- Constructors
	/**
	 * Construct a new instance of this class, associated with the specified
	 * Context instance.
	 *
	 * @param context The associated Context instance
	 */
	public ApplicationContext(String basePath, StandardContext context)
	{
		super();
		this.context = context;
		this.basePath = basePath;
		this.scriptConfig=new ApplicationConfig(this);
	}

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

	/**
	 * The context attributes for this context.
	 */
	private HashMap attributes = new HashMap();

	/**
	 * List of read only attributes for this context.
	 */
	private HashMap readOnlyAttributes = new HashMap();

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

	/**
	 * The ScriptContext implementation associated with this Context.
	 */
	private transient ApplicationConfig scriptConfig = null;
	
	/**
	 * Empty collection to serve as the basis for empty enumerations.
	 * <strong>DO NOT ADD ANY ELEMENTS TO THIS COLLECTION!</strong>
	 */
	private static final ArrayList empty = new ArrayList();

	/**
	 * The merged context initialization parameters for this Context.
	 */
	private HashMap parameters = null;

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

	/**
	 * Base path.
	 */
	private String basePath = null;

	// ------------------------------------------------- WorkflowContext Methods

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

		synchronized (attributes)
		{
			return (attributes.get(name));
		}

	}

	/**
	 * Return an enumeration of the names of the context attributes
	 * associated with this context.
	 */
	public Enumeration getAttributeNames()
	{
		synchronized (attributes)
		{
			return new Enumerator(attributes.keySet(), true);
		}

	}
	/**
	 * Return the value of the specified initialization parameter, or
	 * <code>null</code> if this parameter does not exist.
	 *
	 * @param name Name of the initialization parameter to retrieve
	 */
	public String getInitParameter(final String name)
	{
		mergeParameters();
		synchronized (parameters)
		{
			return ((String) parameters.get(name));
		}
	}

	/**
	 * Return the names of the context's initialization parameters, or an
	 * empty enumeration if the context has no initialization parameters.
	 */
	public Enumeration getInitParameterNames()
	{
		mergeParameters();
		synchronized (parameters)
		{
			return (new Enumerator(parameters.keySet()));
		}

	}

	/**
	 * Return the real path for a given virtual path, if possible; otherwise
	 * return <code>null</code>.
	 *
	 * @param path The path to the desired resource
	 */
	public String getRealPath(String path)
	{
		File file = new File(basePath, path);
		if (file.exists())
		{
			return (file.getAbsolutePath());
		}
		else
		{
			return null;
		}
	}

	/**
	 * Return the URL to the resource that is mapped to a specified path.
	 * The path must begin with a "/" and is interpreted as relative to the
	 * current context root.
	 *
	 * @param path The path to the desired resource
	 *
	 * @exception MalformedURLException if the path is not given
	 *  in the correct form
	 */
	public URL getResource(String path) throws MalformedURLException
	{

		if (path == null || !path.startsWith("/"))
		{
			throw new MalformedURLException(sm.getString("applicationContext.resourcePath.error", path));
		}

		path = FileUtils.normalize(path);
		if (path == null)
		{
			return (null);
		}

		if (path.startsWith("/WEB-INF/lib/") && path.endsWith(".jar"))
		{
			File jarFile = new File(context.getWorkPath(), path);
			if (jarFile.exists())
			{
				return jarFile.toURL();
			}
		}
		else
		{
			File file = new File(basePath, path);
			if (file.exists())
			{
				return file.toURL();
			}
		}

		return (null);

	}

	/**
	 * Return the requested resource as an <code>InputStream</code>.  The
	 * path must be specified according to the rules described under
	 * <code>getResource</code>.  If no such resource can be identified,
	 * return <code>null</code>.
	 *
	 * @param path The path to the desired resource.
	 */
	public InputStream getResourceAsStream(String path)
	{

		URL resource = null;

		try
		{
			resource = getResource(path);
		}
		catch (MalformedURLException e)
		{
			resource = null;
		}

		if (resource != null)
		{
			try
			{
				return new FileInputStream(resource.getFile());
			}
			catch (FileNotFoundException e1)
			{
			}
		}

		return null;
	}

	/**
	 * Writes the specified message to a script log file.
	 *
	 * @param message Message to be written
	 */
	public void log(String message)
	{

		Logger logger = context.getLogger();
		if (logger != null)
		{
			logger.log(context.logName() + message);
		}

	}

	/**
	 * Writes the specified message and exception to a script log file.
	 *
	 * @param message Message to be written
	 * @param throwable Exception to be reported
	 */
	public void log(String message, Throwable throwable)
	{

		Logger logger = context.getLogger();
		if (logger != null)
		{
			logger.log(context.logName() + message, throwable);
		}

	}

	/**
	 * Remove the context attribute with the specified name, if any.
	 *
	 * @param name Name of the context attribute to be removed
	 */
	public void removeAttribute(String name)
	{

		boolean found = false;

		// Remove the specified attribute
		synchronized (attributes)
		{
			// Check for read only attribute
			if (readOnlyAttributes.containsKey(name))
			{
				return;
			}
			found = attributes.containsKey(name);
			if (found)
			{
				attributes.remove(name);
			}
			else
			{
				return;
			}
		}

	}

	/**
	 * Bind the specified value with the specified context attribute name,
	 * replacing any existing value for that name.
	 *
	 * @param name Attribute name to be bound
	 * @param value New attribute value to be bound
	 */
	public void setAttribute(String name, Object value)
	{

		// Name cannot be null
		if (name == null)
		{
			throw new IllegalArgumentException(sm.getString("applicationContext.setAttribute.namenull"));
		}

		// Null value is the same as removeAttribute()
		if (value == null)
		{
			removeAttribute(name);
			return;
		}

		// Add or replace the specified attribute
		synchronized (attributes)
		{
			// Check for read only attribute
			if (readOnlyAttributes.containsKey(name))
			{
				return;
			}
			attributes.put(name, value);
		}
	}

	// -------------------------------------------------------- Package Methods

	/**
	 * Clear all application-created attributes.
	 */
	void clearAttributes()
	{

		// Create list of attributes to be removed
		ArrayList list = new ArrayList();
		synchronized (attributes)
		{
			Iterator iter = attributes.keySet().iterator();
			while (iter.hasNext())
			{
				list.add(iter.next());
			}
		}

		// Remove application originated attributes
		// (read only attributes will be left in place)
		Iterator keys = list.iterator();
		while (keys.hasNext())
		{
			String key = (String) keys.next();
			removeAttribute(key);
		}

	}

	/**
	 * Set an attribute as read only.
	 */
	void setAttributeReadOnly(String name)
	{

		synchronized (attributes)
		{
			if (attributes.containsKey(name))
			{
				readOnlyAttributes.put(name, name);
			}
		}

	}

	/**
			 * Merge the context initialization parameters specified in the application
			 * deployment descriptor with the application parameters described in the
			 * server configuration, respecting the <code>override</code> property of
			 * the application parameters appropriately.
			 */
	private void mergeParameters()
	{
		if (parameters != null)
		{
			return;
		}
		HashMap results = new HashMap();
		String names[] = context.findInitParameters();
		for (int i = 0; i < names.length; i++)
		{
			results.put(names[i], context.findInitParameter(names[i]));
		}
		parameters = results;

	}

	public WorkflowService getWorkflowService()
	{
		return context;
	}

	public ScriptConfig getScriptConfig()
	{
		return scriptConfig;
	}
}
