//----------------------------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.core;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashMap;
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.ContextDeployer;
import org.huihoo.willow.Engine;
import org.huihoo.willow.Globals;
import org.huihoo.willow.Lifecycle;
import org.huihoo.willow.LifecycleException;
import org.huihoo.willow.Loader;
import org.huihoo.willow.loader.WorkflowLoader;
import org.huihoo.workflow.WorkflowException;
import org.huihoo.workflow.runtime.WorkflowContext;
import org.huihoo.workflow.rules.ScriptContext;
import org.huihoo.workflow.xpdl.WorkflowPackage;
import org.huihoo.workflow.impl.monitor.EventMonitorThread;
import org.huihoo.workflow.impl.monitor.LitterCleaner;
import com.zosatapo.commons.store.Store;
/**
* @author reic
*
* To change the template for this generated type comment go to
* Window - Preferences - Java - Code Generation - Code and Comments
*/
public class StandardContext extends WorkflowServiceBase implements ContextDeployer, Context, Serializable
{
private transient Log log = LogFactory.getLog(StandardContext.class);
// ----------------------------------------------------------- Constructors
/**
* Create a new StandardContext component with the default basic Valve.
*/
public StandardContext()
{
super();
}
// ----------------------------------------------------- Instance Variables
/**
* The Engine to which this Context is a child.
*/
protected Engine engine = null;
/**
* The ScriptContext implementation associated with this Context.
*/
private transient ApplicationContext scriptContext = null;
/**
* Compiler classpath to use.
*/
private String compilerClasspath = null;
/**
* The "follow standard delegation model" flag that will be used to
* configure our ClassLoader.
*/
private boolean delegate = false;
/**
* The auto deploy flag for this Context.
*/
private boolean autoDeploy = true;
/**
* The display name of this context.
*/
private String displayName = null;
/**
* The document root for this context.
*/
private String appBase = null;
/**
* The descriptive information string for this implementation.
*/
private static final String info = "org.huohoo.core.StandardContext/1.0";
/**
* The reloadable flag for this context.
*/
private boolean reloadable = false;
/**
* The pathname to the work directory for this context (relative to
* the server's home if not absolute).
*/
private String workDir = null;
/**
* The <code>EngineDeployer</code> to whom we delegate application
* deployment requests.
*/
private ContextDeployer deployer = null;
/**
* The context initialization parameters for this context,
* keyed by name.
*/
private HashMap parameters = new HashMap();
/**
* The context initialization stores for this context,
* keyed by store-key.
*/
private HashMap stores = new HashMap();
/**
* The context initialization factory for this context,
* keyed by factory-key.
*/
private HashMap factories = new HashMap();
/**
* The application available flag for this org.huihoo.workflow context.
*/
private boolean available = false;
/**
* The request processing pause flag (while reloading occurs)
*/
private boolean paused = false;
// ----------------------------------------------------- org.huihoo.workflow context Properties
/**
* 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 the <code>Engine</code> with which we are associated (if any).
*/
public Engine getEngine()
{
return (engine);
}
/**
* Set the <code>Engine</code> with which we are associated (if any).
*
* @param engine The engine that owns this Context
*/
public void setEngine(Engine engine)
{
super.setParent(engine);
Engine oldEngine = this.engine;
this.engine = engine;
support.firePropertyChange("engine", oldEngine, this.engine);
}
/**
* Return the display name of this context.
*/
public String getDisplayName()
{
return (this.displayName);
}
/**
* Return the compiler classpath.
*/
public String getCompilerClasspath()
{
return compilerClasspath;
}
/**
* Set the compiler classpath.
*/
public void setCompilerClasspath(String compilerClasspath)
{
this.compilerClasspath = compilerClasspath;
}
/**
* Set the display name of this context.
*
* @param displayName The new display name
*/
public void setDisplayName(String displayName)
{
String oldDisplayName = this.displayName;
this.displayName = displayName;
support.firePropertyChange("displayName", oldDisplayName, this.displayName);
}
/**
* Return the document root for this Context. This can be an absolute
* pathname, a relative pathname
*/
public String getAppBase()
{
return (this.appBase);
}
/**
* Return the document root for this Context.
*/
public File getAbsoluteAppBase()
{
String absolute_appBase = getAppBase();
File absolute_appBaseFile = new File(absolute_appBase);
if (!absolute_appBaseFile.isAbsolute())
{
Engine parent = (Engine) this.getParent();
absolute_appBaseFile = new File(parent.getAbsoluteEngineBase(), absolute_appBase);
}
return (absolute_appBaseFile);
}
/**
* Set the document root for this org.huihoo.workflow context. This can be an absolute
* pathname, a relative pathname
*
* @param docBase The new document root
*/
public void setAppBase(String appBase)
{
this.appBase = appBase;
}
/**
* Return descriptive information about this Container implementation and
* the corresponding version number, in the format
* <code><description>/<version></code>.
*/
public String getInfo()
{
return (info);
}
/**
* Set the Loader with which this org.huihoo.workflow context is associated.
*
* @param loader The newly associated loader
*/
public synchronized void setLoader(Loader loader)
{
super.setLoader(loader);
}
/**
* Return the reloadable flag for this context.
*/
public boolean getReloadable()
{
return (this.reloadable);
}
/**
* Set the reloadable flag for this context.
*
* @param reloadable The new reloadable flag
*/
public void setReloadable(boolean reloadable)
{
boolean oldReloadable = this.reloadable;
this.reloadable = reloadable;
support.firePropertyChange("reloadable", new Boolean(oldReloadable), new Boolean(this.reloadable));
}
/**
* Return the value of the auto deploy flag. If true, it indicates that
* this xpdl will be dynamically deployed.
*/
public boolean getAutoDeploy()
{
return (this.autoDeploy);
}
/**
* Set the auto deploy flag value for this Context.
*
* @param autoDeploy The new auto deploy flag
*/
public void setAutoDeploy(boolean autoDeploy)
{
boolean oldAutoDeploy = this.autoDeploy;
this.autoDeploy = autoDeploy;
support.firePropertyChange("autoDeploy", oldAutoDeploy, this.autoDeploy);
}
/**
* Return the servlet context for which this Context is a facade.
*/
public ScriptContext getScriptContext()
{
if (scriptContext == null)
{
scriptContext = new ApplicationContext(getBasePath(), this);
}
return (scriptContext);
}
// ------------------------------------------------------ Public Properties
/** Get the absolute path to the work dir.
* To avoid duplication.
*
* @return
*/
public String getWorkPath()
{
File workDir = new File(getWorkDir());
if (!workDir.isAbsolute())
{
File willowHome = new File(System.getProperty(Globals.PROPS_WILLOW_HOME));
String willowHomePath = null;
try
{
willowHomePath = willowHome.getCanonicalPath();
workDir = new File(willowHomePath, getWorkDir());
}
catch (IOException e)
{
}
}
return workDir.getAbsolutePath();
}
/**
* Return the work directory for this org.huihoo.workflow context.
*/
public String getWorkDir()
{
return (this.workDir);
}
/**
* Set the work directory for this org.huihoo.workflow context.
*
* @param workDir The new work directory
*/
public void setWorkDir(String workDir)
{
this.workDir = workDir;
if (started)
{
postWorkDirectory();
}
}
/**
* Return the application available flag for this org.huihoo.workflow context.
*/
public boolean getAvailable()
{
return (this.available);
}
/**
* Set the application available flag for this org.huihoo.workflow context.
*
* @param available The new application available flag
*/
public void setAvailable(boolean available)
{
boolean oldAvailable = this.available;
this.available = available;
support.firePropertyChange("available", new Boolean(oldAvailable), new Boolean(this.available));
}
/**
* Add a new context initialization parameter.
*
* @param name Name of the new parameter
* @param value Value of the new parameter
*
* @exception IllegalArgumentException if the name or value is missing,
* or if this context initialization parameter has already been
* registered
*/
public void addInitParameter(String name, String value)
{
// Validate the proposed context initialization parameter
if ((name == null) || (value == null))
throw new IllegalArgumentException(sm.getString("standardContext.parameter.required"));
if (parameters.get(name) != null)
throw new IllegalArgumentException(sm.getString("standardContext.parameter.duplicate", name));
// Add this parameter to our defined set
synchronized (parameters)
{
parameters.put(name, value);
}
}
/**
* Return the value for the specified context initialization
* parameter name, if any; otherwise return <code>null</code>.
*
* @param name Name of the parameter to return
*/
public String findInitParameter(String name)
{
synchronized (parameters)
{
return ((String) parameters.get(name));
}
}
/**
* Return the names of all defined context initialization parameters
* for this Context. If no parameters are defined, a zero-length
* array is returned.
*/
public String[] findInitParameters()
{
synchronized (parameters)
{
String results[] = new String[parameters.size()];
return ((String[]) parameters.keySet().toArray(results));
}
}
public void removeInitParameter(String name)
{
synchronized (parameters)
{
parameters.remove(name);
}
}
public void addFactoryClass(String name, String value)
{
// Validate the proposed context initialization parameter
if ((name == null) || (value == null))
throw new IllegalArgumentException(sm.getString("standardContext.factory.required"));
if (factories.get(name) != null)
throw new IllegalArgumentException(sm.getString("standardContext.factory.duplicate", name));
// Add this parameter to our defined set
synchronized (factories)
{
factories.put(name, value);
}
}
public String findFactoryClass(String name)
{
synchronized (factories)
{
return ((String) factories.get(name));
}
}
public String[] findFactoryClasses()
{
synchronized (factories)
{
String results[] = new String[factories.size()];
return ((String[]) factories.keySet().toArray(results));
}
}
public void removeFactoryClass(String name)
{
synchronized (factories)
{
factories.remove(name);
}
}
public void addStore(String name, Store value)
{
// Validate the proposed context initialization parameter
if ((name == null) || (value == null))
throw new IllegalArgumentException(sm.getString("standardContext.store.required"));
if (stores.get(name) != null)
throw new IllegalArgumentException(sm.getString("standardContext.store.duplicate", name));
// Add this parameter to our defined set
synchronized (stores)
{
stores.put(name, value);
}
}
public Store findStore(String name)
{
synchronized (stores)
{
return ((Store) stores.get(name));
}
}
public String[] findStores()
{
synchronized (stores)
{
String results[] = new String[stores.size()];
return ((String[]) stores.keySet().toArray(results));
}
}
public void removeStore(String name)
{
synchronized (stores)
{
stores.remove(name);
}
}
/**
* Reload this context, if reloading is supported.
* <p>
* <b>IMPLEMENTATION NOTE</b>: This method is designed to deal with
* reloads required by changes to classes in the underlying repositories
* of our class loader. It does not handle changes to the context
* deployment descriptor. If that has occurred, you should stop this
* org.huihoo.workflow context and create (and start) a new org.huihoo.workflow context instance instead.
*
* @exception IllegalStateException if the <code>reloadable</code>
* property is set to <code>false</code>.
*/
public synchronized void reload()
{
// Validate our current component state
if (!started)
{
throw new IllegalStateException(sm.getString("containerBase.notStarted", logName()));
}
// Make sure reloading is enabled
if (!reloadable)
{
throw new IllegalStateException(sm.getString("standardContext.notReloadable"));
}
log.info(sm.getString("standardContext.reloadingStarted"));
// Stop accepting requests temporarily
setPaused(true);
try
{
stop();
}
catch (LifecycleException e)
{
log.error(sm.getString("standardContext.stoppingContext"), e);
}
try
{
start();
}
catch (LifecycleException e)
{
log.error(sm.getString("standardContext.startingContext"), e);
}
setPaused(false);
}
// --------------------------------------------------------- Public Methods
/**
* Start this org.huihoo.workflow context component.
*
* @exception LifecycleException if a startup error occurs
*/
public synchronized void start() throws LifecycleException
{
if (started)
{
log.info(sm.getString("standardContext.alreadyStarted", logName()));
return;
}
String logName = "willow." + getParent().getName() + "." + getName() + ".Context";
log = LogFactory.getLog(logName);
log.debug("Starting " + logName);
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
setAvailable(false);
boolean ok = true;
if (getLoader() == null)
{
ClassLoader parent = null;
log.debug("Configuring non-privileged default Loader");
parent = getParentClassLoader();
WorkflowLoader webappLoader = new WorkflowLoader(parent);
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
// Post work directory
postWorkDirectory();
// Standard container startup
log.debug("Processing standard container startup");
if (ok)
{
started = true;
// Start our subordinate components, if any
if ((loader != null) && (loader instanceof Lifecycle))
{
((Lifecycle) loader).start();
}
if ((logger != null) && (logger instanceof Lifecycle))
{
((Lifecycle) logger).start();
}
//MUST be called before lifecycle.fireLifecycleEvent(START_EVENT, null);
scriptContext.setAttribute(Globals.ATTR_CLASS_LOADER, getLoader().getClassLoader());
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(START_EVENT, null);
// Start ContainerBackgroundProcessor thread
threadStart();
}
// Set available status depending upon startup success
if (ok)
{
log.debug("Starting completed");
setAvailable(true);
}
else
{
log.error(sm.getString("standardContext.startFailed"));
try
{
stop();
}
catch (Throwable t)
{
log.error(sm.getString("standardContext.startCleanup"), t);
}
setAvailable(false);
}
if (ok)
{
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}
// Close all JARs right away to avoid always opening a peak number
// of files on startup
if (getLoader() instanceof WorkflowLoader)
{
((WorkflowLoader) getLoader()).closeJARs(true);
}
// Reinitializing if something went wrong
if (!ok && started)
{
stop();
}
}
/**
* Stop this org.huihoo.workflow context component.
*
* @exception LifecycleException if a shutdown error occurs
*/
public synchronized void stop() throws LifecycleException
{
// Validate and update our current component state
if (!started)
{
throw new LifecycleException(sm.getString("containerBase.notStarted", logName()));
}
log.debug("Stopping");
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
// Mark this application as unavailable while we shut down
setAvailable(false);
// Stop ContainerBackgroundProcessor thread
threadStop();
// Normal container shutdown processing
log.debug("Processing standard container shutdown");
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(STOP_EVENT, null);
started = false;
try
{
if ((logger != null) && (logger instanceof Lifecycle))
{
((Lifecycle) logger).stop();
}
if ((loader != null) && (loader instanceof Lifecycle))
{
((Lifecycle) loader).stop();
}
}
finally
{
}
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
log.debug("Stopping complete");
}
/**
* Return a String representation of this component.
*/
public String toString()
{
StringBuffer sb = new StringBuffer();
if (getParent() != null)
{
sb.append(getParent().toString());
sb.append(".");
}
sb.append("StandardContext[");
sb.append(getName());
sb.append("]");
return (sb.toString());
}
/**
* Execute a periodic task, such as reloading, etc. This method will be
* invoked inside the classloading context of this container. Unexpected
* throwables will be caught and logged.
*/
public void backgroundProcess()
{
if (!started)
{
return;
}
if (getLoader() != null)
{
if (reloadable && (getLoader().modified()))
{
try
{
Thread.currentThread().setContextClassLoader(StandardContext.class.getClassLoader());
reload();
}
finally
{
if (getLoader() != null)
{
Thread.currentThread().setContextClassLoader(getLoader().getClassLoader());
}
}
}
if (getLoader() instanceof WorkflowLoader)
{
((WorkflowLoader) getLoader()).closeJARs(false);
}
}
LitterCleaner.clear(this);
lifecycle.fireLifecycleEvent("check", null);
}
// ------------------------------------------------------ Protected Methods
/**
* Return a File object representing the base directory for the
* entire servlet container (i.e. the Engine container if present).
*/
protected File engineBaseFile()
{
StandardEngine eng = (StandardEngine) this.getParent();
String engineBase = eng.getEngineBase();
File baseFile = new File(engineBase);
if (!baseFile.isAbsolute())
{
baseFile = new File(System.getProperty(Globals.PROPS_WILLOW_HOME), engineBase);
}
return (baseFile);
}
/**
* Return the request processing paused flag for this org.huihoo.workflow context.
*/
public boolean getPaused()
{
return (this.paused);
}
/**
* Set the appropriate context attribute for our work directory.
*/
private void postWorkDirectory()
{
// Acquire (or calculate) the work directory path
String workDir = getWorkDir();
if (workDir == null)
{
// Retrieve our parent (normally a engine) name
String engineName = null;
String engineWorkDir = null;
Container parentEngine = getParent();
if (parentEngine != null)
{
engineName = parentEngine.getName();
engineWorkDir = ((StandardEngine) parentEngine).getWorkDir();
}
if ((engineName == null) || (engineName.length() < 1))
{
engineName = "_";
}
String temp = getName();
if (temp.startsWith("/"))
{
temp = temp.substring(1);
}
temp = temp.replace('/', '_');
temp = temp.replace('\\', '_');
if (temp.length() < 1)
{
temp = "_";
}
if (engineWorkDir != null)
{
workDir = engineWorkDir + File.separator + temp;
}
else
{
workDir =
org.huihoo.willow.startup.Constants.WORK_DIRECTORY + File.separator + engineName + File.separator + temp;
}
setWorkDir(workDir);
}
// Create this directory if necessary
File dir = new File(workDir);
if (!dir.isAbsolute())
{
File willowHome = new File(System.getProperty(Globals.PROPS_WILLOW_HOME));
String willowHomePath = null;
try
{
willowHomePath = willowHome.getCanonicalPath();
dir = new File(willowHomePath, workDir);
}
catch (IOException e)
{
}
}
dir.mkdirs();
getScriptContext().setAttribute(Globals.ATTR_WORK_DIR, dir);
if (getScriptContext() instanceof ApplicationContext)
{
((ApplicationContext) getScriptContext()).setAttributeReadOnly(Globals.ATTR_WORK_DIR);
}
}
/**
* Set the request processing paused flag for this org.huihoo.workflow context.
*
* @param paused The new request processing paused flag
*/
private void setPaused(boolean paused)
{
this.paused = paused;
}
/**
* Get base path (full path name).
*/
private String getBasePath()
{
String appBase = null;
File file = new File(getAppBase());
if (!file.isAbsolute())
{
appBase = new File(engineBaseFile(), getAppBase()).getPath();
}
else
{
appBase = file.getPath();
}
return appBase;
}
// ------------------------------------------------------- ContextDeployer Methods
public void install(String xpdlFileName, URL xpdlFileURL) throws WorkflowException
{
getDeployer().install(xpdlFileName, xpdlFileURL);
}
public WorkflowPackage findDeployedPackage(String xpdlFileName)
{
return (getDeployer().findDeployedPackage(xpdlFileName));
}
public String[] findDeployedPackages()
{
return (getDeployer().findDeployedPackages());
}
public void remove(String xpdlFileName) throws WorkflowException
{
getDeployer().remove(xpdlFileName);
}
// ------------------------------------------------------ Protected Methods
static String STANDARD_CONTEXT_DEPLOYER = "org.huihoo.willow.core.StandardContextDeployer";
public ContextDeployer getDeployer()
{
if (deployer != null)
{
return deployer;
}
log.info("Create Context deployer for direct deployment ");
try
{
Class c = Class.forName(STANDARD_CONTEXT_DEPLOYER);
deployer = (ContextDeployer) c.newInstance();
Method m = c.getMethod("setContext", new Class[] { Context.class });
m.invoke(deployer, new Object[] { this });
}
catch (Throwable t)
{
log.error("Error creating deployer ", t);
}
return deployer;
}
public void setDeployer(ContextDeployer d)
{
this.deployer = d;
}
//-------------------------------------------------------------
// WorkflowService Implementation
//-------------------------------------------------------------
public WorkflowContext getWorkflowContext()
{
if (scriptContext == null)
{
scriptContext = new ApplicationContext(getBasePath(), this);
}
return (scriptContext);
}
protected void threadStart()
{
if (thread == null && backgroundProcessorDelay > 0)
{
threadDone = false;
String threadName = "ContextBackgroundProcessor[" + toString() + "]";
thread = new Thread(new ContextBackgroundProcessor(), threadName);
thread.setDaemon(true);
thread.start();
}
if (envent_threads.size() > 0)
{
String[] xpdlFileNames = findDeployedPackages();
if (xpdlFileNames != null && xpdlFileNames.length > 0)
{
for (int i = 0; i < xpdlFileNames.length; ++i)
{
WorkflowPackage workflowPackage = findDeployedPackage(xpdlFileNames[i]);
EventMonitorThread eventMonitorThread = this.getEventMonitorThread(workflowPackage);
eventMonitorThread.start();
}
}
}
else
{
//has been stopped
String[] xpdlFileNames = findDeployedPackages();
if (xpdlFileNames != null && xpdlFileNames.length > 0)
{
for (int i = 0; i < xpdlFileNames.length; ++i)
{
WorkflowPackage workflowPackage = findDeployedPackage(xpdlFileNames[i]);
String threadName = "EventMonitorThread[" + toString() + " - " + workflowPackage.getName() + "]";
EventMonitorThread eventMonitorThread = new EventMonitorThread(threadName);
eventMonitorThread.setDaemon(true);
eventMonitorThread.start();
this.putEventMonitorThread(workflowPackage, eventMonitorThread);
}
}
}
}
protected void threadStop()
{
if (thread != null)
{
threadDone = true;
thread.interrupt();
thread = null;
}
if (envent_threads.size() > 0)
{
String[] xpdlFileNames = findDeployedPackages();
if (xpdlFileNames != null && xpdlFileNames.length > 0)
{
for (int i = 0; i < xpdlFileNames.length; ++i)
{
WorkflowPackage workflowPackage = findDeployedPackage(xpdlFileNames[i]);
EventMonitorThread eventMonitorThread = this.getEventMonitorThread(workflowPackage);
eventMonitorThread.interrupt();
try
{
eventMonitorThread.join();
}
catch (InterruptedException e)
{
;
}
this.removeEventMonitorThread(workflowPackage);
eventMonitorThread = null;
}
}
}
}
// -------------------------------------- ContextrExecuteDelay Inner Class
/**
* Private thread class to invoke the backgroundProcess method
* of this container and its children after a fixed delay.
*/
protected class ContextBackgroundProcessor implements Runnable
{
public void run()
{
while (!threadDone)
{
try
{
Thread.sleep(backgroundProcessorDelay * 1000L);
}
catch (InterruptedException e)
{
;
}
if (!threadDone)
{
Container container = StandardContext.this;
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (parent.getLoader() != null)
{
cl = container.getLoader().getClassLoader();
}
try
{
if (container.getLoader() != null)
{
Thread.currentThread().setContextClassLoader(container.getLoader().getClassLoader());
}
container.backgroundProcess();
}
catch (Throwable t)
{
log.error("Exception invoking periodic operation: ", t);
}
finally
{
Thread.currentThread().setContextClassLoader(cl);
}
}
}
}
}
}