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

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilePermission;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.JarFile;

import javax.naming.Binding;
import javax.naming.NameClassPair;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.huihoo.willow.Container;
import org.huihoo.willow.Context;
import org.huihoo.willow.Globals;
import org.huihoo.willow.Lifecycle;
import org.huihoo.willow.LifecycleException;
import org.huihoo.willow.LifecycleListener;
import org.huihoo.willow.Loader;
import org.huihoo.willow.Logger;
import org.huihoo.willow.core.StandardContext;
import org.huihoo.willow.naming.resources.DirContextURLStreamHandler;
import org.huihoo.willow.naming.resources.Resource;
import org.huihoo.willow.util.LifecycleSupport;
import org.huihoo.willow.util.StringManager;
import org.huihoo.workflow.server.WorkflowContext;

public class WorkflowLoader implements Lifecycle, Loader, PropertyChangeListener
{
	private static Log log = LogFactory.getLog(WorkflowLoader.class);

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

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

	/**
	 * Construct a new ApplicationLoader with no defined parent class loader
	 * (so that the actual parent will be the system class loader).
	 */
	public WorkflowLoader()
	{

		this(null);

	}

	/**
	 * Construct a new ApplicationLoader with the specified class loader
	 * to be defined as the parent of the ClassLoader we ultimately create.
	 *
	 * @param parent The parent class loader
	 */
	public WorkflowLoader(ClassLoader parent)
	{
		super();
		this.parentClassLoader = parent;
	}

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

	/**
	 * First load of the class.
	 */
	private static boolean first = true;

	/**
	 * The class loader being managed by this Loader component.
	 */
	private WorkflowClassLoader classLoader = null;

	/**
	 * The Container with which this Loader has been associated.
	 */
	private Container container = null;

	/**
	 * The debugging detail level for this component.
	 */
	private int debug = 0;

	/**
	 * The "follow standard delegation template" flag that will be used to
	 * configure our ClassLoader.
	 */
	private boolean delegate = false;

	/**
	 * The descriptive information about this Loader implementation.
	 */
	private static final String info = "org.huihoo.willow.loader.WorkflowLoader/1.0";

	/**
	 * The lifecycle event support for this component.
	 */
	protected LifecycleSupport lifecycle = new LifecycleSupport(this);

	/**
	 * The Java class name of the ClassLoader implementation to be used.
	 * This class should extend WorkflowAppClassLoader, otherwise, a different 
	 * loader implementation must be used.
	 */
	private String loaderClass = "org.huihoo.willow.loader.WorkflowClassLoader";

	/**
	 * The parent class loader of the class loader we will create.
	 */
	private ClassLoader parentClassLoader = null;

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

	/**
	 * The set of repositories associated with this class loader.
	 */
	private String repositories[] = new String[0];

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

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

	/**
	 * Classpath set in the loader.
	 */
	private String classpath = null;

	// ------------------------------------------------------------- Properties

	/**
	 * Return the Java class loader to be used by this Container.
	 */
	public ClassLoader getClassLoader()
	{

		return ((ClassLoader) classLoader);

	}

	/**
	 * Return the Container with which this Logger has been associated.
	 */
	public Container getContainer()
	{

		return (container);

	}

	/**
	 * Set the Container with which this Logger has been associated.
	 *
	 * @param container The associated Container
	 */
	public void setContainer(Container container)
	{

		// Deregister from the old Container (if any)
		if ((this.container != null) && (this.container instanceof Context))
		{
			((Context) this.container).removePropertyChangeListener(this);
		}

		// Process this property change
		Container oldContainer = this.container;
		this.container = container;
		support.firePropertyChange("container", oldContainer, this.container);

		// Register with the new Container (if any)
		if ((this.container != null) && (this.container instanceof Context))
		{
			setReloadable(((Context) this.container).getReloadable());
			((Context) this.container).addPropertyChangeListener(this);
		}

	}

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

