
src.openwfe.org.engine.impl.launch.SimpleXmlLauncher Maven / Gradle / Ivy
/*
* Copyright (c) 2001-2006, John Mettraux, OpenWFE.org
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* . Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* . Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* . Neither the name of the "OpenWFE" nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* $Id: SimpleXmlLauncher.java 2947 2006-07-17 08:04:50Z jmettraux $
*/
//
// SimpleXmlLauncher.java
//
// [email protected]
//
// generated with
// jtmpl 1.1.01 2004/05/19 ([email protected])
//
package openwfe.org.engine.impl.launch;
import openwfe.org.Utils;
import openwfe.org.ReflectionUtils;
import openwfe.org.AbstractService;
import openwfe.org.xml.XmlUtils;
import openwfe.org.engine.Definitions;
import openwfe.org.engine.launch.Launcher;
import openwfe.org.engine.launch.LaunchException;
import openwfe.org.engine.launch.ProcessLibrary;
import openwfe.org.engine.expool.ExpressionPool;
import openwfe.org.engine.workitem.Attribute;
import openwfe.org.engine.workitem.XmlAttribute;
import openwfe.org.engine.workitem.LaunchItem;
import openwfe.org.engine.workitem.InFlowWorkItem;
import openwfe.org.engine.workitem.AttributeUtils;
import openwfe.org.engine.dispatch.DispatchingException;
import openwfe.org.engine.expressions.ValueUtils;
import openwfe.org.engine.expressions.Environment;
import openwfe.org.engine.expressions.WithChildren;
import openwfe.org.engine.expressions.ApplyException;
import openwfe.org.engine.expressions.BuildException;
import openwfe.org.engine.expressions.FlowExpression;
import openwfe.org.engine.expressions.FlowExpressionId;
import openwfe.org.engine.expressions.DefineExpression;
import openwfe.org.engine.expressions.ProcessDefinition;
import openwfe.org.engine.expressions.AbstractFlowExpression;
import openwfe.org.engine.expressions.SubProcessRefExpression;
import openwfe.org.engine.expressions.raw.RawExpression;
import openwfe.org.engine.participants.Participant;
import openwfe.org.engine.participants.ParticipantMap;
/**
* The vanilla XML openwfe launcher.
*
* CVS Info :
*
$Author: jmettraux $
*
$Id: SimpleXmlLauncher.java 2947 2006-07-17 08:04:50Z jmettraux $
*
* @author [email protected]
*/
public class SimpleXmlLauncher
extends AbstractService
implements Launcher
{
private final static org.apache.log4j.Logger log = org.apache.log4j.Logger
.getLogger(SimpleXmlLauncher.class.getName());
//
// CONSTANTS & co
/**
* This constant is the name of a system property, when it has a value
* of 'true', 'ok' or 'yes', XML schema validation will be used
* against process definitions.
*/
public final static String VALIDATE
= "openwfe.org.engine.launcher.validate";
//
// FIELDS
//private XmlResourceLoader loader = new XmlResourceLoader();
// caching documents
//
// CONSTRUCTORS
//
// METHODS from Launcher
public FlowExpressionId launch (final LaunchItem li, final boolean async)
throws LaunchException
{
final InFlowWorkItem wi = new InFlowWorkItem(li);
//Definitions.getXmlCoder(getContext()).dump("launch", wi);
return
launch(wi, li.getReplyTo(), li.getWorkflowDefinitionUrl(), async);
}
private org.jdom.Element parseXmlDefinition
(final String url,
final InFlowWorkItem wi,
final boolean shouldValidate)
throws
Exception
{
if (log.isDebugEnabled())
log.debug("parseXmlDefinition() from "+url);
if (url.startsWith(URL_FIELD_PREFIX))
//
// the process definition seems to be embedded in a field
// of the workitem
{
final String fieldName = url.substring(URL_FIELD_PREFIX.length());
//final Attribute a = wi.getAttribute(fieldName);
final Attribute a = wi.getAttributes().getField(fieldName);
wi.getAttributes().unsetField(fieldName);
if (a == null)
{
throw new IllegalArgumentException
("Field '"+fieldName+
"' is not present, cannot launch flow defined in it");
}
if (a instanceof XmlAttribute)
{
org.jdom.Content content = ((XmlAttribute)a).getContent();
return (org.jdom.Element)content;
}
final String sXmlDefinition = a.toString();
//log.debug("parseXmlDefinition()\n"+sXmlDefinition);
org.jdom.Element elt = XmlUtils.extractXmlElement(sXmlDefinition);
return elt;
}
//
// classical case : the process definition is pointed at
// through an HTTP URL or something like that
return XmlUtils.extractXml(url, shouldValidate);
}
/**
* Returns the workflowInstanceId of the newly launched flow.
*/
public FlowExpressionId launch
(final InFlowWorkItem wi,
final FlowExpressionId parentId,
final String wfdUrl,
final boolean async)
throws
LaunchException
{
//
// remote launch ?
if (wfdUrl.indexOf(SubProcessRefExpression.ENGINE_SEPARATOR) > -1)
// engine separator is "::"
{
final String wfid = launchExternalProcess(wi, wfdUrl, parentId);
final FlowExpressionId result = new FlowExpressionId();
result.setOwfeVersion(Definitions.OPENWFE_VERSION);
result.setWorkflowInstanceId(wfid);
return result;
}
//
// local launch
final String url = Utils.expandUrl(wfdUrl);
if (log.isDebugEnabled())
log.debug("launch() request for "+url);
try
{
final org.jdom.Element elt =
parseXmlDefinition(url, wi, shouldValidate());
final RawExpression rootExpression =
RawExpression.getEmptyRawExpression(getContext());
//
// misc data
final String wfdName = elt.getAttributeValue("name");
final String wfdRevision = elt.getAttributeValue("revision");
if (wfdName == null || wfdRevision == null)
{
throw new LaunchException
("Process definition is missing a 'name' "+
"and/or a 'revision' attribute.");
}
//
// determine root expression ids
if (parentId == null)
//
// brand new flow
{
FlowExpressionId pid = new FlowExpressionId();
pid.setOwfeVersion(Definitions.OPENWFE_VERSION);
pid.setEngineId(getContext().getApplicationName());
pid.setInitialEngineId(getContext().getApplicationName());
pid.setWorkflowDefinitionUrl(url);
pid.setWorkflowDefinitionName(wfdName);
pid.setWorkflowDefinitionRevision(wfdRevision);
// expression name and id remain to null !!
pid.setWorkflowInstanceId
(RawExpression.determineNewWorkflowInstanceId());
//
// init the expression
rootExpression.init
(getContext(), null, pid, 0, elt);
//
// starting at expressionId '0'
// envId : null
}
else
//
// determine root expression id manually
{
final FlowExpressionId id = new FlowExpressionId();
id.setOwfeVersion(Definitions.OPENWFE_VERSION);
id.setEngineId(getContext().getApplicationName());
id.setInitialEngineId(getContext().getApplicationName());
id.setWorkflowDefinitionUrl(url);
id.setWorkflowDefinitionName(wfdName);
id.setWorkflowDefinitionRevision(wfdRevision);
id.setExpressionName(elt.getName());
id.setExpressionId("0");
id.setWorkflowInstanceId
(RawExpression.determineNewWorkflowInstanceId());
//
// init the expression
rootExpression.init
(getContext(),
null, // envId
parentId,
id,
elt);
}
getExpressionPool().add(rootExpression);
final java.util.Map vars = new java.util.HashMap(1);
vars.put(wfdName, rootExpression.clone());
if (log.isDebugEnabled())
log.debug("launch() '"+wfdName+"' -> "+rootExpression.getId());
//
// do the launch
final FlowExpressionId fei = launchSub
(wi, parentId, rootExpression.getId(), vars, async);
if (log.isDebugEnabled())
{
log.debug
("launch() launched as sub : "+fei);
log.debug
("launch() returning : "+fei.getWorkflowInstanceId());
}
return fei;
}
catch (final Throwable t)
{
throw new LaunchException("launch failed", t);
}
}
/**
* Launches an inner subflow (for example for 'iterator' or 'when').
*
* @param variables the set of variables that have to be put in the new
* environment of the expression. If this param is null, no new environment
* will be created and the expression will use the environment of the
* parent expression (if there is one).
*/
public FlowExpressionId launchSub
(final InFlowWorkItem wi,
final FlowExpressionId parentId,
final FlowExpressionId templateId,
final java.util.Map variables,
final boolean async)
throws
LaunchException
{
loadLibrary();
if (log.isDebugEnabled())
{
log.debug("launchSub() for template "+templateId);
log.debug("launchSub() parentId is "+parentId);
}
RawExpression rawExp = (RawExpression)getExpressionPool()
.fetch(templateId);
try
{
rawExp = rawExp.newInstance(parentId);
//
// this method adds the new expression to the expool
if (log.isDebugEnabled())
log.debug("launchSub() rawExp.parent was "+rawExp.getParent());
rawExp.setParent(parentId);
//
// especially if it's null
//log.debug("launchSub() rawExp.parent is "+rawExp.getParent());
if (variables != null || parentId == null)
//
// build new environment
//
// variables != null : the launching exp requests explicitely
// a new environment
// parent == null : the launched, being an orphan, must have
// a new environment
{
log.debug("launchSub() generating a new env for self");
final Environment env =
Environment.generateEnvironment(rawExp);
//rawExp.storeItself();
//
// it's better here : it fixes bug #1453357 as well
if (log.isDebugEnabled())
log.debug("launchSub() generated env : "+env.getId());
if (variables != null)
//
// subprocess 'parameters'
//
{
env.putAll(variables);
//env.storeItself();
//
// already done in putAll()
}
}
else // (parentId != null)
//
// else : variables is null and parent id is not null
{
log.debug("launchSub() using parent's env");
FlowExpression parent = getExpressionPool().fetch(parentId);
rawExp.setEnvironmentId(parent.getEnvironmentId());
}
rawExp.storeItself();
final FlowExpressionId id = rawExp.getId();
//addChildToParent(parentId, id);
//
// Was previously done in the Iterator.
// Is only necessary of SynchableExpression parents.
//
// applying
final InFlowWorkItem subwi = (InFlowWorkItem)wi.clone();
//Definitions.getXmlCoder(getContext()).dump("launchsub1_", subwi);
if (log.isDebugEnabled())
{
log.debug("launchSub() rawExp.id is "+id);
log.debug("launchSub() rawExp.parent is "+rawExp.getParent());
//log.debug("launchSub() applying "+id);
log.debug("launchSub() launching "+id);
log.debug("launchSub() async ? "+async);
}
if (async)
{
(new Thread()
{
public void run ()
{
try
{
log.debug("launchSub() (now in async thread)");
//getExpressionPool().apply(id, subwi);
getExpressionPool().launch(id, subwi);
if (log.isDebugEnabled())
log.debug("launchSub() launched "+id);
}
catch (final Throwable t)
{
log.warn("launchSub() failure", t);
}
}
}).start();
}
else
{
//getExpressionPool().apply(id, subwi);
getExpressionPool().launch(id, subwi);
}
if (log.isDebugEnabled())
{
log.debug
("launchSub() launched "+id.getWorkflowInstanceId());
}
return id;
}
catch (final Exception e)
{
log.debug
("Failed to 'launch' RawExpression", e);
throw new LaunchException
("Failed to 'launch' RawExpression", e);
}
}
/**
* Fetches the attributes from the uninterpreted raw version of a flow
* expression.
* If rawExpression is an instance of java.util.Map, it will be
* returned straight away.
*/
public java.util.Map fetchAttributes
(final FlowExpression fe, final Object rawExpression)
throws
BuildException
{
if (rawExpression instanceof java.util.Map)
return (java.util.Map)rawExpression;
final org.jdom.Element e = (org.jdom.Element)rawExpression;
final java.util.Map result = XmlUtils.fetchAttributes(e);
if (fe instanceof WithChildren)
{
final String text = e.getTextTrim();
if (text != null &&
text.length() > 0 &&
e.getChildren().size() < 1)
//
// truc
//
{
result.put(WithChildren.A_VALUE, e.getTextTrim());
result.put(WithChildren.A_CONTENT, XmlUtils.xmlToString(e));
}
else if (e.getContent().size() > 0)
//
//
//
// true
//
//
//
{
result.put(WithChildren.A_CONTENT, XmlUtils.xmlToString(e));
}
}
return result;
}
/**
* This method is used by droflo to load completety a workflow
* definition for display or edition.
*/
public ProcessDefinition loadProcessDefinition (final String wfdUrl)
throws BuildException
{
//log.debug("loadProcessDefinition(u)");
final String url = Utils.expandUrl(wfdUrl);
org.jdom.Element elt = null;
try
{
elt = XmlUtils.extractXml(url, shouldValidate());
}
catch (final Throwable t)
{
throw new BuildException("Failed to load "+url, t);
}
return loadProcessDefinition(url, null, elt);
}
/**
* Loads a sub process definition.
*/
public ProcessDefinition loadProcessDefinition
(final FlowExpressionId parentId,
final Object rawExpression)
throws
BuildException
{
//log.debug("loadProcessDefinition(p, e)");
org.jdom.Element xmlBranch = null;
if (rawExpression instanceof org.jdom.Element)
{
xmlBranch = (org.jdom.Element)rawExpression;
}
else if (rawExpression instanceof RawExpression)
{
xmlBranch = (org.jdom.Element)
((RawExpression)rawExpression).getRaw();
}
if (xmlBranch == null)
{
throw new BuildException
("Cannot turn an instance of class "+
rawExpression.getClass().getName()+
" into a process definition");
}
return loadProcessDefinition(null, parentId, xmlBranch);
}
/*
* Either wfdUrl is null, either elt is null, not both should be null
* at the same time.
*/
private ProcessDefinition loadProcessDefinition
(final String wfdUrl,
final FlowExpressionId parentId,
final org.jdom.Element elt)
throws
BuildException
{
//log.debug("loadProcessDefinition(u, p, e)");
String wfdName = null;
String wfdRevision = null;
FlowExpressionId pId = parentId;
if (pId == null)
{
pId = new FlowExpressionId();
//
// misc data
wfdName = elt.getAttributeValue("name");
wfdRevision = elt.getAttributeValue("revision");
//
// tweak parent id
pId.setOwfeVersion(Definitions.OPENWFE_VERSION);
pId.setWorkflowDefinitionUrl(wfdUrl);
pId.setWorkflowDefinitionName(wfdName);
pId.setWorkflowDefinitionRevision(wfdRevision);
}
else
{
wfdName = pId.getWorkflowDefinitionName();
wfdRevision = pId.getWorkflowDefinitionRevision();
}
try
{
final RawExpression rootExpression =
RawExpression.getEmptyRawExpression(getContext());
//
// inits the root expression
rootExpression.init
(getContext(), null, pId, 0, elt);
//log.debug
// ("loadProcessDefinition(d, e) root exp is "+
// rootExpression.getId());
final ProcessDefinition result = new ProcessDefinition
(getContext(),
wfdUrl,
wfdName,
wfdRevision);
final FlowExpression fe = rootExpression.resolveExpression(result);
if (log.isDebugEnabled())
{
log.debug
("loadProcessDefinition() "+
"found something of class >"+fe.getClass().getName()+"<");
}
result.setSelf((DefineExpression)fe);
//log.debug
// ("loadProcessDefinition(d, e) result is \n"+
// result.toString());
return result;
}
catch (final Throwable t)
{
throw new BuildException("Failed to load "+wfdUrl, t);
}
}
/**
* This method is used by droflo to load completety a workflowd
* definition for display or edition.
*/
public ProcessDefinition loadProcessDefinitionFromString
(final String xmlDefinition)
throws
BuildException
{
try
{
return loadProcessDefinition
(null, // wfdUrl
XmlUtils
.extractXmlDocument(xmlDefinition, shouldValidate())
.getRootElement());
}
catch (final BuildException be)
{
throw be;
}
catch (final Exception e)
{
throw new BuildException
("Failed to extract xml from string xml definition", e);
}
}
/**
* This is used by the EvalExpression and also by the
* SubProcessRefExpression when evaluating nested children.
* This method doesn't apply the newly created tree of expressions,
* it simply returns it.
*
* @param caller the FlowExpression calling this eval method.
* @param rawScript the snippet of POOL (Process Oriented OpenWFE Lisp)
* that has to be evaluated.
*
* @return the newly created RawExpression, ready to be applied.
*/
public RawExpression eval
(final FlowExpression caller,
final Object rawScript,
final InFlowWorkItem wi)
throws
Exception
{
if (rawScript == null)
{
log.debug("eval() nothing to eval.");
return null;
}
//
// is a Text (CDATA element) ?
if (rawScript instanceof org.jdom.Text)
//
// trying to return what's behind the variable name
{
final String varName = ((org.jdom.Text)rawScript).getTextTrim();
final Object o = caller.lookupVariable(varName);
if (o != null)
ValueUtils.setResult(wi, AttributeUtils.java2owfe(o));
log.debug("eval() just some text.");
return null;
}
if ( ! (rawScript instanceof org.jdom.Element))
{
throw new ApplyException
("cannot handle XML fragment of class "+
rawScript.getClass().getName());
}
if (log.isDebugEnabled())
{
log.debug
("eval() xml is \n"+
XmlUtils.xmlToString((org.jdom.Content)rawScript));
}
//
// prepare the evaluation work
final RawExpression evalResult =
RawExpression.getEmptyRawExpression(caller.context());
//final FlowExpressionId evalId = caller.getId().copy();
//evalId.setExpressionName(((org.jdom.Element)rawScript).getName());
evalResult.init
(caller.context(),
caller.getEnvironmentId(),
caller.getId(),
//evalId,
0,
rawScript);
getExpressionPool().add(evalResult);
return evalResult;
}
//
// METHODS
protected ExpressionPool getExpressionPool ()
{
return Definitions.getExpressionPool(getContext());
}
protected ParticipantMap getParticipantMap ()
{
return Definitions.getParticipantMap(getContext());
}
/**
* This method is called when the process referenced is an
* external process (not defined as a subprocess of the current
* process definition).
*/
protected String launchExternalProcess
(final InFlowWorkItem wi,
final String ref,
final FlowExpressionId parentId)
throws
LaunchException
{
final String[] ss = ref.split("::");
final String engineId = ss[0];
final String processUrl = ss[1];
if (log.isDebugEnabled())
{
log.debug("launchExternalProcess() engineId >"+engineId+"<");
log.debug("launchExternalProcess() processUrl >"+processUrl+"<");
}
//
// prepare target engine
final Participant participant = getParticipantMap().get(engineId);
//
// prepare launchitem
final LaunchItem li = new LaunchItem
(processUrl,
parentId,
wi);
//
// launch
try
{
final Object o = participant.dispatch(getContext(), li);
return o.toString();
}
catch (final DispatchingException de)
{
throw new LaunchException
("Dispatching (launch) to engine '"+engineId+"' failed", de);
}
catch (final Throwable t)
{
log.warn("launchExternalProcess() unexpected exception", t);
log.warn("launchExternalProcess() returning flowInstanceId = null");
}
return null;
}
/*
* Ensures the library processes got loaded and interpreted.
*/
private void loadLibrary ()
throws LaunchException
{
ProcessLibrary lib = (ProcessLibrary)getContext()
.get(Definitions.S_PROCESS_LIBRARY);
if (lib == null) return;
lib.load();
//
// if the interpretation has already been done, this method call
// returns immediately
}
//
// STATIC METHODS
/**
* Returns true if the VM property 'openwfe.org.launcher.validate' is
* set to true.
*/
public static boolean shouldValidate ()
{
String s = System.getProperty(VALIDATE);
if (s == null) return false;
s = s.toLowerCase();
return
s.equals("true") ||
s.equals("yes") ||
s.equals("ok") ||
s.equals("validate");
}
/**
* Returns a mapping between language codes and the description of
* the workflow definition in that language.
*/
public static java.util.Map extractXmlDescription
(final String processDefinitionUrl)
{
try
{
java.util.Map result = new java.util.HashMap();
org.jdom.Element root = XmlUtils
.extractXml(new java.net.URL(processDefinitionUrl), false);
result.put("root.element.name", root.getName());
// a small trick useful for Launchable instances
java.util.Iterator it = root.getChildren("description").iterator();
while (it.hasNext())
{
org.jdom.Element description = (org.jdom.Element)it.next();
String dLanguage = description.getAttributeValue("language");
String descriptionText = description.getTextNormalize();
if (dLanguage == null)
result.put("default", descriptionText);
else
result.put(dLanguage, descriptionText);
}
return result;
}
catch (Exception e)
{
log.info
("Failed to load description from "+processDefinitionUrl,
e);
return new java.util.HashMap(0);
}
}
/**
* Returns the description of the process in the default language.
*/
public static String extractXmlDescription
(final String processDefinitionUrl, final String language)
{
java.util.Map descriptionMap =
extractXmlDescription(processDefinitionUrl);
if (language == null)
return (String)descriptionMap.get("default");
String description = (String)descriptionMap.get(language);
if (description != null) return description;
return (String)descriptionMap.get("default");
}
}