src.openwfe.org.engine.expressions.SubProcessRefExpression 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: SubProcessRefExpression.java 2846 2006-06-20 21:58:03Z jmettraux $
*/
//
// SubProcessRefExpression.java
//
// [email protected]
//
// generated with
// jtmpl 1.0.04 20.11.2001 John Mettraux ([email protected])
//
package openwfe.org.engine.expressions;
import openwfe.org.Utils;
import openwfe.org.OpenWfeException;
import openwfe.org.ApplicationContext;
import openwfe.org.xml.XmlUtils;
import openwfe.org.time.Time;
import openwfe.org.engine.Definitions;
import openwfe.org.engine.expool.PoolException;
import openwfe.org.engine.expool.ExpressionPool;
import openwfe.org.engine.launch.LaunchException;
import openwfe.org.engine.listen.reply.OkReply;
import openwfe.org.engine.history.History;
import openwfe.org.engine.workitem.Attribute;
import openwfe.org.engine.workitem.LaunchItem;
import openwfe.org.engine.workitem.InFlowWorkItem;
import openwfe.org.engine.workitem.StringAttribute;
import openwfe.org.engine.workitem.StringMapAttribute;
import openwfe.org.engine.expressions.raw.RawExpression;
/**
* A leaf (terminal) expression : a reference to a subProcess
*
* CVS Info :
*
$Author: jmettraux $
*
$Date: 2006-06-20 23:58:03 +0200 (Tue, 20 Jun 2006) $
*
$Id: SubProcessRefExpression.java 2846 2006-06-20 21:58:03Z jmettraux $
*
* @author [email protected]
*/
public class SubProcessRefExpression
extends ZeroChildExpression
implements WithChildren
{
private final static org.apache.log4j.Logger log = org.apache.log4j.Logger
.getLogger(SubProcessRefExpression.class.getName());
//
// CONSTANTS
/**
* Instructs the expression to look for the subprocess name in the given
* variable.
*/
public final static String A_VARIABLE_REF
= "variable-ref";
/**
* Instructs the expression to look for the subprocess name in the given
* field of the applied workitem.
*/
public final static String A_FIELD_REF
= "field-ref";
/**
* The name of the subprocess to launch is given directly as a ref.
* It may also be possible that this ref contains a dollar notation
* syntax referencing a variable or a field like in "${target}" or
* "${field:target}" or even "${field:target_prefix}_${index}".
* See the 'dollar notation' in the documentation.
*/
public final static String A_REF
= "ref";
/**
* When the attribute 'forget' of this expression is set to 'true' (or
* 'yes'), then the flow initiating the subprocess won't block until
* it finishes, it won't wait on it.
*/
public final static String A_FORGET
= "forget";
/**
* When the attribute 'fields' is present, the workitem in use in the
* new subprocess instance will be empty except for the fields named as
* value of this attribute.
* If this attribute has an empty value, the workitem will have no fields
* at all.
* If this attribute is not present, the workitem will be a copy of the
* workitem in use in the parent flow.
*/
public final static String A_FIELDS
= "fields";
/**
* In the subprocess itself, the variable __cparams__ will contain an
* XML representation of the children of the subprocess as called.
*/
public final static String V_CPARAMS
= "__cparams__";
/**
* '::' is used in workflow definition URLs like
* "mainEngine::http://flowdef.server.company/flow1.xml" or
* "secondaryEngine::http://flowdef.server.other.company/flow3.xml",
* it separates the name of the engine supposed to run the flow from
* the URL of the definition itself.
*/
public final static String ENGINE_SEPARATOR = "::";
//
// FIELDS
private FlowExpressionId subId = null;
private boolean forgetting = false;
private java.util.Map launchVariables = null;
private ChildrenTracker tracker = null;
//
// CONSTRUCTORS
//
// BEAN METHODS
/*
* debug
*
public void setParent (final FlowExpressionId fei)
{
log.debug("setParent() \n to "+fei+"\n for "+this.getId());
super.setParent(fei);
}
*/
public FlowExpressionId getSubId ()
{
return this.subId;
}
public void setSubId (final FlowExpressionId fei)
{
this.subId = fei;
}
public boolean isForgetting ()
{
return this.forgetting;
}
public void setForgetting (final boolean b)
{
this.forgetting = b;
}
public java.util.Map getLaunchVariables ()
{
return this.launchVariables;
}
public void setLaunchVariables (final java.util.Map m)
{
this.launchVariables = m;
}
public ChildrenTracker getTracker ()
{
return this.tracker;
}
public void setTracker (final ChildrenTracker ct)
{
this.tracker = ct;
}
//
// METHODS from FlowExpression
/**
* Either launches a subProcess, either launches an external process
*/
public void apply (final InFlowWorkItem wi)
throws ApplyException
{
/*
log.debug("apply() id : "+this.getId());
log.debug("apply() parent : "+this.getParent());
log.debug("apply() next : "+this.getNext());
*/
//
// preparation
this.forgetting = shouldForget(wi);
// ref
String ref = determineReference(wi);
ref = checkIfLocalEngine(ref);
if (log.isDebugEnabled())
log.debug("apply() ref >"+ref+"< for "+this.getId());
if (isUrl(ref))
{
outerLaunch(wi, ref);
return;
}
innerLaunch(wi, ref);
}
/**
* If the launch was done, will reply to the parent expression, else
* it will trigger the launch (if all children did reply).
* This method is synchronized to avoid collision between children replies.
*/
public synchronized void reply (final InFlowWorkItem wi)
throws ReplyException
{
if (log.isDebugEnabled())
log.debug("reply() from "+wi.getLastExpressionId());
//xmlDebugDump("reply");
//
// some debug output
if (this.tracker == null)
{
log.debug("reply() no tracker : replying to parent.");
replyToParent(wi);
return;
}
//
// a child replies...
this.tracker.reply(wi);
if (this.tracker.hasNext())
{
log.debug("reply() applying next child");
this.tracker.applyNext(this);
return;
}
//
// fetch the rest of the content of the tracker
this.launchVariables.put
//(A_CONTENT, this.tracker.getRemainingContent());
(V_CPARAMS, this.tracker.getRemainingContent());
//log.debug
// ("reply() remainingContent : \n"+
// this.tracker.getRemainingContent());
//
// fetch the results from the children evaluation
this.launchVariables.putAll(this.tracker.getResults());
//
// children did reply, now do the launch...
try
{
doLaunch(this.tracker.getWorkitem());
}
catch (final ApplyException ae)
{
throw new ReplyException
("'after children evaluation' sublaunch failure", ae);
}
this.tracker = null;
//
// nullifying tracker so that next call to reply() will
// end up in a call to the parent expression.
}
/**
* Launches a flow whose definition has to be fetched from a URL.
* Such flows never cares about 'nested children'.
*/
protected void outerLaunch
(final InFlowWorkItem wi,
final String ref)
throws
ApplyException
{
final InFlowWorkItem launchWi = prepareSubWorkitem(wi);
FlowExpressionId parentId = this.getId();
//this.launchDone = true;
if (isForgetting())
//
// no parent id needed for subflow
//
parentId = null;
else
//
// store itself, subprocess will reply
// (potentially very late, thus a bit of persistence is required)
//
this.storeItself();
//
// history logging
historyLog
(wi,
History.EVT_FLOW_START,
null,
"launching ref=\""+ref+"\"");
//
// do the launch
try
{
getLauncher().launch(launchWi, parentId, ref, areLaunchesAsync());
// there are no variables passed to 'outer' subprocesses
}
catch (final LaunchException le)
{
throw new ApplyException
("outer launch failure "+ref, le);
}
if (this.isForgetting())
//
// don't wait for any reply from the subprocess just launched
//
applyToParent(wi);
}
/**
* Launches a flow whose definition is an already know subprocess
* (already bound locally or globally (engine-level)).
*/
public void innerLaunch
(final InFlowWorkItem wi,
final String ref)
throws
ApplyException
{
//
// launching
if (log.isDebugEnabled())
log.debug("innerLaunch() ref is \""+ref+"\"");
final Object oValue = lookupVariable(ref);
if (oValue == null)
{
throw new ApplyException
("ref '"+ref+"' points to nothing.");
}
if (log.isDebugEnabled())
{
log.debug
("innerLaunch() oValue.class "+oValue.getClass().getName());
}
if ( ! (oValue instanceof FlowExpressionId) &&
! (oValue instanceof RawExpression))
{
log.warn
("innerLaunch() oValue class : "+oValue.getClass().getName());
throw new ApplyException
("ref '"+ref+"' doesn't point to a subprocess.");
}
RawExpression rawSub = null;
if (oValue instanceof RawExpression)
{
rawSub = (RawExpression)oValue;
rawSub = (RawExpression)rawSub.clone();
rawSub.getId().setWorkflowInstanceId
(RawExpression.determineNewWorkflowInstanceId());
try
{
getExpressionPool().add(rawSub);
}
catch (final PoolException e)
{
throw new ApplyException
("Failed to store new template into expression pool", e);
}
this.subId = rawSub.getId();
}
else
{
this.subId = (FlowExpressionId)oValue;
final FlowExpression re = getExpressionPool().fetch(this.subId);
if (re == null)
{
throw new ApplyException
("innerLaunch() "+
"subprocess '"+ref+
"' points at "+this.subId+
" but it's gone from the expool");
}
if (log.isDebugEnabled())
{
log.debug
("innerLaunch() "+this.subId+
" is of class "+re.getClass().getName());
}
/*
if ( ! (re instanceof RawExpression))
{
log.debug
("cannot launch subprocess out of "+
re.getClass().getName());
Utils.logStackTrace(log, "innerLaunch()");
return;
}
*/
rawSub = (RawExpression)re;
}
if (log.isDebugEnabled())
log.debug("innerLaunch() inner subprocess at "+this.subId);
//
// feeding attributes into variables
this.launchVariables = new java.util.HashMap();
final java.util.Iterator it =
this.getAttributes().keySet().iterator();
while (it.hasNext())
{
final String key = (String)it.next();
this.launchVariables.put(key, lookupAttribute(key, wi));
//log.debug("innerLaunch() put variable '"+key+"'");
}
//final String[] sChildrenToMap = rawSub.getChildrenToMap();
final String s =
rawSub.lookupAttribute(DefineExpression.A_MAP_CHILDREN, wi);
if (log.isDebugEnabled())
log.debug("innerLaunch() map-children=\""+s+"\"");
String[] sChildrenToMap = null;
if (s != null) sChildrenToMap = s.split(",\\s*");
if (sChildrenToMap == null || sChildrenToMap.length < 1)
{
log.debug("innerLaunch() no children to map, launching directly");
doLaunch(wi);
return;
}
else if (log.isDebugEnabled())
{
log.debug
("innerLaunch() childrenToMap.length : "+sChildrenToMap.length);
}
//
// if (sChildrenToMap.length > 0)
final String sContent = (String)this.launchVariables.get(A_CONTENT);
this.tracker = new ChildrenTracker();
//log.debug("innerLaunch() new tracker : "+this.tracker);
this.tracker.init(sContent, sChildrenToMap, wi);
//this.storeItself();
//
// done in the tracker
// begin tracking children
try
{
this.tracker.applyNext(this);
}
catch (final ReplyException re)
{
throw new ApplyException
("failed to begin with children evaluation", re);
}
}
/**
* This method is called by reply() when all the (nested) children
* replied.
*/
protected void doLaunch
(final InFlowWorkItem wi)
throws
ApplyException
{
FlowExpressionId parentId = this.getId();
if (this.isForgetting())
{
parentId = null;
}
if (log.isDebugEnabled())
{
log.debug("doLaunch() this.id is "+this.getId());
log.debug("doLaunch() this.parent is "+this.getParent());
log.debug("doLaunch() parentId is "+parentId);
}
//
// making sure that the variable __cparams__ is set
String sContent = (String)this.launchVariables.get(A_CONTENT);
if (sContent != null &&
( ! this.launchVariables.keySet().contains(V_CPARAMS)))
{
this.launchVariables.put(V_CPARAMS, sContent);
if (log.isDebugEnabled())
log.debug("doLaunch() cparams from content : "+sContent);
}
//
// launching
try
{
getLauncher().launchSub
((InFlowWorkItem)wi.clone(),
parentId,
this.subId, // template id
this.launchVariables,
areLaunchesAsync());
}
catch (final LaunchException le)
{
throw new ApplyException
("Failed to launch inner subprocess "+this.subId, le);
}
if (this.isForgetting())
//
// don't wait for a reply of the subprocess
//
applyToParent(wi);
}
/*
* Checks whether a ref is a URL or not (it may also comprise
* a 'engineId::' prefix).
*/
private boolean isUrl (final String ref)
throws ApplyException
{
if (ref == null || ref.length() < 1)
{
throw new ApplyException
("Cannot launch null or \"\" flow url");
}
if (Utils.isUrl(ref)) return true;
final int i = ref.indexOf("::");
if (i > -1)
{
if (i+2 == ref.length()) return false;
// avoiding 'out of index'...
return Utils.isUrl(ref.substring(i+2));
}
return false;
}
//
// METHODS overridden from FlowExpression
/**
* this method is overriden to allow later resolution of the referenced
* subProcess (ie linking of the expression to the references subProcess).
*/
public void setAttributes (java.util.Map m)
{
super.setAttributes(m);
}
//
// METHODS
/**
* Returns true if flow launches should return immediately (this info
* is fetched from the engine's application context).
*/
protected boolean areLaunchesAsync ()
{
return context().getBoolean(ExpressionPool.P_ASYNC_LAUNCH, true);
}
/*
* This method will remove any 'engine::' prefix if engine is
* the id of the local engine
* (thus the launch will be internal)
*
* (should help fix bug #928820)
*/
private String checkIfLocalEngine (final String reference)
{
if (reference.indexOf(ENGINE_SEPARATOR) < 0) return reference;
final String localEngineId = context().getApplicationName();
if (localEngineId == null)
{
log.warn
("application param '"+Definitions.ENGINE_ID+
"' is missing, cannot determine engineId");
return reference;
}
String[] ss = reference.split("::");
if (ss[0].equals(localEngineId)) return ss[1];
return reference;
}
/**
* Given expression attributes, workitem fields and this expression
* variables, determines the ref, ie the subprocess name or url that
* is supposed to be launched.
*/
protected String determineReference (final InFlowWorkItem wi)
throws ApplyException
{
final String varName = lookupAttribute(A_VARIABLE_REF, wi);
if (varName != null)
{
String ref = (String)lookupVariable(varName);
if (ref == null)
{
throw new ApplyException
("no variable set under name '"+varName+"'");
}
return ref;
}
final String fieldName = lookupAttribute(A_FIELD_REF, wi);
if (fieldName != null)
{
Attribute aRef = wi.getAttributes().get(fieldName);
String ref = null;
if (aRef != null) ref = aRef.toString();
if (ref == null)
{
throw new ApplyException
("workitem holds no field named '"+fieldName+"'");
}
return ref;
}
final String ref = lookupAttribute(A_REF, wi);
if (ref == null)
{
throw new ApplyException
("No 'variable-ref', 'field-ref' or 'ref' attribute in "+
"'subprocess' expression");
}
return ref;
}
private void addFieldsFromRegex
(final String regex,
final InFlowWorkItem parentWi,
final InFlowWorkItem newWi)
{
final java.util.Iterator it =
parentWi.getAttributes().keyNamesMatching(regex).iterator();
while (it.hasNext())
{
final String fieldName = ((String)it.next()).trim();
if (log.isDebugEnabled())
log.debug("prepareFields() adding field '"+fieldName+"'");
final Attribute a = parentWi.getAttribute(fieldName);
newWi.getAttributes().put(new StringAttribute(fieldName), a);
}
}
private void addFieldWithValue
(final String assignation,
//final InFlowWorkItem parentWi,
final InFlowWorkItem newWi)
{
final String[] ss = assignation.split(" *= *");
final String fieldName = ss[0].trim();
final String value = ss[1].trim();
if (log.isDebugEnabled())
{
log.debug
("addFieldWithValue() newWi['"+fieldName+"'] = '"+value+"'");
}
newWi.getAttributes().put
(new StringAttribute(fieldName), new StringAttribute(value));
}
private void addFieldAlias
(final String aliasing,
final InFlowWorkItem parentWi,
final InFlowWorkItem newWi)
{
final String[] ss = aliasing.split(" *as *");
final String newFieldName = ss[0].trim();
final String parentFieldName = ss[1].trim();
final Attribute parentValue = parentWi.getAttribute(parentFieldName);
if (parentValue != null)
{
newWi.getAttributes()
.put(new StringAttribute(newFieldName), parentValue);
}
}
private InFlowWorkItem prepareSubWorkitem (final InFlowWorkItem parentWi)
{
final String sFields = lookupAttribute(A_FIELDS, parentWi);
final InFlowWorkItem newWi = (InFlowWorkItem)parentWi.clone();
if (sFields == null) return newWi;
newWi.setAttributes(new StringMapAttribute());
String[] fields = sFields.split(", *");
for (int i=0; i -1)
addFieldWithValue(fields[i], newWi);
else if (fields[i].indexOf(" as ") > -1)
addFieldAlias(fields[i], parentWi, newWi);
else
addFieldsFromRegex(fields[i], parentWi, newWi);
}
return newWi;
}
/*
* wi.field > var > wfd
*/
private boolean shouldForget (final InFlowWorkItem wi)
{
// may be dangerous ??!!
if (Utils.toBoolean(wi.getAttribute(A_FORGET)))
return true;
if (Utils.toBoolean(lookupVariable(A_FORGET)))
return true;
if (Utils.toBoolean(lookupAttribute(A_FORGET, wi)))
return true;
return false;
}
}