/*
 * Created on 2004-6-1
 *
 * To change the template for this generated file go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */
package org.huihoo.willow.loader;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilePermission;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Policy;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.jar.Attributes.Name;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.huihoo.willow.Lifecycle;
import org.huihoo.willow.LifecycleException;
import org.huihoo.willow.LifecycleListener;
import org.huihoo.willow.bootstrap.*;
import org.huihoo.willow.util.JarFilenameFilter;
import org.huihoo.willow.util.StringManager;

import com.zosatapo.commons.util.compat.JdkCompat;

/**
 * @author Administrator
 *
 * To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */
public class WorkflowClassLoader extends URLClassLoader implements Reloader, Lifecycle
{

	private static Log log = LogFactory.getLog(WorkflowClassLoader.class);

	protected class PrivilegedFindResource implements PrivilegedAction
	{

		private File file;
		private String path;

		PrivilegedFindResource(File file, String path)
		{
			this.file = file;
			this.path = path;
		}

		public Object run()
		{
			return findResourceInternal(file, path);
		}

	}

	// ------------------------------------------------------- Static Variables
	/** 
	 * Jdk Compatibility Support.
	 */
	private static JdkCompat jdkCompat = JdkCompat.getJdkCompat();

	/**
	 * Set of package names which are not allowed to be loaded from a workflow
	 * class loader without delegating first.
	 */
	private static final String[] packageTriggers = { "javax", // Java extensions
		"java" // Java language
	};

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

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

	/**
	 * Construct a new ClassLoader with no defined repositories and no
	 * parent ClassLoader.
	 */
	public WorkflowClassLoader()
	{

		super(new URL[0]);
		this.parent = getParent();
		system = getSystemClassLoader();
		securityManager = System.getSecurityManager();

		if (securityManager != null)
		{
			refreshPolicy();
		}

	}

	/**
	 * Construct a new ClassLoader with no defined repositories and no
	 * parent ClassLoader.
	 */
	public WorkflowClassLoader(ClassLoader parent)
	{

		super(new URL[0], parent);

		this.parent = getParent();

		system = getSystemClassLoader();
		securityManager = System.getSecurityManager();

		if (securityManager != null)
		{
			refreshPolicy();
		}
	}

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

	/**
	 * The cache of ResourceEntry for classes and resources we have loaded,
	 * keyed by resource name.
	 */
	protected HashMap resourceEntries = new HashMap();

	/**
	 * The list of not found resources.
	 */
	protected HashMap notFoundResources = new HashMap();

	/**
	 * The debugging detail level of this component.
	 */
	protected int debug = 0;

	/**
	 * Should this class loader delegate to the parent class loader
	 * <strong>before</strong> searching its own repositories (i.e. the
	 * usual Java2 delegation model)?  If set to <code>false</code>,
	 * this class loader will search its own repositories first, and
	 * delegate to the parent only if the class or resource is not
	 * found locally.
	 */
	protected boolean delegate = false;

	/**
	 * Last time a JAR was accessed.
	 */
	protected long lastJarAccessed = 0L;

	/**
	 * The list of JARs last modified dates, in the order they should be
	 * searched for locally loaded classes or resources.
	 */
	protected long[] lastModifiedDates = new long[0];

	/**
	 * The list of local repositories, in the order they should be searched
	 * for locally loaded classes or resources.
	 * 
	 * only for jar
	 */
	protected String[] jarRepositories = new String[0];

	protected File[] jarRealFiles = new File[0];

	protected JarFile[] jarFiles = new JarFile[0];

	/**
	 * The list of local repositories, in the order they should be searched
	 * for locally loaded classes or resources.
	 * 
	 * only for directories
	 */
	protected String[] dirRepositories = new String[0];

	protected File[]   dirRealFiles = new File[0];

	/**
	 * The jars which will be monitored for modified.
	 * 
	 * Hashtable [monJarFolder]      
	 *           --------->Vector1 (jarName11,jarName12)
	 *           --------->Vector2 (jarName21,jarName22)
	 */
	protected Hashtable monJarCaches = new Hashtable();

	/**
	 * The path which will be monitored for added Jar files.
	 */
	protected String[] monJarPaths = new String[0];
	protected File[] monJarPathFiles = new File[0];

	/**
	 * A list of read File and Jndi Permission's required if this loader
	 * is for a web application context.
	 */
	private ArrayList permissionList = new ArrayList();

	/**
	 * Path where resources loaded from JARs will be extracted.
	 */
	private File loaderDir = null;

	/**
	 * The PermissionCollection for each CodeSource for a web
	 * application context.
	 */
	private HashMap loaderPC = new HashMap();

	/**
	 * Instance of the SecurityManager installed.
	 */
	private SecurityManager securityManager = null;

	/**
	 * The parent class loader.
	 */
	private ClassLoader parent = null;

	/**
	 * The system class loader.
	 */
	private ClassLoader system = null;

	/**
	 * Has this component been started?
	 */
	protected boolean started = false;

	/**
	 * Has external repositories.
	 */
	protected boolean hasExternalRepositories = false;

	/**
	 * All permission.
	 */
	private Permission allPermission = new java.security.AllPermission();

	// ------------------------------------------------------------- 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 "delegate first" flag for this class loader.
	 */
	public boolean getDelegate()
	{

		return (this.delegate);

	}

	/**
	 * Set the "delegate first" flag for this class loader.
	 *
	 * @param delegate The new "delegate first" flag
	 */
	public void setDelegate(boolean delegate)
	{

		this.delegate = delegate;

	}

	/**
	 * If there is a Java SecurityManager create a read FilePermission
	 * for the file directory path.
	 *
	 * @param path file directory path
	 */
	public void addPermission(String path)
	{
		if (path == null)
		{
			return;
		}

		if (securityManager != null)
		{
			Permission permission = null;

			if (!path.endsWith(File.separator))
			{
				permission = new FilePermission(path, "read");
				addPermission(permission);
				path = path + File.separator;
			}
			permission = new FilePermission(path + "-", "read");
			addPermission(permission);
		}
	}

