
src.openwfe.org.engine.expressions.sync.GenericSyncExpression 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: GenericSyncExpression.java 2668 2006-05-25 15:15:15Z jmettraux $
*/
//
// GenericSyncExpression.java
//
// [email protected]
//
// generated with
// jtmpl 1.1.00 16.08.2003 John Mettraux ([email protected])
//
package openwfe.org.engine.expressions.sync;
import java.util.Collections;
import openwfe.org.Utils;
import openwfe.org.engine.workitem.InFlowWorkItem;
import openwfe.org.engine.expressions.ApplyException;
import openwfe.org.engine.expressions.ReplyException;
import openwfe.org.engine.expressions.FlowExpression;
import openwfe.org.engine.expressions.FlowExpressionId;
import openwfe.org.engine.expressions.CompositeFlowExpression;
/**
* All the concurrence (multi-choice) patterns may be resolved by this
* sync expression.
*
*
* <concurrence
* sync="generic"
* count="x|*"
* merge="first|last|highest|lowest"
* merge-type="mix|override"
* remaining="cancel|forget"
* >
*
*
* - count: on how many concurrence branches should we wait ?
* - merge: which branch shall have priority for the merge ?
* - merge-type: mixing fields or completely obsfucating with fields
* of the 'winning' incoming workitem ?
* - remaining: when count is reached, should we cancel or let
* other branches die (forgotten) ?
*
*
* CVS Info :
*
$Author: jmettraux $
*
$Id: GenericSyncExpression.java 2668 2006-05-25 15:15:15Z jmettraux $
*
* @author [email protected]
*/
public class GenericSyncExpression
extends ClassicalSyncExpression
{
private final static org.apache.log4j.Logger log = org.apache.log4j.Logger
.getLogger(GenericSyncExpression.class.getName());
//
// CONSTANTS & co
/**
* Since OpenWFE 1.5.0, this generic sync expression is able
* to act like the other sync expressions (whose classes became
* obsolete).
*/
public final static String A_SYNC
= "sync";
public final static String A_COUNT
= "count";
public final static String A_MERGE
= "merge";
public final static String A_MERGE_TYPE
= "merge-type";
public final static String A_REMAINING
= "remaining";
public final static String MG_FIRST
= "first";
public final static String MG_LAST
= "last";
public final static String MG_HIGHEST
= "highest";
public final static String MG_LOWEST
= "lowest";
public final static String DEFAULT_MG
= MG_FIRST;
public final static String MT_MIX
= "mix";
public final static String MT_OVERRIDE
= "override";
public final static String DEFAULT_MT
= MT_MIX;
public final static String REM_CANCEL
= "cancel";
public final static String REM_FORGET
= "forget";
public final static String DEFAULT_REM
= REM_CANCEL;
//
// FIELDS
// config fields
private int count = -1;
private String merge = null;
private String mergeType = null;
private String remaining = null;
// run fields
private java.util.List childrenAltitudes = null;
private int replyCount = 0;
private InFlowWorkItem overridenWorkitem = null;
private java.util.List incomingWorkitems = null;
private java.util.List unreadyQueue = null;
//
// CONSTRUCTORS
/*
public GenericSyncExpression ()
{
super();
}
*/
/**
* This method is called by SyncUtils.determineSyncExpression().
* It parses the attributes of the parent [synchable]Expression to
* determine on this sync should behave.
*/
public void init
(final SynchableExpression se,
final java.util.List childList,
final InFlowWorkItem wi)
{
//
// do the job
if (log.isDebugEnabled())
log.debug("init() on behalf of "+se.getId());
//
// count
final String sCount = se.lookupAttribute(A_COUNT, wi);
try
{
this.count = Integer.parseInt(sCount);
}
catch (final Exception e)
{
this.count = -1;
}
if (log.isDebugEnabled())
log.debug("init() count : "+this.count);
//
// merge
this.merge = se.lookupAttribute(A_MERGE, wi);
if ( ! (MG_FIRST.equals(this.merge) ||
MG_LAST.equals(this.merge) ||
MG_HIGHEST.equals(this.merge) ||
MG_LOWEST.equals(this.merge)))
{
this.merge = DEFAULT_MG;
}
if (log.isDebugEnabled())
log.debug("init() merge : '"+this.merge+"'");
//
// merge type
this.mergeType = se.lookupAttribute(A_MERGE_TYPE, wi);
if ( ! (MT_MIX.equals(this.mergeType) ||
MT_OVERRIDE.equals(this.mergeType)))
{
this.mergeType = DEFAULT_MT;
}
if (log.isDebugEnabled())
log.debug("init() merge : '"+this.mergeType+"'");
//
// remaining
this.remaining = se.lookupAttribute(A_REMAINING, wi);
if ( ! (REM_CANCEL.equals(this.remaining) ||
REM_FORGET.equals(this.remaining)))
{
this.remaining = DEFAULT_REM;
}
if (log.isDebugEnabled())
log.debug("init() remaining : '"+this.remaining+"'");
//
// prepare workitem queues
if (childList == null)
{
this.incomingWorkitems = new java.util.LinkedList();
this.unreadyQueue = new java.util.LinkedList();
}
else
{
this.incomingWorkitems = new java.util.ArrayList(childList.size());
this.unreadyQueue = new java.util.ArrayList(childList.size());
}
//
// keep track of the order among children
// (which is the highest ?)
if (this.merge.equals(MG_LAST) || this.merge.equals(MG_FIRST))
//
// don't do the following [unnecessary operations]
{
return;
}
this.childrenAltitudes = listAltitudes(childList);
}
private java.util.List listAltitudes (final java.util.List childList)
{
if (childList == null) return new java.util.LinkedList();
final java.util.List result = new java.util.ArrayList(childList.size());
final java.util.Iterator it = childList.iterator();
while (it.hasNext())
{
final FlowExpressionId fei = (FlowExpressionId)it.next();
//log.debug("listAltitudes() adding "+fei);
result.add(fei);
}
return result;
}
//
// BEAN METHODS
// config fields
public int getCount () { return this.count; }
public String getMerge () { return this.merge; }
public String getMergeType () { return this.mergeType; }
public String getRemaining () { return this.remaining; }
public void setCount (final int i) { this.count = i; }
public void setMerge (final String s) { this.merge = s; }
public void setMergeType (final String s) { this.mergeType = s; }
public void setRemaining (final String s) { this.remaining = s; }
// run fields
/**
* Returns how many children have replied so far.
*/
public int getReplyCount ()
{
return this.replyCount;
}
/**
* When 'merge' is set to 'override', this field contains the
* temporary result of the sync.
*/
public InFlowWorkItem getOverridenWorkitem ()
{
return this.overridenWorkitem;
}
/**
* The list of workitems already arrived back from the children.
*/
public java.util.List getIncomingWorkitems ()
{
return this.incomingWorkitems;
}
/**
* The returned list is used by this expression to keep track of
* the 'altitude' of each child.
* This measurement is used in case of 'highest' or 'lowest' merge
* style.
*/
public java.util.List getChildrenAltitudes ()
{
return this.childrenAltitudes;
}
/**
* A sync expression is not ready until its unreadyQueue is flushed and
* set to null ;
* replies to an unready sync expression are stored in this unreadyQueue.
*/
public java.util.List getUnreadyQueue ()
{
return this.unreadyQueue;
}
public void setReplyCount (final int i)
{
this.replyCount = i;
}
public void setOverridenWorkitem (final InFlowWorkItem wi)
{
this.overridenWorkitem = wi;
}
public void setIncomingWorkitems (final java.util.List l)
{
this.incomingWorkitems = l;
}
public void setChildrenAltitudes (final java.util.List l)
{
this.childrenAltitudes = l;
}
public void setUnreadyQueue (final java.util.List l)
{
this.unreadyQueue = l;
}
//
// METHODS from SyncExpression
/**
* From the verb 'to ready' : readies the SyncExpression instance
* (eventually flushes its reply queue).
*/
public synchronized void ready (final SynchableExpression se)
throws ReplyException
{
if (this.unreadyQueue != null)
{
final java.util.List queue = this.unreadyQueue;
this.unreadyQueue = null;
//
// sync expression is now considered as ready.
//synchronized (se)
//{
se.storeItself();
//}
final java.util.Iterator it = queue.iterator();
while (it.hasNext())
{
final InFlowWorkItem wi = (InFlowWorkItem)it.next();
//log.debug("ready() replying for "+wi.getLastExpressionId());
this.reply(se, wi);
}
}
}
public void addChild (final FlowExpressionId fei)
{
super.addChild(fei);
if (this.childrenAltitudes != null)
this.childrenAltitudes.add(fei.getWorkflowInstanceId());
}
private void removeChild (final FlowExpressionId fei)
{
//
// 1st attempt : with the fei
boolean removed = getChildren().remove(fei);
if (removed)
{
log.debug("removeChild() removed (fei)");
return;
}
//
// 2nd attempt : with the workflowInstanceId
FlowExpressionId toRemove = null;
final java.util.Iterator it = this.getChildren().iterator();
while (it.hasNext())
{
final FlowExpressionId child = (FlowExpressionId)it.next();
if (child.getWorkflowInstanceId()
.equals(fei.getWorkflowInstanceId()))
{
toRemove = child;
break;
}
}
if (toRemove != null)
{
log.debug("removeChild() removed (wfid)");
getChildren().remove(toRemove);
}
}
public synchronized void reply
(final SynchableExpression se, final InFlowWorkItem wi)
throws
ReplyException
{
if (this.unreadyQueue != null)
//
// sync expression not yet ready
{
log.debug("reply() sync expression not ready : queueing reply");
this.unreadyQueue.add(wi.clone());
//synchronized (se)
//{
se.storeItself();
//}
return;
}
if (log.isDebugEnabled())
log.debug("reply() from "+wi.getLastExpressionId());
final int childCount = this.getChildren().size();
//log.debug("reply() child count : "+childCount);
//log.debug("reply() before :\n"+dumpChildren());
//
// is sync over ?
boolean syncIsOver = false;
receiveWorkitem(wi);
removeChild(wi.getLastExpressionId());
syncIsOver = syncIsOver ||
getChildren().size() < 1;
syncIsOver = syncIsOver ||
(getCount() > 0 && this.replyCount >= this.count);
if (log.isDebugEnabled())
{
log.debug
("reply() delta is "+(childCount - this.getChildren().size()));
//log.debug
// ("reply() after :\n"+dumpChildren());
log.debug
("reply() is sync over for "+se.getId()+" ? "+syncIsOver);
}
if (syncIsOver)
{
terminateSync(se);
return;
}
//
// sync is not over, let's save the synchable expression
//synchronized (se)
//{
se.storeItself();
//}
}
/*
* Stores the workitem for later synchronization (there are optimizations
* here for some sync tactics like MT_OVERRIDE)
*/
private void receiveWorkitem (final InFlowWorkItem wi)
{
//log.debug
// ("receiveWorkitem() wi comes from "+wi.getLastExpressionId());
//log.debug
// ("receiveWorkitem() replyCount is "+this.replyCount);
//
//if (this.overridenWorkitem != null)
//{
// log.debug
// ("receiveWorkitem() overridenWi is "+
// this.overridenWorkitem.getLastExpressionId());
//}
//
//log.debug
// ("receiveWorkitem() incomingWorkitems size is "+
// getIncomingWorkitems().size());
this.replyCount++;
//
// if MT_MIX
if (getMergeType().equals(MT_MIX))
{
if (getMerge().equals(MG_HIGHEST) || getMerge().equals(MG_LOWEST))
{
getIncomingWorkitems().add(wi.clone());
return;
}
if (this.overridenWorkitem == null)
{
this.overridenWorkitem = (InFlowWorkItem)wi.clone();
return;
}
if (getMerge().equals(MG_FIRST))
{
this.overridenWorkitem = MergeUtils.merge
(wi, this.overridenWorkitem);
return;
}
//
// else if MG_LAST
this.overridenWorkitem = MergeUtils.merge
(this.overridenWorkitem, wi);
return;
}
//
// else MT_OVERRIDE
if (this.overridenWorkitem == null)
{
this.overridenWorkitem = (InFlowWorkItem)wi.clone();
return;
}
if (getMerge().equals(MG_FIRST))
{
if (this.overridenWorkitem != null) return;
this.overridenWorkitem = (InFlowWorkItem)wi.clone();
return;
}
if (getMerge().equals(MG_LAST))
{
this.overridenWorkitem = (InFlowWorkItem)wi.clone();
return;
}
// MG_HIGHEST or MG_LOWEST
final int currentAltitude = determineAltitude(this.overridenWorkitem);
final int incomingAltitude = determineAltitude(wi);
//log.debug("receiveWorkitem() currentAltitude "+currentAltitude);
//log.debug("receiveWorkitem() incomingAltitude "+incomingAltitude);
if (incomingAltitude < 0)
{
throw new IllegalArgumentException
("Not waiting on the incoming workitem");
}
else if (getMerge().equals(MG_HIGHEST))
{
if (incomingAltitude < currentAltitude)
this.overridenWorkitem = (InFlowWorkItem)wi.clone();
}
else // if (getMerge().equals(MG_LOWEST))
{
if (incomingAltitude > currentAltitude)
this.overridenWorkitem = (InFlowWorkItem)wi.clone();
}
}
/*
* Returns at which altitude the participant expression that emitted
* the workitem is located in the chilren altitude list.
*/
private int determineAltitude (final InFlowWorkItem wi)
{
final String wfiid = wi.getLastExpressionId().getWorkflowInstanceId();
return getChildrenAltitudes().indexOf(wfiid);
}
/*
* Does the work of terminating the synchronization.
*/
private void terminateSync (final SynchableExpression se)
throws ReplyException
{
//
// should we cancel or forget (stub) remaining children ?
try
{
treatRemainingChildren(se);
}
catch (final ApplyException ae)
{
log.warn
("Failed to '"+getRemaining()+
"' remaining children", ae);
throw new ReplyException
("Failed to '"+getRemaining()+
"' remaining children", ae);
}
//
// determine resulting workitem
InFlowWorkItem resultingWorkitem = null;
if (this.overridenWorkitem != null)
{
resultingWorkitem = this.overridenWorkitem;
}
else //if (getMergeType().equals(MT_MIX))
{
//log.debug("terminateSync() doing the merge...");
java.util.Collections.sort(getChildrenAltitudes());
if (getMerge().equals(MG_HIGHEST))
java.util.Collections.reverse(getChildrenAltitudes());
final java.util.Map answerMap = determineAnswerMap();
final java.util.Iterator it = getChildrenAltitudes().iterator();
while (it.hasNext())
{
final String wfiid = (String)it.next();
//log.debug("terminateSync() considering altitude "+wfiid);
final InFlowWorkItem workitem =
(InFlowWorkItem)answerMap.get(wfiid);
if (workitem == null) continue;
//log.debug
// ("terminateSync() workitem is "+
// workitem.getLastExpressionId());
resultingWorkitem =
MergeUtils.merge(resultingWorkitem, workitem);
//log.debug
// ("terminateSync() resulting workitem is "+
// resultingWorkitem.getLastExpressionId());
}
}
//
// this log output should be disabled after a certain
// test period.
//
if (resultingWorkitem == null)
log.warn("reply() resultingWorkitem is null");
//
// reply to parent expression
se.replyToParent(resultingWorkitem);
}
/*
* Returns a mapping fei: wi
*/
private java.util.Map determineAnswerMap ()
{
final java.util.Map result =
new java.util.HashMap(getIncomingWorkitems().size());
final java.util.Iterator it = getIncomingWorkitems().iterator();
while (it.hasNext())
{
final InFlowWorkItem wi = (InFlowWorkItem)it.next();
//log.debug
// ("determineAnswerMap() found answer for "+
// wi.getLastExpressionId().getWorkflowInstanceId());
result.put(wi.getLastExpressionId().getWorkflowInstanceId(), wi);
}
return result;
}
private void treatRemainingChildren
(final SynchableExpression se)
throws
ApplyException
{
if (log.isDebugEnabled())
{
log.debug
("treatRemainingChildren() - "+getChildren().size()+
" children in total");
}
final java.util.List alreadyReplied =
new java.util.ArrayList(getIncomingWorkitems().size());
java.util.Iterator it = getIncomingWorkitems().iterator();
while (it.hasNext())
{
final InFlowWorkItem wi = (InFlowWorkItem)it.next();
alreadyReplied.add(wi.getLastExpressionId());
}
it = getChildren().iterator();
while (it.hasNext())
{
final FlowExpressionId fei = (FlowExpressionId)it.next();
if (alreadyReplied.contains(fei)) continue;
if (log.isDebugEnabled())
{
log.debug
("treatRemainingChildren() '"+getRemaining()+"' on "+fei);
}
if (REM_CANCEL.equals(getRemaining()))
{
se.getExpressionPool().childCancel(/*(FlowExpression)se, */fei);
}
else
{
se.getExpressionPool().forget(fei);
}
}
}
//
// METHODS
/*
private String dumpChildren ()
{
final StringBuffer sb = new StringBuffer();
sb
.append("---children---")
.append(" instance : ")
.append(this)
.append("\n");
final java.util.Iterator it = this.getChildren().iterator();
while (it.hasNext())
{
sb
.append(" * ")
.append(it.next().toString())
.append("\n");
}
sb.append("---c---");
return sb.toString();
}
*/
}