//----------------------------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.workflow.store;

import org.huihoo.commons.jdbc.Store;
import org.huihoo.workflow.*;
import org.huihoo.workflow.WorkflowException;

public class FactoryManager
{

	public static java.io.PrintWriter getLogWriter()
	{
		synchronized (logSync)
		{
			return logWriter;
		}
	}

	public static void setLogWriter(java.io.PrintWriter out)
	{
		synchronized (logSync)
		{
			logWriter = out;
		}
	}

	//---------------------------------------------------------------

	public static synchronized WorkflowTemplate getWorkflowTemplate(
		Store caseSTORE,
		Store userSTORE,
		SchemaContext schemaCONTEX)
		throws WorkflowException
	{

		// Gets the classloader of the code that called this method, may 
		// be null.
		ClassLoader callerCL = FactoryManager.getCallerClassLoader();

		return (getWorkflowTemplate(caseSTORE, userSTORE, schemaCONTEX, callerCL));
	}

	public static synchronized WorkflowTemplateFactory getFactory(Store caseSTORE, Store userSTORE)
		throws WorkflowException
	{
		println("FactoryManager.getFactory(\"" + caseSTORE + "\",\"" + userSTORE + "\")");

		if (!initialized)
		{
			initialize();
		}

		// Gets the classloader of the code that called this method, may 
		// be null.
		ClassLoader callerCL = FactoryManager.getCallerClassLoader();

		// Walk through the loaded factories attempting to locate someone
		// who understands the given URL.
		for (int i = 0; i < factories.size(); i++)
		{
			FactoryInfo di = (FactoryInfo) factories.elementAt(i);
			// If the caller does not have permission to load the factory then 
			// skip it.
			if (getCallerClass(callerCL, di.factoryClassName) != di.factoryClass)
			{
				println("    skipping: " + di);
				continue;
			}
			try
			{
				println("    trying " + di);
				if (di.factory.acceptsStore(caseSTORE, userSTORE))
				{
					// Success!
					println("getFactory returning " + di);
					return (di.factory);
				}
			}
			catch (WorkflowException ex)
			{
				// Drop through and try the next factory.
			}
		}

		println("getFactory: no suitable factory");
		throw new WorkflowException("No suitable factory");
	}

	/**
	 * Registers the given factory with the <code>FactoryManager</code>.
	 * A newly-loaded factory class should call
	 * the method <code>registerFactory</code> to make itself
	 * known to the <code>FactoryManager</code>.
	 *
	 * @param factory the new Template Factory that is to be registered with the
	 *               <code>FactoryManager</code>
	 * @exception WorkflowException if a database access error occurs
	 */
	public static synchronized void registerFactory(WorkflowTemplateFactory factory) throws WorkflowException
	{
		if (!initialized)
		{
			initialize();
		}

		FactoryInfo di = new FactoryInfo();
		di.factory = factory;
		di.factoryClass = factory.getClass();
		di.factoryClassName = di.factoryClass.getName();
		factories.addElement(di);
		println("registerFactory: " + di);
	}

	/**
	 * Drops a factory from the <code>FactoryManager</code>'s list.  Applets can only
	 * deregister factories from their own classloaders.
	 *
	 * @param factory the Template Factory to drop 
	 * @exception WorkflowException if a database access error occurs
	 */
	public static synchronized void deregisterFactory(WorkflowTemplateFactory factory) throws WorkflowException
	{
		// Gets the classloader of the code that called this method, may 
		// be null.
		ClassLoader callerCL = FactoryManager.getCallerClassLoader();
		println("FactoryManager.deregisterFactory: " + factory);

		// Walk through the loaded factories.
		int i;
		FactoryInfo di = null;
		for (i = 0; i < factories.size(); i++)
		{
			di = (FactoryInfo) factories.elementAt(i);
			if (di.factory == factory)
			{
				break;
			}
		}
		// If we can't find the factory just return.
		if (i >= factories.size())
		{
			println("    couldn't find factory to unload");
			return;
		}

		// If the caller does not have permission to load the factory then 
		// throw a security exception.
		if (getCallerClass(callerCL, di.factoryClassName) != di.factoryClass)
		{
			throw new SecurityException();
		}

		// Remove the factory.  Other entries in factories get shuffled down.
		factories.removeElementAt(i);

	}

	/**
	 * Retrieves an Enumeration with all of the currently loaded Template factories
	 * to which the current caller has access.
	 *
	 * <P><B>Note:</B> The classname of a factory can be found using
	 * <CODE>d.getClass().getName()</CODE>
	 *
	 * @return the list of Template Factorys loaded by the caller's class loader
	 */
	public static synchronized java.util.Enumeration getFactories()
	{
		java.util.Vector result = new java.util.Vector();

		if (!initialized)
		{
			initialize();
		}

		// Gets the classloader of the code that called this method, may 
		// be null.
		ClassLoader callerCL = FactoryManager.getCallerClassLoader();

		// Walk through the loaded factories.
		for (int i = 0; i < factories.size(); i++)
		{
			FactoryInfo di = (FactoryInfo) factories.elementAt(i);
			// If the caller does not have permission to load the factory then 
			// skip it.
			if (getCallerClass(callerCL, di.factoryClassName) != di.factoryClass)
			{
				println("    skipping: " + di);
				continue;
			}
			result.addElement(di.factory);
		}

		return (result.elements());
	}

