/*
* This file is part of the WfMOpen project.
* Copyright (C) 2001-2003 Danet GmbH (www.danet.de), GS-AN.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Id: AbstractProcess.java 2677 2007-12-04 12:54:46Z drmlipp $
*
* $Log$
* Revision 1.24 2007/09/21 06:19:35 mlipp
* Fixed problem with NamingException during process deletion.
*
* Revision 1.23 2007/09/14 12:34:41 drmlipp
* Improved map initialization.
*
* Revision 1.22 2007/05/03 21:58:16 mlipp
* Internal refactoring for making better use of local EJBs.
*
* Revision 1.21 2007/03/27 21:59:43 mlipp
* Fixed lots of checkstyle warnings.
*
* Revision 1.20 2007/02/27 14:34:13 drmlipp
* Some refactoring to reduce cyclic dependencies.
*
* Revision 1.19 2007/01/25 23:00:31 mlipp
* Fixed result().
*
* Revision 1.18 2006/12/03 22:44:20 mlipp
* Fixed return object of requester() for subflows.
*
* Revision 1.17 2006/11/19 21:53:47 mlipp
* Finished support for native Java types.
*
* Revision 1.16 2006/10/19 11:16:30 drmlipp
* Block activity deadlines cannot be delayed.
*
* Revision 1.15 2006/10/17 22:58:39 mlipp
* Continuing implementation of suspended exception handling.
*
* Revision 1.14 2006/10/17 15:55:12 drmlipp
* Introducing new state ...suspended.abandoning.
*
* Revision 1.13 2006/10/07 20:41:34 mlipp
* Merged J2EE 1.4 adaptions from test branch.
*
* Revision 1.12 2006/09/29 12:32:08 drmlipp
* Consistently using WfMOpen as projct name now.
*
* Revision 1.11 2006/07/18 13:18:14 drmlipp
* Merged changes from wfmopen-1.3.x branch up to 1.3.4.
*
* Revision 1.6.2.20 2006/03/15 10:46:07 drmlipp
* Fixed problems with E4X and empty input/output documents.
*
* Revision 1.6.2.19 2006/03/14 16:44:27 drmlipp
* Fixed loop.
*
* Revision 1.6.2.18 2006/03/07 13:51:32 drmlipp
* Finished transition to E4X usage for actual parameter evaluation.
*
* Revision 1.10 2006/03/08 14:46:43 drmlipp
* Synchronized with 1.3.3p5.
*
* Revision 1.9 2005/10/10 20:02:12 mlipp
* Synchronized with 1.3.3.
*
* Revision 1.8 2005/04/22 15:10:53 drmlipp
* Merged changes from 1.3 branch up to 1.3p15.
*
* Revision 1.6.2.11 2005/04/22 14:09:27 drmlipp
* Support specification of all extended attributes in Value attribute.
*
* Revision 1.6.2.10 2005/04/18 11:11:20 drmlipp
* More event handling optimization.
*
* Revision 1.6.2.9 2005/04/16 21:18:30 drmlipp
* Made audit event filtering more flexible and added possibility to turn
* off audit log.
*
* Revision 1.6.2.8 2005/04/15 21:07:39 drmlipp
* Added "storeAuditEvents" flag.
*
* Revision 1.6.2.7 2005/04/15 08:26:34 drmlipp
* Fixed isHandled.
*
* Revision 1.6.2.6 2005/04/14 21:40:52 drmlipp
* Optimized event feedback.
*
* Revision 1.6.2.5 2005/04/14 15:08:51 drmlipp
* Added "filterable" event auditing.
*
* Revision 1.7 2005/02/04 14:25:26 drmlipp
* Synchronized with 1.3rc2.
*
* Revision 1.6.2.4 2005/02/01 21:15:51 drmlipp
* Fixed audit event generation for deferred choice.
*
* Revision 1.6.2.3 2005/02/01 16:08:41 drmlipp
* Implemented deferred choice.
*
* Revision 1.6.2.2 2005/01/31 21:12:58 drmlipp
* Continued implementation of deferred choice.
*
* Revision 1.6.2.1 2005/01/31 15:41:12 drmlipp
* Started implementation of deferred choice.
*
* Revision 1.6 2005/01/21 09:34:56 drmlipp
* Moved initialization of debug attribute to proper class.
*
* Revision 1.5 2005/01/12 22:10:35 mlipp
* Added trim to string comparison.
*
* Revision 1.4 2005/01/10 22:19:35 mlipp
* Added extension attribute for debug mode.
*
* Revision 1.3 2005/01/02 20:49:13 mlipp
* First version of debug mode.
*
* Revision 1.2 2004/08/19 13:24:49 drmlipp
* Fixed AVK errors and (many) warnings.
*
* Revision 1.1.1.6 2004/08/18 15:17:38 drmlipp
* Update to 1.2
*
* Revision 1.201 2004/07/14 12:08:39 lipp
* Fixed problem with default for condition type.
*
* Revision 1.200 2004/07/14 11:06:03 lipp
* Supplied default for condition type.
*
* Revision 1.199 2004/07/04 17:34:19 lipp
* Fixed problem with event source.
*
* Revision 1.198 2004/06/30 12:05:39 lipp
* Added jelly variable scoping.
*
* Revision 1.197 2004/06/29 14:52:46 lipp
* Unpacking values from JavaScript argument evaluation.
*
* Revision 1.196 2004/06/29 13:16:29 lipp
* Fixed jelly script argument access.
*
* Revision 1.195 2004/06/28 20:32:00 lipp
* Added support for accessing process data in jelly scripts.
*
* Revision 1.194 2004/06/28 14:59:49 lipp
* Started jelly interpretation of actual XML arguments.
*
* Revision 1.193 2004/05/09 18:42:59 lipp
* Finished process instantiation restructuring.
*
* Revision 1.192 2004/05/07 15:02:27 lipp
* Removed legacy initialization code.
*
* Revision 1.191 2004/05/06 19:39:17 lipp
* Restructured block activity handling.
*
* Revision 1.190 2004/05/05 09:44:07 lipp
* Finished SAX based process creation (no cleanup of old code, yet).
*
* Revision 1.189 2004/05/04 19:48:07 lipp
* Getting on with SAX based process creation.
*
* Revision 1.188 2004/05/03 15:38:11 lipp
* Getting on with SAX based process creation.
*
* Revision 1.187 2004/04/30 13:46:19 lipp
* Getting on with SAX based initialization.
*
* Revision 1.186 2004/04/30 12:44:53 lipp
* Fixed SAX initialization bug.
*
* Revision 1.185 2004/04/29 15:39:31 lipp
* Getting on with SAX based initialization.
*
* Revision 1.184 2004/04/28 14:19:20 lipp
* Getting started with SAX based process creation.
*
* Revision 1.183 2004/04/12 19:33:52 lipp
* Clarified application invocation interface.
*
* Revision 1.182 2004/04/06 21:18:39 lipp
* Reflect value conversions in audit event.
*
* Revision 1.181 2004/04/06 15:34:19 lipp
* Supporting values of type Document.
*
* Revision 1.180 2004/04/01 20:53:04 lipp
* Fixed problem with missing Type attribute.
*
* Revision 1.179 2004/03/25 14:41:46 lipp
* Added possibility to specify actual parameters as XML.
*
* Revision 1.178 2004/03/21 12:37:12 lipp
* Removed left over event logger.
*
* Revision 1.177 2004/03/20 21:08:43 lipp
* Added access to requesting processes' channels.
*
* Revision 1.176 2004/03/20 19:28:02 lipp
* Keeping relationship to super-flow both for sync and asynch call.
*
* Revision 1.175 2004/03/18 09:14:45 lipp
* Workaround for transformer bug.
*
* Revision 1.174 2004/02/21 14:42:43 lipp
* Moved TransitionManager to domain package. Having it in its own
* package caused too many circular dependencies (and there are good
* points for having it in the domain package anyway).
*
* Revision 1.173 2004/02/16 15:38:51 lipp
* Fixed handling of non-well formed result values.
*
* Revision 1.172 2004/02/06 13:37:35 lipp
* Added channel close notification.
*
* Revision 1.171 2004/01/21 09:54:45 lipp
* Adapted to contextSignature modification.
*
* Revision 1.170 2003/12/16 21:56:02 lipp
* Track all activity closures, transition manager might be reused.
*
* Revision 1.169 2003/12/16 16:46:25 lipp
* Let process close activities to avoid inconsistent transition manager
* states.
*
* Revision 1.168 2003/11/26 16:40:12 huaiyang
* workflow for jboss calling ejbs toString.
*
* Revision 1.167 2003/11/17 16:16:36 lipp
* Fixed refresh.
*
* Revision 1.166 2003/10/28 13:24:16 lipp
* Fixed handling of RemoveException.
*
* Revision 1.165 2003/09/25 12:00:05 lipp
* Avoid client dependency on rhino.
*
* Revision 1.164 2003/09/25 11:01:20 lipp
* Fixed usage of jsScope (may not be used remotely).
*
* Revision 1.163 2003/09/24 13:48:49 lipp
* Fixed JavaScript access to process relevant data of type Date. Fixed
* thread handling for block activities.
*
* Revision 1.162 2003/09/23 17:05:04 lipp
* Fixed various problems with block activities.
*
* Revision 1.161 2003/09/22 20:50:12 lipp
* Most of deadline handling for block activities.
*
* Revision 1.160 2003/09/22 15:53:44 lipp
* Receiving deadline events.
*
* Revision 1.159 2003/09/22 12:51:44 lipp
* Fixed key creation for block activity (must be unique).
*
* Revision 1.158 2003/09/22 12:32:57 lipp
* Implemented deadline creation for block activities.
*
* Revision 1.157 2003/09/21 21:28:14 lipp
* Introducing "virtual" block activity.
*
* Revision 1.156 2003/09/19 13:11:13 lipp
* New way of handling timeouts for subflows.
*
* Revision 1.155 2003/09/18 14:08:16 lipp
* Added abandon method to handle deadlines.
*
* Revision 1.154 2003/09/15 15:43:40 lipp
* Initial version of handling exceptions in transition manager.
*
* Revision 1.153 2003/09/04 08:46:44 lipp
* Fixed client dependency on rhino.jar.
*
* Revision 1.152 2003/07/09 13:25:14 lipp
* Accept strings as values for fields of type performer.
*
* Revision 1.151 2003/07/04 15:54:14 lipp
* Fixed handling of W3C DOM result.
*
* Revision 1.150 2003/06/27 08:51:45 lipp
* Fixed copyright/license information.
*
* Revision 1.149 2003/06/26 22:08:04 lipp
* Added handling of JDOM results.
*
* Revision 1.148 2003/06/05 21:21:20 lipp
* Real fix of state table problem.
*
* Revision 1.147 2003/06/05 17:24:44 lipp
* Fixed bug in transition table.
*
* Revision 1.146 2003/05/31 20:05:25 lipp
* Added support for different condition types.
*
* Revision 1.145 2003/05/25 20:47:00 lipp
* Fixed initialization.
*
* Revision 1.144 2003/05/16 08:08:49 lipp
* Handling OTHERWISE condition type.
*
* Revision 1.143 2003/05/07 14:45:49 lipp
* Implemented synchronous subflow.
*
* Revision 1.142 2003/05/05 14:39:50 lipp
* Moved code for removing process automatically to event handling.
*
* Revision 1.141 2003/05/05 07:04:51 lipp
* Handling parameters for sub-flow now.
*
* Revision 1.140 2003/05/02 14:55:59 lipp
* Resolved some more package dependencies.
*
* Revision 1.139 2003/04/26 18:56:24 lipp
* Moved extended interfaces to own package.
*
* Revision 1.138 2003/04/26 16:11:15 lipp
* Moved some classes to reduce package dependencies.
*
* Revision 1.137 2003/04/25 14:50:59 lipp
* Fixed javadoc errors and warnings.
*
* Revision 1.136 2003/04/24 20:50:13 lipp
* Fixed some warnings.
*
* Revision 1.135 2003/04/23 14:27:35 lipp
* Improved modelling of header data.
*
* Revision 1.134 2003/04/19 18:33:29 lipp
* Improved handling of info.
*
* Revision 1.133 2003/04/16 09:52:48 lipp
* Added initialization of formal parameters as data fields.
*
* Revision 1.132 2003/04/09 07:51:26 lipp
* Storing description now.
*
* Revision 1.131 2003/04/08 13:08:45 lipp
* Fixed null pointer exception.
*
* Revision 1.130 2003/04/03 11:44:06 lipp
* Support for W3C DOM arguments.
*
* Revision 1.129 2003/04/02 11:20:06 lipp
* Moved type adaption to framework.
*
* Revision 1.128 2003/04/02 09:30:05 lipp
* Supporting more data types.
*
* Revision 1.127 2003/03/31 16:50:28 huaiyang
* Logging using common-logging.
*
* Revision 1.126 2003/03/28 14:42:35 lipp
* Moved some code to XPDLUtil.
*
* Revision 1.125 2003/03/28 12:44:08 lipp
* Moved XPDL related constants to XPDLUtil.
*
* Revision 1.124 2003/03/28 11:41:59 lipp
* More changes for data type support.
*
* Revision 1.123 2003/03/27 16:32:20 lipp
* Started support for addditional data types.
*
* Revision 1.122 2003/03/13 14:07:13 lipp
* Improved implementation of condition evaluation.
*
*/
package de.danet.an.workflow.domain;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.lang.reflect.Method;
import java.text.ParseException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import org.apache.commons.jelly.JellyContext;
import org.apache.commons.jelly.JellyException;
import org.apache.commons.jelly.Script;
import org.apache.commons.jelly.XMLOutput;
import org.apache.commons.jelly.parser.XMLParser;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.XmlSaxHandler;
import org.dom4j.io.SAXContentHandler;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.DOMBuilder;
import org.jdom.output.SAXOutputter;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Wrapper;
import org.mozilla.javascript.xml.XMLObject;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLFilterImpl;
import de.danet.an.util.XMLUtil;
import de.danet.an.util.sax.BodyFilter;
import de.danet.an.util.sax.HandlerStack;
import de.danet.an.util.sax.NamespaceAttributesFilter;
import de.danet.an.util.sax.StackedHandler;
import de.danet.an.util.sax.XmlnsUrisPatcher;
import de.danet.an.workflow.util.SAXEventBufferImpl;
import de.danet.an.workflow.util.XPDLUtil;
import de.danet.an.workflow.internalapi.ExtActivityLocal;
import de.danet.an.workflow.internalapi.ExtProcessLocal;
import de.danet.an.workflow.internalapi.ScriptException;
import de.danet.an.workflow.localapi.ActivityLocal;
import de.danet.an.workflow.localapi.ProcessLocal;
import de.danet.an.workflow.localapi.TransitionLocal;
import de.danet.an.workflow.localcoreapi.WfActivityLocal;
import de.danet.an.workflow.localcoreapi.WfProcessLocal;
import de.danet.an.workflow.omgcore.AlreadyRunningException;
import de.danet.an.workflow.omgcore.CannotChangeRequesterException;
import de.danet.an.workflow.omgcore.CannotStartException;
import de.danet.an.workflow.omgcore.CannotStopException;
import de.danet.an.workflow.omgcore.InvalidControlOperationException;
import de.danet.an.workflow.omgcore.InvalidDataException;
import de.danet.an.workflow.omgcore.InvalidPriorityException;
import de.danet.an.workflow.omgcore.InvalidStateException;
import de.danet.an.workflow.omgcore.NotRunningException;
import de.danet.an.workflow.omgcore.ProcessData;
import de.danet.an.workflow.omgcore.ProcessDataInfo;
import de.danet.an.workflow.omgcore.ResultNotAvailableException;
import de.danet.an.workflow.omgcore.TransitionNotAllowedException;
import de.danet.an.workflow.omgcore.UpdateNotAllowedException;
import de.danet.an.workflow.omgcore.WfAuditEvent;
import de.danet.an.workflow.omgcore.WfRequester;
import de.danet.an.workflow.omgcore.WfStateAuditEvent;
import de.danet.an.workflow.omgcore.WfExecutionObject.ClosedState;
import de.danet.an.workflow.omgcore.WfExecutionObject.NotRunningState;
import de.danet.an.workflow.omgcore.WfExecutionObject.OpenState;
import de.danet.an.workflow.omgcore.WfExecutionObject.State;
import de.danet.an.workflow.api.CannotRemoveException;
import de.danet.an.workflow.api.DefaultProcessData;
import de.danet.an.workflow.api.ExternalReference;
import de.danet.an.workflow.api.FormalParameter;
import de.danet.an.workflow.api.InvalidKeyException;
import de.danet.an.workflow.api.Participant;
import de.danet.an.workflow.api.ProcessDefinition;
import de.danet.an.workflow.api.SAXEventBuffer;
import de.danet.an.workflow.api.Transition;
import de.danet.an.workflow.api.Activity.ClosedCompletedState;
import de.danet.an.workflow.api.Activity.Implementation;
import de.danet.an.workflow.api.Activity.JoinAndSplitMode;
import de.danet.an.workflow.api.Activity.StartFinishMode;
import de.danet.an.workflow.api.Activity.SubFlowImplementation;
import de.danet.an.workflow.api.Process;
/**
* <code>AbstractProcess</code> represents the base implementation
* of the interface {@link ProcessLocal <code>ProcessLocal</code>}.<P>
*
* With logger level <code>DEBUG</code>, event handling information
* and information about process context changes will be logged.
*/
public abstract class AbstractProcess extends AbstractExecutionObject
implements TimedObject, Serializable {
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog(AbstractProcess.class);
/** The immutable root for JavaScript evaluation. */
private static final ScriptableObject GLOBAL_JS_SCOPE
= (new Context()).initStandardObjects (null, false);
static {
Context cx = Context.enter();
try {
cx.evaluateString (GLOBAL_JS_SCOPE, "java", "<init>", 1, null);
} catch (JavaScriptException e) {
logger.warn("Cannot initialize \"java\" in rhino context: "
+ e.getMessage());
}
try {
cx.evaluateString (GLOBAL_JS_SCOPE, "new XML()", "<init>", 1, null);
} catch (JavaScriptException e) {
logger.warn("Cannot initialize \"XML\" in rhino context: "
+ e.getMessage());
}
GLOBAL_JS_SCOPE.sealObject ();
Context.exit();
}
private static class TimeoutInfo implements Serializable {
public Long activity;
public int dlIndex;
public TimeoutInfo (Long a, int d) {
activity = a;
dlIndex = d;
}
public String toString () {
return "TimeoutInfo[activity=" + activity
+ ",deadlineIndex=" + dlIndex + "]";
}
}
private Collection participants = null;
/** The TransitionManager for this process. */
private TransitionManager transitionMgrCache = null;
/** The JavaScript scope for this process. */
private ScriptableObject jsScopeCache = null;
/** The jelly context for this process. */
private JellyContext jellyContextCache = null;
/** All block activity representations by key. */
private Map baRepresentations = new HashMap ();
/** Set in init, cleared in subsequent refresh */
private boolean initedBeforeRefresh = false;
/**
* Creates a new <code>AbstractProcess</code>.
*/
public AbstractProcess () {
}
//
// Persistent attribute accessors and associated methods
//
/**
* The getter method for the persistent attribute <code>requester</code>.
*
* @return the value of requester.
*/
protected abstract WfRequester getPaRequester();
/**
* The getter method for the persistent attribute <code>id</code>.
*
* @return the value of process id.
* @see #setPaId
*/
protected abstract String getPaId();
/**
* The setter method for the persistent attribute <code>Id</code>.
*
* @param newId the new value of process id.
* @see #getPaId
*/
protected abstract void setPaId(String newId);
/**
* The getter method for the persistent attribute <code>createTime</code>.
*
* @return the value of createTime.
*/
protected abstract Date getPaCreateTime();
/**
* The getter method for the persistent attribute
* <code>processMgrName</code>.
*
* @return the value of processMgrName.
* @see #setPaProcessMgrName
*/
protected abstract String getPaProcessMgrName();
/**
* The setter method for the persistent attribute
* <code>processMgrName</code>.
*
* @param newProcessMgrName the new value of processMgrName.
* @see #getPaProcessMgrName
*/
protected abstract void setPaProcessMgrName(String newProcessMgrName);
/**
* The getter method for the persistent attribute
* <code>processMgrVersion</code>.
*
* @return the value of processMgrVersion.
* @see #setPaProcessMgrVersion
*/
protected abstract String getPaProcessMgrVersion();
/**
* The setter method for the persistent attribute
* <code>processMgrVersion</code>.
*
* @param newProcessMgrVersion the new value of processMgrVersion.
* @see #getPaProcessMgrVersion
*/
protected abstract void setPaProcessMgrVersion(String newProcessMgrVersion);
/**
* The getter method for the persistent attribute <code>processDef</code>.
*
* @return the value of processDef.
* @see #setPaProcessDef
*/
protected abstract ProcessDefinition getPaProcessDef();
/**
* The setter method for the persistent attribute <code>processDef</code>.
*
* @param newProcessDef the new value of processDef.
* @see #getPaProcessDef
*/
protected abstract void setPaProcessDef(ProcessDefinition newProcessDef);
/**
* The getter method for the persistent attribute <code>processData</code>.
*
* @return the value of processData.
*/
protected abstract ProcessData getPaProcessData();
/**
* The getter method for the persistent attribute <code>priority</code>.
*
* @return the value of priority.
* @see #setPriority
*/
protected abstract Priority getPaPriority();
/**
* The setter method for the persistent attribute <code>priority</code>.
*
* @param newPriority the new value of priority.
* @see #getPaPriority
*/
protected abstract void setPaPriority(Priority newPriority);
/**
* The getter method for the persistent attribute
* <code>blockDeadlines</code>.
*
* @return the value of blockDeadlines.
*/
protected abstract Map getPaBlockDeadlines();
/**
* Returns the transition manager.
* @return the transition manager
*/
protected TransitionManager transitionManager() {
if (transitionMgrCache == null) {
// create new transition manager
transitionMgrCache = new TransitionManager(this);
}
return transitionMgrCache;
}
/**
* Initializes the class, i.e. resets all attributes to default
* values. Note that
* {@link #refresh <code>refresh</code>} will be called subsequently.
*
* @param procDef the process definition.
* @see #dispose
*/
protected void init (ProcessDefinition procDef) {
super.init();
jsScopeCache = null;
jellyContextCache = null;
setPaProcessDef (procDef);
setPaTypedState (NotRunningState.NOT_STARTED);
setPaProcessMgrName (procDef.mgrName());
setPaProcessMgrVersion (procDef.version());
setPaDescription (procDef.processHeader().description());
setPaAuditEventSelection(procDef.auditEventSelection());
setPaStoreAuditEvents(procDef.storeAuditEvents());
baRepresentations.clear ();
setPaPriority(Priority.NORMAL);
HandlerStack hs = new HandlerStack (new SAXInitializer ());
hs.setContextData ("packageId", procDef.packageId());
try {
procDef.toSAX().emit(hs.contentHandler());
} catch (SAXException e) {
logger.error (e.getMessage (), e);
throw new IllegalArgumentException (e.getMessage ());
}
// fire an appropriate audit event
if (getPaAuditEventSelection()
== ProcessDefinition.AUDIT_SELECTION_ALL_EVENTS) {
fireAuditEvent
(new DefaultCreateProcessAuditEvent
(auditEventBase(WfAuditEvent.PROCESS_CREATED),
activityRequesterInfo(getPaRequester())));
fireAuditEvent
(new DefaultDataAuditEvent
(auditEventBase(WfAuditEvent.PROCESS_CONTEXT_CHANGED),
null, getPaProcessData()));
}
}
/**
* Called after change of persistent attributes. May be used to
* synchronise state derived from persistent attributes with
* the new values.
*
* @see #init
*/
protected void refresh () {
super.refresh ();
transitionMgrCache = null;
jsScopeCache = null;
jellyContextCache = null;
participants = null;
if (initedBeforeRefresh) {
initedBeforeRefresh = false;
} else {
baRepresentations.clear ();
}
}
/**
* Releases all allocated resources. The object will be in an
* unusable state until resources are reallocated by calling
* {@link #init <code>init</code>} and
* {@link #refresh <code>refresh</code>}.
*/
protected void dispose () {
super.dispose ();
participants = null;
// deallocate TransitionManager
transitionMgrCache = null;
baRepresentations.clear ();
}
//
// Domain methods
//
/**
* Indicates if some other object is equal to this one. <P>
*
* Note that <code>obj</code> may be a stub representing the
* object. Stubs do not in general implement
* <code>equals</code> correctly, so while
* <code>this.equals(obj)</code> does indicate the equality as
* defined for this class, <code>obj.equals(this)</code> generally
* does not.
*
* @param obj the object to compare with.
* @return <code>true</code> if the other object is equal.
*/
public boolean equals (Object obj) {
return (obj instanceof AbstractProcess)
&& getPaKey().equals (((AbstractProcess)obj).key())
|| (obj instanceof WfProcessLocal)
&& getPaKey().equals (((WfProcessLocal)obj).key());
}
/**
* Provide a new unique activity key.
*
* @return the key.
*/
protected abstract Long createActivityKey ();
/**
* Returns the requester for this process. Method of the omg interface.
* @return requester for this process.
*/
public WfRequester requester() {
WfRequester req = getPaRequester();
if (req instanceof SubProcRequester) {
try {
return lookupActivityLocal
(((SubProcRequester)req).requestingActivity()).toActivity();
} catch (InvalidKeyException e) {
return null;
}
}
return req;
}
/**
* Returns the requester for this process without resolving the
* internally used SubProcRequester to the associated WfActivity.
* @return requester for this process.
*/
public WfRequester unresolvedRequester() {
return getPaRequester();
}
/**
* Return the process this process is a subflow of.
* @return the process or <code>null</code> if this process is not
* a subflow
*/
public Process requestingProcess () {
if (!(getPaRequester() instanceof SubProcRequester)) {
return null;
}
String key = ((SubProcRequester)getPaRequester()).requestingActivity();
try {
ActivityLocal act = lookupActivityLocal (key);
return ((ExtProcessLocal)act.containerLocal()).toProcess();
} catch (InvalidKeyException e) {
logger.warn (toString() + " cannot find requesting activity "
+ key + " : " + e.getMessage ());
return null;
}
}
/**
* Returns the creation time of the process.
* @return the creation time.
*/
public Date createTime () {
return getPaCreateTime();
}
/**
* For the first iteration throws an
* CannotChangeRequesterException. Method of the omg interface.
* @param requester the requester.
* @throws CannotChangeRequesterException if modification is not allowed.
* @ejb.interface-method view-type="remote"
*/
public void setRequester(WfRequester requester)
throws CannotChangeRequesterException {
throw new CannotChangeRequesterException();
}
/**
* Return the context of this <code>WfExecutionObject</code>.
* The process data object returned is a copy of the name value
* association, the values are not copied, however.
*
* @return the process relevant data that define the context of the
* process.
*/
public ProcessData processContext () {
return new DefaultProcessData (getPaProcessData());
}
/**
* Filter out duplicate endDocument events.
*/
private class DupEndFilter extends XMLFilterImpl {
private boolean gotEnd = false;
public DupEndFilter (ContentHandler parent) {
setContentHandler (parent);
}
public void endDocument() throws SAXException {
if (!gotEnd) {
super.endDocument ();
gotEnd = true;
}
}
}
/**
* Updates process context of the execution object.
* @param newValues the name-value pairs to be set.
* @throws InvalidDataException If a name or value type does not match
* the signature of this process.
* @throws UpdateNotAllowedException If the update is not allowed.
*/
public void setProcessContext (ProcessData newValues)
throws InvalidDataException, UpdateNotAllowedException {
if (!workflowState().equals (State.OPEN)) {
throw new UpdateNotAllowedException
("Process is not in state open.");
}
// verify first to avoid partial change.
ProcessDataInfo sig = getPaProcessDef().contextSignature ();
for (Iterator i = newValues.keySet().iterator(); i.hasNext();) {
String name = (String)i.next();
Object type = sig.get (name);
if (type == null) {
throw new InvalidDataException ("No such data field: " + name);
}
Object v = newValues.get(name);
if (v == null) {
continue;
}
if ((type instanceof ExternalReference)
&& XPDLUtil.isJavaType((ExternalReference)type)) {
Class vc = null;
try {
vc = XPDLUtil.getJavaType((ExternalReference)type);
} catch (ClassNotFoundException e) {
throw (InvalidDataException)
(new InvalidDataException
("Required Java class no longer available: "
+ e.getMessage())).initCause(e);
}
if (vc.isAssignableFrom(v.getClass())) {
continue;
} else {
throw new InvalidDataException
("Context entry " + name + " is "
+ v.getClass().getName() + " must be instance of "
+ vc.getName());
}
}
if ((type instanceof SAXEventBuffer)
|| (type instanceof ExternalReference)
|| type.equals(org.w3c.dom.Element.class)) {
boolean elemList = false;
if (v instanceof List) {
if (((List)v).size() == 0) {
elemList = true;
} else {
Iterator ti = ((List)v).iterator ();
if (ti.next() instanceof Element) {
elemList = true;
}
}
}
if (! ((v instanceof Element)
|| (v instanceof Document)
|| elemList
|| (v instanceof org.w3c.dom.Element)
|| (v instanceof org.w3c.dom.DocumentFragment)
|| (v instanceof org.w3c.dom.Document)
|| (v instanceof SAXEventBuffer))) {
throw new InvalidDataException
("Not a usable XML representation: " + name);
}
continue;
}
if (type instanceof Class) {
Class vc = v.getClass();
if (v instanceof Float) {
vc = Double.class;
} else if (v instanceof Integer) {
vc = Long.class;
}
if (type == Participant.class) {
type = String.class;
}
if (! ((Class)type).isAssignableFrom (vc)) {
throw new InvalidDataException
("Values for data field \"" + name
+ "\" must be of type " + ((Class)type).getName ()
+ " (is " + v.getClass().getName () + ")");
}
continue;
}
throw new InvalidDataException
("Invalid type for data field \"" + name
+ "\": " + ((Class)type).getName ());
}
// now do changes
DOMBuilder builder = null;
ProcessData oldValues = new DefaultProcessData();
for (Iterator i = (new ArrayList (newValues.keySet())).iterator();
i.hasNext();) {
String name = (String)i.next();
oldValues.put(name, getPaProcessData().get(name));
Object v = newValues.get(name);
if (logger.isDebugEnabled ()) {
logger.debug ("Setting context data item " + name + " = " + v);
}
Object type = sig.get (name);
if ((type instanceof ExternalReference)
&& XPDLUtil.isJavaType((ExternalReference)type)) {
// accept literally
} else if (v instanceof Float) {
v = new Double (((Float)v).doubleValue ());
newValues.put (name, v);
} else if (v instanceof Integer) {
v = new Long (((Integer)v).longValue ());
newValues.put (name, v);
} else if ((v instanceof Element)
|| (v instanceof Document)
|| (v instanceof List)) {
try {
if (logger.isDebugEnabled ()) {
logger.debug
("Convering item " + name + " from JDOM to SAX");
}
SAXOutputter outputter = new SAXOutputter();
SAXEventBufferImpl b = new SAXEventBufferImpl ();
outputter.setContentHandler(b);
outputter.setLexicalHandler(b);
if (v instanceof Document) {
outputter.output ((Document)v);
} else {
List l;
if (v instanceof List) {
l = (List)v;
} else {
l = new ArrayList ();
l.add (v);
}
outputter.output (l);
}
b.pack();
v = b;
newValues.put (name, v);
} catch (JDOMException e) {
logger.error (e.getMessage (), e);
throw new InvalidDataException (e.getMessage ());
}
} else if ((v instanceof org.w3c.dom.Element)
|| (v instanceof org.w3c.dom.DocumentFragment)
|| (v instanceof org.w3c.dom.Document)) {
try {
if (logger.isDebugEnabled ()) {
logger.debug
("Convering item " + name + " from W3C DOM to SAX");
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer t = tf.newTransformer ();
SAXEventBufferImpl b = new SAXEventBufferImpl ();
// There seems to be a bug in Xalan that causes it to
// fire two endDocument events when transforming a
// DocumentFragment, filter it out.
t.transform (new DOMSource ((org.w3c.dom.Node)v),
new SAXResult(new DupEndFilter(b)));
b.pack();
v = b;
newValues.put (name, v);
} catch (TransformerConfigurationException e) {
String s = "Error converting DOM to SAX: "+e.getMessage ();
logger.error (s, e);
throw new IllegalStateException (s);
} catch (TransformerException e) {
String s = "Error converting DOM to SAX: "+e.getMessage ();
logger.error (s, e);
throw new InvalidDataException (s);
}
}
getPaProcessData().put(name, v);
}
// fire appropriate audit event
if (getPaAuditEventSelection()
== ProcessDefinition.AUDIT_SELECTION_ALL_EVENTS) {
fireAuditEvent
(new DefaultDataAuditEvent
(auditEventBase(WfAuditEvent.PROCESS_CONTEXT_CHANGED),
oldValues, newValues));
}
}
/**
* The <code>result</code> method returns the process context.
*
* @return the result.
* @exception ResultNotAvailableException not thrown, intermediate results
* are available.
* @ejb.interface-method view-type="remote"
*/
public ProcessData result ()
throws ResultNotAvailableException {
ProcessDataInfo resSig = processDefinition().resultSignature();
ProcessData procCtx = getPaProcessData();
ProcessData resData = new DefaultProcessData();
for (Iterator i = resSig.keySet().iterator(); i.hasNext();) {
String key = (String)i.next();
resData.put(key, procCtx.get(key));
}
return resData;
}
/**
* Returns a collection of activities of the process. This can usually
* be handled more efficiently by the derived class that provides the
* persistence. The default implementation simply wraps each local
* objects.
*
* @return collection of activities of the process
*/
public Collection steps() {
Collection res = new ArrayList ();
for (Iterator i = stepsLocal().iterator(); i.hasNext();) {
ExtActivityLocal act = (ExtActivityLocal)i.next();
res.add (act.toActivity());
}
return res;
}
/**
* Returns all WfActivityLocals associated with this <code>WfProcess</code>.
* @return the collection of all the WfActivities.
*/
public abstract Collection stepsLocal();
/**
* Returns the {@link ActivityLocal <code>Activity</code>} with the
* given key.
* @param key the
* {@link de.danet.an.workflow.localcoreapi.WfActivityLocal#key key}
* of the activity
* @return the activity associated with the key
* @throws InvalidKeyException if no activity with the given key
* exists
*/
public abstract ActivityLocal activityByKeyLocal (String key)
throws InvalidKeyException;
/**
* Returns all transitions associated with this <code>WfProcess</code>.
* @return the collection of all the WfActivities.
*/
public abstract List transitionsLocal();
/**
* Return all <code>WfActivity</code> objects that are in a certain state.
* @param state the given state.
* @return the collection of all WfActivities.
* @throws InvalidStateException if an invalid state has been specified.
*/
public Collection activitiesInState (String state)
throws InvalidStateException {
State s = State.fromString (state);
Collection returnList = new ArrayList();
Iterator it = stepsLocal().iterator();
while (it.hasNext()) {
ExtActivityLocal act = (ExtActivityLocal)it.next();
if (act.typedState().isSameOrSubState (s)) {
returnList.add(act.toActivity());
}
}
return returnList;
}
/**
* Return all <code>WfActivity</code> objects that belong to the
* given block activity.
* @param blockActivity the given block activity
* @return the collection of all WfActivities
*/
public Collection activitiesOfBlockActivity (String blockActivity) {
Collection returnList = new ArrayList();
Iterator it = stepsLocal().iterator();
while (it.hasNext()) {
ActivityLocal act = (ActivityLocal)it.next();
String ba = act.blockActivity();
if (ba != null && ba.equals (blockActivity)) {
returnList.add(act);
}
}
return returnList;
}
/**
* Returns the associated process defintion.
* @return the process definition
*/
public ProcessDefinition processDefinition () {
return getPaProcessDef ();
}
/**
* Returns the cleanup mode for the process as defined in its process
* definition.
* @return the cleanup mode.
*/
protected int cleanupMode () {
return processDefinition().cleanupMode();
}
/**
* Return the remote version of this object. This may be, but need
* not be <code>this</code>, depending on the way remote objects
* are implemented.
*
* @return the client side object.
*/
public abstract Process toProcess ();
/**
* Return the version of this object to be passed to as local reference.
*
* @return the client side object.
*/
public abstract ExtProcessLocal toProcessLocal ();
/**
* Helper for implementing access to process data in JavaScript.
*/
public class DataAccessor implements Serializable {
private String itemName;
private Scriptable scope;
/**
* Create a new <code>DataAccessor</code> for the given item.
* @param item the process data item to access.
*/
public DataAccessor (Scriptable scope, String item) {
this.scope = scope;
itemName = item;
}
/**
* Return the the value of the item this object was constructed for.
* @param so needed to match rhino's interface requirements, ignored.
* @return the value.
*/
public Object getValue(ScriptableObject so) {
Object res = getPaProcessData().get (itemName);
if (res instanceof Date) {
try {
res = Context.getCurrentContext().newObject
(so, "Date", new Object[]
{ new Long(((Date)res).getTime()) });
} catch (JavaScriptException e) {
logger.error (e.getMessage (), e);
}
return res;
}
if (res instanceof SAXEventBuffer) {
try {
XmlSaxHandler sh = XmlObject.Factory.newXmlSaxHandler();
((SAXEventBuffer)res).emit(sh.getContentHandler());
XmlObject xo = sh.getObject();
XmlCursor cur = xo.newCursor();
while (!cur.isStart()) {
if (cur.isEnddoc()) {
try {
Context cx = Context.enter();
return cx.evaluateString
(scope, "<></>", "<script>", 1, null);
} finally {
Context.exit ();
}
}
cur.toNextToken();
}
xo = cur.getObject();
Object wxo = Context.javaToJS (xo, scope);
res = Context.getCurrentContext().newObject
(scope, "XML", new Object[] { wxo });
} catch (SAXException e) {
logger.error (e.getMessage (), e);
} catch (XmlException e) {
logger.error (e.getMessage (), e);
}
return res;
}
return res;
}
}
/**
* Return a scope for evaluating JavaScript. The scope includes
* the process relevant data as top-level variables. This scope
* may be used as prototype for a thread specific evaluation
* context only. This implies that it can not be used to modify
* process data. This behaviour is intended.
* @return the process specific sope.
*/
public Scriptable jsScope () {
if (jsScopeCache == null) {
try {
jsScopeCache = (ScriptableObject)
(new Context()).newObject (GLOBAL_JS_SCOPE);
jsScopeCache.setPrototype (GLOBAL_JS_SCOPE);
jsScopeCache.setParentScope (null);
Method getter = DataAccessor.class.getMethod
("getValue", new Class[] { ScriptableObject.class });
Map procData = getPaProcessData ();
for (Iterator i = procData.keySet().iterator(); i.hasNext ();) {
String key = (String)i.next();
DataAccessor da = new DataAccessor (jsScopeCache, key);
try {
jsScopeCache.defineProperty (key, da, getter, null, 0);
} catch (JavaScriptException e) {
logger.error (e.getMessage (), e);
}
}
jsScopeCache.sealObject ();
} catch (NoSuchMethodException e) {
logger.error (e.getMessage (), e);
} catch (JavaScriptException e) {
logger.error (e.getMessage (), e);
}
}
return jsScopeCache;
}
/**
* Evaluate the given script using the process specific scope as
* returned by {@link #jsScope <code>jsScope</code>}. <P>
*
* results of type JavaScript Date are converted to Java Date.
*
* @param script the script
* @return the result
* @throws ScriptException if evaluation fails
*/
public Serializable evalScript (String script) throws ScriptException {
Context cx = Context.enter();
try {
Scriptable proto = jsScope();
Scriptable scope = cx.newObject (proto);
scope.setPrototype (proto);
scope.setParentScope (null);
Object res = cx.evaluateString (scope, script, "<script>", 1, null);
if (res instanceof Wrapper) {
res = ((Wrapper)res).unwrap ();
} else if (res instanceof XMLObject) {
SAXEventBufferImpl seb = new SAXEventBufferImpl();
if (((XMLObject)res).getClassName().equals("XMLList")) {
seb.startDocument();
for (int i = 0; true; i++) {
Object item = ((XMLObject)res).get(i, (XMLObject)res);
if (item.equals(Scriptable.NOT_FOUND)) {
break;
}
xmlObjectToSax(seb, (XMLObject)item, true);
}
seb.endDocument();
} else {
xmlObjectToSax(seb, (XMLObject)res, false);
}
seb.pack();
return seb;
} else if ((res instanceof Scriptable)
&& ((Scriptable)res).getClassName().equals ("Date")) {
Scriptable s = (Scriptable)res;
Object gt = Scriptable.NOT_FOUND;
for (Scriptable c = s;
c != null && gt.equals(Scriptable.NOT_FOUND);
c = c.getPrototype()) {
gt = c.get("getTime", s);
}
Number millis = (Number)((Function)gt).call
(cx, scope, s, new Object[] {});
return new Date (millis.longValue());
}
return (Serializable)res;
} catch (JavaScriptException e) {
logger.error (e.getMessage (), e);
throw new ScriptException
(e.getMessage (),
(e.getValue() instanceof Throwable)
? (Throwable)e.getValue() : e);
} catch (SAXException e) {
logger.error (e.getMessage (), e);
throw new ScriptException (e.getMessage (), e);
} finally {
Context.exit();
}
}
/**
* Serialize a JavaScript XML object into the SAX event buffer.
*
* @param seb the SAX event buffer
* @param xmlObj the XML object
* @param fragment if <code>startDocument</code> and
* <code>endDocument</code> events are to be suppressed
* @throws SAXException
*/
private void xmlObjectToSax
(SAXEventBufferImpl seb, XMLObject xmlObj, boolean fragment)
throws SAXException {
Wrapper wrap = (Wrapper) ScriptableObject.callMethod
((XMLObject)xmlObj, "getXmlObject", new Object[0]);
XmlObject result = (XmlObject) wrap.unwrap();
XmlCursor cursor = result.newCursor();
result = cursor.getObject();
ContentHandler ch = seb;
if (fragment) {
ch = new BodyFilter (ch);
}
result.save(ch, seb, (new XmlOptions()).setSaveOuter());
}
/**
* Evaluate the given expressions using the process specific data
* (the scope as returned by {@link #jsScope
* <code>jsScope</code>} for JavaScript).
* @param expressionData an array of object arrays containing the result
* type and the expression
* @return an array that contains the result of each evaluation.
* Note that the result may be an exception.
*/
public Serializable[] evalExpressions (Object[][] expressionData) {
Serializable[] res = new Serializable[expressionData.length];
for (int i = 0; i < expressionData.length; i++) {
try {
Object[] item = expressionData[i];
Object resType = item[0];
String expr = (String)item[1];
if (XPDLUtil.isXMLType (resType)
&& expr.length() > 0 && expr.startsWith("<j:jelly")) {
res[i] = evalXML (expr);
} else {
res[i] = evalScript (expr);
}
if (resType.equals(Long.class) && (res[i] instanceof Double)) {
res[i] = new Long(((Double)res[i]).longValue());
}
} catch (Exception e) {
res[i] = e;
}
}
return res;
}
private Serializable evalXML (String xml)
throws SAXException, IOException {
try {
SAXParserFactory spf = SAXParserFactory.newInstance ();
spf.setValidating (false);
spf.setNamespaceAware (true);
spf.setFeature
("http://xml.org/sax/features/namespace-prefixes", true);
XMLReader xr = null;
try {
spf.setFeature
("http://xml.org/sax/features/xmlns-uris", true);
xr = spf.newSAXParser().getXMLReader();
} catch (SAXException e) {
xr = new XmlnsUrisPatcher
(spf.newSAXParser().getXMLReader());
}
SAXEventBufferImpl seb = new SAXEventBufferImpl ();
XMLFilterImpl filter = new XMLFilterImpl () {
private int level = 0;
public void startElement
(String uri, String localName, String qName,
Attributes atts) throws SAXException {
if (level > 0) {
super.startElement (uri,localName,qName,atts);
}
level += 1;
}
public void endElement
(String uri, String localName, String qName)
throws SAXException{
level -= 1;
if (level > 0) {
super.endElement (uri, localName, qName);
}
}
};
filter.setContentHandler (seb);
xr.setContentHandler (filter);
xr.parse (new InputSource
(new StringReader
("<temporary-root>" + xml + "</temporary-root>")));
seb.pack();
XMLStreamReader rdr = seb.createXMLStreamReader ();
while (rdr.next () != XMLStreamReader.START_ELEMENT) {
}
if (rdr.getNamespaceURI().equals ("jelly:core")
&& rdr.getLocalName().equals ("jelly")) {
return evalJelly (seb);
}
return seb;
} catch (ParserConfigurationException e) {
throw new IllegalArgumentException
("Error initiliazing schema type: " + e.getMessage ());
} catch (SAXException e) {
throw new IllegalArgumentException
("Error initiliazing schema type: " + e.getMessage ());
} catch (XMLStreamException e) {
throw new IllegalArgumentException
("Error initiliazing schema type: " + e.getMessage ());
} catch (IOException e) {
throw new IllegalArgumentException
("Error initiliazing schema type: " + e.getMessage ());
}
}
private class ExtXMLParser extends XMLParser {
public void configure () {
super.configure ();
}
}
private JellyContext jellyContext() {
if (jellyContextCache == null) {
jellyContextCache = new JellyContext() {
private Map xmlVals = new HashMap ();
public Object getVariable(String name) {
if (getPaProcessData().containsKey (name)) {
Object v = getPaProcessData().get (name);
if (v instanceof SAXEventBuffer) {
if (!xmlVals.containsKey (name)) {
SAXContentHandler hdlr
= new SAXContentHandler ();
try {
((SAXEventBuffer)v).emit (hdlr);
} catch (SAXException e) {
throw (IllegalArgumentException)
(new IllegalArgumentException
("Error converting SAX events: "
+ e.getMessage ())).initCause(e);
}
xmlVals.put (name, hdlr.getDocument());
}
return xmlVals.get (name);
}
return v;
} else {
return super.getVariable (name);
}
}
};
}
return jellyContextCache;
}
private SAXEventBufferImpl evalJelly (SAXEventBufferImpl seb)
throws SAXException {
if (logger.isDebugEnabled ()) {
logger.debug ("Evaluating jelly script");
}
try {
JellyContext context = new JellyContext (jellyContext ());
ExtXMLParser jellyParser = new ExtXMLParser ();
jellyParser.setContext(context);
jellyParser.configure ();
seb.emit (new NamespaceAttributesFilter(jellyParser));
Script script = jellyParser.getScript ();
script.compile ();
SAXEventBufferImpl jres = new SAXEventBufferImpl ();
jres.startDocument ();
script.run (context, new XMLOutput(jres));
jres.endDocument ();
jres.pack ();
return jres;
} catch (JellyException e) {
throw (IllegalArgumentException)(new IllegalArgumentException
("Error evaluating jelly script: " + e.getMessage ()))
.initCause (e);
}
}
/* Comment copied from Interface. */
public void closeActivity (ExtActivityLocal activity, State closedState) {
// initialize transition manager before changing activity state!
TransitionManager tm = transitionManager();
activity.doCloseActivity (closedState);
tm.update (activity);
// everything else is done when the activity's close event is
// received, because we want to do it in another transaction
}
/* Comment copied from Interface. */
public boolean choose (ExtActivityLocal activity)
throws TransitionNotAllowedException {
State state = activity.typedState ();
if (state.isSameOrSubState (NotRunningState.NOT_STARTED)) {
// this activity has been reset by some other activity
return false;
}
if (!state.isSameOrSubState (OpenState.RUNNING)
&& !state.isSameOrSubState (NotRunningState.SUSPENDED)) {
throw new TransitionNotAllowedException
("Cannot choose activity in state " + state + " must be "
+ OpenState.RUNNING + " or " + NotRunningState.SUSPENDED);
}
if (!activity.preliminarilyChosen ()) {
return true;
}
transitionManager().resetCompeting (activity);
return true;
}
//
// State change API implementation
//
private static Map vstates = null;
/**
* Returns the {@link java.util.Map <code>Map</code>} that maps
* the process states to a {@link java.util.Map <code>List</code>}
* of reachable process states. <P>
*
* The map returns the state transitions allowed as parameters of
* {@link
* de.danet.an.workflow.localcoreapi.WfExecutionObjectLocal#changeState
* <code>changeState</code>} only. I.e. the map does not reflect
* all possible transitions, there may be more, but those are only
* accessible to the workflow engine itself.
* @return the resulting map.
*/
protected Map getStateTransitionMap () {
// Map must be initialized lazily. Static initialization holds
// danger of "race conditions" with constant initialization
// (resulting in null-entries). And new map must be fully initialized
// before assigning to member variable to avoid concurrent
// initialization.
if (vstates == null) {
Map transMap = new HashMap();
// Transitions from open.not_running.not_started
List l = new ArrayList ();
transMap.put(NotRunningState.NOT_STARTED, l);
l.add (ClosedState.TERMINATED);
l.add (OpenState.RUNNING);
// Transitions from open.running
l = new ArrayList ();
transMap.put(RunningState.RUNNING, l);
l.add (NotRunningState.SUSPENDED);
l.add (ClosedState.TERMINATED);
// Transitions from open.not_running.suspended
l = new ArrayList ();
transMap.put(SuspendedState.SUSPENDED, l);
l.add (OpenState.RUNNING);
l.add (ClosedState.ABORTED);
vstates = Collections.unmodifiableMap(transMap);
}
return vstates;
}
/**
* Adds the possibility to change the state to
* <code>OpenState.RUNNING</code> (i.e. "<code>start()</code>") to
* the <code>changeState</code> method of the superclass.
*
* @param newState the new state.
* @throws InvalidStateException If <code>newState</code> is an invalid
* state for the execution object.
* @throws TransitionNotAllowedException If the transition from the current
* state to <code>newState</code> is not allowed.
*/
public void changeState (State newState)
throws InvalidStateException,
TransitionNotAllowedException {
try {
if (typedState() == NotRunningState.NOT_STARTED
&& newState == OpenState.RUNNING) {
start ();
return;
}
} catch (CannotStartException e) {
throw new TransitionNotAllowedException
(e.getClass().getName() + ": " + e.getMessage());
} catch (AlreadyRunningException e) {
throw new TransitionNotAllowedException
(e.getClass().getName() + ": " + e.getMessage());
}
super.changeState (newState);
}
/**
* Starts a process.
* @throws CannotStartException when the process cannot be started (e.g.,
* because it is not properly initialized).
* @throws AlreadyRunningException when the process has already been
* started.
* @ejb.interface-method view-type="remote"
*/
public void start () throws CannotStartException,
AlreadyRunningException {
if (typedState() != NotRunningState.NOT_STARTED) {
throw new AlreadyRunningException
(toString() + " state is: " + state());
}
updateState (RunningState.RUNNING);
}
/**
* Enable or disable debugging of the activity.
* @param debug if the activity is to be debugged
* @throws InvalidStateException if changing debug mode is not
* allowed
*/
public void setDebugEnabled (boolean debug) throws InvalidStateException {
setPaDebug (debug);
Iterator it = stepsLocal().iterator();
while (it.hasNext()) {
ExtActivityLocal act = (ExtActivityLocal)it.next();
act.setDebugEnabled (true);
}
return;
}
/* Comment copied from Interface. */
public void terminate () throws CannotStopException,
NotRunningException {
mayCloseCheck (ClosedState.TERMINATED);
if (stepsLocal() != null) {
for (Iterator it = stepsLocal().iterator(); it.hasNext();) {
ActivityLocal act = (ActivityLocal)it.next();
State s = act.typedState();
if (s.isSameOrSubState(NotRunningState.SUSPENDED)
|| s == ClosedState.ABORTED) {
throw new CannotStopException
(act.toString() + " is suspended.");
}
}
String unstoppable = null;
try {
setPaTypedState(RunningState.TERMINATING);
for (Iterator it = stepsLocal().iterator(); it.hasNext();) {
ActivityLocal act = (ActivityLocal)it.next();
if (act.workflowState() == State.OPEN
&& (!act.typedState().isSameOrSubState
(NotRunningState.NOT_STARTED))) {
try {
act.terminate();
} catch (CannotStopException e) {
unstoppable = act.toString()
+ " cannot be terminated: " + e.getMessage();
} catch (NotRunningException e) {
// cannot happen
logger.error (e.getMessage(), e);
}
}
}
} finally {
setPaTypedState(RunningState.RUNNING);
}
if (unstoppable != null) {
throw new CannotStopException (unstoppable);
}
}
updateState(ClosedState.TERMINATED);
}
/* Comment copied from Interface. */
public void abort () throws CannotStopException,
NotRunningException {
mayCloseCheck (ClosedState.ABORTED);
doAbort ();
}
private void doAbort ()
throws CannotStopException {
if (stepsLocal() != null) {
String unstoppable = null;
try {
setPaTypedState(SuspendedState.ABORTING);
for (Iterator it = stepsLocal().iterator(); it.hasNext();) {
ActivityLocal act = (ActivityLocal)it.next();
try {
if (act.typedState().isSameOrSubState
(NotRunningState.SUSPENDED)) {
act.abort ();
} else if (act.typedState().isSameOrSubState
(OpenState.RUNNING)) {
try {
act.terminate();
} catch (CannotStopException e) {
act.suspend ();
act.abort ();
}
}
} catch (CannotStopException e) {
unstoppable = act.toString()
+ " cannot be closed: " + e.getMessage();
} catch (InvalidControlOperationException e) {
// cannot happen, we check states before changing
logger.error (e.getMessage(), e);
}
}
} finally {
setPaTypedState(SuspendedState.SUSPENDED);
}
if (unstoppable != null) {
throw new CannotStopException (unstoppable);
}
}
updateState(ClosedState.ABORTED);
}
private void mayCloseCheck (ClosedState s)
throws CannotStopException, NotRunningException {
if (!validTypedStates().contains (s)) {
if (typedState().isSameOrSubState(OpenState.RUNNING)) {
throw new CannotStopException(toString() + " is " + state());
} else {
throw new NotRunningException (toString() + " is " + state());
}
}
}
//
// Timers
//
/**
* Handle the timeout of a timer.
* @param info the context.
*/
public void handleTimeout (Serializable info) {
TimeoutInfo toi = (TimeoutInfo)info;
List dls = (List)getPaBlockDeadlines().get(toi.activity);
Deadline dl = (Deadline)dls.get(toi.dlIndex);
if (logger.isDebugEnabled ()) {
logger.debug (toString () + " received timeout for " + dl);
}
// now accept deadline
dl.setState(Deadline.STATE_REACHED);
// notify persistence layer about change
getPaBlockDeadlines().put
(toi.activity, getPaBlockDeadlines().get(toi.activity));
// now find not completed/not started activities and/or abandon
Collection openActs = new ArrayList ();
for (Iterator i = activitiesOfBlockActivity
(toi.activity.toString ()).iterator (); i.hasNext ();) {
ExtActivityLocal a = (ExtActivityLocal)i.next ();
if (a.typedState().isSameOrSubState(OpenState.RUNNING)
|| a.typedState().isSameOrSubState(NotRunningState.SUSPENDED)) {
openActs.add (a);
// complete this if deadline is synchronous
if (dl.getExecution() == Deadline.SYNCHR) {
a.initiateAbandoning(true, dl.getExceptionName());
transitionManager().update (a);
}
}
}
if (openActs.size() > 0) {
BAForExceptionHandling ba = (BAForExceptionHandling)
blockActivityRepresentation (toi.activity.toString());
ba.update (dls, dl.getExecution() == Deadline.SYNCHR
? (State)ClosedCompletedState.ABANDONED
: (State)RunningState.RUNNING,
openActs);
handleException (ba, dl.getExceptionName());
}
}
//
// State change handling
//
/**
* Check if this class handles a specific event.
* @param event the event to check
* @return <code>true</code> if event is handled
*/
public static boolean isHandled (WfAuditEvent event) {
try {
if (!(event instanceof WfStateAuditEvent)) {
return false;
}
if (event.eventType().equals(WfAuditEvent.ACTIVITY_STATE_CHANGED)) {
State newState = State.fromString
(((WfStateAuditEvent)event).newState());
if (newState.isSameOrSubState(State.CLOSED)) {
return true;
}
} else if (event.eventType()
.equals(WfAuditEvent.PROCESS_STATE_CHANGED)) {
State oldState = State.fromString
(((WfStateAuditEvent)event).oldState());
State newState = State.fromString
(((WfStateAuditEvent)event).newState());
if (oldState.isSameOrSubState(NotRunningState.NOT_STARTED)) {
if (newState == RunningState.RUNNING) {
return true;
}
} else if (oldState.isSameOrSubState
(NotRunningState.NOT_STARTED)) {
if (newState == ClosedState.TERMINATED) {
return true;
}
} else if (oldState.isSameOrSubState
(NotRunningState.SUSPENDED)) {
if (newState == RunningState.RUNNING) {
return true;
}
if (newState == ClosedState.ABORTED) {
return true;
}
} else if (oldState.isSameOrSubState(OpenState.RUNNING)) {
if (newState == ClosedCompletedState.NORMAL) {
return true;
}
if (newState == ClosedState.TERMINATED) {
return true;
}
}
}
} catch (InvalidStateException e) {
throw (IllegalArgumentException)
(new IllegalArgumentException (e.getMessage ()))
.initCause (e);
}
return false;
}
/**
* Handles the given audit event.
* @param event the event.
* @ejb.interface-method view-type="remote"
*/
public void handleAuditEvent (WfAuditEvent event) {
if (event.activityKey() == null) {
super.handleAuditEvent (event);
return;
}
// One of our activities has closed (events are pre-filtered)
State ps = typedState();
if (ps.isSameOrSubState(RunningState.TERMINATING)
|| ps.isSameOrSubState(SuspendedState.ABORTING)) {
return;
}
State closedState = null;
try {
closedState = State.fromString
(((WfStateAuditEvent)event).newState());
} catch (InvalidStateException e) {
// actually, this is impossible
logger.error (e.getMessage ());
return;
}
if (closedState.isSameOrSubState (ClosedState.ABORTED)) {
if (ps.isSameOrSubState(OpenState.RUNNING)) {
updateInterim (SuspendedState.SUSPENDED);
} else if ((! ps.isSameOrSubState (NotRunningState.SUSPENDED))) {
return;
}
try {
doAbort ();
} catch (CannotStopException e) {
logger.warn
("Cannot abort process: " + e.getMessage ());
}
return;
}
if (ps == RunningState.RUNNING) {
continueProcessing ();
}
}
/**
* Handles a terminated audit event.
* @param event the event.
*/
protected void handleTerminatedEvent (WfStateAuditEvent event) {
WfRequester req = unresolvedRequester();
if ((req instanceof SubProcRequester)
&& (((SubProcRequester)req).execution()
== SubFlowImplementation.SYNCHR)) {
String key = ((SubProcRequester)req).requestingActivity();
try {
ActivityLocal act = lookupActivityLocal (key);
if (act.typedState () != RunningState.ABANDONING
&& act.typedState() != ClosedCompletedState.ABANDONED) {
act.terminate ();
}
} catch (InvalidKeyException e) {
logger.warn (toString() + " cannot notify requesting activity "
+ key + " : " + e.getMessage ());
} catch (InvalidControlOperationException e) {
logger.warn(toString()+" cannot terminate requesting activity "
+ key + " : " + e.getMessage ());
}
}
handleClosedEvent (event);
}
/**
* Handles a aborting audit event.
* @param event the event.
*/
protected void handleAbortedEvent (WfStateAuditEvent event) {
WfRequester req = unresolvedRequester();
if ((req instanceof SubProcRequester)
&& (((SubProcRequester)req).execution()
== SubFlowImplementation.SYNCHR)) {
String key = ((SubProcRequester)req).requestingActivity();
try {
ExtActivityLocal act = lookupActivityLocal (key);
act.abortRequester();
} catch (InvalidKeyException e) {
logger.warn (toString() + " cannot notify requesting activity "
+ key + " : " + e.getMessage ());
}
}
handleClosedEvent (event);
}
/**
* Handles a completed audit event.
* @param event the event.
*/
protected void handleCompletedEvent (WfStateAuditEvent event) {
WfRequester req = unresolvedRequester();
if ((req instanceof SubProcRequester)
&& (((SubProcRequester)req).execution()
== SubFlowImplementation.SYNCHR)) {
String key = ((SubProcRequester)req).requestingActivity();
try {
ActivityLocal act = lookupActivityLocal (key);
if (act.typedState() != RunningState.ABANDONING
&& act.typedState() != ClosedCompletedState.ABANDONED) {
ProcessData res = processContext();
FormalParameter[] fps
= processDefinition().formalParameters();
ProcessData pd = new ProcessDataWithParams (fps);
for (int i = 0; i < fps.length; i++) {
FormalParameter fp = fps[i];
if (fp.mode() == FormalParameter.Mode.IN) {
continue;
}
String fpn = fps[i].id();
pd.put (fpn, res.get (fpn));
}
act.setResult (pd);
act.complete ();
}
} catch (InvalidKeyException e) {
logger.warn (toString() + " cannot notify requesting activity "
+ key + " : " + e.getMessage ());
} catch (InvalidDataException e) {
logger.warn
(toString() + " cannot set data on requesting activity "
+ key + " : " + e.getMessage ());
} catch (InvalidControlOperationException e) {
logger.warn (toString() + " cannot notify requesting activity "
+ key + " : " + e.getMessage ());
}
}
handleClosedEvent (event);
}
/**
* Called when the process is closed. Derived classes should
* should override this method and notify clients waiting on
* channels that the channel is closed and clean up any data
* structures associated with channels.
*/
protected void closeChannels () {
}
private void handleClosedEvent (WfStateAuditEvent event) {
stopTimers ();
closeChannels ();
int cleanupMode = cleanupMode();
if (cleanupMode == ProcessDefinition.REMOVE_AUTOMATIC
|| (cleanupMode == ProcessDefinition.REMOVE_COMPLETED
&& event.newState().startsWith("closed.completed"))) {
try {
removeThis ();
} catch (CannotRemoveException e) {
// cannot happen, we got this event because process
// was closed, so it must be removable
logger.error ("Closed process not removable (?): "
+ e.getMessage (), e);
}
}
}
/* Comment copied from interface. */
public void handleException (ExtActivityLocal activity, String exceptionName) {
transitionManager().update (activity, exceptionName);
if (typedState() == RunningState.RUNNING) {
continueProcessing ();
}
}
/**
* Remove this process. Since this involves the persistence layer,
* this method must be provided by the derived class.
* @throws CannotRemoveException if the process is still in progress
*/
protected abstract void removeThis () throws CannotRemoveException;
/**
* Handles a started audit event.
* @param event the event.
*/
protected void handleStartedEvent (WfStateAuditEvent event) {
// start all activities
continueProcessing ();
}
/**
* Handles a resumed audit event.
* @param event the event.
*/
protected void handleResumedEvent (WfStateAuditEvent event) {
continueProcessing ();
}
private void continueProcessing () {
// checkAllActivities
TransitionManager tm = transitionManager();
if (tm.isAtEnd()) {
State resState = ClosedCompletedState.NORMAL;
for (Iterator i = stepsLocal().iterator(); i.hasNext();) {
State s = ((ActivityLocal)i.next()).typedState();
if (s.isSameOrSubState (ClosedState.TERMINATED)) {
resState = ClosedState.TERMINATED;
break; // can't get worse, aborted activity
// is handled separately.
}
}
updateState(resState);
return;
}
// start all runnableActivities
Collection activitiesToStart = tm.startableActivities();
for (Iterator it = activitiesToStart.iterator(); it.hasNext();) {
ExtActivityLocal a = (ExtActivityLocal)it.next();
try {
String ba = a.blockActivity();
if (ba != null && tm.isTransitionSource (ba)) {
// We are about to start an activity that is part
// of a block activity with exception
// condition. Find out if it is the first activity
// to be started in the set.
boolean isFirst = true;
for (Iterator i = activitiesOfBlockActivity(ba).iterator();
i.hasNext ();) {
ActivityLocal bm = (ActivityLocal)i.next ();
if (!bm.typedState()
.isSameOrSubState (NotRunningState.NOT_STARTED)) {
isFirst = false;
break;
}
}
if (isFirst) {
armDeadlines (ba);
}
}
a.start();
} catch (AlreadyRunningException e) {
// can't do much about this, shouldn't happen
logger.error (e.getMessage(), e);
}
}
}
private void armDeadlines (String ba) {
if (logger.isDebugEnabled ()) {
logger.debug ("Starting first activity of "
+ "block activity " + ba);
}
try {
int dlIndex = 0;
Date now = new Date();
Long bak = new Long(ba);
for (Iterator dli = ((List)getPaBlockDeadlines()
.get(bak)).iterator(); dli.hasNext ();
dlIndex += 1) {
Deadline dl = (Deadline)dli.next ();
if (dl.getState() != Deadline.STATE_INITIAL) {
continue;
}
dl.arm (this, now, toProcessLocal(),
new TimeoutInfo (bak, dlIndex));
}
} catch (NumberFormatException e) {
// can't do much about this, shouldn't happen
logger.error (e.getMessage(), e);
}
}
/**
* Returns an audit event object with process relevant information
* initialized.
* @param eventType event type
* @return the process related information.
*/
protected WfAuditEvent auditEventBase(String eventType) {
return new DefaultAuditEvent
(toProcess(), eventType, getPaKey(), getPaName(),
getPaProcessMgrName(), getPaProcessMgrVersion(),
getPaAuditEventSelection(), getPaStoreAuditEvents());
}
//
// Helpers
//
/**
* Return an arbitrary activity with the given key. The activity
* need not belong to this process.
* @param key activity key
* @return the activity
* @throws InvalidKeyException if the activity cannot be found
*/
protected abstract ExtActivityLocal lookupActivityLocal (String key)
throws InvalidKeyException;
/**
* Retrieve the base event information about a requesting activity.
* @param req the requester.
* @return the base info using an audit event as DTO.
*/
protected abstract WfAuditEvent activityRequesterInfo(WfRequester req);
/**
* Return a runtime representation of a block activity. Block
* activities are not persisted. However, the persistence layer
* needs a representation to assign a "from"-activity to exception
* transitions starting at block activities. This representation
* is subsequently used by the domain layer only and therefore
* provided by the domain layer.
* @param key the block activity key
* @return the block activity representation
*/
protected ActivityLocal blockActivityRepresentation (String key) {
ActivityLocal act = (ActivityLocal)baRepresentations.get (key);
if (act == null) {
act = new BAForExceptionHandling (key);
baRepresentations.put (key, act);
}
return act;
}
/**
* Return string representation for debugging purposes.
* @return a string representation.
*/
public String toString() {
return "Process[key=" + getPaKey () + "]";
}
//
// Process instantiation
//
/**
* Helper class for retrieving the initialization information from
* the process definition.
*/
public class SAXInitializer extends StackedHandler {
private Map actIdMap = null;
private Map trefsMap = null;
private Map actSetDefs = null;
private String actSetId = null;
private SAXEventBufferImpl actSetDef = null;
private ProcessData procDataMap = getPaProcessData();
private String extAttrName = null;
private String extAttrValue = null;
/**
* Create a new SAX initializer with the given parameters.
*/
public SAXInitializer () {
actIdMap = new HashMap();
trefsMap = new HashMap();
actSetDefs = new HashMap ();
}
/**
* Receive notification of the beginning of an element.
*
* @param uri the Namespace URI, or the empty string if the
* element has no Namespace URI or if Namespace processing is not
* being performed.
* @param loc the local name (without prefix), or the empty string
* if Namespace processing is not being performed.
* @param raw the raw XML 1.0 name (with prefix), or the empty
* string if raw names are not available.
* @param a the attributes attached to the element. If there are
* no attributes, it shall be an empty Attributes object.
* @throws SAXException not thrown.
*/
public void startElement
(String uri, String loc, String raw, Attributes a)
throws SAXException {
if (loc.equals ("WorkflowProcess")) {
setPaName(a.getValue("Name"));
setPaId(a.getValue("Id"));
} else if (loc.equals ("Applications")) {
getStack().push (new DefaultHandler ());
} else if (loc.equals ("DataField")
|| loc.equals ("FormalParameter")) {
setContextData ("ProcDataId", a.getValue ("Id"));
setContextData ("InitialValue", null);
} else if (loc.equals ("DataType")) {
getStack().push (new XPDLUtil.SAXDataTypeHandler ());
} else if (loc.equals ("Activity")) {
getStack().push
(new ActivityInitializer
(actIdMap, trefsMap, actSetDefs, null));
} else if (loc.equals ("Transition")) {
getStack().push
(new TransitionInitializer (actIdMap, trefsMap, null));
} else if (loc.equals ("ActivitySet")) {
actSetId = a.getValue ("Id");
actSetDef = new SAXEventBufferImpl ();
getStack().push (actSetDef);
} else if (loc.equals ("ExtendedAttribute")
&& getStack().getRelativeDepth() == 4) {
extAttrName = a.getValue("Name");
extAttrValue = a.getValue("Value");
}
}
/**
* Receive notification of the end of an element.
*
* @param uri the Namespace URI, or the empty string if the
* element has no Namespace URI or if Namespace processing is not
* being performed.
* @param loc the local name (without prefix), or the empty string
* if Namespace processing is not being performed.
* @param raw the raw XML 1.0 name (with prefix), or the empty
* string if raw names are not available.
* @throws SAXException not thrown.
*/
public void endElement(String uri, String loc, String raw)
throws SAXException {
if (loc.equals ("Priority")) {
try {
setPaPriority(Priority.fromInt
(Integer.parseInt(text().trim())));
} catch (InvalidPriorityException e) {
logger.error (e.getMessage (), e);
throw new SAXException (e);
}
} else if (loc.equals ("InitialValue")) {
Object dt = getContextData ("DataType");
try {
if (dt.equals (String.class)) {
setContextData ("InitialValue", text ());
} else if (dt.equals (Double.class)) {
setContextData ("InitialValue", new Double(text ()));
} else if (dt.equals (Long.class)) {
setContextData ("InitialValue", new Long(text ()));
} else if (dt.equals (Date.class)) {
setContextData ("InitialValue",
XMLUtil.parseXsdDateTime(text ()));
} else if (dt.equals (Boolean.class)) {
setContextData ("InitialValue", new Boolean(text ()));
} else if (dt.equals (Participant.class)) {
setContextData ("InitialValue", text ());
} else if ((dt instanceof ExternalReference)
|| (dt instanceof SAXEventBuffer)
|| dt.equals (org.w3c.dom.Element.class)) {
setContextData ("InitialValue",
parseInitialXMLValue (text()));
}
} catch (NumberFormatException e) {
throw new SAXException
("Cannot convert to type: " + e.getMessage ());
} catch (ParseException e) {
throw new SAXException
("Cannot convert to type: " + e.getMessage ());
}
} else if (loc.equals ("DataField")
|| loc.equals ("FormalParameter")) {
procDataMap.put (getContextData ("ProcDataId"),
getContextData ("InitialValue"));
} else if (loc.equals ("ActivitySet")) {
actSetDefs.put (actSetId, actSetDef);
} else if (loc.equals ("ExtendedAttribute")
&& extAttrName != null) {
if (extAttrValue == null) {
extAttrValue = text().trim();
}
if (extAttrName.equals ("Debug")
&& extAttrValue.equalsIgnoreCase ("true")) {
try {
setDebugEnabled (true);
} catch (InvalidStateException e) {
logger.error ("Cannot set debug enabled: "
+ e.getMessage (), e);
}
}
extAttrName = null;
extAttrValue = null;
}
}
}
private SAXEventBuffer parseInitialXMLValue (String text) {
try {
SAXParserFactory spf = SAXParserFactory.newInstance ();
spf.setValidating (false);
spf.setNamespaceAware (true);
spf.setFeature
("http://xml.org/sax/features/namespace-prefixes", true);
XMLReader xr = null;
try {
spf.setFeature
("http://xml.org/sax/features/xmlns-uris", true);
xr = spf.newSAXParser().getXMLReader();
} catch (SAXException e) {
xr = new XmlnsUrisPatcher
(spf.newSAXParser().getXMLReader());
}
SAXEventBufferImpl seb = new SAXEventBufferImpl ();
XMLFilterImpl filter = new XMLFilterImpl () {
private int level = 0;
public void startElement
(String uri, String localName, String qName,
Attributes atts) throws SAXException {
if (level > 0) {
super.startElement (uri,localName,qName,atts);
}
level += 1;
}
public void endElement
(String uri, String localName, String qName)
throws SAXException{
level -= 1;
if (level > 0) {
super.endElement (uri, localName, qName);
}
}
};
filter.setContentHandler (seb);
xr.setContentHandler (filter);
xr.parse (new InputSource
(new StringReader
("<temporary-root>" + text + "</temporary-root>")));
seb.pack();
return seb;
} catch (ParserConfigurationException e) {
throw new IllegalArgumentException
("Error initiliazing schema type: " + e.getMessage ());
} catch (SAXException e) {
throw new IllegalArgumentException
("Error initiliazing schema type: " + e.getMessage ());
} catch (IOException e) {
throw new IllegalArgumentException
("Error initiliazing schema type: " + e.getMessage ());
}
}
/**
* Helper class for retrieving the Activity information from
* the process definition.
*/
public class ActivityInitializer extends StackedHandler {
private Map actIdMap = null;
private Map trefsMap = null;
private Map actSetDefs = null;
private String blkActKey = null;
private String actId = null;
private Priority priority = Priority.NORMAL;
private String name = null;
private String description = null;
private StartFinishMode startMode = StartFinishMode.AUTOMATIC;
private StartFinishMode finishMode = StartFinishMode.AUTOMATIC;
private JoinAndSplitMode joinMode = JoinAndSplitMode.AND;
private JoinAndSplitMode splitMode = JoinAndSplitMode.AND;
private String performer = null;
private List deadlines = new ArrayList ();
private boolean waitingForStartMode = false;
private boolean waitingForFinishMode = false;
private List trefs = null;
private List impls = null;
private String actSetRef = null;
private int dlMode;
private String dlCond = null;
private String dlException = null;
private boolean deferredChoice = false;
private String extAttrName = null;
private String extAttrValue = null;
/**
* Create a new initializer with the given parameters.
* @param actIds activity id map
* @param trefs transition reference map
* @param actSets activity set definition map
* @param blkActKey the key of the block activity this
* activity belongs to or <code>null</code>
*/
public ActivityInitializer
(Map actIds, Map trefs, Map actSets, String blkActKey) {
actIdMap = actIds;
trefsMap = trefs;
actSetDefs = actSets;
this.blkActKey = blkActKey;
}
/**
* Receive notification of the beginning of an element.
*
* @param uri the Namespace URI, or the empty string if the
* element has no Namespace URI or if Namespace processing is not
* being performed.
* @param loc the local name (without prefix), or the empty string
* if Namespace processing is not being performed.
* @param raw the raw XML 1.0 name (with prefix), or the empty
* string if raw names are not available.
* @param a the attributes attached to the element. If there are
* no attributes, it shall be an empty Attributes object.
* @throws SAXException not thrown.
*/
public void startElement
(String uri, String loc, String raw, Attributes a)
throws SAXException {
if (loc.equals ("Activity")) {
actId = a.getValue ("Id");
name = a.getValue ("Name");
trefs = new ArrayList ();
impls = new ArrayList ();
} else if (loc.equals ("StartMode")) {
waitingForStartMode = true;
} else if (loc.equals ("FinishMode")) {
waitingForFinishMode = true;
} else if (loc.equals ("Automatic")) {
if (waitingForStartMode) {
startMode = StartFinishMode.AUTOMATIC;
} else if (waitingForFinishMode) {
finishMode = StartFinishMode.AUTOMATIC;
}
} else if (loc.equals ("Manual")) {
if (waitingForStartMode) {
startMode = StartFinishMode.MANUAL;
} else if (waitingForFinishMode) {
finishMode = StartFinishMode.MANUAL;
}
} else if (loc.equals ("Join")) {
joinMode = JoinAndSplitMode.fromString (a.getValue("Type"));
} else if (loc.equals ("Split")) {
splitMode = JoinAndSplitMode.fromString (a.getValue("Type"));
} else if (loc.equals ("TransitionRef")) {
trefs.add (a.getValue ("Id"));
} else if (loc.equals ("Tool")) {
getStack().push (ToolBasedImpl.saxConstructor ());
} else if (loc.equals ("SubFlow")) {
getStack().push (ProcBasedImpl.saxConstructor ());
} else if (loc.equals ("Deadline")) {
dlMode = Deadline.SYNCHR;
if (a.getValue("Execution") != null
&& a.getValue("Execution").equals ("ASYNCHR")) {
dlMode = Deadline.ASYNCHR;
}
} else if (loc.equals ("BlockActivity")) {
actSetRef = a.getValue("BlockId");
} else if (loc.equals ("ExtendedAttribute")
&& getStack().getRelativeDepth() == 2) {
extAttrName = a.getValue("Name");
extAttrValue = a.getValue("Value");
}
}
/**
* Receive notification of the end of an element.
*
* @param uri the Namespace URI, or the empty string if the
* element has no Namespace URI or if Namespace processing is not
* being performed.
* @param loc the local name (without prefix), or the empty string
* if Namespace processing is not being performed.
* @param raw the raw XML 1.0 name (with prefix), or the empty
* string if raw names are not available.
* @throws SAXException not thrown.
*/
public void endElement(String uri, String loc, String raw)
throws SAXException {
if (loc.equals ("Activity")) {
if (actSetRef != null) {
// create block activity, i.e. the activities defined
// in the activity set
Long baKey
= new Long (-createActivityKey().longValue());
BlockActivity ba = new BAForProcessInstantiation
(AbstractProcess.this, baKey.toString (),
(SAXEventBuffer)actSetDefs.get (actSetRef),
(String)getContextData ("packageId"),
actSetDefs, actId, joinMode, splitMode);
actIdMap.put(actId, ba);
getPaBlockDeadlines().put (baKey, deadlines);
} else {
// create a simple activity
WfActivityLocal act = createActivity
(blkActKey, priority, name, description,
startMode, finishMode, joinMode, splitMode,
impls.size() == 0 ? null
: ((Implementation[])impls.toArray
(new Implementation[impls.size()])),
performer, deadlines, deferredChoice,
getPaAuditEventSelection(), getPaStoreAuditEvents());
actIdMap.put (actId, act);
}
trefsMap.put (actId, trefs);
} else if (loc.equals ("Priority")) {
try {
priority = Priority
.fromInt (Integer.parseInt(text().trim()));
} catch (InvalidPriorityException e) {
throw new SAXException (e);
}
} else if (loc.equals ("Description")) {
description = text().trim();
} else if (loc.equals ("StartMode")) {
waitingForStartMode = false;
} else if (loc.equals ("FinishMode")) {
waitingForFinishMode = false;
} else if (loc.equals ("Performer")) {
performer = text().trim();
} else if (loc.equals ("Tool") || loc.equals ("SubFlow")) {
impls.add (getContextData ("implementation"));
} else if (loc.equals ("DeadlineCondition")) {
dlCond = text();
} else if (loc.equals ("ExceptionName")) {
dlException = text();
} else if (loc.equals ("Deadline")) {
deadlines.add (new Deadline (dlMode, dlCond, dlException));
} else if (loc.equals ("ExtendedAttribute")
&& extAttrName != null) {
if (extAttrValue == null) {
extAttrValue = text().trim();
}
if (extAttrName.equals ("DeferredChoice")
&& extAttrValue.equalsIgnoreCase ("true")) {
deferredChoice = true;
}
extAttrName = null;
extAttrValue = null;
}
}
}
/**
* Return a new initializer with the given parameters.
* @param actIds activity id map
* @param trefs transition reference map
* @param actSets activity set definition map
* @param blkActKey the id of the containing block activity or
* <code>null</code>
* @return the initializer
*/
ActivityInitializer activityInitializer
(Map actIds, Map trefs, Map actSets, String blkActKey) {
return new ActivityInitializer (actIds, trefs, actSets, blkActKey);
}
/**
* Factory method that create new persistent objects of type
* <code>WfActivity</code>. Must be implement by the persistence
* layer.
*
* @param blockActId if the activity is part of a block activity,
* else <code>null</code>
* @param priority a <code>Priority</code> value
* @param name the activity's name
* @param description activity description
* @param startMode the start mode
* @param finishMode the finish mode
* @param joinMode the join mode
* @param splitMode the split mode
* @param implementation the implementation description
* @param performer the performer
* @param deadlines the deadlines
* @param deferredChoice if true, the split is to be made as
* deferred choice
* @param auditEventSelection the audit event selection
* @param storeAuditEvents if true, audit events are stored in the
* database
* @return the created activity.
*/
protected abstract WfActivityLocal createActivity
(String blockActId, Priority priority, String name, String description,
StartFinishMode startMode, StartFinishMode finishMode,
JoinAndSplitMode joinMode, JoinAndSplitMode splitMode,
Implementation[] implementation, String performer, List deadlines,
boolean deferredChoice, int auditEventSelection,
boolean storeAuditEvents);
/**
* Helper class for retrieving the transition information from
* the process definition.
*/
public class TransitionInitializer extends StackedHandler {
private Map actIdMap = null;
private Map trefsMap = null;
private String blkActId = null;
private String transId = null;
private String fromId = null;
private String toId = null;
private String condType = null;
private String condExpr = null;
/**
* Create a new initializer with the given parameters.
* @param actIds activity id map
* @param trefs transition reference map
* @param blkActId the Id of the block activity being
* created, i.e. to which the transitions belong.
*/
public TransitionInitializer (Map actIds, Map trefs, String blkActId) {
actIdMap = actIds;
trefsMap = trefs;
this.blkActId = blkActId;
}
/**
* Receive notification of the beginning of an element.
*
* @param uri the Namespace URI, or the empty string if the
* element has no Namespace URI or if Namespace processing is not
* being performed.
* @param loc the local name (without prefix), or the empty string
* if Namespace processing is not being performed.
* @param raw the raw XML 1.0 name (with prefix), or the empty
* string if raw names are not available.
* @param a the attributes attached to the element. If there are
* no attributes, it shall be an empty Attributes object.
* @throws SAXException not thrown.
*/
public void startElement
(String uri, String loc, String raw, Attributes a)
throws SAXException {
if (loc.equals ("Transition")) {
transId = a.getValue ("Id");
fromId = a.getValue ("From");
toId = a.getValue ("To");
} else if (loc.equals ("Condition")) {
condType = a.getValue ("Type");
if (condType == null) {
condType = "CONDITION";
}
}
}
/**
* Receive notification of the end of an element.
*
* @param uri the Namespace URI, or the empty string if the
* element has no Namespace URI or if Namespace processing is not
* being performed.
* @param loc the local name (without prefix), or the empty string
* if Namespace processing is not being performed.
* @param raw the raw XML 1.0 name (with prefix), or the empty
* string if raw names are not available.
* @throws SAXException not thrown.
*/
public void endElement(String uri, String loc, String raw)
throws SAXException {
if (loc.equals ("Condition")) {
condExpr = text();
} else if (loc.equals ("Transition")) {
createTransition
(actIdMap, trefsMap, transId, fromId, toId,
condType, condExpr, blkActId);
}
}
}
/**
* Return a new initializer with the given parameters.
* @param actIds activity id map
* @param trefs transition reference map
* @param blkActId the Id of the block activity being
* created, i.e. to which the transitions belong.
* @return the initializer
*/
TransitionInitializer transitionInitializer
(Map actIds, Map trefs, String blkActId) {
return new TransitionInitializer (actIds, trefs, blkActId);
}
private void createTransition
(Map actIdMap, Map trefsMap,
String transId, String fromId, String toId,
String condType, String condExpr, String blkActId) {
// find associated "from"-activities
int order = evalTransitionOrder(transId, fromId, trefsMap);
ActivityLocal fromAct = (ActivityLocal)actIdMap.get(fromId);
BAForProcessInstantiation fromBA = null;
List fromActivities = new ArrayList();
if (fromAct instanceof BlockActivity) {
// we do not generate exception transitions from block
// internals to block externals
if (condExpr != null
&& (condType.equals ("EXCEPTION")
|| condType.equals ("DEFAULTEXCEPTION"))) {
fromActivities.add
(blockActivityRepresentation(fromAct.key()));
} else {
fromBA = (BAForProcessInstantiation)fromAct;
fromActivities.addAll (fromBA.exitActivities());
}
} else {
fromActivities.add (fromAct);
}
ActivityLocal toAct = (ActivityLocal)actIdMap.get(toId);
BAForProcessInstantiation toBA = null;
List toActivities = new ArrayList();
if (toAct instanceof BlockActivity) {
toBA = (BAForProcessInstantiation)toAct;
toActivities.addAll (toBA.entryActivities ());
} else {
toActivities.add (toAct);
}
// create all the possible transitions
for (Iterator fromIt = fromActivities.iterator(); fromIt.hasNext();){
ActivityLocal fa = (ActivityLocal)fromIt.next();
for (Iterator toIt = toActivities.iterator(); toIt.hasNext();){
ActivityLocal ta = (ActivityLocal)toIt.next();
// enhance transition id if block activities are
// involved
String id = transId;
if (blkActId != null) {
id += "/" + blkActId;
}
if (fromBA != null){
id += "/" + fromBA.setId() + "/"
+ fromBA.getMemberId (fa.key());
}
if (toBA != null){
id += "/" + toBA.setId() + "/"
+ toBA.getMemberId (ta.key());
}
// create and register transition(s)
int ct = Transition.COND_TYPE_CONDITION;
if (condExpr != null) {
if (condType.equals ("OTHERWISE")) {
ct = Transition.COND_TYPE_OTHERWISE;
} else if (condType.equals ("EXCEPTION")) {
ct = Transition.COND_TYPE_EXCEPTION;
} else if (condType.equals ("DEFAULTEXCEPTION")) {
ct = Transition.COND_TYPE_DEFAULTEXCEPTION;
}
}
doCreateTransition (id, transId, order, fa, ta, ct, condExpr);
}
}
}
/**
* Returns the transition order of the given transition id as defined with
* the set of transitions assigned to a given activity id - using the given
* map of transition refrences.
* @param transId the transition id to define the order for
* @param actId the activity id to determine the order for
* @param trefsMap the map of transition references
* @return the order value
*/
private int evalTransitionOrder
(String transId, String actId, Map trefsMap) {
int order = 0;
List trefs = (List)trefsMap.get (actId);
if (trefs == null) {
return Integer.MAX_VALUE;
}
Iterator refIt = trefs.iterator();
while (refIt.hasNext()) {
String refId = (String)refIt.next();
if (transId.equals(refId)){
break;
}
order += 1;
}
return order;
}
/**
* Persist a new transition with given id, from-activity,
* to-activity. The created transitions must be made persistent by
* the persistence layer.
* @param id the transition id
* @param group the transition group id
* @param order the transition priority
* @param fromAct the from activity
* @param toAct the to activity
* @param condType the type of the condition
* @param condition condition of this transition
* @return the created transition.
*/
protected abstract TransitionLocal doCreateTransition
(String id, String group, int order,
ActivityLocal fromAct, ActivityLocal toAct,
int condType, String condition);
}