	/**
	 * If there is a Java SecurityManager create a read FilePermission
	 * or JndiPermission for URL.
	 *
	 * @param url URL for a file or directory on local system
	 */
	public void addPermission(URL url)
	{
		if (url != null)
		{
			addPermission(url.toString());
		}
	}

	/**
	 * If there is a Java SecurityManager create a Permission.
	 *
	 * @param url URL for a file or directory on local system
	 */
	public void addPermission(Permission permission)
	{
		if ((securityManager != null) && (permission != null))
		{
			permissionList.add(permission);
		}
	}

	/**
	 * Return the JAR paths.
	 */
	public String[] getJarPaths()
	{
		return (String[]) this.monJarPaths.clone();
	}

	public String getJarPathsString()
	{
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < monJarPaths.length; i++)
		{
			sb.append(monJarPaths[i]).append(":");
		}
		return sb.toString();
	}

	/**
	 * Change the Jar folder.
	 */
	public void addJarPath(String jarPath, File jarPathFile)
	{
		for (int i = 0; i < monJarPaths.length; i++)
		{
			if (monJarPaths.equals(monJarPaths[i]))
				return;
		}

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

		File file_results[] = new File[monJarPathFiles.length + 1];
		for (int i = 0; i < monJarPathFiles.length; i++)
		{
			file_results[i] = monJarPathFiles[i];
		}
		file_results[monJarPathFiles.length] = jarPathFile;
		monJarPathFiles = file_results;
	}

	/**
	 * Change the work directory.
	 */
	public void setWorkDir(File workDir)
	{
		this.loaderDir = new File(workDir, "loader");
	}

	// ------------------------------------------------------- Reloader Methods

	/**
	 * Add a new repository to the set of places this ClassLoader can look for
	 * classes to be loaded.
	 *
	 * @param repository Name of a source of classes to be loaded, such as a
	 *  directory pathname, a JAR file pathname, or a ZIP file pathname
	 *
	 * @exception IllegalArgumentException if the specified repository is
	 *  invalid or does not exist
	 */
	public void addRepository(String repository)
	{

		// Ignore any of the standard repositories, as they are set up using
		// either addJar or addRepository
		if (repository.startsWith("/WEB-INF/lib") || repository.startsWith("/WEB-INF/classes"))
		{
			return;
		}

		// Add this repository to our underlying class loader
		try
		{
			URL url = new URL(repository);

			super.addURL(url);
			hasExternalRepositories = true;
		}
		catch (MalformedURLException e)
		{
			throw new IllegalArgumentException(e.toString());
		}

	}

	/**
	 * Add a new repository to the set of places this ClassLoader can look for
	 * classes to be loaded.
	 *
	 * @param repository Name of a source of classes to be loaded, such as a
	 *  directory pathname, a JAR file pathname, or a ZIP file pathname
	 *
	 * @exception IllegalArgumentException if the specified repository is
	 *  invalid or does not exist
	 */
	synchronized void addRepository(String repository, File file)
	{
		if (repository == null || file == null)
		{
			return;
		}
		
		if (!file.isDirectory())
		{
			addJar(repository, file);
			return;
		}

		if (log.isDebugEnabled())
		{
			log.debug("addRepository(" + repository + ")");
		}

		int i;

		// Add this repository to our internal list
		String[] result = new String[dirRepositories.length + 1];
		for (i = 0; i < dirRepositories.length; i++)
		{
			result[i] = dirRepositories[i];
		}
		result[dirRepositories.length] = repository;
		dirRepositories = result;

		// Add the file to the list
		File[] result2 = new File[dirRealFiles.length + 1];
		for (i = 0; i < dirRealFiles.length; i++)
		{
			result2[i] = dirRealFiles[i];
		}
		result2[dirRealFiles.length] = file;
		dirRealFiles = result2;

	}

	synchronized void addJar(String jarPath, File jarFile)
	{

		if (jarPath == null || jarFile == null)
		{
			return;
		}

		if (log.isDebugEnabled())
		{
			log.debug("addJar(" + jarPath + ")");
		}

		addMonitorJar(jarPath, jarFile);

		int i;

		String[] result = new String[jarRepositories.length + 1];
		for (i = 0; i < jarRepositories.length; i++)
		{
			result[i] = jarRepositories[i];
		}
		result[jarRepositories.length] = jarPath;
		jarRepositories = result;

		long[] result2 = new long[lastModifiedDates.length + 1];
		for (i = 0; i < lastModifiedDates.length; i++)
		{
			result2[i] = lastModifiedDates[i];
		}
		result2[lastModifiedDates.length] = jarFile.lastModified();
		lastModifiedDates = result2;

		JarFile[] result3 = new JarFile[jarFiles.length + 1];
		for (i = 0; i < jarFiles.length; i++)
		{
			result3[i] = jarFiles[i];
		}
		try
		{
			result3[jarFiles.length] = new JarFile(jarFile);
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		jarFiles = result3;
	}

	/**
	 * Return a String array of the current repositories for this class
	 * loader.  If there are no repositories, a zero-length array is
	 * returned.For security reason, returns a clone of the Array (since 
	 * String are immutable).
	 */
	public String[] findRepositories()
	{

		return ((String[]) dirRepositories.clone());

	}

	/**
	 * Have one or more classes or resources been modified so that a reload
	 * is appropriate?
	 */
	public boolean modified()
	{

		if (log.isDebugEnabled())
			log.debug("modified()");

		// Checking for modified loaded resources
		int length = jarRepositories.length;

		// A rare race condition can occur in the updates of the two arrays
		// It's totally ok if the latest class added is not checked (it will
		// be checked the next time
		int length2 = lastModifiedDates.length;
		if (length > length2)
		{
			length = length2;
		}

		for (int i = 0; i < length; i++)
		{
				long lastModified =jarRealFiles[i].lastModified();
				if (lastModified != lastModifiedDates[i])
				{
						log.debug(
							"  Resource '"
								+ jarRealFiles[i]
								+ "' was modified; Date is now: "
								+ new java.util.Date(lastModified)
								+ " Was: "
								+ new java.util.Date(lastModifiedDates[i]));
					return (true);
				}

		}
		
		int monJarPathSize=monJarPaths.length;
		
		int jarSize=0;
		int curJarSize=	0;
		int j=0;	
		
		for(int i=0;i<monJarPathSize;++i)
		{
			File  monJarDir=monJarPathFiles[i];			
			Vector monJarVector=(Vector)monJarCaches.get(monJarPaths[i]);
			File[] jarfiles=monJarDir.listFiles(new JarFilenameFilter());	
			
			jarSize=monJarVector.size();
			curJarSize=	jarfiles.length;
			while(j<jarSize && j<curJarSize)
			{
				if(jarfiles[j].getName().equals(monJarVector.elementAt(j)))
				{
					//Missing JAR
				  log.info("    Additional JARs have been added : '" 
									+ jarfiles[j].getName() + "'");
				  return (true);
				}
				
				++j;
			}
			
			if (j<curJarSize)
			{
				// There was more JARs
				log.info("    Additional JARs have been added");
				return (true);
				}
			}
			
			if (j<jarSize)
			{
				// There was less JARs
				log.info("    Additional JARs have been added");
				return (true);
			}

		// No classes have been modified
		return (false);
	}

	/**
	 * Render a String representation of this object.
	 */
	public String toString()
	{

		StringBuffer sb = new StringBuffer("WorkflowClassLoader\r\n");
		sb.append("  delegate: ");
		sb.append(delegate);
		sb.append("\r\n");
		sb.append("  repositories:\r\n");
		if (dirRepositories != null)
		{
			for (int i = 0; i < dirRepositories.length; i++)
			{
				sb.append("    ");
				sb.append(dirRepositories[i]);
				sb.append("\r\n");
			}
		}
		if (this.parent != null)
		{
			sb.append("----------> Parent Classloader:\r\n");
			sb.append(this.parent.toString());
			sb.append("\r\n");
		}
		return (sb.toString());

	}

	// ---------------------------------------------------- ClassLoader Methods

	/**
	 * Find the specified class in our local repositories, if possible.  If
	 * not found, throw <code>ClassNotFoundException</code>.
	 *
	 * @param name Name of the class to be loaded
	 *
	 * @exception ClassNotFoundException if the class was not found
	 */
	public Class findClass(String name) throws ClassNotFoundException
	{

		if (log.isDebugEnabled())
			log.debug("    findClass(" + name + ")");

		// (1) Permission to define this class when using a SecurityManager
		if (securityManager != null)
		{
			int i = name.lastIndexOf('.');
			if (i >= 0)
			{
				try
				{
					if (log.isTraceEnabled())
						log.trace("      securityManager.checkPackageDefinition");
					securityManager.checkPackageDefinition(name.substring(0, i));
				}
				catch (Exception se)
				{
					if (log.isTraceEnabled())
						log.trace("      -->Exception-->ClassNotFoundException", se);
					throw new ClassNotFoundException(name);
				}
			}
		}

		// Ask our superclass to locate this class, if possible
		// (throws ClassNotFoundException if it is not found)
		Class clazz = null;
		try
		{
			if (log.isTraceEnabled())
				log.trace("      findClassInternal(" + name + ")");
			try
			{
				clazz = findClassInternal(name);
			}
			catch (ClassNotFoundException cnfe)
			{
				if (!hasExternalRepositories)
				{
					throw cnfe;
				}
			}
			catch (AccessControlException ace)
			{
				ace.printStackTrace();
				throw new ClassNotFoundException(name);
			}
			catch (RuntimeException e)
			{
				if (log.isTraceEnabled())
					log.trace("      -->RuntimeException Rethrown", e);
				throw e;
			}
			if ((clazz == null) && hasExternalRepositories)
			{
				try
				{
					clazz = super.findClass(name);
				}
				catch (AccessControlException ace)
				{
					throw new ClassNotFoundException(name);
				}
				catch (RuntimeException e)
				{
					if (log.isTraceEnabled())
						log.trace("      -->RuntimeException Rethrown", e);
					throw e;
				}
			}
			if (clazz == null)
			{
				if (log.isDebugEnabled())
					log.debug("    --> Returning ClassNotFoundException");
				throw new ClassNotFoundException(name);
			}
		}
		catch (ClassNotFoundException e)
		{
			if (log.isTraceEnabled())
				log.trace("    --> Passing on ClassNotFoundException");
			throw e;
		}

		// Return the class we have located
		if (log.isTraceEnabled())
			log.debug("      Returning class " + clazz);
		if ((log.isTraceEnabled()) && (clazz != null))
			log.debug("      Loaded by " + clazz.getClassLoader());
		return (clazz);

	}

	/**
	 * Find the specified resource in our local repository, and return a
	 * <code>URL</code> refering to it, or <code>null</code> if this resource
	 * cannot be found.
	 *
	 * @param name Name of the resource to be found
	 */
	public URL findResource(final String name)
	{

		if (log.isDebugEnabled())
			log.debug("    findResource(" + name + ")");

		URL url = null;

		ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
		if (entry == null)
		{
			entry = findResourceInternal(name, name);
		}
		if (entry != null)
		{
			url = entry.source;
		}

		if ((url == null) && hasExternalRepositories)
		{
			url = super.findResource(name);
		}

		if (log.isDebugEnabled())
		{
			if (url != null)
				log.debug("    --> Returning '" + url.toString() + "'");
			else
				log.debug("    --> Resource not found, returning null");
		}
		return (url);

	}

	/**
	 * Return an enumeration of <code>URLs</code> representing all of the
	 * resources with the given name.  If no resources with this name are
	 * found, return an empty enumeration.
	 *
	 * @param name Name of the resources to be found
	 *
	 * @exception IOException if an input/output error occurs
	 */
	public Enumeration findResources(String name) throws IOException
	{

		if (log.isDebugEnabled())
		{
			log.debug("    findResources(" + name + ")");
		}

		Vector result = new Vector();

		int jarFilesLength = jarRealFiles.length;
		int repositoriesLength = dirRepositories.length;

		int i;

		// Looking at the repositories
		for (i = 0; i < repositoriesLength; i++)
		{
			File fullPathFile = new File(dirRealFiles[i], name);

			try
			{
				result.addElement(fullPathFile.toURL());
			}
			catch (MalformedURLException e)
			{
				// Ignore
			}
		}

		// Looking at the JAR files
		synchronized (jarRealFiles)
		{
			openJARs();

			for (i = 0; i < jarFilesLength; i++)
			{
				JarEntry jarEntry = jarFiles[i].getJarEntry(name);
				if (jarEntry != null)
				{
					try
					{
						String jarFakeUrl = getURI(jarRealFiles[i]).toString();
						jarFakeUrl = "jar:" + jarFakeUrl + "!/" + name;
						result.addElement(new URL(jarFakeUrl));
					}
					catch (MalformedURLException e)
					{
						// Ignore
					}
				}
			}
		}

		// Adding the results of a call to the superclass
		if (hasExternalRepositories)
		{
			Enumeration otherResourcePaths = super.findResources(name);

			while (otherResourcePaths.hasMoreElements())
			{
				result.addElement(otherResourcePaths.nextElement());
			}

		}

		return result.elements();

	}

	/**
	 * Find the resource with the given name.  A resource is some data
	 * (images, audio, text, etc.) that can be accessed by class code in a
	 * way that is independent of the location of the code.  The name of a
	 * resource is a "/"-separated path name that identifies the resource.
	 * If the resource cannot be found, return <code>null</code>.
	 * <p>
	 * This method searches according to the following algorithm, returning
	 * as soon as it finds the appropriate URL.  If the resource cannot be
	 * found, returns <code>null</code>.
	 * <ul>
	 * <li>If the <code>delegate</code> property is set to <code>true</code>,
	 *     call the <code>getResource()</code> method of the parent class
	 *     loader, if any.</li>
	 * <li>Call <code>findResource()</code> to find this resource in our
	 *     locally defined repositories.</li>
	 * <li>Call the <code>getResource()</code> method of the parent class
	 *     loader, if any.</li>
	 * </ul>
	 *
	 * @param name Name of the resource to return a URL for
	 */
	public URL getResource(String name)
	{

		if (log.isDebugEnabled())
		{
			log.debug("getResource(" + name + ")");
		}
		URL url = null;

		// (1) Delegate to parent if requested
		if (delegate)
		{
			if (log.isDebugEnabled())
			{
				log.debug("  Delegating to parent classloader " + parent);
			}
			ClassLoader loader = parent;
			if (loader == null)
			{
				loader = system;
			}
			url = loader.getResource(name);
			if (url != null)
			{
				if (log.isDebugEnabled())
				{
					log.debug("  --> Returning '" + url.toString() + "'");
				}
				return (url);
			}
		}

		// (2) Search local repositories
		url = findResource(name);
		if (url != null)
		{
			// Locating the repository for special handling in the case 
			// of a JAR
			ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
			try
			{
				String repository = entry.codeBase.toString();
				if ((repository.endsWith(".jar")) && (!(name.endsWith(".class"))))
				{
					// Copy binary content to the work directory if not present
					File resourceFile = new File(loaderDir, name);
					url = resourceFile.toURL();
				}
			}
			catch (Exception e)
			{
				// Ignore
			}
			if (log.isDebugEnabled())
				log.debug("  --> Returning '" + url.toString() + "'");
			return (url);
		}

		// (3) Delegate to parent unconditionally if not already attempted
		if (!delegate)
		{
			ClassLoader loader = parent;
			if (loader == null)
				loader = system;
			url = loader.getResource(name);
			if (url != null)
			{
				if (log.isDebugEnabled())
					log.debug("  --> Returning '" + url.toString() + "'");
				return (url);
			}
		}

		// (4) Resource was not found
		if (log.isDebugEnabled())
			log.debug("  --> Resource not found, returning null");
		return (null);

	}

	/**
	 * Find the resource with the given name, and return an input stream
	 * that can be used for reading it.  The search order is as described
	 * for <code>getResource()</code>, after checking to see if the resource
	 * data has been previously cached.  If the resource cannot be found,
	 * return <code>null</code>.
	 *
	 * @param name Name of the resource to return an input stream for
	 */
	public InputStream getResourceAsStream(String name)
	{

		if (log.isDebugEnabled())
			log.debug("getResourceAsStream(" + name + ")");
		InputStream stream = null;

		// (0) Check for a cached copy of this resource
		stream = findLoadedResource(name);
		if (stream != null)
		{
			if (log.isDebugEnabled())
				log.debug("  --> Returning stream from cache");
			return (stream);
		}

		// (1) Delegate to parent if requested
		if (delegate)
		{
			if (log.isDebugEnabled())
				log.debug("  Delegating to parent classloader " + parent);
			ClassLoader loader = parent;
			if (loader == null)
				loader = system;
			stream = loader.getResourceAsStream(name);
			if (stream != null)
			{
				// FIXME - cache???
				if (log.isDebugEnabled())
					log.debug("  --> Returning stream from parent");
				return (stream);
			}
		}

		// (2) Search local repositories
		if (log.isDebugEnabled())
			log.debug("  Searching local repositories");
		URL url = findResource(name);
		if (url != null)
		{
			// FIXME - cache???
			if (log.isDebugEnabled())
				log.debug("  --> Returning stream from local");
			stream = findLoadedResource(name);
			try
			{
				if (hasExternalRepositories && (stream == null))
					stream = url.openStream();
			}
			catch (IOException e)
			{
				; // Ignore
			}
			if (stream != null)
				return (stream);
		}

		// (3) Delegate to parent unconditionally
		if (!delegate)
		{
			if (log.isDebugEnabled())
				log.debug("  Delegating to parent classloader unconditionally " + parent);
			ClassLoader loader = parent;
			if (loader == null)
				loader = system;
			stream = loader.getResourceAsStream(name);
			if (stream != null)
			{
				// FIXME - cache???
				if (log.isDebugEnabled())
					log.debug("  --> Returning stream from parent");
				return (stream);
			}
		}

		// (4) Resource was not found
		if (log.isDebugEnabled())
			log.debug("  --> Resource not found, returning null");
		return (null);

	}

	/**
	 * Load the class with the specified name.  This method searches for
	 * classes in the same manner as <code>loadClass(String, boolean)</code>
	 * with <code>false</code> as the second argument.
	 *
	 * @param name Name of the class to be loaded
	 *
	 * @exception ClassNotFoundException if the class was not found
	 */
	public Class loadClass(String name) throws ClassNotFoundException
	{

		return (loadClass(name, false));

	}

	/**
	 * Load the class with the specified name, searching using the following
	 * algorithm until it finds and returns the class.  If the class cannot
	 * be found, returns <code>ClassNotFoundException</code>.
	 * <ul>
	 * <li>Call <code>findLoadedClass(String)</code> to check if the
	 *     class has already been loaded.  If it has, the same
	 *     <code>Class</code> object is returned.</li>
	 * <li>If the <code>delegate</code> property is set to <code>true</code>,
	 *     call the <code>loadClass()</code> method of the parent class
	 *     loader, if any.</li>
	 * <li>Call <code>findClass()</code> to find this class in our locally
	 *     defined repositories.</li>
	 * <li>Call the <code>loadClass()</code> method of our parent
	 *     class loader, if any.</li>
	 * </ul>
	 * If the class was found using the above steps, and the
	 * <code>resolve</code> flag is <code>true</code>, this method will then
	 * call <code>resolveClass(Class)</code> on the resulting Class object.
	 *
	 * @param name Name of the class to be loaded
	 * @param resolve If <code>true</code> then resolve the class
	 *
	 * @exception ClassNotFoundException if the class was not found
	 */
	public Class loadClass(String name, boolean resolve) throws ClassNotFoundException
	{

		if (log.isDebugEnabled())
			log.debug("loadClass(" + name + ", " + resolve + ")");
		Class clazz = null;

		// Don't load classes if class loader is stopped
		if (!started)
		{
			log.info(sm.getString("workflowClassLoader.stopped"));
			throw new ThreadDeath();
		}

		// (0) Check our previously loaded local class cache
		clazz = findLoadedClass0(name);
		if (clazz != null)
		{
			if (log.isDebugEnabled())
				log.debug("  Returning class from cache");
			if (resolve)
				resolveClass(clazz);
			return (clazz);
		}

		// (0.1) Check our previously loaded class cache
		clazz = findLoadedClass(name);
		if (clazz != null)
		{
			if (log.isDebugEnabled())
				log.debug("  Returning class from cache");
			if (resolve)
				resolveClass(clazz);
			return (clazz);
		}

		// (0.2) Try loading the class with the system class loader, to prevent
		//       the webapp from overriding J2SE classes
		try
		{
			clazz = system.loadClass(name);
			if (clazz != null)
			{
				if (resolve)
					resolveClass(clazz);
				return (clazz);
			}
		}
		catch (ClassNotFoundException e)
		{
			// Ignore
		}

		// (0.5) Permission to access this class when using a SecurityManager
		if (securityManager != null)
		{
			int i = name.lastIndexOf('.');
			if (i >= 0)
			{
				try
				{
					securityManager.checkPackageAccess(name.substring(0, i));
				}
				catch (SecurityException se)
				{
					String error = "Security Violation, attempt to use " + "Restricted Class: " + name;
					System.out.println(error);
					//se.printStackTrace();
					log.info(error, se);
					throw new ClassNotFoundException(error);
				}
			}
		}

		boolean delegateLoad = delegate || filter(name);

		// (1) Delegate to our parent if requested
		if (delegateLoad)
		{
			if (log.isDebugEnabled())
				log.debug("  Delegating to parent classloader1 " + parent);
			ClassLoader loader = parent;
			if (loader == null)
				loader = system;
			try
			{
				clazz = loader.loadClass(name);
				if (clazz != null)
				{
					if (log.isDebugEnabled())
						log.debug("  Loading class from parent");
					if (resolve)
						resolveClass(clazz);
					return (clazz);
				}
			}
			catch (ClassNotFoundException e)
			{
				;
			}
		}

		// (2) Search local repositories
		if (log.isDebugEnabled())
			log.debug("  Searching local repositories");
		try
		{
			clazz = findClass(name);
			if (clazz != null)
			{
				if (log.isDebugEnabled())
					log.debug("  Loading class from local repository");
				if (resolve)
					resolveClass(clazz);
				return (clazz);
			}
		}
		catch (ClassNotFoundException e)
		{
			;
		}

		// (3) Delegate to parent unconditionally
		if (!delegateLoad)
		{
			if (log.isDebugEnabled())
				log.debug("  Delegating to parent classloader at end: " + parent);
			ClassLoader loader = parent;
			if (loader == null)
				loader = system;
			try
			{
				clazz = loader.loadClass(name);
				if (clazz != null)
				{
					if (log.isDebugEnabled())
						log.debug("  Loading class from parent");
					if (resolve)
						resolveClass(clazz);
					return (clazz);
				}
			}
			catch (ClassNotFoundException e)
			{
				;
			}
		}

		throw new ClassNotFoundException(name);
	}

	/**
	 * Get the Permissions for a CodeSource.  If this instance
	 * of WorkflowClassLoader is for a web application context,
	 * add read FilePermission or JndiPermissions for the base
	 * directory (if unpacked),
	 * the context URL, and jar file resources.
	 *
	 * @param CodeSource where the code was loaded from
	 * @return PermissionCollection for CodeSource
	 */
	protected PermissionCollection getPermissions(CodeSource codeSource)
	{

		String codeUrl = codeSource.getLocation().toString();
		PermissionCollection pc;
		if ((pc = (PermissionCollection) loaderPC.get(codeUrl)) == null)
		{
			pc = super.getPermissions(codeSource);
			if (pc != null)
			{
				Iterator perms = permissionList.iterator();
				while (perms.hasNext())
				{
					Permission p = (Permission) perms.next();
					pc.add(p);
				}
				loaderPC.put(codeUrl, pc);
			}
		}
		return (pc);

	}

	/**
	 * Returns the search path of URLs for loading classes and resources.
	 * This includes the original list of URLs specified to the constructor,
	 * along with any URLs subsequently appended by the addURL() method.
	 * @return the search path of URLs for loading classes and resources.
	 */
	public URL[] getURLs()
	{

		URL[] external = super.getURLs();

		int filesLength = dirRealFiles.length;
		int jarFilesLength = jarRealFiles.length;
		int length = filesLength + jarFilesLength + external.length;
		int i;

		try
		{

			URL[] urls = new URL[length];
			for (i = 0; i < length; i++)
			{
				if (i < filesLength)
				{
					urls[i] = getURL(dirRealFiles[i]);
				}
				else if (i < filesLength + jarFilesLength)
				{
					urls[i] = getURL(jarRealFiles[i - filesLength]);
				}
				else
				{
					urls[i] = external[i - filesLength - jarFilesLength];
				}
			}

			return urls;

		}
		catch (MalformedURLException e)
		{
			return (new URL[0]);
		}

	}

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

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

	/**
	 * Get the lifecycle listeners associated with this lifecycle. If this 
	 * Lifecycle has no listeners registered, a zero-length array is returned.
	 */
	public LifecycleListener[] findLifecycleListeners()
	{
		return new LifecycleListener[0];
	}

	/**
	 * Remove a lifecycle event listener from this component.
	 *
	 * @param listener The listener to remove
	 */
	public void removeLifecycleListener(LifecycleListener listener)
	{
	}

	/**
	 * Start the class loader.
	 *
	 * @exception LifecycleException if a lifecycle error occurs
	 */
	public void start() throws LifecycleException
	{

		started = true;

	}

	/**
	 * Stop the class loader.
	 *
	 * @exception LifecycleException if a lifecycle error occurs
	 */
	public void stop() throws LifecycleException
	{

		started = false;

		int length = dirRealFiles.length;
		for (int i = 0; i < length; i++)
		{
			dirRealFiles[i] = null;
		}

		length = jarRealFiles.length;
		for (int i = 0; i < length; i++)
		{
			try
			{
				if (jarFiles[i] != null)
				{
					jarFiles[i].close();
				}
			}
			catch (IOException e)
			{
				// Ignore
			}
			jarRealFiles[i] = null;
			jarFiles[i] = null;
		}

		notFoundResources.clear();
		resourceEntries.clear();

		dirRepositories = null;
		dirRealFiles = null;

		jarRealFiles = null;
		jarFiles = null;
		jarRepositories = null;

		monJarPaths = null;
		lastModifiedDates = null;
		monJarPathFiles = null;
		hasExternalRepositories = false;
		parent = null;

		permissionList.clear();
		loaderPC.clear();

		if (loaderDir != null)
		{
			deleteDir(loaderDir);
		}

		LogFactory.release(this);

	}

	/**
	 * Used to periodically signal to the classloader to release 
	 * JAR resources.
	 */
	public void closeJARs(boolean force)
	{
		if (jarRealFiles.length > 0)
		{
			try
			{
				synchronized (jarRealFiles)
				{
					if (force || (System.currentTimeMillis() > (lastJarAccessed + 90000)))
					{
						for (int i = 0; i < jarRealFiles.length; i++)
						{
							if (jarFiles[i] != null)
							{
								jarFiles[i].close();
								jarFiles[i] = null;
								jarRealFiles[i] = null;
							}
						}
					}
				}
			}
			catch (IOException e)
			{
				log("Failed to close JAR", e);
			}
		}
	}

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

	/**
	 * Used to periodically signal to the classloader to release JAR resources.
	 */
	protected void openJARs()
	{
		if (started && (jarRealFiles.length > 0))
		{
			lastJarAccessed = System.currentTimeMillis();
			if (jarRealFiles[0] == null)
			{
				try
				{
					for (int i = 0; i < jarRealFiles.length; i++)
					{
						jarFiles[i] = new JarFile(jarRealFiles[i]);
					}
				}
				catch (IOException e)
				{
					log("Failed to open JAR", e);
				}
			}
		}
	}

	/**
	 * Find specified class in local repositories.
	 *
	 * @return the loaded class, or null if the class isn't found
	 */
	protected Class findClassInternal(String name) throws ClassNotFoundException
	{

		if (!validate(name))
		{
			throw new ClassNotFoundException(name);
		}

		String tempPath = name.replace('.', '/');
		String classPath = tempPath + ".class";

		ResourceEntry entry = null;

		entry = findResourceInternal(name, classPath);

		if ((entry == null) || (entry.binaryContent == null))
			throw new ClassNotFoundException(name);

		Class clazz = entry.loadedClass;
		if (clazz != null)
		{
			return clazz;
		}

		// Looking up the package
		String packageName = null;
		int pos = name.lastIndexOf('.');
		if (pos != -1)
			packageName = name.substring(0, pos);

		Package pkg = null;

		if (packageName != null)
		{

			pkg = getPackage(packageName);

			// Define the package (if null)
			if (pkg == null)
			{
				if (entry.manifest == null)
				{
					definePackage(packageName, null, null, null, null, null, null, null);
				}
				else
				{
					definePackage(packageName, entry.manifest, entry.codeBase);
				}
			}

		}

		// Create the code source object
		CodeSource codeSource = new CodeSource(entry.codeBase, entry.certificates);

		if (securityManager != null)
		{

			// Checking sealing
			if (pkg != null)
			{
				boolean sealCheck = true;
				if (pkg.isSealed())
				{
					sealCheck = pkg.isSealed(entry.codeBase);
				}
				else
				{
					sealCheck = (entry.manifest == null) || !isPackageSealed(packageName, entry.manifest);
				}
				if (!sealCheck)
					throw new SecurityException(
						"Sealing violation loading " + name + " : Package " + packageName + " is sealed.");
			}

		}

		if (entry.loadedClass == null)
		{
			synchronized (this)
			{
				if (entry.loadedClass == null)
				{
					clazz = defineClass(name, entry.binaryContent, 0, entry.binaryContent.length, codeSource);
					entry.loadedClass = clazz;
					entry.binaryContent = null;
					entry.source = null;
					entry.codeBase = null;
					entry.manifest = null;
					entry.certificates = null;
				}
				else
				{
					clazz = entry.loadedClass;
				}
			}
		}
		else
		{
			clazz = entry.loadedClass;
		}

		return clazz;

	}

	/**
	 * Find specified resource in local repositories. This block
	 * will execute under an AccessControl.doPrivilege block.
	 *
	 * @return the loaded resource, or null if the resource isn't found
	 */
	private ResourceEntry findResourceInternal(File file, String path)
	{
		ResourceEntry entry = new ResourceEntry();
		try
		{
			entry.source = getURI(new File(file, path));
			entry.codeBase = getURL(new File(file, path));
		}
		catch (MalformedURLException e)
		{
			return null;
		}
		return entry;
	}

	/**
	 * Find specified resource in local repositories.
	 *
	 * @return the loaded resource, or null if the resource isn't found
	 */
	protected ResourceEntry findResourceInternal(String name, String path)
	{

		if (!started)
		{
			log.info(sm.getString("workflowClassLoader.stopped"));
			return null;
		}

		if ((name == null) || (path == null))
		{
			return null;
		}

		ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
		if (entry != null)
		{
			return entry;
		}

		int contentLength = -1;
		InputStream binaryStream = null;

		int jarFilesLength = jarRealFiles.length;
		int repositoriesLength = dirRepositories.length;

		int i;

		for (i = 0;(entry == null) && (i < repositoriesLength); i++)
		{

			File fullPathFile = new File(dirRealFiles[i], path);

			// Note : Not getting an exception here means the resource was
			// found
			if (securityManager != null)
			{
				PrivilegedAction dp = new PrivilegedFindResource(dirRealFiles[i], path);
				entry = (ResourceEntry) AccessController.doPrivileged(dp);
			}
			else
			{
				entry = findResourceInternal(dirRealFiles[i], path);
			}
			
			entry.lastModified = fullPathFile.lastModified();
			
			if (fullPathFile.exists())
			{
				try
				{					
					entry.codeBase = dirRealFiles[i].toURL();
					entry.source=fullPathFile.toURL();
					binaryStream = fullPathFile.toURL().openStream();
					contentLength = binaryStream.available();
				}
				catch (Exception e)
				{
					return null;
				}
			}
		}

		if ((entry == null) && (notFoundResources.containsKey(name)))
		{
			return null;
		}

		JarEntry jarEntry = null;

		synchronized (jarRealFiles)
		{

			openJARs();
			for (i = 0;(entry == null) && (i < jarFilesLength); i++)
			{

				jarEntry = jarFiles[i].getJarEntry(path);

				if (jarEntry != null)
				{

					entry = new ResourceEntry();
					try
					{
						entry.codeBase = jarRealFiles[i].toURL();
						String jarFakeUrl = getURI(jarRealFiles[i]).toString();
						jarFakeUrl = "jar:" + jarFakeUrl + "!/" + path;
						entry.source = new URL(jarFakeUrl);
						entry.lastModified = jarRealFiles[i].lastModified();
					}
					catch (MalformedURLException e)
					{
						return null;
					}
					
					contentLength = (int) jarEntry.getSize();

					try
					{
						entry.manifest = jarFiles[i].getManifest();
						binaryStream = jarFiles[i].getInputStream(jarEntry);
					}
					catch (IOException e)
					{
						return null;
					}

					// Extract resources contained in JAR to the workdir
					if (!(path.endsWith(".class")))
					{
						byte[] buf = new byte[1024];
						File resourceFile = new File(loaderDir, jarEntry.getName());
						if (!resourceFile.exists())
						{
							Enumeration entries = jarFiles[i].entries();
							while (entries.hasMoreElements())
							{
								JarEntry jarEntry2 = (JarEntry) entries.nextElement();
								if (!(jarEntry2.isDirectory()) && (!jarEntry2.getName().endsWith(".class")))
								{
									resourceFile = new File(loaderDir, jarEntry2.getName());
									resourceFile.getParentFile().mkdirs();
									FileOutputStream os = null;
									InputStream is = null;
									try
									{
										is = jarFiles[i].getInputStream(jarEntry2);
										os = new FileOutputStream(resourceFile);
										while (true)
										{
											int n = is.read(buf);
											if (n <= 0){break;}
											os.write(buf, 0, n);
										}
									}
									catch (IOException e)
									{
										// Ignore
									}
									finally
									{
										try{if (is != null){is.close();}}
										catch (IOException e){}
										try{if (os != null){os.close();}}
										catch (IOException e){}
									}
								}
							}
						}
					}
				}
			}

			if (entry == null)
			{
				synchronized (notFoundResources)
				{
					notFoundResources.put(name, name);
				}
				return null;
			}

			if (binaryStream != null)
			{

				byte[] binaryContent = new byte[contentLength];

				try
				{
					int pos = 0;

					while (true)
					{
						int n = binaryStream.read(binaryContent, pos, binaryContent.length - pos);
						if (n <= 0)
							break;
						pos += n;
					}
					binaryStream.close();
				}
				catch (IOException e)
				{
					e.printStackTrace();
					return null;
				}
				catch (Exception e)
				{
					e.printStackTrace();
					return null;
				}

				entry.binaryContent = binaryContent;

				// The certificates are only available after the JarEntry 
				// associated input stream has been fully read
				if (jarEntry != null)
				{
					entry.certificates = jarEntry.getCertificates();
				}

			}

		}

		// Add the entry in the local resource repository
		synchronized (resourceEntries)
		{
			// Ensures that all the threads which may be in a race to load
			// a particular class all end up with the same ResourceEntry
			// instance
			ResourceEntry entry2 = (ResourceEntry) resourceEntries.get(name);
			if (entry2 == null)
			{
				resourceEntries.put(name, entry);
			}
			else
			{
				entry = entry2;
			}
		}

		return entry;

	}

	/**
	 * Returns true if the specified package name is sealed according to the
	 * given manifest.
	 */
	protected boolean isPackageSealed(String name, Manifest man)
	{

		String path = name + "/";
		Attributes attr = man.getAttributes(path);
		String sealed = null;
		if (attr != null)
		{
			sealed = attr.getValue(Name.SEALED);
		}
		if (sealed == null)
		{
			if ((attr = man.getMainAttributes()) != null)
			{
				sealed = attr.getValue(Name.SEALED);
			}
		}
		return "true".equalsIgnoreCase(sealed);

	}

	/**
	 * Finds the resource with the given name if it has previously been
	 * loaded and cached by this class loader, and return an input stream
	 * to the resource data.  If this resource has not been cached, return
	 * <code>null</code>.
	 *
	 * @param name Name of the resource to return
	 */
	protected InputStream findLoadedResource(String name)
	{

		ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
		if (entry != null)
		{
			if (entry.binaryContent != null)
				return new ByteArrayInputStream(entry.binaryContent);
		}
		return (null);

	}

	/**
	 * Finds the class with the given name if it has previously been
	 * loaded and cached by this class loader, and return the Class object.
	 * If this class has not been cached, return <code>null</code>.
	 *
	 * @param name Name of the resource to return
	 */
	protected Class findLoadedClass0(String name)
	{

		ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
		if (entry != null)
		{
			return entry.loadedClass;
		}
		return (null); // FIXME - findLoadedResource()

	}

	/**
	 * Refresh the system policy file, to pick up eventual changes.
	 */
	protected void refreshPolicy()
	{

		try
		{
			// The policy file may have been modified to adjust 
			// permissions, so we're reloading it when loading or 
			// reloading a Container
			Policy policy = Policy.getPolicy();
			policy.refresh();
		}
		catch (AccessControlException e)
		{
			// Some policy files may restrict this, even for the core,
			// so this exception is ignored
		}

	}

	/**
	 * Filter classes.
	 * 
	 * @param name class name
	 * @return true if the class should be filtered
	 */
	protected boolean filter(String name)
	{

		if (name == null)
			return false;

		// Looking up the package
		String packageName = null;
		int pos = name.lastIndexOf('.');
		if (pos != -1)
			packageName = name.substring(0, pos);
		else
			return false;

		for (int i = 0; i < packageTriggers.length; i++)
		{
			if (packageName.startsWith(packageTriggers[i]))
				return true;
		}

		return false;

	}

	/**
	 * Validate a classname. As per SRV.9.7.2, we must restict loading of 
	 * classes from J2SE (java.*) and classes of the servlet API 
	 * (javax.servlet.*). That should enhance robustness and prevent a number
	 * of user error (where an older version of servlet.jar would be present
	 * in /WEB-INF/lib).
	 * 
	 * @param name class name
	 * @return true if the name is valid
	 */
	protected boolean validate(String name)
	{

		if (name == null)
			return false;
		if (name.startsWith("java."))
			return false;

		return true;

	}

	/**
	 * Get URL.
	 */
	protected URL getURL(File file) throws MalformedURLException
	{

		File realFile = file;
		try
		{
			realFile = realFile.getCanonicalFile();
		}
		catch (IOException e)
		{
			// Ignore
		}
		return realFile.toURL();

	}

	/**
	 * Get URL.
	 */
	protected URL getURI(File file) throws MalformedURLException
	{

		return jdkCompat.getURI(file);

	}

	/**
	 * Delete the specified directory, including all of its contents and
	 * subdirectories recursively.
	 *
	 * @param dir File object representing the directory to be deleted
	 */
	protected static 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();

	}

	/**
	 * Log a debugging output message.
	 *
	 * @param message Message to be logged
	 */
	private void log(String message)
	{

		System.out.println("WorkflowClassLoader: " + message);

	}

	/**
	 * Log a debugging output message with an exception.
	 *
	 * @param message Message to be logged
	 * @param throwable Exception to be logged
	 */
	private void log(String message, Throwable throwable)
	{

		System.out.println("WorkflowClassLoader: " + message);
		throwable.printStackTrace(System.out);

	}

	private void addMonitorJar(String jarPath, File jarFile)
	{
		for (int i = 0; i < monJarPaths.length; ++i)
		{
			if (jarPath.startsWith(monJarPaths[i]))
			{
				Vector cache = (Vector) monJarCaches.get(monJarPaths);
				if (cache == null)
				{
					cache = new Vector();
					monJarCaches.put(monJarPaths[i], cache);
				}

				String jarName = jarPath.substring(monJarPaths[i].length());
				while (jarName.startsWith("/"))
				{
					//just single file name
					jarName = jarName.substring(1);
				}

				File[] result = new File[monJarPathFiles.length + 1];
				for (i = 0; i < monJarPathFiles.length; i++)
				{
					result[i] = monJarPathFiles[i];
				}
				result[monJarPathFiles.length] = jarFile;
				monJarPathFiles = result;

				break;
			}
		}
	}
}
