src.openwfe.org.engine.expressions.ParticipantExpression 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: ParticipantExpression.java 2718 2006-06-02 05:24:52Z jmettraux $
*/
//
// ParticipantExpression.java
//
// [email protected]
//
// generated with
// jtmpl 1.0.04 20.11.2001 John Mettraux ([email protected])
//
package openwfe.org.engine.expressions;
import openwfe.org.time.Time;
import openwfe.org.engine.Definitions;
import openwfe.org.engine.expool.ExpressionPool;
import openwfe.org.engine.history.History;
import openwfe.org.engine.workitem.Attribute;
import openwfe.org.engine.workitem.BooleanAttribute;
import openwfe.org.engine.workitem.CancelItem;
import openwfe.org.engine.workitem.HistoryItem;
import openwfe.org.engine.workitem.InFlowWorkItem;
import openwfe.org.engine.dispatch.DispatchingException;
import openwfe.org.engine.expressions.state.FrozenState;
import openwfe.org.engine.participants.Filter;
import openwfe.org.engine.participants.Participant;
import openwfe.org.engine.participants.ParticipantMap;
/**
* A leaf (terminal) expression : a reference to a participant.
* This expression now includes the 'dyna-participant' functionality
*
* CVS Info :
*
$Author: jmettraux $
*
$Date: 2006-06-02 07:24:52 +0200 (Fri, 02 Jun 2006) $
*
$Id: ParticipantExpression.java 2718 2006-06-02 05:24:52Z jmettraux $
*
* @author [email protected]
*/
public class ParticipantExpression
extends ZeroChildExpression
implements ExpressionWithTimeOut
{
private final static org.apache.log4j.Logger log = org.apache.log4j.Logger
.getLogger(ParticipantExpression.class.getName());
//
// CONSTANTS
/**
* The attribute 'ref' indicates to this expression which is the participant
* that should receive the workitem.
* See also 'default-ref', 'field-ref', 'variable-ref' and 'else-ref'.
*/
public final static String A_REF
= "ref";
/**
* Use this attribute 'filter' to link this participant expression to a
* filter.
*/
public final static String A_FILTER
= "filter";
private final static String HISTORY_TEXT
= "ok";
/**
* In the flow definition, use this attribute of 'participant' to
* determine the value of the '__description__' field.
*/
public final static String A_DESCRIPTION
= "description";
/**
* States the name of the field that will be added (and later removed) to
* the workitem if the description attribute is present.
*/
public final static String DESCRIPTION_FIELD_NAME
= "__description__";
/**
* use this parameter name to set the default participant who
* will receive the workitem if no participant is indicated
* in the workitem
*/
public final static String A_DEFAULT_REF
= "default-ref";
/**
* use this parameter name to tell the expression in which field of
* the incoming workitem the target participant name will be found
*/
public final static String A_FIELD_REF
= "field-ref";
/**
* use this param name to indicate to the engine in which variable it
* shall find the name of the participant to which it should dispatch the
* workitem.
*/
public final static String A_VARIABLE_REF
= "variable-ref";
/**
* When 'field-ref' is not set, the target participant is to be found
* in the '__next_participant__' field of the workitem. (And of course,
* if it is not set, default-ref, will be consulted)
*/
public final static String DEFAULT_FIELD_REF
= "__next_participant__";
/**
* This 'else-ref' attribute accepts a comma separated list of successful
* 'failover participant', ie participants that will receive the workitem
* if delivery failed to the regularly referenced participant ('ref').
* Each participant in this else list will get probed until dispatching
* is successful.
* If none of them is available, the participant expression will get frozen.
*/
public final static String A_ELSE_REF
= "else-ref";
/**
* If the attribute 'forget' is set to 'true', the participant expression
* will just take care of dispatching the workitem to the participant.
* It will not wait for the reply and will immediately hand the flow
* back to its parent expression.
*/
public final static String A_FORGET
= "forget";
/**
* If the dispatched workitem is to be forgotten by the engine, a field
* named '__forgotten__' will be set to true in it, so that the
* target could lookup this field and know the situation.
*/
public final static String F_FORGOTTEN_FIELD
= "__forgotten__";
//
// FIELDS
private Filter filter = null;
/* *
* If this participant has a filter, at dispatch time, the workitem
* is stored in this field (and the expression state is stored again
* in the expression pool via 'storeItself')
*/
private InFlowWorkItem appliedWorkitem = null;
/**
* If this value is defined, each time this expression is applied a field
* named '__description__' will be set in the workitem dispatched to the
* participant.
* When the participant will reply, the field will be unset.
*/
protected String description = null;
/**
* When setting the description field, this expression will
* keep track of the old value and set back when the participant replies.
*/
protected Attribute oldDescriptionValue = null;
protected String dynaParticipantName = null;
// it is determined once and only once, after it is
// kept, even when the expression gets stored
//
// CONSTRUCTORS
//
// BEAN METHODS
public Filter getFilter () { return this.filter; }
public InFlowWorkItem getAppliedWorkitem () { return this.appliedWorkitem; }
public void setFilter (Filter f) { this.filter = f; }
public void setAppliedWorkitem (InFlowWorkItem wi) { this.appliedWorkitem = wi; }
//
// METHODS
/*
* debug override
*/
public void setParent (final FlowExpressionId fei)
{
//log.debug("setParent() \n for "+this.getId()+"\n to "+fei);
//openwfe.org.Utils.logStackTrace(log, "setParent()");
super.setParent(fei);
}
/**
* Extracts the name of the participants who should receive the workitem
* (it looks in the expression and if necessary in the workitem).
*/
protected String getParticipantName (final InFlowWorkItem wi)
throws ApplyException
{
if (this.dynaParticipantName != null)
//
// alreay determined
//
return this.dynaParticipantName;
//log.debug("getParticipantName() name has to be determined");
//
// determine key for looking up participant
String defaultParticipantName = lookupAttribute(A_DEFAULT_REF, wi);
String ref = lookupAttribute(A_REF, wi);
String fieldRef = lookupAttribute(A_FIELD_REF, wi);
String variableRef = lookupAttribute(A_VARIABLE_REF, wi);
if (fieldRef == null && variableRef == null)
fieldRef = DEFAULT_FIELD_REF;
//
// lookup name of 'real participant'
Object participantName = null;
if (ref != null)
participantName = ref;
else if (fieldRef != null && this.appliedWorkitem != null)
participantName = this.appliedWorkitem.getAttribute(fieldRef);
else if (variableRef != null)
participantName = lookupVariable(variableRef);
if (participantName == null)
this.dynaParticipantName = defaultParticipantName;
else
this.dynaParticipantName = participantName.toString();
if (this.dynaParticipantName == null)
this.dynaParticipantName = lookupAttribute(A_REF, wi);
if (this.dynaParticipantName == null)
{
throw new ApplyException
("Could not determine dynamically or statically the target "+
"participant. Is '"+A_FIELD_REF+
"', '"+A_VARIABLE_REF+"' or '"+A_REF+
"' set in the flow definition ?");
}
//log.debug
// ("getParticipantName() result : >"+this.dynaParticipantName+"<");
return this.dynaParticipantName;
}
/**
* Retrieves the participant associated with this participant
*/
protected Participant lookupParticipant (final InFlowWorkItem wi)
throws ApplyException
{
//log.debug("this.attributes = "+this.attributes);
String participantName = getParticipantName(wi);
//log.debug("this.applicationContext = "+this.applicationContext);
final ParticipantMap pMap = Definitions.getParticipantMap(context());
if (pMap == null)
{
throw new ApplyException
("Cannot find participantMap service. "+
"Cannot retrieve any participant.");
}
final Participant result = pMap.get(participantName);
if (result == null)
{
throw new ApplyException
("Participant '"+participantName+"' not found");
}
wi.setParticipantName(participantName);
return result;
}
/**
* Applies this expression, ie dispatches the workitem to the
* participant according to the participant map
* (etc-engine/participants.xml)
*/
public void apply (final InFlowWorkItem wi)
throws ApplyException
{
touchApplyTime();
ValueUtils.cleanResult(wi);
// so that participants do not see any '__result__' field
//
// should forget ?
final String s = lookupAttribute(A_FORGET, wi);
final boolean shouldForget = (s != null && s.trim().equals("true"));
//
// determine filter and description now
if (this.getAttributes().keySet().contains(A_FILTER))
{
final String filterName = lookupAttribute(A_FILTER, wi);
final FilterDefinitionExpression fde =
(FilterDefinitionExpression)this.lookupVariable(filterName);
if (fde != null)
{
fde.setApplicationContext(this.context());
// making sure it has a context
this.filter = fde.buildFilter(wi);
}
else
{
log.warn("apply() did not find filter '"+filterName+"'");
}
}
this.description = lookupAttribute(A_DESCRIPTION, wi);
//log.debug("apply() description is \""+this.description+"\"");
//
// do the job...
this.tag(wi);
this.appliedWorkitem = (InFlowWorkItem)wi.clone();
//
// should we set a description field ?
if (this.description != null)
{
this.oldDescriptionValue = wi
.getAttribute(DESCRIPTION_FIELD_NAME);
wi.getAttributes().puts(DESCRIPTION_FIELD_NAME, this.description);
}
//
// apply filter if any
InFlowWorkItem itemToDispatch = wi;
if (this.filter != null)
itemToDispatch = this.filter.constrain(wi);
if (shouldForget)
itemToDispatch.getAttributes().puts(F_FORGOTTEN_FIELD, "true");
//
// store itself in the expression to keep track of apply time and
// other such fields
storeItself();
//
// dispatch
regularDispatch(itemToDispatch);
//
// forget ?
if (shouldForget)
{
if (log.isDebugEnabled())
log.debug("apply() forget is set to 'true', resuming flow");
try
{
wi.getAttributes().remove(F_FORGOTTEN_FIELD);
this.reply(wi);
}
catch (final ReplyException e)
{
throw new ApplyException
("participant with forget set to 'true' : "+
"failed to immediately reply to parent");
}
}
}
/**
* Does the job of regular (not 'else-ref' based dispatching).
*/
protected void dispatch
(final Participant participant,
final String participantName,
final InFlowWorkItem wi)
throws
ApplyException,
DispatchingException
{
// identify participant name
wi.setParticipantName(participantName);
// dispatch
participant.dispatch(context(), wi);
}
/**
* Initiates the dispatching mechanism.
*/
protected void regularDispatch (final InFlowWorkItem wi)
{
String pName = null;
try
{
final Participant p = lookupParticipant(wi);
pName = getParticipantName(wi);
dispatch(p, pName, wi);
this.historyLog
(wi,
History.EVT_DISPATCH,
pName,
this.getClass().getName());
return;
}
catch (final Throwable t)
{
if (log.isDebugEnabled())
{
log.debug
("regularDispatch() failed to dispatch to '"+
pName+"'", t);
}
log.warn
("regularDispatch() failed to dispatch to '"+
pName+"'\n"+t);
this.historyLog
(wi,
History.EVT_DISPATCH_FAILURE,
pName,
""+t);
}
elseDispatch(wi);
}
/**
* Is called by regularDispatch() in case of failure. Freezes the
* expression if it itself fails.
*/
protected void elseDispatch (final InFlowWorkItem wi)
{
final String sElseList = lookupAttribute(A_ELSE_REF, wi);
if (sElseList != null)
{
String[] ss = sElseList.split(",");
for (int i=0; i "+getId());
if (this.appliedWorkitem == null)
//
// no need to send a cancel item, the expression was not applied
//
return null;
final Participant participant =
lookupParticipant(this.appliedWorkitem);
final CancelItem ci = new CancelItem
(getId(), getParticipantName(this.appliedWorkitem));
try
{
participant.dispatch(context(), ci);
if (log.isDebugEnabled())
log.debug("cancel() cancelItem dispatched");
}
catch (DispatchingException de)
{
throw new ApplyException
("Failed to notify participant '"+
getParticipantName(this.appliedWorkitem)+
"' with cancel item", de);
}
super.cancel();
return this.appliedWorkitem;
}
/*
* cloning the filter is not necessary so this method override
* is not necessary anymore.
*
public Object clone ()
{
final ParticipantExpression clone =
(ParticipantExpression)super.clone();
clone.setFilter(getFilter());
return clone;
}
*/
//
// METHODS from ExpressionWithTimeOut
/**
* This method should return false if the purge daemon should not
* consider the implemented and instantiated Expression during its run.
*/
public boolean isTimeOutActivated ()
{
if (getApplyTime() == null ||
(this.getState() instanceof FrozenState))
{
return false;
}
return (determineTimeOut().longValue() != -1);
}
/**
* This method is called by the ExpressionStore when it notices that the
* ParticipantExpression expired.
*/
public void timeOutReply ()
throws ReplyException
{
//
// tagging the workitem with timeout history...
String participantName = "unknown";
try
{
participantName = getParticipantName(this.appliedWorkitem);
}
catch (final ApplyException e)
{
// leave to 'unknown'
}
this.appliedWorkitem.addHistoryItem
(participantName, "participant timed out");
//
// log timeout in history
historyLog
(this.appliedWorkitem, EVT_TIMED_OUT, participantName, "");
if (log.isDebugEnabled())
{
log.debug
("timeOutReply() "+
"setting '"+Definitions.V_TIMED_OUT+"' to 'true'");
}
//getExpressionPool().setVariableInParent
// (this, Definitions.V_TIMED_OUT, Boolean.TRUE);
this.appliedWorkitem.getAttributes()
.put(Definitions.V_TIMED_OUT, new BooleanAttribute(true));
if (log.isDebugEnabled())
log.debug("timeOutReply() cancelling self...");
try
{
this.cancel();
}
catch (ApplyException ae)
{
if (log.isDebugEnabled())
log.debug("Failed to send cancel message", ae);
throw new ReplyException
("Failed to send cancel message", ae);
}
//
// resume flow
if (log.isDebugEnabled())
log.debug("timeOutReply() resuming flow");
super.reply(this.appliedWorkitem);
}
/**
* By implementing this method, expressions define how their
* timeout is determined.
* The priority list for setting the timeout of a
* participant expression is :
*
* - expression's 'timeout' attribute
* - expression's variable scope ('__timeout__')
-
*
- participant's timeout parameter
* (as found in the participant map)
* - expressionPool's 'expressionTimeout' parameter (default)
*
*/
public Long determineTimeOut ()
{
Long timeOut = TimeoutUtils
.determineTimeout(this, this.appliedWorkitem);
if (timeOut == null)
//
// determine time out at participant level
{
Participant participant = null;
try
{
participant = this.lookupParticipant(this.appliedWorkitem);
}
catch (final ApplyException ae)
{
log.info
("determineTimeOut() "+
"Couldn't determine participant for timeout computation",
ae);
}
if (participant != null)
{
String sTimeOut = null;
if (participant.getParams() != null)
sTimeOut = (String)participant.getParams().get(TIMEOUT);
//if (sTimeOut != null)
timeOut = TimeoutUtils.determineTimeout(sTimeOut);
}
}
//
// determine timeOut at engine (expressionPool) level
// (lowest priority)
if (timeOut == null)
timeOut = getExpressionPool().getExpressionTimeOut();
// NB : returning a timeout of "-1" means timeout is deactivated
// for the participant
if (timeOut == null) timeOut = new Long(-1);
//
// this transformation shouldn't be necessary
if (log.isDebugEnabled())
{
if (timeOut.longValue() > -1)
{
log.debug
("determineTimeOut() "+
"time out for participant is set to "+timeOut.longValue()+
" ("+Time.toTimeString(timeOut, false)+").");
}
else
{
log.debug
("determineTimeOut() no time out for participant.");
}
}
return timeOut;
}
}