		int oldDebug = this.debug;
		this.debug = debug;
		support.firePropertyChange("debug", new Integer(oldDebug), new Integer(this.debug));

	}

	/**
	 * Return the "follow standard delegation template" flag used to configure
	 * our ClassLoader.
	 */
	public boolean getDelegate()
	{

		return (this.delegate);

	}

	/**
	 * Set the "follow standard delegation template" flag used to configure
	 * our ClassLoader.
	 *
	 * @param delegate The new flag
	 */
	public void setDelegate(boolean delegate)
	{

		boolean oldDelegate = this.delegate;
		this.delegate = delegate;
		support.firePropertyChange("delegate", new Boolean(oldDelegate), new Boolean(this.delegate));

	}

	/**
	 * Return descriptive information about this Loader implementation and
	 * the corresponding version number, in the format
	 * <code>&lt;description&gt;/&lt;version&gt;</code>.
	 */
	public String getInfo()
	{

		return (info);

	}

	/**
	 * Return the ClassLoader class name.
	 */
	public String getLoaderClass()
	{

		return (this.loaderClass);

	}

	/**
	 * Set the ClassLoader class name.
	 *
	 * @param loaderClass The new ClassLoader class name
	 */
	public void setLoaderClass(String loaderClass)
	{

		this.loaderClass = loaderClass;

	}

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

		return (this.reloadable);

	}

	/**
	 * Set the reloadable flag for this Loader.
	 *
	 * @param reloadable The new reloadable flag
	 */
	public void setReloadable(boolean reloadable)
	{

		// Process this property change
		boolean oldReloadable = this.reloadable;
		this.reloadable = reloadable;
		support.firePropertyChange("reloadable", new Boolean(oldReloadable), new Boolean(this.reloadable));

	}

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

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

	/**
	 * Add a new repository to the set of repositories for this class loader.
	 *
	 * @param repository Repository to be added
	 */
	public void addRepository(String repository)
	{

		log.debug(sm.getString("applicationLoader.addRepository", repository));

		for (int i = 0; i < repositories.length; i++)
		{
			if (repository.equals(repositories[i]))
			{
				return;
			}
		}
		String results[] = new String[repositories.length + 1];
		for (int i = 0; i < repositories.length; i++)
		{
			results[i] = repositories[i];
		}
		results[repositories.length] = repository;
		repositories = results;

		if (started && (classLoader != null))
		{
			classLoader.addRepository(repository);
			setClassPath();
		}

	}

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

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

	}

	public String[] getRepositories()
	{
		return ((String[]) repositories.clone());
	}

	/** Extra repositories for this loader
	 */
	public String getRepositoriesString()
	{
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < repositories.length; i++)
		{
			sb.append(repositories[i]).append(":");
		}
		return sb.toString();
	}

	/** 
	 * Classpath, as set in org.apache.catalina.jsp_classpath context
	 * property
	 *
	 * @return The classpath
	 */
	public String getClasspath()
	{
		return classpath;
	}

	/**
	 * Has the internal repository associated with this Loader been modified,
	 * such that the loaded classes should be reloaded?
	 */
	public boolean modified()
	{

		return (classLoader.modified());

	}

	/**
	 * Used to periodically signal to the classloader to release JAR resources.
	 */
	public void closeJARs(boolean force)
	{
		if (classLoader != null)
		{
			classLoader.closeJARs(force);
		}
	}

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

		support.removePropertyChangeListener(listener);

	}

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

		StringBuffer sb = new StringBuffer("ApplicationLoader[");
		if (container != null)
		{
			sb.append(container.getName());
		}
		sb.append("]");
		return (sb.toString());

	}

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

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

		lifecycle.addLifecycleListener(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 lifecycle.findLifecycleListeners();

	}

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

		lifecycle.removeLifecycleListener(listener);

	}

	/**
	 * Start this component, initializing our associated class loader.
	 *
	 * @exception LifecycleException if a lifecycle error occurs
	 */
	public void start() throws LifecycleException
	{

		if (started)
		{
			throw new LifecycleException(sm.getString("applicationLoader.alreadyStarted"));
		}
		log.debug(sm.getString("applicationLoader.starting"));
		lifecycle.fireLifecycleEvent(START_EVENT, null);
		started = true;

		if (container.getResources() == null)
		{
			log.info("No resources for " + container);
			return;
		}

//		// Register a stream handler factory for the JNDI protocol
//		URLStreamHandlerFactory streamHandlerFactory = new DirContextURLStreamHandlerFactory();
//		if (first)
//		{
//			first = false;
//			try
//			{
//				URL.setURLStreamHandlerFactory(streamHandlerFactory);
//			}
//			catch (Exception e)
//			{
//				// Log and continue anyway, this is not critical
//				log.error("Error registering jndi stream handler", e);
//			}
//			catch (Throwable t)
//			{
//				// This is likely a dual registration
//				log.info("Dual registration of jndi stream handler: " + t.getMessage());
//			}
//		}

		// Construct a class loader based on our current repositories list
		try
		{

			classLoader = createClassLoader();
			classLoader.setResources(container.getResources());
			classLoader.setDebug(this.debug);
			classLoader.setDelegate(this.delegate);

			for (int i = 0; i < repositories.length; i++)
			{
				classLoader.addRepository(repositories[i]);
			}

			// Configure our repositories
			setRepositories();
			setClassPath();

			setPermissions();

			if (classLoader instanceof Lifecycle)
				 ((Lifecycle) classLoader).start();

			// Binding the Webapp class loader to the directory context
			DirContextURLStreamHandler.bind((ClassLoader) classLoader, this.container.getResources());

		}
		catch (Throwable t)
		{
			log.error("LifecycleException ", t);
			throw new LifecycleException("start: ", t);
		}

	}

	/**
	 * Stop this component, finalizing our associated class loader.
	 *
	 * @exception LifecycleException if a lifecycle error occurs
	 */
	public void stop() throws LifecycleException
	{

		// Validate and update our current component state
		if (!started)
			throw new LifecycleException(sm.getString("applicationLoader.notStarted"));
		if (log.isDebugEnabled())
			log.debug(sm.getString("applicationLoader.stopping"));
		lifecycle.fireLifecycleEvent(STOP_EVENT, null);
		started = false;

		// Remove context attributes as appropriate
		if (container instanceof Context)
		{
			WorkflowContext templateContext = ((Context) container).getWorkflowContext();
			templateContext.removeAttribute(Globals.ATTR_CLASS_PATH);
		}

		// Throw away our current class loader
		if (classLoader instanceof Lifecycle)
		{
			((Lifecycle) classLoader).stop();
		}
		classLoader = null;
	}

	// ----------------------------------------- PropertyChangeListener Methods

	/**
	 * Process property change events from our associated Context.
	 *
	 * @param event The property change event that has occurred
	 */
	public void propertyChange(PropertyChangeEvent event)
	{

		// Validate the source of this event
		if (!(event.getSource() instanceof Context))
		{
			return;
		}
		Context context = (Context) event.getSource();

		// Process a relevant property change
		if (event.getPropertyName().equals("reloadable"))
		{
			try
			{
				setReloadable(((Boolean) event.getNewValue()).booleanValue());
			}
			catch (NumberFormatException e)
			{
				log.error(sm.getString("applicationLoader.reloadable", event.getNewValue().toString()));
			}
		}

	}

	// ------------------------------------------------------- Private Methods

	/**
	 * Create associated classLoader.
	 */
	private WorkflowClassLoader createClassLoader() throws Exception
	{

		Class clazz = Class.forName(loaderClass);
		WorkflowClassLoader classLoader = null;

		if (parentClassLoader == null)
		{
			parentClassLoader = Thread.currentThread().getContextClassLoader();
		}
		Class[] argTypes = { ClassLoader.class };
		Object[] args = { parentClassLoader };
		Constructor constr = clazz.getConstructor(argTypes);
		classLoader = (WorkflowClassLoader) constr.newInstance(args);

		return classLoader;

	}

	/**
	 * Log a message on the Logger associated with our Container (if any)
	 *
	 * @param message Message to be logged
	 */
	private void log(String message)
	{

		Logger logger = null;
		if (container != null)
		{
			logger = container.getLogger();
		}
		if (logger != null)
		{
			logger.log("ApplicationLoader[" + container.getName() + "]: " + message);
		}
		else
		{
			String containerName = null;
			if (container != null)
			{
				containerName = container.getName();
			}
			System.out.println("ApplicationLoader[" + containerName + "]: " + message);
		}

	}

	/**
	 * Log a message on the Logger associated with our Container (if any)
	 *
	 * @param message Message to be logged
	 * @param throwable Associated exception
	 */
	private void log(String message, Throwable throwable)
	{

		Logger logger = null;
		if (container != null)
		{
			logger = container.getLogger();
		}
		if (logger != null)
		{
			logger.log("ApplicationLoader[" + container.getName() + "] " + message, throwable);
		}
		else
		{
			String containerName = null;
			if (container != null)
			{
				containerName = container.getName();
			}
			System.out.println("ApplicationLoader[" + containerName + "]: " + message);
			System.out.println("" + throwable);
			throwable.printStackTrace(System.out);
		}

	}

	/**
	 * Configure associated class loader permissions.
	 */
	private void setPermissions()
	{

		if (System.getSecurityManager() == null)
		{
			return;
		}

		if (!(container instanceof Context))
		{
			return;
		}

		// Tell the class loader the root of the context
		WorkflowContext templateContext = ((Context) container).getWorkflowContext();

		// Assigning permissions for the work directory
		File workDir = (File) templateContext.getAttribute(Globals.ATTR_WORK_DIR);
		if (workDir != null)
		{
			try
			{
				String workDirPath = workDir.getCanonicalPath();
				classLoader.addPermission(new FilePermission(workDirPath, "read,write"));
				classLoader.addPermission(new FilePermission(workDirPath + File.separator + "-", "read,write,delete"));
			}
			catch (IOException e)
			{
				// Ignore
			}
		}

		try
		{

			URL rootURL = templateContext.getResource("/");
			classLoader.addPermission(rootURL);

			String contextRoot = templateContext.getRealPath("/");
			if (contextRoot != null)
			{
				try
				{
					contextRoot = (new File(contextRoot)).getCanonicalPath();
					classLoader.addPermission(contextRoot);
				}
				catch (IOException e)
				{
					// Ignore
				}
			}

			URL classesURL = templateContext.getResource("/WEB-INF/classes/");
			classLoader.addPermission(classesURL);
			URL libURL = templateContext.getResource("/WEB-INF/lib/");
			classLoader.addPermission(libURL);

			if (contextRoot != null)
			{

				if (libURL != null)
				{
					File rootDir = new File(contextRoot);
					File libDir = new File(rootDir, "WEB-INF/lib/");
					try
					{
						String path = libDir.getCanonicalPath();
						classLoader.addPermission(path);
					}
					catch (IOException e)
					{
					}
				}

			}
			else
			{

				if (workDir != null)
				{
					if (libURL != null)
					{
						File libDir = new File(workDir, "WEB-INF/lib/");
						try
						{
							String path = libDir.getCanonicalPath();
							classLoader.addPermission(path);
						}
						catch (IOException e)
						{
						}
					}
					if (classesURL != null)
					{
						File classesDir = new File(workDir, "WEB-INF/classes/");
						try
						{
							String path = classesDir.getCanonicalPath();
							classLoader.addPermission(path);
						}
						catch (IOException e)
						{
						}
					}
				}

			}

		}
		catch (MalformedURLException e)
		{
		}

	}

	/**
	 * Configure the repositories for our class loader, based on the
	 * associated Context.
	 */
	private void setRepositories()
	{

		if (!(container instanceof Context))
		{
			return;
		}
		WorkflowContext templateContext = ((Context) container).getWorkflowContext();
		if (templateContext == null)
		{
			return;
		}

		// Loading the work directory
		File workDir = (File) templateContext.getAttribute(Globals.ATTR_WORK_DIR);
		if (workDir == null)
		{
			log.info("No work dir for " + templateContext);
		}

		if (log.isDebugEnabled())
			log.debug(sm.getString("applicationLoader.deploy", workDir.getAbsolutePath()));

		classLoader.setWorkDir(workDir);

		DirContext resources = container.getResources();

		// Setting up the class repository (/WEB-INF/classes), if it exists

		String classesPath = "/WEB-INF/classes";
		DirContext classes = null;

		try
		{
			Object object = resources.lookup(classesPath);
			if (object instanceof DirContext)
			{
				classes = (DirContext) object;
			}
		}
		catch (NamingException e)
		{
			// Silent catch: it's valid that no /WEB-INF/classes collection
			// exists
		}

		if (classes != null)
		{

			File classRepository = null;

			String absoluteClassesPath = templateContext.getRealPath(classesPath);

			if (absoluteClassesPath != null)
			{

				classRepository = new File(absoluteClassesPath);

			}
			else
			{

				classRepository = new File(workDir, classesPath);
				classRepository.mkdirs();
				copyDir(classes, classRepository);

			}

			if (log.isDebugEnabled())
				log.debug(sm.getString("applicationLoader.classDeploy", classesPath, classRepository.getAbsolutePath()));

			// Adding the repository to the class loader
			classLoader.addRepository(classesPath + "/", classRepository);

		}

		// Setting up the JAR repository (/WEB-INF/lib), if it exists

		String libPath = "/WEB-INF/lib";

		classLoader.setJarPath(libPath);

		DirContext libDir = null;
		// Looking up directory /WEB-INF/lib in the context
		try
		{
			Object object = resources.lookup(libPath);
			if (object instanceof DirContext)
				libDir = (DirContext) object;
		}
		catch (NamingException e)
		{
			// Silent catch: it's valid that no /WEB-INF/lib collection
			// exists
		}

		if (libDir != null)
		{

			boolean copyJars = false;
			String absoluteLibPath = templateContext.getRealPath(libPath);

			File destDir = null;

			if (absoluteLibPath != null)
			{
				destDir = new File(absoluteLibPath);
			}
			else
			{
				copyJars = true;
				destDir = new File(workDir, libPath);
				destDir.mkdirs();
			}

			// Looking up directory /WEB-INF/lib in the context
			try
			{
				NamingEnumeration enum = resources.listBindings(libPath);
				while (enum.hasMoreElements())
				{

					Binding binding = (Binding) enum.nextElement();
					String filename = libPath + "/" + binding.getName();
					if (!filename.endsWith(".jar"))
					{
						continue;
					}

					// Copy JAR in the work directory, always (the JAR file
					// would get locked otherwise, which would make it
					// impossible to update it or remove it at runtime)
					File destFile = new File(destDir, binding.getName());

					log.debug(sm.getString("applicationLoader.jarDeploy", filename, destFile.getAbsolutePath()));

					Resource jarResource = (Resource) binding.getObject();
					if (copyJars)
					{
						if (!copy(jarResource.streamContent(), new FileOutputStream(destFile)))
						{
							continue;
						}
					}

					try
					{
						JarFile jarFile = new JarFile(destFile);
						classLoader.addJar(filename, jarFile, destFile);
					}
					catch (Exception ex)
					{
						// Catch the exception if there is an empty jar file
						// Should ignore and continute loading other jar files 
						// in the dir
					}

				}
			}
			catch (NamingException e)
			{
				// Silent catch: it's valid that no /WEB-INF/lib directory
				// exists
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}

		}

	}

	/**
	 * Set the appropriate context attribute for our class path.  This
	 * is required only because Jasper depends on it.
	 */
	private void setClassPath()
	{

		// Validate our current state information
		if (!(container instanceof Context))
		{
			return;
		}
		WorkflowContext templateContext = ((Context) container).getWorkflowContext();
		if (templateContext == null)
		{
			return;
		}

		if (container instanceof StandardContext)
		{
			String baseClasspath = ((StandardContext) container).getCompilerClasspath();
			if (baseClasspath != null)
			{
				templateContext.setAttribute(Globals.ATTR_CLASS_PATH, baseClasspath);
				return;
			}
		}

		StringBuffer classpath = new StringBuffer();

		// Assemble the class path information from our class loader chain
		ClassLoader loader = getClassLoader();
		int layers = 0;
		int n = 0;
		while (loader != null)
		{
			if (!(loader instanceof URLClassLoader))
			{
				String cp = getClasspath(loader);
				if (cp == null)
				{
					log.info("Unknown loader " + loader + " " + loader.getClass());
					break;
				}
				else
				{
					if (n > 0)
					{
						classpath.append(File.pathSeparator);
					}
					classpath.append(cp);
					n++;
				}
				break;
				//continue;
			}
			URL repositories[] = ((URLClassLoader) loader).getURLs();
			for (int i = 0; i < repositories.length; i++)
			{
				String repository = repositories[i].toString();
				if (repository.startsWith("file://"))
				{
					repository = repository.substring(7);
				}
				else if (repository.startsWith("file:"))
				{
					repository = repository.substring(5);
				}
				else if (repository.startsWith("jndi:"))
				{
					repository = templateContext.getRealPath(repository.substring(5));
				}
				else
				{
					continue;
				}
				if (repository == null)
				{
					continue;
				}
				if (n > 0)
				{
					classpath.append(File.pathSeparator);
				}
				classpath.append(repository);
				n++;
			}
			loader = loader.getParent();
			layers++;
		}

		this.classpath = classpath.toString();

		// Store the assembled class path as a template context attribute
		templateContext.setAttribute(Globals.ATTR_CLASS_PATH, classpath.toString());

	}

	// try to extract the classpath from a loader that is not URLClassLoader
	private String getClasspath(ClassLoader loader)
	{
		try
		{
			Method m = loader.getClass().getMethod("getClasspath", new Class[] {
			});
			log.trace("getClasspath " + m);
			if (m == null)
			{
				return null;
			}
			Object o = m.invoke(loader, new Object[] {
			});

			log.debug("gotClasspath " + o);
			if (o instanceof String)
			{
				return (String) o;
			}
			return null;
		}
		catch (Exception ex)
		{
			log.debug("getClasspath ", ex);
		}
		return null;
	}

	/**
	 * Copy directory.
	 */
	private boolean copyDir(DirContext srcDir, File destDir)
	{

		try
		{

			NamingEnumeration enum = srcDir.list("");
			while (enum.hasMoreElements())
			{
				NameClassPair ncPair = (NameClassPair) enum.nextElement();
				String name = ncPair.getName();
				Object object = srcDir.lookup(name);
				File currentFile = new File(destDir, name);
				if (object instanceof Resource)
				{
					InputStream is = ((Resource) object).streamContent();
					OutputStream os = new FileOutputStream(currentFile);
					if (!copy(is, os))
					{
						return false;
					}
				}
				else if (object instanceof InputStream)
				{
					OutputStream os = new FileOutputStream(currentFile);
					if (!copy((InputStream) object, os))
					{
						return false;
					}
				}
				else if (object instanceof DirContext)
				{
					currentFile.mkdir();
					copyDir((DirContext) object, currentFile);
				}
			}

		}
		catch (NamingException e)
		{
			return false;
		}
		catch (IOException e)
		{
			return false;
		}

		return true;

	}

	/**
	 * Copy a file to the specified temp directory. This is required only
	 * because Jasper depends on it.
	 */
	private boolean copy(InputStream is, OutputStream os)
	{

		try
		{
			byte[] buf = new byte[4096];
			while (true)
			{
				int len = is.read(buf);
				if (len < 0)
				{
					break;
				}
				os.write(buf, 0, len);
			}
			is.close();
			os.close();
		}
		catch (IOException e)
		{
			return false;
		}

		return true;

	}
}