//----------------------------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.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.Context;
import org.huihoo.willow.Engine;
import org.huihoo.willow.EngineDeployer;
import org.huihoo.willow.Globals;
import org.huihoo.willow.Lifecycle;
import org.huihoo.willow.LifecycleException;
import org.huihoo.willow.Service;
import org.huihoo.willow.session.SessionManager;
import org.huihoo.willow.store.UserDatabaseRealm;
import org.huihoo.willow.util.ServerInfo;
import org.huihoo.workflow.store.RealmDatabase;
/**
* @author reic
*
* To change the template for this generated type comment go to
* Window - Preferences - Java - Code Generation - Code and Comments
*/
public class StandardEngine extends ContainerBase implements EngineDeployer, Engine
{
private static Log log = LogFactory.getLog(StandardEngine.class);
// ----------------------------------------------------------- Constructors
/**
* Create a new StandardEngine component with the default basic Valve.
*/
public StandardEngine()
{
super();
// By default, the engine will hold the reloading thread
backgroundProcessorDelay = 10;
}
// ----------------------------------------------------- Instance Variables
/**
* The child Context belonging to this , keyed by name.
*/
protected HashMap children = new HashMap();
/**
* The descriptive information string for this implementation.
*/
private static final String info = "org.huihoo.willow.core.StandardEngine/1.0";
/**
* The <code>Service</code> that owns this Engine, if any.
*/
private Service service = null;
/**
* The application root for this Engine.
*/
private String engineBase = ".";
/**
* The auto deploy flag for this Engine.
*/
private boolean autoDeploy = true;
/**
* The deploy on startup flag for this Engine.
*/
private boolean deployOnStartup = true;
/**
* The Java class name of the default context configuration class
* for deployed web applications.
*/
private String configClass = "org.huihoo.willow.startup.ContextConfig";
/**
* The Java class name of the default ParameterContext implementation class for
* deployed web applications.
*/
private String contextClass = "org.huihoo.willow.core.StandardContext";
/**
* The <code>EngineDeployer</code> to whom we delegate application
* deployment requests.
*/
private EngineDeployer deployer = null;
/**
* The session timeout (in minutes) for this web application.
*/
private int sessionTimeout = 30;
/**
* Frequency of the session expiration, and related manager operations.
* SessionManager operations will be done once for the specified amount of
* backgrondProcess calls (ie, the lower the amount, the most often the
* checks will occur).
*/
private int managerChecksFrequency = 6;
/**
* Iteration count for background processing.
*/
private int count = 0;
/**
* Work Directory base for applications.
*/
private String workDir = null;
/**
* The Realm with which this Container is associated.
*/
private RealmDatabase realmDatabase = null;
/**
* The SessionManager implementation with which this Engine is associated.
*/
protected SessionManager manager = null;
// ------------------------------------------------------------- Properties
public RealmDatabase getRealmDatabase()
{
return this.realmDatabase;
}
public void setRealmDatabase(RealmDatabase realmDatabase)
{
// Change components if necessary
RealmDatabase oldRealm = this.realmDatabase;
if (oldRealm == realmDatabase)
return;
this.realmDatabase = realmDatabase;
// Stop the old component if necessary
if (started && (oldRealm != null) && (oldRealm instanceof Lifecycle))
{
try
{
((Lifecycle) oldRealm).stop();
}
catch (LifecycleException e)
{
log.error("StandardEngine.setRealmDatabase: stop: ", e);
}
}
// Start the new component if necessary
if ((realmDatabase != null) && (realmDatabase instanceof UserDatabaseRealm))
{
((UserDatabaseRealm) realmDatabase).setEngine(this);
}
if (started && (realmDatabase != null) && (realmDatabase instanceof Lifecycle))
{
try
{
((Lifecycle) realmDatabase).start();
}
catch (LifecycleException e)
{
log.error("StandardEngine.setRealmDatabase: start: ", e);
}
}
support.firePropertyChange("realmDatabase", oldRealm, this.realmDatabase);
}
/**
* Return the application root for this Engine. This can be an absolute
* pathname, a relative pathname
*/
public String getEngineBase()
{
return (this.engineBase);
}
/**
* Return the application root for this Engine.
*/
public File getAbsoluteEngineBase()
{
String absolute_engineBase = getEngineBase();
File absolute_engineBaseFile = new File(absolute_engineBase);
if (!absolute_engineBaseFile.isAbsolute())
{
absolute_engineBaseFile =
new File(System.getProperty(Globals.PROPS_WILLOW_HOME), absolute_engineBase);
}
return absolute_engineBaseFile;
}
/**
* Set the application root for this Engine. This can be an absolute
* pathname, a relative pathname
*
* @param appBase The new application root
*/
public void setEngineBase(String engineBase)
{
String oldEngineBase = this.engineBase;
this.engineBase = engineBase;
support.firePropertyChange("engineBase", oldEngineBase, this.engineBase);
}
/**
* Return the value of the auto deploy flag. If true, it indicates that
* this Engine's child webapps will be dynamically deployed.
*/
public boolean getAutoDeploy()
{
return (this.autoDeploy);
}
/**
* Set the auto deploy flag value for this Engine.
*
* @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 Java class name of the context configuration class
* for new web applications.
*/
public String getConfigClass()
{
return (this.configClass);
}
/**
* Set the Java class name of the context configuration class
* for new web applications.
*
* @param configClass The new context configuration class
*/
public void setConfigClass(String configClass)
{
String oldConfigClass = this.configClass;
this.configClass = configClass;
support.firePropertyChange("configClass", oldConfigClass, this.configClass);
}
/**
* Return the Java class name of the ParameterContext implementation class
* for new web applications.
*/
public String getContextClass()
{
return (this.contextClass);
}
/**
* Set the Java class name of the ParameterContext implementation class
* for new web applications.
*
* @param contextClass The new context implementation class
*/
public void setContextClass(String contextClass)
{
String oldContextClass = this.contextClass;
this.contextClass = contextClass;
support.firePropertyChange("contextClass", oldContextClass, this.contextClass);
}
/**
* Return the default session timeout (in minutes) for this
* org.huihoo.workflow client
*/
public int getSessionTimeout()
{
return (this.sessionTimeout);
}
/**
* Set the default session timeout (in minutes) for this
* org.huihoo.workflow client
*
* @param timeout The new default session timeout
*/
public void setSessionTimeout(int timeout)
{
int oldSessionTimeout = this.sessionTimeout;
this.sessionTimeout = (timeout == 0) ? -1 : timeout;
support.firePropertyChange(
"sessionTimeout",
new Integer(oldSessionTimeout),
new Integer(this.sessionTimeout));
}
/**
* Return the <code>Service</code> with which we are associated (if any).
*/
public Service getService()
{
return (this.service);
}
/**
* Set the <code>Service</code> with which we are associated (if any).
*
* @param service The service that owns this Engine
*/
public void setService(Service service)
{
this.service = service;
}
/**
* Return the child Context, associated with this , with
* the specified name (if any); otherwise, return <code>null</code>
*
* @param name Name of the child Context to be retrieved
*/
public Context findChild(String name)
{
if (name == null)
return (null);
synchronized (children)
{ // Required by post-start changes
return ((Context) children.get(name));
}
}
/**
* Return the set of children Contexts associated with this .
* If this has no children, a zero-length array is returned.
*/
public Context[] findChildren()
{
synchronized (children)
{
Context results[] = new Context[children.size()];
return ((Context[]) children.values().toArray(results));
}
}
/**
* Remove an existing child Context from association with this
*
* @param child Existing child Context to be removed
*/
public void removeChild(Context child)
{
synchronized (children)
{
if (children.get(child.getName()) == null)
return;
children.remove(child.getName());
}
if (started && (child instanceof Lifecycle))
{
try
{
if (child instanceof ContainerBase)
{
if (((ContainerBase) child).started)
{
((Lifecycle) child).stop();
}
}
else
{
((Lifecycle) child).stop();
}
}
catch (LifecycleException e)
{
log.error("ContainerBase.removeChild: stop: ", e);
}
}
}
/**
* Return the value of the deploy on startup flag. If true, it indicates
* that this Engine's child webapps should be discovred and automatically
* deployed at startup time.
*/
public boolean getDeployOnStartup()
{
return (this.deployOnStartup);
}
/**
* Set the deploy on startup flag value for this Engine.
*
* @param autoDeploy The new deploy on startup flag
*/
public void setDeployOnStartup(boolean deployOnStartup)
{
boolean oldDeployOnStartup = this.deployOnStartup;
this.deployOnStartup = deployOnStartup;
support.firePropertyChange("deployOnStartup", oldDeployOnStartup, this.deployOnStartup);
}
/**
* Return the SessionManager with which this Engine is associated.
*/
public SessionManager getSessionManager()
{
return (manager);
}
/**
* Set the SessionManager with which this Engine is associated.
*
* @param manager The newly associated SessionManager
*/
public synchronized void setSessionManager(SessionManager manager)
{
// Change components if necessary
SessionManager oldManager = this.manager;
if (oldManager == manager)
{
return;
}
this.manager = manager;
// Stop the old component if necessary
if (started && (oldManager != null) && (oldManager instanceof Lifecycle))
{
try
{
((Lifecycle) oldManager).stop();
}
catch (LifecycleException e)
{
log.error("StandardEngine.setSessionManager: stop: ", e);
}
}
// Start the new component if necessary
if (manager != null)
manager.setEngine(this);
if (started && (manager != null) && (manager instanceof Lifecycle))
{
try
{
((Lifecycle) manager).start();
}
catch (LifecycleException e)
{
log.error("StandardEngine.setSessionManager: start: ", e);
}
}
// Report this property change to interested listeners
support.firePropertyChange("sessionManager", oldManager, this.manager);
}
/**
* Return the canonical, fully qualified, name of the engine
* this Container represents.
*/
public String getName()
{
return (name);
}
/**
* Set the canonical, fully qualified, name of the virtual Engine
* this Container represents.
*
* @param name Virtual Engine name
*
* @exception IllegalArgumentException if name is null
*/
public void setName(String name)
{
if (name == null)
{
throw new IllegalArgumentException(sm.getString("standardEngine.nullName"));
}
name = name.toLowerCase(); // Internally all names are lower case
String oldName = this.name;
this.name = name;
support.firePropertyChange("name", oldName, this.name);
}
/**
* Engine work directory base.
*/
public String getWorkDir()
{
if (workDir != null)
{
return workDir;
}
workDir = org.huihoo.willow.startup.Constants.WORK_DIRECTORY;
return (workDir);
}
public File getAbsoluteWorkDir()
{
String absolute_workDir = getWorkDir();
File absolute_workDirFile = new File(absolute_workDir);
if (!absolute_workDirFile.isAbsolute())
{
absolute_workDirFile =
new File(System.getProperty(Globals.PROPS_WILLOW_HOME), absolute_workDir);
}
return absolute_workDirFile;
}
/**
* Engine work directory base.
*/
public void setWorkDir(String workDir)
{
this.workDir = workDir;
}
// --------------------------------------------------------- Public Methods
/**
* Add a child Context
*
* @param child Child context to be added
*/
public void addChild(Context child)
{
log.debug("Add child " + child + " " + this);
synchronized (children)
{
if (children.get(child.getName()) != null)
{
throw new IllegalArgumentException(
"addChild: Child name '" + child.getName() + "' is not unique");
}
child.setEngine(this);
if (started && (child instanceof Lifecycle))
{
try
{
((Lifecycle) child).start();
}
catch (LifecycleException e)
{
log.error("standardEngine.addChild: start: ", e);
throw new IllegalStateException("standardEngine.addChild: start: " + e);
}
}
children.put(child.getName(), child);
}
}
/**
* Return descriptive information about this Container implementation and
* the corresponding version number, in the format
* <code><description>/<version></code>.
*/
public String getInfo()
{
return (info);
}
/**
* Start this Engine component.
*
* @exception LifecycleException if a startup error occurs
*/
public void start() throws LifecycleException
{
if (started)
{
return;
}
// Log our server identification information
log.info("Starting WorkflowService Engine: " + ServerInfo.getServerInfo());
if ((realmDatabase != null) && (realmDatabase instanceof Lifecycle))
{
try
{
((Lifecycle) realmDatabase).start();
}
catch (LifecycleException e)
{
log.error("StandardEngine.setRealmDatabase: start: ", e);
}
}
if (manager == null)
{
manager = new SessionManager();
manager.setEngine(this);
}
if ((manager != null) && (manager instanceof Lifecycle))
{
try
{
((Lifecycle) manager).start();
}
catch (LifecycleException e)
{
log.error("StandardEngine.setSessionManager: start: ", e);
}
}
// Start our child containers, if any
Context children[] = findChildren();
for (int i = 0; i < children.length; i++)
{
if (children[i] instanceof Lifecycle)
{
((Lifecycle) children[i]).start();
}
}
// Standard container startup
super.start();
}
public void stop() throws LifecycleException
{
// Stop our child containers, if any
Context children[] = findChildren();
for (int i = 0; i < children.length; i++)
{
if (children[i] instanceof Lifecycle)
{
((Lifecycle) children[i]).stop();
}
}
if ((realmDatabase != null) && (realmDatabase instanceof Lifecycle))
{
try
{
((Lifecycle) realmDatabase).stop();
}
catch (LifecycleException e)
{
log.error("StandardEngine.setRealmDatabase: stop: ", e);
}
}
if ((manager != null) && (manager instanceof Lifecycle))
{
try
{
((Lifecycle) manager).stop();
}
catch (LifecycleException e)
{
log.error("StandardEngine.setSessionManager: stop: ", e);
}
}
super.stop();
}
/**
* Return a String representation of this component.
*/
public String toString()
{
StringBuffer sb = new StringBuffer("StandardEngine[");
sb.append(getName());
sb.append("]");
return (sb.toString());
}
// ------------------------------------------------------- EngineDeployer Methods
/**
* Install a new web application, whose web application archive is at the
* specified URL, into this container with the specified context path.
* A context path of "" (the empty string) should be used for the root
* application for this container. Otherwise, the context path must
* start with a slash.
* <p>
* If this application is successfully installed, a ContainerEvent of type
* <code>INSTALL_EVENT</code> will be sent to all registered listeners,
* with the newly created <code>ParameterContext</code> as an argument.
*
* @param contextName The context path to which this application should
* be installed (must be unique)
* @param war A URL of type "jar:" that points to a WAR file, or type
* "file:" that points to an unpacked directory structure containing
* the web application to be installed
*
* @exception IllegalArgumentException if the specified context path
* is malformed (it must be "" or start with a slash)
* @exception IllegalStateException if the specified context path
* is already attached to an existing web application
* @exception IOException if an input/output error was encountered
* during installation
*/
public void install(String contextName, URL war) throws IOException
{
getDeployer().install(contextName, war);
}
/**
* Return the Context for the deployed application that is associated
* with the specified context path (if any); otherwise return
* <code>null</code>.
*
* @param contextName The context path of the requested web application
*/
public Context findDeployedApp(String contextName)
{
return (getDeployer().findDeployedApp(contextName));
}
/**
* Return the context paths of all deployed web applications in this
* Container. If there are no deployed applications, a zero-length
* array is returned.
*/
public String[] findDeployedApps()
{
return (getDeployer().findDeployedApps());
}
/**
* Remove an existing web application, attached to the specified context
* path. If this application is successfully removed, a
* ContainerEvent of type <code>REMOVE_EVENT</code> will be sent to all
* registered listeners, with the removed <code>Context</code> as
* an argument.
*
* @param contextName The context path of the application to be removed
*
* @exception IllegalArgumentException if the specified context path
* is malformed (it must be "" or start with a slash)
* @exception IllegalArgumentException if the specified context path does
* not identify a currently installed web application
* @exception IOException if an input/output error occurs during
* removal
*/
public void remove(String contextName) throws IOException
{
getDeployer().remove(contextName);
}
/**
* Remove an existing web application, attached to the specified context
* path. If this application is successfully removed, a
* ContainerEvent of type <code>REMOVE_EVENT</code> will be sent to all
* registered listeners, with the removed <code>Context</code> as
* an argument. Deletes the web application war file and/or directory
* if they exist in the Engine's appBase.
*
* @param contextName The context path of the application to be removed
* @param undeploy boolean flag to remove web application from server
*
* @exception IllegalArgumentException if the specified context path
* is malformed (it must be "" or start with a slash)
* @exception IllegalArgumentException if the specified context path does
* not identify a currently installed web application
* @exception IOException if an input/output error occurs during
* removal
*/
public void remove(String contextName, boolean undeploy) throws IOException
{
getDeployer().remove(contextName, undeploy);
}
/**
* Start an existing web application, attached to the specified context
* path. Only starts a web application if it is not running.
*
* @param contextName The context path of the application to be started
*
* @exception IllegalArgumentException if the specified context path
* is malformed (it must be "" or start with a slash)
* @exception IllegalArgumentException if the specified context path does
* not identify a currently installed web application
* @exception IOException if an input/output error occurs during
* startup
*/
public void start(String contextName) throws IOException
{
getDeployer().start(contextName);
}
/**
* Stop an existing web application, attached to the specified context
* path. Only stops a web application if it is running.
*
* @param contextName The context path of the application to be stopped
*
* @exception IllegalArgumentException if the specified context path
* is malformed (it must be "" or start with a slash)
* @exception IllegalArgumentException if the specified context path does
* not identify a currently installed web application
* @exception IOException if an input/output error occurs while stopping
* the web application
*/
public void stop(String contextName) throws IOException
{
getDeployer().stop(contextName);
}
/**
* 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()
{
lifecycle.fireLifecycleEvent("check", null);
count = (count + 1) % managerChecksFrequency;
if ((getSessionManager() != null) && (count == 0))
{
getSessionManager().processExpires();
}
}
// ------------------------------------------------------ Protected Methods
static String STANDARD_ENGINE_DEPLOYER = "org.huihoo.willow.core.StandardEngineDeployer";
public EngineDeployer getDeployer()
{
if (deployer != null)
{
return deployer;
}
log.info("Create Engine deployer for direct deployment ");
try
{
Class c = Class.forName(STANDARD_ENGINE_DEPLOYER);
deployer = (EngineDeployer) c.newInstance();
Method m = c.getMethod("setEngine", new Class[] { Engine.class });
m.invoke(deployer, new Object[] { this });
}
catch (Throwable t)
{
log.error("Error creating deployer ", t);
}
return deployer;
}
public void setDeployer(EngineDeployer d)
{
this.deployer = d;
}
/**
* Start the background thread that will periodically check for
* session timeouts.
*/
protected void threadStart()
{
if (thread != null)
return;
if (backgroundProcessorDelay <= 0)
return;
threadDone = false;
String threadName = "EngineBackgroundProcessor[" + toString() + "]";
thread = new Thread(new EngineBackgroundProcessor(), threadName);
thread.setDaemon(true);
thread.start();
}
/**
* Stop the background thread that is periodically checking for
* session timeouts.
*/
protected void threadStop()
{
if (thread == null)
return;
threadDone = true;
thread.interrupt();
thread = null;
}
// -------------------------------------- EngineExecuteDelay Inner Class
/**
* Private thread class to invoke the backgroundProcess method
* of this container and its children after a fixed delay.
*/
protected class EngineBackgroundProcessor implements Runnable
{
public void run()
{
while (!threadDone)
{
try
{
Thread.sleep(backgroundProcessorDelay * 1000L);
}
catch (InterruptedException e)
{
;
}
if (!threadDone)
{
Engine engine=StandardEngine.this;
ClassLoader cl = Thread.currentThread().getContextClassLoader();
try
{
backgroundProcess();
}
catch (Throwable t)
{
log.error("Exception invoking periodic operation: ", t);
}
finally
{
Thread.currentThread().setContextClassLoader(cl);
}
Context[] children = engine.findChildren();
for (int i = 0; i < children.length; i++)
{
if (children[i].getBackgroundProcessorDelay() <= 0)
{
try
{
if (children[i].getLoader() != null)
{
Thread.currentThread().setContextClassLoader(children[i].getLoader().getClassLoader());
}
children[i].backgroundProcess();
}
catch (Throwable t)
{
log.error("Exception invoking periodic operation: ", t);
}
finally
{
Thread.currentThread().setContextClassLoader(cl);
}
}
}
}
}
}
}
}