	/**
	 * Prints a message to the current Template log stream.
	 *
	 * @param message a log or tracing message
	 */
	public static void println(String message)
	{
		synchronized (logSync)
		{
			if (logWriter != null)
			{
				logWriter.println(message);

				// automatic flushing is never enabled, so we must do it ourselves
				logWriter.flush();
			}
		}
	}

	//------------------------------------------------------------------------

	// Returns the class object that would be created if the code calling the 
	// factory manager had loaded the factory class, or null if the class
	// is inaccessible.
	private static Class getCallerClass(ClassLoader callerClassLoader, String factoryClassName)
	{
		Class callerC = null;

		try
		{
			callerC = Class.forName(factoryClassName, true, callerClassLoader);
		}
		catch (Exception ex)
		{
			callerC = null; // being very careful 
		}

		return callerC;
	}

	private static void loadInitialFactories()
	{
		String factories;
		try
		{
			factories =
				(String) java.security.AccessController.doPrivileged(
					new sun.security.action.GetPropertyAction("template.factories"));
		}
		catch (Exception ex)
		{
			factories = null;
		}
		println("FactoryManager.initialize: template.factories = " + factories);
		if (factories == null)
		{
			return;
		}
		while (factories.length() != 0)
		{
			int x = factories.indexOf(':');
			String factory;
			if (x < 0)
			{
				factory = factories;
				factories = "";
			}
			else
			{
				factory = factories.substring(0, x);
				factories = factories.substring(x + 1);
			}
			if (factory.length() == 0)
			{
				continue;
			}
			try
			{
				println("FactoryManager.Initialize: loading " + factory);
				Class.forName(factory, true, ClassLoader.getSystemClassLoader());
			}
			catch (Exception ex)
			{
				println("FactoryManager.Initialize: load failed: " + ex);
			}
		}
	}

	//  Worker method called by the public getWorkflowTemplate() methods.
	private static synchronized WorkflowTemplate getWorkflowTemplate(
		Store caseSTORE,
		Store userSTORE,
		SchemaContext schemaCONTEX,
		ClassLoader callerCL)
		throws WorkflowException
	{
		//		if (url == null)
		//		{
		//			throw new WorkflowException("The url cannot be null");
		//		}
		//
		//		println("FactoryManager.getWorkflowTemplate(\"" + url + "\")");

		if (!initialized)
		{
			initialize();
		}

		// Walk through the loaded factories attempting to make a connection.
		// Remember the first exception that gets raised so we can reraise it.
		WorkflowException reason = null;
		for (int i = 0; i < factories.size(); i++)
		{
			FactoryInfo di = (FactoryInfo) factories.elementAt(i);

			// If the caller does not have permission to load the factory then 
			// skip it.
			if (getCallerClass(callerCL, di.factoryClassName) != di.factoryClass)
			{
				println("    skipping: " + di);
				continue;
			}
			try
			{
				println("    trying " + di);

				WorkflowTemplate result = di.factory.create(caseSTORE, userSTORE, schemaCONTEX);
				if (result != null)
				{
					// Success!
					println("getWorkflowTemplate returning " + di);
					return (result);
				}
			}
			catch (WorkflowException ex)
			{
				if (reason == null)
				{
					reason = ex;
				}
			}
		}

		// if we got here nobody could connect.
		if (reason != null)
		{
			println("getWorkflowTemplate failed: " + reason);
			throw reason;
		}

		println("getWorkflowTemplate: no suitable factory");

		throw new WorkflowException("No suitable factory");
	}

	// Class initialization.
	static void initialize()
	{
		if (initialized)
		{
			return;
		}
		initialized = true;
		loadInitialFactories();
		println("Template FactoryManager initialized");
	}

	// Prevent the FactoryManager class from being instantiated.
	private FactoryManager()
	{
	}

	// Returns the caller's class loader, or null if none
	private static ClassLoader getCallerClassLoader()
	{
		return Thread.currentThread().getContextClassLoader();
	}

	private static java.util.Vector factories = new java.util.Vector();
	private static java.io.PrintWriter logWriter = null;
	private static java.io.PrintStream logStream = null;
	private static boolean initialized = false;

	private static Object logSync = new Object();

}

// FactoryInfo is a package-private support class.
class FactoryInfo
{
	WorkflowTemplateFactory factory;
	Class factoryClass;
	String factoryClassName;

	public String toString()
	{
		return ("factory[className=" + factoryClassName + "," + factory + "]");
	}
}