//----------------------------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.FilePermission;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
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.util.JarFilenameFilter;
import org.huihoo.willow.util.LifecycleSupport;
import org.huihoo.willow.util.StringManager;
import org.huihoo.workflow.rules.ScriptContext;
/**
* @author Administrator
*
* To change the template for this generated type comment go to
* Window - Preferences - Java - Code Generation - Code and Comments
*/
public class WorkflowLoader implements Lifecycle, Loader, PropertyChangeListener
{
private static Log log = LogFactory.getLog(WorkflowLoader.class);
// ----------------------------------------------------------- Constructors
/**
* Construct a new workflowLoader with no defined parent class loader
* (so that the actual parent will be the system class loader).
*/
public WorkflowLoader()
{
this(null);
}
/**
* Construct a new workflowLoader 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 model" 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 workflowClassLoader, 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];
/**
* The string manager for this package.
*/
protected static final StringManager sm = StringManager.getManager(Constants.PACKAGE);
/**
* 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)
{
this.container = container;
}
/**
* 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 model" flag used to configure
* our ClassLoader.
*/
public boolean getDelegate()
{
return (this.delegate);
}
/**
* Set the "follow standard delegation model" 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><description>/<version></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("workflowLoader.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 wangll.willow.wfs_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("workflowLoader[");
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);
}
private boolean initialized = false;
/**
* 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("workflowLoader.alreadyStarted"));
}
log.debug(sm.getString("workflowLoader.starting"));
lifecycle.fireLifecycleEvent(START_EVENT, null);
started = true;
// Construct a class loader based on our current repositories list
try
{
classLoader = createClassLoader();
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();
}
}
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("workflowLoader.notStarted"));
}
log.debug(sm.getString("workflowLoader.stopping"));
lifecycle.fireLifecycleEvent(STOP_EVENT, null);
started = false;
classLoader = null;
}
// ----------------------------------------- PropertyChangeListener Methods
/**
* Process property change events from our associated Container.
*
* @param event The property change event that has occurred
*/
public void propertyChange(PropertyChangeEvent event)
{
// Validate the source of this event
if (!(event.getSource() instanceof Container))
return;
// Process a relevant property change
if (event.getPropertyName().equals("reloadable"))
{
try
{
setReloadable(((Boolean) event.getNewValue()).booleanValue());
}
catch (NumberFormatException e)
{
log.error(sm.getString("workflowLoader.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("workflowLoader[" + container.getName() + "]: " + message);
}
else
{
String containerName = null;
if (container != null)
{
containerName = container.getName();
}
System.out.println("workflowLoader[" + 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("workflowLoader[" + container.getName() + "] " + message, throwable);
}
else
{
String containerName = null;
if (container != null)
{
containerName = container.getName();
}
System.out.println("workflowLoader[" + 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
ScriptContext scriptContext = ((Context) container).getScriptContext();
// Assigning permissions for the work directory
File workDir = (File) scriptContext.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 = scriptContext.getResource("/");
classLoader.addPermission(rootURL);
String contextRoot = scriptContext.getRealPath("/");
try
{
contextRoot = (new File(contextRoot)).getCanonicalPath();
classLoader.addPermission(contextRoot);
}
catch (IOException e)
{
// Ignore
}
URL classesURL = scriptContext.getResource("/WEB-INF/classes/");
classLoader.addPermission(classesURL);
URL libURL = scriptContext.getResource("/WEB-INF/lib/");
classLoader.addPermission(libURL);
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)
{
}
}
}
catch (MalformedURLException e)
{
}
}
/**
* Configure the repositories for our class loader, based on the
* associated Container.
*/
private void setRepositories()
{
if (!(container instanceof Context))
{
return;
}
ScriptContext scriptContext = ((Context) container).getScriptContext();
if (scriptContext == null)
{
return;
}
// Loading the work directory
File workDir = (File) scriptContext.getAttribute(Globals.ATTR_WORK_DIR);
if (workDir == null)
{
log.info("No work dir for " + scriptContext);
}
else
{
log.debug(sm.getString("workflowLoader.deploy", workDir.getAbsolutePath()));
classLoader.setWorkDir(workDir);
}
// Setting up the class repository (/WEB-INF/classes), if it exists
String classesPath = "/WEB-INF/classes";
String absoluteClassesPath = scriptContext.getRealPath(classesPath);
if (absoluteClassesPath != null)
{
File classRepository = new File(absoluteClassesPath);
// Adding the repository to the class loader
log.debug(
sm.getString("workflowLoader.classDeploy", classesPath, classRepository.getAbsolutePath()));
classLoader.addRepository(classesPath + "/", classRepository);
}
// Setting up the JAR repository (/WEB-INF/lib), if it exists
String libPath = "/WEB-INF/lib";
String absoluteLibPath = scriptContext.getRealPath(libPath);
if (absoluteLibPath != null)
{
classLoader.addJarPath(libPath, new File(absoluteLibPath));
File destDir = new File(absoluteLibPath);
File[] files = destDir.listFiles(new JarFilenameFilter());
for (int i = 0; i < files.length; ++i)
{
String filename = libPath + "/" + files[i].getName();
File destFile = files[i];
try
{
classLoader.addJar(filename, 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
}
}
}
}
/**
* 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;
}
ScriptContext ScriptContext = ((Context) container).getScriptContext();
if (ScriptContext == null)
{
return;
}
if (container instanceof StandardContext)
{
String baseClasspath = ((StandardContext) container).getCompilerClasspath();
if (baseClasspath != null)
{
ScriptContext.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 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
{
continue;
}
if (repository == null)
{
continue;
}
if (n > 0)
{
classpath.append(File.pathSeparator);
}
classpath.append(repository);
n++;
}
loader = loader.getParent();
}
this.classpath = classpath.toString();
// Store the assembled class path as a servlet context attribute
ScriptContext.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[] {
});
if (log.isTraceEnabled())
{
}
log.trace("getClasspath " + m);
if (m == null)
{
return null;
}
Object o = m.invoke(loader, new Object[] {
});
if (log.isDebugEnabled())
{
log.debug("gotClasspath " + o);
}
if (o instanceof String)
{
return (String) o;
}
return null;
}
catch (Exception ex)
{
if (log.isDebugEnabled())
{
log.debug("getClasspath ", ex);
}
}
return null;
}
}