Java tutorial
/* * Copyright (c) 2004-2012 The YAWL Foundation. All rights reserved. * The YAWL Foundation is a collaboration of individuals and * organisations who are committed to improving workflow technology. * * This file is part of YAWL. YAWL is free software: you can * redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation. * * YAWL 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 Lesser General * Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with YAWL. If not, see <http://www.gnu.org/licenses/>. */ package org.yawlfoundation.yawl.engine; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jdom2.Document; import org.jdom2.Element; import org.yawlfoundation.yawl.authentication.YClient; import org.yawlfoundation.yawl.authentication.YExternalClient; import org.yawlfoundation.yawl.authentication.YSessionCache; import org.yawlfoundation.yawl.elements.*; import org.yawlfoundation.yawl.elements.data.YParameter; import org.yawlfoundation.yawl.elements.state.YIdentifier; import org.yawlfoundation.yawl.elements.state.YInternalCondition; import org.yawlfoundation.yawl.engine.announcement.AnnouncementContext; import org.yawlfoundation.yawl.engine.instance.InstanceCache; import org.yawlfoundation.yawl.engine.interfce.interfaceA.InterfaceADesign; import org.yawlfoundation.yawl.engine.interfce.interfaceA.InterfaceAManagement; import org.yawlfoundation.yawl.engine.interfce.interfaceA.InterfaceAManagementObserver; import org.yawlfoundation.yawl.engine.interfce.interfaceB.InterfaceBClient; import org.yawlfoundation.yawl.engine.interfce.interfaceB.InterfaceBClientObserver; import org.yawlfoundation.yawl.engine.interfce.interfaceB.InterfaceBInterop; import org.yawlfoundation.yawl.engine.time.YTimedObject; import org.yawlfoundation.yawl.engine.time.YTimer; import org.yawlfoundation.yawl.exceptions.*; import org.yawlfoundation.yawl.logging.YEventLogger; import org.yawlfoundation.yawl.logging.YLogDataItem; import org.yawlfoundation.yawl.logging.YLogDataItemList; import org.yawlfoundation.yawl.logging.YLogPredicate; import org.yawlfoundation.yawl.logging.table.YAuditEvent; import org.yawlfoundation.yawl.schema.XSDType; import org.yawlfoundation.yawl.schema.YDataValidator; import org.yawlfoundation.yawl.unmarshal.YMarshal; import org.yawlfoundation.yawl.util.*; import java.io.InputStream; import java.net.URI; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * @author Lachlan Aldred * Date: 17/06/2003 * Time: 13:46:54 * * @author Michael Adams (refactoring for v2.0-2.1) */ public class YEngine implements InterfaceADesign, InterfaceAManagement, InterfaceBClient, InterfaceBInterop { // STATIC MEMBERS // // Engine statuses public enum Status { Dormant, Initialising, Running, Terminating } // Workitem completion types public enum WorkItemCompletion { Normal, Force, Fail } // Constants private static final YPersistenceManager _pmgr = new YPersistenceManager(); private static final boolean ENGINE_PERSISTS_BY_DEFAULT = false; private static YEngine _thisInstance; // reference to self private static YEventLogger _yawllog; private static YCaseNbrStore _caseNbrStore; private static Logger _logger; private static Set<YTimedObject> _expiredTimers; private static boolean _generateUIMetaData = true; // extended attributes private static boolean _persisting; private static boolean _restoring; // NON-STATIC MEMBERS // private YWorkItemRepository _workItemRepository; protected YNetRunnerRepository _netRunnerRepository; private Map<YIdentifier, YSpecification> _runningCaseIDToSpecMap; private Map<String, YAWLServiceReference> _yawlServices; private Map<String, YExternalClient> _externalClients; private YSpecificationTable _specifications; private InterfaceAManagementObserver _interfaceAClient; private InterfaceBClientObserver _interfaceBClient; private Status _engineStatus; private YSessionCache _sessionCache; private YAnnouncer _announcer; // handles all i'face notifys private YAWLServiceReference _defaultWorklist; private InstanceCache _instanceCache; private YBuildProperties _buildProps; private String _engineClassesRootFilePath; private boolean _allowGenericAdminID; /********************************************************************************/ /** * The Constructor - called from getInstance(). */ private YEngine() { _engineStatus = Status.Initialising; // initialise global objects _sessionCache = new YSessionCache(); _workItemRepository = new YWorkItemRepository(); _caseNbrStore = YCaseNbrStore.getInstance(); _announcer = new YAnnouncer(this); // the 'pusher' of interface events _specifications = new YSpecificationTable(); _instanceCache = new InstanceCache(); _logger = LogManager.getLogger(YEngine.class); _netRunnerRepository = new YNetRunnerRepository(); _runningCaseIDToSpecMap = new ConcurrentHashMap<YIdentifier, YSpecification>(); _yawlServices = new ConcurrentHashMap<String, YAWLServiceReference>(); _externalClients = new ConcurrentHashMap<String, YExternalClient>(); } /** * Initialises the engine (if not already initialised) & returns the engine instance. * @param persisting true if engine state is to be persisted * @return a reference to the initialised engine * @throws YPersistenceException if there's a problem restoring from persistence */ public static YEngine getInstance(boolean persisting, boolean gatherHbnStats) throws YPersistenceException { if (_thisInstance == null) { _thisInstance = new YEngine(); _persisting = persisting; _logger.debug("--> YEngine: Creating initial instance"); // init the process logger _yawllog = YEventLogger.getInstance(_thisInstance); // Initialise the persistence layer & restore state if (_persisting) { _pmgr.initialise(true); _pmgr.setStatisticsEnabled(gatherHbnStats); _caseNbrStore.setPersisting(true); _thisInstance.restore(); } else { _pmgr.setEnabled(false); // Default clients and services should always be available _thisInstance.loadDefaultClients(); } // Init completed - set engine status to up and running _logger.info("Marking engine status = RUNNING"); _thisInstance.setEngineStatus(Status.Running); } return _thisInstance; } public static YEngine getInstance(boolean persisting) throws YPersistenceException { return getInstance(persisting, false); } /** * Initialises the engine (if not already initialised) & returns the engine instance, * using the default persistence flag. * @return a reference to the initialised engine */ public static YEngine getInstance() { try { return getInstance(ENGINE_PERSISTS_BY_DEFAULT); } catch (Exception e) { throw new RuntimeException("Failure to instantiate the engine."); } } /** * Checks if the engine is currently running * @return true if running, false otherwise */ public static boolean isRunning() { return (_thisInstance != null) && (_thisInstance.getEngineStatus() == Status.Running); } /** * Restores persisted data when the engine restarts. * @throws YPersistenceException when there's a problem with the restore process */ private void restore() throws YPersistenceException { _logger.debug("--> restore"); _restoring = true; YEngineRestorer restorer = new YEngineRestorer(_thisInstance, _pmgr); try { _pmgr.setRestoring(true); startTransaction(); // restore data objects from persistence (sequence is important) restorer.restoreStaticObjects(); _caseNbrStore = restorer.restoreNextAvailableCaseNumber(); restorer.restoreInstances(); _expiredTimers = restorer.restoreTimedObjects(); restorer.restartRestoredProcessInstances(); // complete transaction commitTransaction(); _pmgr.setRestoring(false); _workItemRepository.cleanseRepository(); // synch with net runners dump(); // log result (if debugging) } catch (YPersistenceException ype) { _logger.fatal("Failure to restart engine from persistence image", ype); throw new YPersistenceException("Failure to restart engine from persistence image"); } catch (Exception e) { // a non-YPersistenceException means the restore failed, but the engine is // still operational _logger.error("Persisted state failed to fully restore - engine is " + "operational but may be in an inconsistent state. Exception: ", e); } finally { _logger.debug("restore <---"); _restoring = false; restorer.persistDefaultClients(); // delayed til restoring is complete } } /** * Loads the logon accounts for the standard client apps and services from a * properties file on startup when they have not previously been persisted (ie. on * first startup) or when persistence is disabled. * @return the set of default clients loaded. * @throws YPersistenceException A passthrough - since it is only called when * restoring or when persistence is disabled, this exception is never thrown. */ protected Set<YClient> loadDefaultClients() throws YPersistenceException { YDefClientsLoader loader = new YDefClientsLoader(); for (YExternalClient client : loader.getLoadedClients()) { addExternalClient(client); } for (YAWLServiceReference service : loader.getLoadedServices()) { addYawlService(service); } return loader.getAllLoaded(); } /** * Indicate if user interface metadata is to be generated within a task's input XML doclet. * @param generate true to generate metadata, false to not generate it */ public void setGenerateUIMetaData(boolean generate) { _generateUIMetaData = generate; } /** * Indicates if user interface metadata will be generated within a task's input XML doclet. * @return true=UIMetaData generated, false=UIMetaData not supported */ public boolean generateUIMetaData() { return _generateUIMetaData; } public InstanceCache getInstanceCache() { return _instanceCache; } public Map<String, YParameter> getParameters(YSpecificationID specID, String taskID, boolean input) { Map<String, YParameter> result = null; YTask task = getTaskDefinition(specID, taskID); if (task != null) { YDecomposition decomp = task.getDecompositionPrototype(); if (decomp != null) { result = input ? decomp.getInputParameters() : decomp.getOutputParameters(); } } return result; } public String getEngineClassesRootFilePath() { return _engineClassesRootFilePath; } public void setEngineClassesRootFilePath(String path) { String pkgPath = "WEB-INF/classes/org/yawlfoundation/yawl/"; _engineClassesRootFilePath = path + pkgPath; } // called when servlet init() has completed public void initialised(int maxWaitSeconds) { _announcer.announceEngineInitialisationCompletion(getYAWLServices(), maxWaitSeconds); // Now that the engine's running, process any expired timers if (_expiredTimers != null) { for (YTimedObject timer : _expiredTimers) timer.handleTimerExpiry(); } } public void shutdown() { _announcer.shutdownObserverGateways(); _announcer.shutdownInterfaceXListeners(); _sessionCache.shutdown(); YTimer.getInstance().shutdown(); // stop timer threads YTimer.getInstance().cancel(); // stop the timer if (_pmgr != null) _pmgr.closeFactory(); } public void initBuildProperties(InputStream stream) { _buildProps = new YBuildProperties(); _buildProps.load(stream); } public YBuildProperties getBuildProperties() { return _buildProps; } public YSessionCache getSessionCache() { return _sessionCache; } public void checkEngineRunning() throws YEngineStateException { if (getEngineStatus() != Status.Running) { throw new YEngineStateException("Unable to accept request as engine" + " not in running state: Current state = " + getEngineStatus().name()); } } /*********************************************************************************/ /* These two 'register' methods are called by the standalone gui */ public void registerInterfaceAClient(InterfaceAManagementObserver observer) { _interfaceAClient = observer; } public void registerInterfaceBObserver(InterfaceBClientObserver observer) { _interfaceBClient = observer; } /*********************************************************************************/ /** * Registers an InterfaceB Observer Gateway with the engine in order to receive callbacks. * @param gateway the gateway to register * @throws YAWLException if the observerGateway has a null scheme value. */ public void registerInterfaceBObserverGateway(ObserverGateway gateway) throws YAWLException { _announcer.registerInterfaceBObserverGateway(gateway); } public YAnnouncer getAnnouncer() { return _announcer; } public void setEngineStatus(Status status) { _engineStatus = status; } public Status getEngineStatus() { return _engineStatus; } public AnnouncementContext getAnnouncementContext() { return _announcer.getAnnouncementContext(); } public int reannounceEnabledWorkItems() throws YStateException { return _announcer.reannounceEnabledWorkItems(); } public int reannounceExecutingWorkItems() throws YStateException { return _announcer.reannounceExecutingWorkItems(); } public int reannounceFiredWorkItems() throws YStateException { return _announcer.reannounceFiredWorkItems(); } public void reannounceWorkItem(YWorkItem workItem) throws YStateException { _announcer.reannounceWorkItem(workItem); } /*****************************************************************************/ /** * Adds a net runner instance to the engine caches. The specification is derived from * the runner instance. * @param runner the runner to add */ protected void addRunner(YNetRunner runner) { YSpecificationID specID = runner.getSpecificationID(); YSpecification specification = _specifications.getSpecification(specID); addRunner(runner, specification); } /** * Adds a net runner instance to the engine caches. * @param runner the runner to add * @param specification its specification */ public void addRunner(YNetRunner runner, YSpecification specification) { if (specification != null) { runner.setEngine(this); _netRunnerRepository.add(runner); _runningCaseIDToSpecMap.put(runner.getCaseID(), specification); _instanceCache.addCase(runner.getCaseID().toString(), specification.getSpecificationID(), runner.getNetData().getData(), null, runner.getStartTime()); // announce the add to the standalone gui (if any) if (_interfaceBClient != null) { _interfaceBClient.addCase(specification.getSpecificationID(), runner.getCaseID().toString()); } } } /** * Returns a vector of all net runners for a top level caseID. * @param primaryCaseID the id of the case * @return Vector of net runners for the case */ private List<YNetRunner> getRunnersForPrimaryCase(YIdentifier primaryCaseID) { return _netRunnerRepository.getAllRunnersForCase(primaryCaseID); } public YNetRunner getNetRunner(YWorkItem workItem) { return _netRunnerRepository.get(workItem); } public YNetRunner getNetRunner(YIdentifier identifier) { return _netRunnerRepository.get(identifier); } public YNetRunnerRepository getNetRunnerRepository() { return _netRunnerRepository; } public String getNetData(String caseID) throws YStateException { // if this is a root net case id, the net data is equivalent to the case data if (!caseID.contains(".")) return getCaseData(caseID); YNetRunner subNetRunner = _netRunnerRepository.get(caseID); if (subNetRunner != null) { return subNetRunner.getNetData().getData(); } else throw new YStateException("Received invalid case id '" + caseID + "'."); } /**************************************************************************/ /** * Adds the specification(s) (expressed as an xml string) to the engine * @param specStr an XML formatted specification * @param ignoreErrors ignore verification errors and load the spec anyway. * @param verificationHandler an in/out param passing any error messages. * @return the specification ids of the successfully loaded specs */ public List<YSpecificationID> addSpecifications(String specStr, boolean ignoreErrors, YVerificationHandler verificationHandler) throws YPersistenceException { _logger.debug("--> addSpecifications"); List<YSpecificationID> result = new Vector<YSpecificationID>(); List<YSpecification> newSpecifications; try { newSpecifications = YMarshal.unmarshalSpecifications(specStr); } catch (YSyntaxException e) { // catch the xml parser's exception, transform it into YAWL format // and abort the load for (String msg : e.getMessage().split("\n")) { verificationHandler.error(null, msg); } _logger.debug("<-- addSpecifications: syntax exceptions found"); return result; } if (newSpecifications != null) { for (YSpecification specification : newSpecifications) { specification.verify(verificationHandler); //if the error messages are empty or contain only warnings if (ignoreErrors || !verificationHandler.hasErrors()) { if (loadSpecification(specification)) { if (_persisting && !_restoring) { try { storeObject(specification); } catch (YPersistenceException e) { throw new YPersistenceException("Failure whilst persisting new specification", e); } } result.add(specification.getSpecificationID()); } else { String errDetail = specification.getSchemaVersion().isBetaVersion() ? "URI: " + specification.getURI() : "UID: " + specification.getID(); errDetail += "- Version: " + specification.getSpecVersion(); verificationHandler.error(this, "There is a specification with an identical id to [" + errDetail + "] already loaded into the engine."); } } } } _logger.debug("<-- addSpecifications: {} IDs loaded", result.size()); return result; } /** * Loads a specification * @param spec the specification to load * @return true if spec is loaded, false if it was already loaded */ public boolean loadSpecification(YSpecification spec) { return _specifications.loadSpecification(spec); } /** * Removes a previously loaded specification from the engine * @param specID the identifier of the specification to unload * @throws YStateException if the spec is still in use (with a live case) * @throws YPersistenceException if there's some persistence problem */ public void unloadSpecification(YSpecificationID specID) throws YStateException, YPersistenceException { _logger.debug("--> unloadSpecification: URI={}", specID.toString()); if (_specifications.contains(specID)) { YSpecification specToUnload = _specifications.getSpecification(specID); // Reject unload request if we have active cases using it if (_runningCaseIDToSpecMap.values().contains(specToUnload)) { throw new YStateException("Cannot unload specification '" + specID + "' as one or more cases are currently active against it."); } _logger.info("Removing process specification {}", specID); _specifications.unloadSpecification(specToUnload); _yawllog.removeSpecificationFromCache(specID); deleteObject(specToUnload); } else { // the spec's not in the engine throw new YStateException("Engine contains no such specification with id '" + specID + "'."); } _logger.debug("<-- unloadSpecification"); } /** * Provides the set of specification ids for specs loaded into the engine. It returns * those that were loaded as well as those with running instances that are unloaded. * * @return A set of specification ids */ public Set<YSpecificationID> getLoadedSpecificationIDs() { return _specifications.getSpecIDs(); } /** * Returns the latest loaded version of a specification identified by 'key' * @param key the spec's identifier (v2.0+) or uri (pre-v2.0) * @return the matching specification or null if no match found */ public YSpecification getLatestSpecification(String key) { return _specifications.getLatestSpecification(key); } public YSpecification getSpecification(YSpecificationID specID) { return _specifications.getSpecification(specID); } public YSpecification getSpecificationForCase(YIdentifier caseID) { return _runningCaseIDToSpecMap.get(caseID); } public YSpecification getProcessDefinition(YSpecificationID specID) { return _specifications.getSpecification(specID); } public String getSpecificationDataSchema(YSpecificationID specID) { YSpecification spec = _specifications.getSpecification(specID); if (spec != null) { YDataValidator validator = spec.getDataValidator(); if (validator != null) { return validator.getSchema(); } } return null; } public String getLoadStatus(YSpecificationID specID) { return _specifications.contains(specID) ? YSpecification._loaded : YSpecification._unloaded; } /***********************************************************************/ /** * Given a process specification id return the cases that are its running * instances. * * @param specID the process specification id string. * @return a set of YIdentifer caseIDs that are run time instances of the * process specification with id = specID */ public Set<YIdentifier> getCasesForSpecification(YSpecificationID specID) { Set<YIdentifier> resultSet = new HashSet<YIdentifier>(); if (_specifications.contains(specID)) { for (YIdentifier caseID : _runningCaseIDToSpecMap.keySet()) { YSpecification specForCaseID = _runningCaseIDToSpecMap.get(caseID); if (specForCaseID.getSpecificationID().equals(specID)) { resultSet.add(caseID); } } } return resultSet; } /** * Gets the complete map of all running case ids, grouped by specification id * @return a map of [YSpecificationID, List<Yidentifier>] */ public Map<YSpecificationID, List<YIdentifier>> getRunningCaseMap() { Map<YSpecificationID, List<YIdentifier>> caseMap = new HashMap<YSpecificationID, List<YIdentifier>>(); for (YIdentifier caseID : _runningCaseIDToSpecMap.keySet()) { YSpecification specForCaseID = _runningCaseIDToSpecMap.get(caseID); YSpecificationID specID = specForCaseID.getSpecificationID(); List<YIdentifier> list = caseMap.get(specID); if (list == null) { list = new ArrayList<YIdentifier>(); caseMap.put(specID, list); } list.add(caseID); } return caseMap; } protected YIdentifier startCase(YSpecificationID specID, String caseParams, URI completionObserver, String caseID, YLogDataItemList logData, String serviceRef, boolean delayed) throws YStateException, YDataStateException, YQueryException, YPersistenceException { // check spec is loaded and is latest version (YStateException if not) YSpecification specification = _specifications.getSpecificationForCaseStart(specID); // check & format case data params (if any) Element data = formatCaseParams(caseParams, specification); YNetRunner runner = new YNetRunner(_pmgr, specification.getRootNet(), data, caseID); _netRunnerRepository.add(runner); logCaseStarted(specID, runner, completionObserver, caseParams, logData, serviceRef, delayed); // persist it if ((!_restoring) && (_pmgr != null)) { _pmgr.storeObject(runner); } runner.continueIfPossible(_pmgr); runner.start(_pmgr); YIdentifier runnerCaseID = runner.getCaseID(); // special case: if spec contains exactly one task, and its empty, // the case (and runner) has already completed, so don't update map if (runner.hasActiveTasks()) { _runningCaseIDToSpecMap.put(runnerCaseID, specification); // announce the new case to the standalone gui (if any) if (_interfaceBClient != null) { _logger.debug("Asking client to add case {}", runnerCaseID.toString()); _interfaceBClient.addCase(specID, runnerCaseID.toString()); } } return runnerCaseID; } protected Element formatCaseParams(String paramStr, YSpecification spec) throws YStateException { Element data = null; if (paramStr != null && !"".equals(paramStr)) { data = JDOMUtil.stringToElement(paramStr); if (data == null) { throw new YStateException("Invalid or malformed caseParams."); } else if (!(spec.getRootNet().getID().equals(data.getName()) || (spec.getURI().equals(data.getName())))) { throw new YStateException("Invalid caseParams: outermost element name must match " + "specification URI or root net name."); } } return data; } private void logCaseStarted(YSpecificationID specID, YNetRunner runner, URI completionObserver, String caseParams, YLogDataItemList logData, String serviceRef, boolean delayed) { YIdentifier caseID = runner.getCaseID(); _announcer.announceCheckCaseConstraints(specID, caseID.toString(), caseParams, true); // ix _announcer.announceCaseStart(specID, caseID, serviceRef, delayed); // ib if (completionObserver != null) { YAWLServiceReference observer = getRegisteredYawlService(completionObserver.toString()); if (observer != null) { runner.setObserver(observer); } else { _logger.warn("Completion observer [{}] is not a registered YAWL service.", completionObserver); } } // log case start event YLogPredicate logPredicate = runner.getNet().getLogPredicate(); if (logPredicate != null) { String predicate = logPredicate.getParsedStartPredicate(runner.getNet()); if (predicate != null) { logData.add(new YLogDataItem("Predicate", "OnLaunch", predicate, "string")); } } _yawllog.logCaseCreated(_pmgr, specID, caseID, logData, serviceRef); // cache instance _instanceCache.addCase(caseID.toString(), specID, caseParams, logData, runner.getStartTime()); } /** * Finalises a case completion. * @param caseID the id of the completing case * @throws YPersistenceException if theres a persistence problem */ protected void removeCaseFromCaches(YIdentifier caseID) { _logger.debug("--> removeCaseFromCaches: Case={}", caseID.get_idString()); _netRunnerRepository.remove(caseID); _runningCaseIDToSpecMap.remove(caseID); _instanceCache.removeCase(caseID.toString()); // announce the completion to the standalone gui (if any) if (_interfaceBClient != null) _interfaceBClient.removeCase(caseID.toString()); _logger.debug("<-- removeCaseFromCaches"); } /** * Cancels a running case. * @param caseID the identifier of the cancelling case * @throws YPersistenceException if there's some persistence problem */ public void cancelCase(YIdentifier caseID, String serviceHandle) throws YPersistenceException, YEngineStateException { _logger.debug("--> cancelCase"); checkEngineRunning(); if (caseID == null) { throw new IllegalArgumentException("Attempt to cancel a case using a null caseID"); } _logger.info("Deleting persisted process instance: {}", caseID); Set<YWorkItem> removedItems = _workItemRepository.removeWorkItemsForCase(caseID); YNetRunner runner = _netRunnerRepository.get(caseID); synchronized (_pmgr) { startTransaction(); if (_persisting) clearWorkItemsFromPersistence(removedItems); YTimer.getInstance().cancelTimersForCase(caseID.toString()); removeCaseFromCaches(caseID); if (runner != null) runner.cancel(_pmgr); clearCaseFromPersistence(caseID); _yawllog.logCaseCancelled(_pmgr, caseID, null, serviceHandle); for (YWorkItem item : removedItems) { _yawllog.logWorkItemEvent(_pmgr, item, YWorkItemStatus.statusCancelledByCase, null); } commitTransaction(); _announcer.announceCaseCancellation(caseID, getYAWLServices()); } } /** * @deprecated use cancelCase(YIdentifier, String) * @param id * @throws YPersistenceException * @throws YEngineStateException */ public void cancelCase(YIdentifier id) throws YPersistenceException, YEngineStateException { cancelCase(id, null); } public String launchCase(YSpecificationID specID, String caseParams, URI completionObserver, YLogDataItemList logData) throws YStateException, YDataStateException, YPersistenceException, YEngineStateException, YQueryException { return launchCase(specID, caseParams, completionObserver, null, logData, null, false); } public String launchCase(YSpecificationID specID, String caseParams, URI completionObserver, YLogDataItemList logData, String serviceHandle) throws YStateException, YDataStateException, YPersistenceException, YEngineStateException, YQueryException { return launchCase(specID, caseParams, completionObserver, null, logData, serviceHandle, false); } public String launchCase(YSpecificationID specID, String caseParams, URI completionObserver, String caseID, YLogDataItemList logData, String serviceHandle, boolean delayed) throws YStateException, YDataStateException, YEngineStateException, YQueryException, YPersistenceException { _logger.debug("--> launchCase"); // ensure that the caseid passed (if any) is not already in use if ((caseID != null) && (getCaseID(caseID) != null)) { throw new YStateException("CaseID '" + caseID + "' is already active."); } checkEngineRunning(); synchronized (_pmgr) { startTransaction(); try { YIdentifier yCaseID = startCase(specID, caseParams, completionObserver, caseID, logData, serviceHandle, delayed); if (yCaseID != null) { commitTransaction(); announceEvents(yCaseID); return yCaseID.toString(); } else throw new YStateException("Unable to start case."); } catch (YAWLException ye) { _logger.error("Failure returned from startCase - Rolling back Hibernate TXN", ye); rollbackTransaction(); ye.rethrow(); } } _logger.debug("<-- launchCase"); return null; } public YIdentifier getCaseID(String caseIDStr) { _logger.debug("--> getCaseID"); return _netRunnerRepository.getCaseIdentifier(caseIDStr); } private Set<YNetElement> getCaseLocations(YIdentifier caseID) { Set<YNetElement> allLocations = new HashSet<YNetElement>(); for (YIdentifier identifier : caseID.getDescendants()) { allLocations.addAll(identifier.getLocations()); } return allLocations; } public String getStateTextForCase(YIdentifier caseID) { _logger.debug("--> getStateTextForCase: ID={}", caseID.get_idString()); String cr = System.getProperty("line.separator"); String hashLine = StringUtil.repeat('#', 60); StringBuilder stateText = new StringBuilder(); stateText.append(hashLine).append(cr).append("CaseID: ").append(caseID).append(cr).append("Spec: ") .append(_runningCaseIDToSpecMap.get(caseID)).append(cr).append(hashLine).append(cr); for (YNetElement element : getCaseLocations(caseID)) { stateText.append("CaseIDs in: ").append(element.toString()).append(cr); if (element instanceof YCondition) { stateText.append("\thashcode: ").append(element.hashCode()).append(cr); for (YIdentifier identifier : ((YConditionInterface) element).getIdentifiers()) { stateText.append('\t').append(identifier.toString()).append(cr); } } else if (element instanceof YTask) { YTask task = (YTask) element; for (YInternalCondition internalCondition : task.getAllInternalConditions()) { if (internalCondition.containsIdentifier()) { stateText.append('\t').append(internalCondition.toString()).append(cr); for (YIdentifier identifier : internalCondition.getIdentifiers()) { stateText.append("\t\t").append(identifier.toString()).append(cr); } } } } } return stateText.toString(); } public String getStateForCase(YIdentifier caseID) { YSpecification spec = _runningCaseIDToSpecMap.get(caseID); if (spec == null) { return "<caseState/>"; } XNode stateNode = new XNode("caseState"); stateNode.addAttribute("caseID", caseID); stateNode.addAttribute("specID", spec.getSpecificationID().toString()); for (YNetElement element : getCaseLocations(caseID)) { if (element instanceof YCondition) { YCondition condition = (YCondition) element; XNode conditionNode = stateNode.addChild("condition"); conditionNode.addAttribute("id", condition.toString()); conditionNode.addAttribute("name", condition.getName()); conditionNode.addAttribute("documentation", condition.getDocumentation()); for (YIdentifier identifier : condition.getIdentifiers()) { conditionNode.addChild("identifier", identifier.toString()); } XNode flowsIntoNode = conditionNode.addChild("flowsInto"); for (YFlow flow : condition.getPostsetFlows()) { String doc = (flow.getDocumentation() != null) ? flow.getDocumentation() : ""; XNode nextRefNode = flowsIntoNode.addChild("nextElementRef"); nextRefNode.addAttribute("id", flow.getNextElement().getID()); nextRefNode.addAttribute("documentation", doc); } } else if (element instanceof YTask) { YTask task = (YTask) element; XNode taskNode = stateNode.addChild("task"); taskNode.addAttribute("id", task.toString()); taskNode.addAttribute("name", task.getDecompositionPrototype().getID()); for (YInternalCondition internalCondition : task.getAllInternalConditions()) { if (internalCondition.containsIdentifier()) { taskNode.addChild(internalCondition.toXNode()); } } } } return stateNode.toString(); } public String getCaseData(String caseID) throws YStateException { // if this is for a sub-net, act accordingly if (caseID.contains(".")) return getNetData(caseID); YIdentifier id = getCaseID(caseID); if (id != null) { YNetRunner runner = _netRunnerRepository.get(id); return runner.getNetData().getData(); } throw new YStateException("Invalid case id '" + caseID + "'."); } /** * Returns a list of the YIdentifiers objects for running cases. * @return List of running cases */ public List<YIdentifier> getRunningCaseIDs() { return new ArrayList<YIdentifier>(_runningCaseIDToSpecMap.keySet()); } /** * @return the next available case number. */ public String getNextCaseNbr() { return _caseNbrStore.getNextCaseNbr(_pmgr); } /** * AJH: Public method which returns the next available caseID * Note: This is only available with a non-persisting engine and is used * to ascertain the case ID prior to launching a case (eg. for an XForms * execution framework). * @return A unique case ID * @throws YPersistenceException if there's a problem persisting the change */ public String allocateCaseID() throws YPersistenceException { if (isPersisting()) { throw new YPersistenceException( "Pre-allocated CaseIDs are not available in a persisting engine instance"); } return YCaseNbrStore.getInstance().getNextCaseNbr(null); } /** * Suspends the execution of a case - currently only called from YAdminGUI. * @param caseID the id of the case to suspend * @throws YPersistenceException if there's a problem persisting the change * @throws YStateException if case cannot be suspended given the current engine */ public void suspendCase(YIdentifier caseID) throws YPersistenceException, YStateException { synchronized (_pmgr) { startTransaction(); try { suspendCase(_pmgr, caseID); commitTransaction(); } catch (Exception e) { _logger.error("Failure to suspend case " + caseID, e); rollbackTransaction(); throw new YStateException("Could not suspend case (See log for details)"); } } } /** * Suspends the execution of a case. * @param pmgr the persistence manager object * @param id the case id to clear * @throws YPersistenceException if there's a problem clearing the case * @throws YStateException if case cannot be suspended given the current engine * operating state */ private void suspendCase(YPersistenceManager pmgr, YIdentifier id) throws YPersistenceException, YStateException { _logger.debug("--> suspendCase: CaseID = ", id.toString()); // Reject call if this case not currently in a normal state YNetRunner topLevelNet = _netRunnerRepository.get(id); if (!topLevelNet.hasNormalState()) { throw new YStateException("Case " + topLevelNet.getCaseID() + " cannot be suspended as currently not executing normally (SuspendStatus=" + topLevelNet.getExecutionStatus() + ")"); } else { // Go thru all runners and set status to suspending for (YNetRunner runner : getRunnersForPrimaryCase(id)) { _logger.debug("Current status of runner {} = {}", runner.get_caseID(), runner.getExecutionStatus()); runner.setStateSuspending(); _announcer.announceCaseSuspending(id, getYAWLServices()); if (pmgr != null) pmgr.updateObject(runner); } _logger.info("Case {} is attempting to suspend", topLevelNet.getCaseID()); // See if we can progress this case into a fully suspended state. progressCaseSuspension(pmgr, id); } _logger.debug("<-- suspendCase"); } /** * Resumes execution of a case. * @param id the id of the case to resume * @throws YPersistenceException if there's a problem persisting the resumed case * @throws YStateException if case cannot be resumed */ public void resumeCase(YIdentifier id) throws YPersistenceException, YStateException { synchronized (_pmgr) { startTransaction(); try { resumeCase(_pmgr, id); commitTransaction(); announceEvents(id); _announcer.announceCaseResumption(id, getYAWLServices()); } catch (Exception e) { _logger.error("Failure to resume case " + id, e); rollbackTransaction(); throw new YStateException("Could not resume case (See log for details)"); } } } /** * Resumes execution of a case. * @param pmgr the persistence manager object * @param id the id of the case to resume * @throws YPersistenceException if there's a problem persisting the resumed case * @throws YStateException if case cannot be resumed * @throws YDataStateException * @throws YQueryException */ private void resumeCase(YPersistenceManager pmgr, YIdentifier id) throws YPersistenceException, YStateException, YDataStateException, YQueryException { _logger.debug("--> resumeCase: CaseID = {}", id.toString()); // reject call if this case not currently suspended or suspending if (_netRunnerRepository.get(id).isInSuspense()) { for (YNetRunner runner : getRunnersForPrimaryCase(id)) { _logger.debug("Current status of runner {} = {}", runner.get_caseID(), runner.getExecutionStatus()); runner.setStateNormal(); runner.kick(pmgr); // Update persistence only if this runner has not completed. If it has // completed (as in the case where we resume a case and the last // workitem has previously been completed), the above call to 'kick' // will have progressed the net to its end point, so the persistence // object will have been deleted. if ((pmgr != null) && (!runner.isCompleted())) { pmgr.updateObject(runner); } } _logger.info("Case {} has resumed execution", id); } else { throw new YStateException( "Case " + id + " cannot be suspended as currently not executing normally (SuspendStatus=" + _netRunnerRepository.get(id).getExecutionStatus() + ")"); } _logger.debug("<-- resumeCase"); } /** * Attempts to progress the suspension status of a case. * * Where a Case is "suspending", we scan the workitems associated with all the nets * associated with the case, and where no workitems are enabled, executing or fired, * we progress the suspension state from "suspending" to "suspended". * @param pmgr the persistence manager object * @param caseID the id of the case to progress * @throws YPersistenceException if there's a problem getting the case * @throws YStateException if case cannot be progressed */ private void progressCaseSuspension(YPersistenceManager pmgr, YIdentifier caseID) throws YPersistenceException, YStateException { _logger.debug("--> progressCaseSuspension: CaseID={}", caseID); if (!_netRunnerRepository.get(caseID).isSuspending()) { throw new YStateException( "Case " + caseID + " cannot be suspended as case not currently attempting to suspend."); } YIdentifier topNetID = caseID.getRootAncestor(); boolean executingTasks = false; List<YNetRunner> runners = getRunnersForPrimaryCase(topNetID); for (YNetRunner runner : runners) { // Go thru busy and executing tasks and see if we have any atomic tasks for (YTask task : runner.getActiveTasks()) { if (task instanceof YAtomicTask) { _logger.debug("One or more executing atomic tasks found for case - " + " Cannot fully suspend at this time"); executingTasks = true; break; } } } // If no executing tasks found go thru nets and set state to suspended if (!executingTasks) { for (YNetRunner runner : runners) { runner.setStateSuspended(); if (pmgr != null) pmgr.updateObject(runner); } _logger.info("Case {} has suspended successfully.", caseID); _announcer.announceCaseSuspended(topNetID, getYAWLServices()); } _logger.debug("<-- progressCaseSuspension"); } /** * @param id the id of the case * @return the case level data for the case */ public YNetData getCaseData(YIdentifier id) { YNetRunner runner = _netRunnerRepository.get(id); return (runner != null) ? runner.getNetData() : null; } /** updates the case data with the data passed after completion of an exception handler */ public boolean updateCaseData(String idStr, String data) throws YPersistenceException { YNetRunner runner = _netRunnerRepository.get(idStr); if (runner != null && data != null) { synchronized (_pmgr) { startTransaction(); try { YNet net = runner.getNet(); Element updatedVars = JDOMUtil.stringToElement(data); for (Element eVar : updatedVars.getChildren()) { net.assignData(_pmgr, eVar.clone()); } commitTransaction(); return true; } catch (Exception e) { rollbackTransaction(); _logger.error("Problem updating Case Data for case " + idStr, e); } } } return false; } /** @return the current case data for the case id passed */ public Document getCaseDataDocument(String id) { YNetRunner runner = _netRunnerRepository.get(id); return (runner != null) ? runner.getNet().getInternalDataDocument() : null; } // announces deferred events from all this case's net runners // private void announceEvents(YIdentifier caseID) { YIdentifier rootCaseID = caseID.getRootAncestor(); for (YNetRunner runner : getRunnersForPrimaryCase(rootCaseID)) { _announcer.announceToGateways(runner.refreshAnnouncements()); } } private void announceIfTimeServiceTimeout(YNetRunner netRunner, YWorkItem workItem) { if (_announcer.hasInterfaceXListeners()) { if (netRunner.isTimeServiceTask(workItem)) { List timeOutSet = netRunner.getTimeOutTaskSet(workItem); _announcer.announceTimeServiceExpiry(workItem, timeOutSet); } } } /***************************************************************************/ /** * Returns the task definition, not the task instance. * * @param specID the specification id * @param taskID the task id * @return the task definition object. */ public YTask getTaskDefinition(YSpecificationID specID, String taskID) { YTask task = null; YSpecification spec = _specifications.getSpecification(specID); if (spec != null) { Set<YDecomposition> decompositions = spec.getDecompositions(); for (YDecomposition decomposition : decompositions) { if (decomposition instanceof YNet) { YNet net = (YNet) decomposition; YExternalNetElement element = net.getNetElement(taskID); if ((element != null) && (element instanceof YTask)) { task = (YTask) element; break; // found it } } } } return task; } public YWorkItemRepository getWorkItemRepository() { return _workItemRepository; } public Set<YWorkItem> getAvailableWorkItems() { Set<YWorkItem> allItems = new HashSet<YWorkItem>(); Set<YWorkItem> enabledItems = _workItemRepository.getEnabledWorkItems(); Set<YWorkItem> firedItems = _workItemRepository.getFiredWorkItems(); _logger.debug("--> getAvailableWorkItems: Enabled={}, Fired={}", enabledItems.size(), firedItems.size()); allItems.addAll(enabledItems); allItems.addAll(firedItems); _logger.debug("<-- getAvailableWorkItems"); return allItems; } public YWorkItem getWorkItem(String workItemID) { return _workItemRepository.get(workItemID); } public Set<YWorkItem> getAllWorkItems() { return _workItemRepository.getWorkItems(); } public YWorkItem startWorkItem(String itemID, YClient client) throws YStateException, YDataStateException, YQueryException, YPersistenceException, YEngineStateException { YWorkItem item = getWorkItem(itemID); if (item != null) { return startWorkItem(item, client); } throw new YStateException("No work item found with id = " + itemID); } public Element getStartingDataSnapshot(String itemID) throws YStateException, YEngineStateException, YDataStateException, YQueryException { checkEngineRunning(); YWorkItem workItem = getWorkItem(itemID); if (workItem != null) { if (workItem.getStatus() != YWorkItemStatus.statusEnabled) { throw new YStateException("This method only accepts work items with 'Enabled' status"); } YNetRunner netRunner = getNetRunner(workItem.getCaseID()); YTask task = (YTask) netRunner.getNetElement(workItem.getTaskID()); if (task != null) { return task.getStartingDataSnapshot(); } throw new YStateException("No current task found with id = " + workItem.getTaskID()); } throw new YStateException("No work item found with id = " + itemID); } /** * Starts a work item. If the workitem param is enabled this method fires the task * and returns the first of its child instances in the executing state. * Else if the workitem is fired then it moves the state from fired to executing. * Either way the method returns the resultant work item. * * @param workItem the enabled, or fired workitem. * @param client the YAWL external client or service starting the workitem * @return the resultant work item in the executing state. * @throws YStateException if the workitem is not in either of these * states. * @throws YDataStateException */ public YWorkItem startWorkItem(YWorkItem workItem, YClient client) throws YStateException, YDataStateException, YQueryException, YPersistenceException, YEngineStateException { _logger.debug("--> startWorkItem"); checkEngineRunning(); YWorkItem startedItem = null; synchronized (_pmgr) { startTransaction(); try { YNetRunner netRunner = null; if (workItem != null) { switch (workItem.getStatus()) { case statusEnabled: netRunner = getNetRunner(workItem.getCaseID()); startedItem = startEnabledWorkItem(netRunner, workItem, client); break; case statusFired: netRunner = getNetRunner(workItem.getCaseID().getParent()); startedItem = startFiredWorkItem(netRunner, workItem, client); break; case statusDeadlocked: startedItem = workItem; break; default: // this work item is likely already executing. rollbackTransaction(); throw new YStateException(String.format("Item [%s]: status [%s] does not permit starting.", workItem.getIDString(), workItem.getStatus())); } } else { rollbackTransaction(); throw new YStateException("Cannot start null work item."); } // COMMIT POINT commitTransaction(); if (netRunner != null) announceEvents(netRunner.getCaseID()); _logger.debug("<-- startWorkItem"); } catch (YAWLException ye) { rollbackTransaction(); ye.rethrow(); } catch (Exception e) { rollbackTransaction(); _logger.error("Failure starting workitem " + workItem.getIDString(), e); throw new YStateException(e.getMessage()); } } return startedItem; } private YWorkItem startEnabledWorkItem(YNetRunner netRunner, YWorkItem workItem, YClient client) throws YStateException, YDataStateException, YQueryException, YPersistenceException, YEngineStateException { YWorkItem startedItem = null; YTask task = (YTask) netRunner.getNetElement(workItem.getTaskID()); List<YIdentifier> childCaseIDs = netRunner.attemptToFireAtomicTask(_pmgr, workItem.getTaskID()); if (childCaseIDs != null) { boolean oneStarted = false; for (YIdentifier childID : childCaseIDs) { YWorkItem childItem = workItem.createChild(_pmgr, childID); if (!oneStarted) { netRunner.startWorkItemInTask(_pmgr, childItem); childItem.setStatusToStarted(_pmgr, client); startedItem = childItem; oneStarted = true; } Element dataList = task.getData(childID); childItem.setData(_pmgr, dataList); _instanceCache.addParameters(childItem, task, dataList); } } return startedItem; } private YWorkItem startFiredWorkItem(YNetRunner netRunner, YWorkItem workItem, YClient client) throws YStateException, YDataStateException, YQueryException, YPersistenceException, YEngineStateException { netRunner.startWorkItemInTask(_pmgr, workItem); workItem.setStatusToStarted(_pmgr, client); YTask task = (YTask) netRunner.getNetElement(workItem.getTaskID()); Element dataList = task.getData(workItem.getCaseID()); workItem.setData(_pmgr, dataList); _instanceCache.addParameters(workItem, task, dataList); return workItem; } /** * Completes the work item. * * @param workItem * @param data * @param logPredicate - a pre-parse of the completion log predicate for this item * @param completionType - one of the completion types 'normal' (ordinary completion) * 'force' (forced completion) or 'fail' (forced fail) completion * @throws YStateException */ public void completeWorkItem(YWorkItem workItem, String data, String logPredicate, WorkItemCompletion completionType) throws YStateException, YDataStateException, YQueryException, YPersistenceException, YEngineStateException { if (_logger.isDebugEnabled()) { _logger.debug("--> completeWorkItem\nWorkItem = {}\nXML = {}", workItem != null ? workItem.get_thisID() : "null", data); } checkEngineRunning(); synchronized (_pmgr) { startTransaction(); try { if (workItem != null) { YNetRunner netRunner = getNetRunner(workItem.getCaseID().getParent()); if (workItem.getStatus().equals(YWorkItemStatus.statusExecuting)) { completeExecutingWorkitem(workItem, netRunner, data, logPredicate, completionType); } else if (workItem.getStatus().equals(YWorkItemStatus.statusDeadlocked)) { _workItemRepository.removeWorkItemFamily(workItem); } else { throw new YStateException( "WorkItem with ID [" + workItem.getIDString() + "] not in executing state."); } // COMMIT POINT commitTransaction(); if (netRunner != null) announceEvents(netRunner.getCaseID()); } else throw new YStateException("WorkItem argument is equal to null."); } catch (YAWLException ye) { rollbackTransaction(); ye.rethrow(); } catch (Exception e) { rollbackTransaction(); _logger.error("Exception completing workitem", e); } } _logger.debug("<-- completeWorkItem"); } private void completeExecutingWorkitem(YWorkItem workItem, YNetRunner netRunner, String data, String logPredicate, WorkItemCompletion completionType) throws YStateException, YDataStateException, YQueryException, YPersistenceException, YEngineStateException { workItem.setExternalLogPredicate(logPredicate); workItem.cancelTimer(); // if any if (completionType != WorkItemCompletion.Fail) { announceIfTimeServiceTimeout(netRunner, workItem); workItem.setStatusToComplete(_pmgr, completionType); Document doc = getDataDocForWorkItemCompletion(workItem, data, completionType); workItem.completeData(_pmgr, doc); if (netRunner.completeWorkItemInTask(_pmgr, workItem, doc)) { cleanupCompletedWorkItem(workItem, netRunner, doc); /* When a Task is enabled twice by virtue of having two enabling sets of * tokens in the current marking the work items are not created twice. * Instead an Enabled work item is created for one of the enabling sets. * Once that task has well and truly finished it is then an appropriate * time to notify the worklists that it is enabled again.*/ netRunner.continueIfPossible(_pmgr); } } else cancelWorkItem(workItem); } public YWorkItem skipWorkItem(YWorkItem workItem, YClient client) throws YStateException, YDataStateException, YQueryException, YPersistenceException, YEngineStateException { // start item, get output data, get children, complete each child YWorkItem startedItem = startWorkItem(workItem, client); if (startedItem != null) { String data = mapOutputDataForSkippedWorkItem(startedItem, startedItem.getDataString()); Set<YWorkItem> children = workItem.getChildren(); for (YWorkItem child : children) completeWorkItem(child, data, null, WorkItemCompletion.Normal); } else { throw new YStateException("Could not skip workitem: " + workItem.getIDString()); } return startedItem; } private Document getDataDocForWorkItemCompletion(YWorkItem workItem, String data, WorkItemCompletion completionType) throws YStateException { if (completionType != WorkItemCompletion.Normal) { data = mapOutputDataForSkippedWorkItem(workItem, data); } Document doc = JDOMUtil.stringToDocument(data); JDOMUtil.stripAttributes(doc.getRootElement()); return doc; } private String mapOutputDataForSkippedWorkItem(YWorkItem workItem, String data) throws YStateException { // get input and output params for task YSpecificationID specID = workItem.getSpecificationID(); String taskID = workItem.getTaskID(); YTask task = getTaskDefinition(specID, taskID); Map<String, YParameter> inputs = task.getDecompositionPrototype().getInputParameters(); Map<String, YParameter> outputs = task.getDecompositionPrototype().getOutputParameters(); if (outputs.isEmpty()) { // no output data to map return StringUtil.wrap("", taskID); } // map data values to params Element itemData = JDOMUtil.stringToElement(data); Element outputData = itemData != null ? itemData.clone() : new Element(taskID); // remove the input-only params from output data for (String name : inputs.keySet()) if (outputs.get(name) == null) outputData.removeChild(name); // for each output param: // 1. if matching output Element, do nothing // 2. else if matching input param, use its value // 3. else if default value specified, use its value // 4. else use default value for the param's data type List<YParameter> outParamList = new ArrayList<YParameter>(outputs.values()); Collections.sort(outParamList); // get in right order for (YParameter outParam : outParamList) { String name = outParam.getName(); if (outputData.getChild(name) != null) continue; // matching I/O element // the output param has no corresponding input param, so add an element String defaultValue = outParam.getDefaultValue(); if (defaultValue == null) { String typeName = outParam.getDataTypeName(); if (!XSDType.isBuiltInType(typeName)) { throw new YStateException( String.format("Could not skip work item [%s]: Output-Only parameter [%s]" + " requires a default value.", workItem.getIDString(), name)); } defaultValue = JDOMUtil.getDefaultValueForType(typeName); } Element outData = new Element(name); outData.setText(defaultValue); outputData.addContent(outData); } return JDOMUtil.elementToStringDump(outputData); } /** * Determines whether or not a task will allow a dynamically * created new instance to be created. MultiInstance Task with * dynamic instance creation. * * @param workItemID the workItemID of a sibling work item. * @throws YStateException if task is not MultiInstance, or * if task does not allow dynamic instance creation, * or if current number of instances is not less than the maxInstances * for the task. */ public void checkElegibilityToAddInstances(String workItemID) throws YStateException { YWorkItem item = _workItemRepository.get(workItemID); if (item != null) { if (item.getStatus().equals(YWorkItemStatus.statusExecuting)) { if (item.allowsDynamicCreation()) { YIdentifier identifier = item.getCaseID().getParent(); YNetRunner netRunner = getNetRunner(identifier); if (!netRunner.isAddEnabled(item.getTaskID(), item.getCaseID())) { throw new YStateException("Adding instances is not possible in " + "current state."); } } else { throw new YStateException("WorkItem[" + workItemID + "] does not allow new instance creation."); } } else { throw new YStateException("WorkItem[" + workItemID + "] is not in appropriate (executing) " + "state for instance adding."); } } else { throw new YStateException("No work Item Found with id : " + workItemID); } } /** * Checks whether new dynamic workitem instances can be started for a task * @pre the workitem is in executing state * @param workItemID the id of the workitem to check against * @return true if a new workitem can be dynamically spawned */ public boolean canAddNewInstances(String workItemID) { YWorkItem item = _workItemRepository.get(workItemID); if (item != null) { YIdentifier identifier = item.getCaseID().getParent(); YNetRunner netRunner = getNetRunner(identifier); return netRunner.isAddEnabled(item.getTaskID(), item.getCaseID()); } return false; } /** * Creates a new work item instance when possible. * * @param workItem the id of a work item inside the task to have a new instance. * @param paramValueForMICreation format "<data>[InputParam]*</data> * InputParam == <varName>varValue</varName> * @return the work item of the new instance. * @throws YStateException if the task is not able to create a new instance, due to * its state or its design. * @throws YPersistenceException if there's a problem with the persistence session */ public YWorkItem createNewInstance(YWorkItem workItem, String paramValueForMICreation) throws YStateException, YPersistenceException { if (workItem == null) throw new YStateException("No work item found."); // will throw a YStateException if not eligible checkElegibilityToAddInstances(workItem.getIDString()); String taskID = workItem.getTaskID(); YIdentifier siblingID = workItem.getCaseID(); YNetRunner netRunner = getNetRunner(siblingID.getParent()); synchronized (_pmgr) { startTransaction(); try { Element paramValue = JDOMUtil.stringToElement(paramValueForMICreation); YIdentifier id = netRunner.addNewInstance(_pmgr, taskID, workItem.getCaseID(), paramValue); YWorkItem firedItem = workItem.getParent().createChild(_pmgr, id); commitTransaction(); return firedItem; //success!!!! } catch (Exception e) { rollbackTransaction(); throw new YStateException(e.getMessage()); } } } public YWorkItem suspendWorkItem(String workItemID) throws YStateException, YPersistenceException { YWorkItem workItem = _workItemRepository.get(workItemID); if ((workItem != null) && (workItem.hasLiveStatus())) { synchronized (_pmgr) { startTransaction(); workItem.setStatusToSuspended(_pmgr); commitTransaction(); } } return workItem; } public YWorkItem unsuspendWorkItem(String workItemID) throws YStateException, YPersistenceException { YWorkItem workItem = _workItemRepository.get(workItemID); if ((workItem != null) && (workItem.getStatus().equals(YWorkItemStatus.statusSuspended))) { synchronized (_pmgr) { startTransaction(); workItem.setStatusToUnsuspended(_pmgr); commitTransaction(); } } return workItem; } // rolls back a workitem from executing to fired public void rollbackWorkItem(String workItemID) throws YStateException, YPersistenceException { YWorkItem workItem = _workItemRepository.get(workItemID); if ((workItem != null) && workItem.getStatus().equals(YWorkItemStatus.statusExecuting)) { synchronized (_pmgr) { startTransaction(); workItem.rollBackStatus(_pmgr); YNetRunner netRunner = getNetRunner(workItem.getCaseID().getParent()); if (netRunner.rollbackWorkItem(_pmgr, workItem.getCaseID(), workItem.getTaskID())) { commitTransaction(); } else { rollbackTransaction(); throw new YStateException( "Unable to rollback: work Item[" + workItemID + "] is not in executing state."); } } } else throw new YStateException("Work Item[" + workItemID + "] not found."); } public Set getChildrenOfWorkItem(YWorkItem workItem) { return (workItem == null) ? null : _workItemRepository.getChildrenOf(workItem.getIDString()); } /** updates the workitem with the data passed after completion of an exception handler */ public boolean updateWorkItemData(String workItemID, String data) { YWorkItem workItem = getWorkItem(workItemID); if (workItem != null) { synchronized (_pmgr) { try { boolean localTransaction = startTransaction(); Element eleData = JDOMUtil.stringToElement(data); workItem.setData(_pmgr, eleData); if (localTransaction) commitTransaction(); _instanceCache.updateWorkItemData(workItem, eleData); return true; } catch (YPersistenceException e) { return false; } } } return false; } public void cancelWorkItem(YWorkItem workItem) { try { if ((workItem != null) && workItem.getStatus().equals(YWorkItemStatus.statusExecuting)) { YNetRunner runner = getNetRunner(workItem.getCaseID().getParent()); synchronized (_pmgr) { startTransaction(); workItem.setStatusToDeleted(_pmgr); YWorkItem parent = workItem.getParent(); if ((parent != null) && (parent.getChildren().size() == 1)) { runner.cancelTask(_pmgr, workItem.getTaskID()); } else ((YAtomicTask) workItem.getTask()).cancel(_pmgr, workItem.getCaseID()); runner.kick(_pmgr); cleanupCompletedWorkItem(workItem, runner, null); commitTransaction(); announceEvents(runner.getCaseID()); } } } catch (Exception e) { _logger.error("Failure whilst cancelling workitem", e); } } private void cleanupCompletedWorkItem(YWorkItem workItem, YNetRunner netRunner, Document data) throws YPersistenceException, YStateException { _instanceCache.closeWorkItem(workItem, data); YWorkItem parent = workItem.getParent(); if (parent != null) { // If case is suspending, see if we can progress into a fully suspended state if (netRunner.isSuspending()) { progressCaseSuspension(_pmgr, parent.getCaseID()); } } } private void cancelTimer(YWorkItem workItem) { if (workItem != null) { if (workItem.hasTimerStarted()) { YTimer.getInstance().cancelTimerTask(workItem.getIDString()); } YWorkItem parent = workItem.getParent(); if (parent != null && parent.hasTimerStarted()) { Set<YWorkItem> children = parent.getChildren(); if (children == null || children.size() == 1) { YTimer.getInstance().cancelTimerTask(parent.getIDString()); } } } } /*********************************************************************/ public YAWLServiceReference getRegisteredYawlService(String yawlServiceID) { return _yawlServices.get(yawlServiceID); } /** * Returns a set of YAWL services registered in the engine. * * @return Set of services */ public Set<YAWLServiceReference> getYAWLServices() { return new HashSet<YAWLServiceReference>(_yawlServices.values()); } /** * Adds a YAWL service to the engine. * @param yawlService */ public void addYawlService(YAWLServiceReference yawlService) throws YPersistenceException { _logger.debug("--> addYawlService: Service={}", yawlService.getURI()); _yawlServices.put(yawlService.getURI(), yawlService); if (!_restoring && isPersisting()) { _logger.info("Persisting YAWL Service {} with ID {}", yawlService.getURI(), yawlService.getServiceID()); storeObject(yawlService); } _logger.debug("<-- addYawlService"); } /** * @param serviceURI * @return the removed service reference * @throws YPersistenceException */ public YAWLServiceReference removeYawlService(String serviceURI) throws YPersistenceException { YAWLServiceReference service = _yawlServices.remove(serviceURI); if ((service != null) && isPersisting()) { _logger.info("Deleting persisted entry for YAWL service {} with ID {}", service.getURI(), service.getServiceID()); try { deleteObject(service); } catch (YPersistenceException e) { _logger.fatal("Failure whilst removing YAWL service", e); throw e; } } return service; } /** * Adds an external client credentials object to the engine. An external client is * an application that connects to the engine (as opposed to a service) * @param client the external client to add */ public boolean addExternalClient(YExternalClient client) throws YPersistenceException { String userID = client.getUserName(); if ((userID != null) && (client.getPassword() != null) && (!_externalClients.containsKey(userID))) { _externalClients.put(userID, client); if (!_restoring && isPersisting()) { doPersistAction(client, YPersistenceManager.DB_INSERT); } return true; } else return false; } public boolean updateExternalClient(String id, String password, String doco) throws YPersistenceException { YExternalClient client = _externalClients.get(id); if (client != null) { client.setPassword(password); client.setDocumentation(doco); doPersistAction(client, YPersistenceManager.DB_UPDATE); } return (client != null); } public YExternalClient getExternalClient(String name) { return _externalClients.get(name); } public Set<YExternalClient> getExternalClients() { return new HashSet<YExternalClient>(_externalClients.values()); } public Set getUsers() { _logger.debug("<-- getUsers: Returned {} entries", _externalClients.size()); return getExternalClients(); } public YExternalClient removeExternalClient(String clientName) throws YPersistenceException { if (!clientName.equals("admin")) { YExternalClient client = _externalClients.remove(clientName); if (client != null) { _sessionCache.disconnect(client); // if the client is connected if (isPersisting()) { try { deleteObject(client); } catch (YPersistenceException e) { _logger.fatal("Failure whilst removing YAWL external client", e); throw e; } } } return client; } else { _logger.error("Removing the generic admin user is not allowed."); return null; } } /** * Sets the custom service that will serve as the default worklist. Called on * startup with values loaded from web.xml * @param paramStr the URL and password of the service (separated by a hash) * @throws RuntimeException if the parameters read from web.xml are incorrectly * formatted */ public void setDefaultWorklist(String paramStr) { String[] parts = paramStr.split("#"); if (parts.length != 2) { throw new RuntimeException("FATAL: Could not set default worklist from " + "configuration file. No default worklist set. Cannot proceed."); } _defaultWorklist = new YAWLServiceReference(parts[0], null, "DefaultWorklist", PasswordEncryptor.encrypt(parts[1], null), ""); _defaultWorklist.setAssignable(false); _yawlServices.put(_defaultWorklist.getURI(), _defaultWorklist); } public YAWLServiceReference getDefaultWorklist() { return _defaultWorklist; } public void setAllowAdminID(boolean allow) { _allowGenericAdminID = allow; try { if (allow) { // if its not yet there, add it if (!_externalClients.containsKey("admin")) { addExternalClient(new YExternalClient("admin", PasswordEncryptor.encrypt("YAWL", null), "generic admin user")); } } else { // if its already there, remove it YExternalClient admin = _externalClients.remove("admin"); if (admin != null) { deleteObject(admin); } } } catch (YPersistenceException e) { _logger.error("Failure whilst persisting 'admin' user", e); } } public boolean isGenericAdminAllowed() { return _allowGenericAdminID; } /**********************************************************************/ /** * Indicates if persistence is enabled. * @return True=Persistent, False=Not Persistent */ public static boolean isPersisting() { return _persisting; } /** * Indicates if persistence should be enabled. * @param persist true to persist, false to not persist */ private static void setPersisting(boolean persist) { _persisting = persist; } /** * Public interface to allow engine clients to ask the engine to store an object reference in its * persistent storage. It does this in its own transaction block.<P> * * @param obj * @throws YPersistenceException */ public void storeObject(Object obj) throws YPersistenceException { doPersistAction(obj, YPersistenceManager.DB_INSERT); } public void updateObject(Object obj) throws YPersistenceException { doPersistAction(obj, YPersistenceManager.DB_UPDATE); } public void deleteObject(Object obj) throws YPersistenceException { doPersistAction(obj, YPersistenceManager.DB_DELETE); } private void doPersistAction(Object obj, int action) throws YPersistenceException { if (isPersisting() && (_pmgr != null)) { synchronized (_pmgr) { boolean isLocalTransaction = startTransaction(); switch (action) { case YPersistenceManager.DB_UPDATE: _pmgr.updateObject(obj); break; case YPersistenceManager.DB_DELETE: _pmgr.deleteObject(obj); break; case YPersistenceManager.DB_INSERT: _pmgr.storeObject(obj); break; } if (isLocalTransaction) commitTransaction(); } } } private boolean startTransaction() throws YPersistenceException { return (_pmgr != null) && _pmgr.startTransaction(); } private void commitTransaction() throws YPersistenceException { if (_pmgr != null) _pmgr.commit(); } private void rollbackTransaction() throws YPersistenceException { if (_pmgr != null) _pmgr.rollbackTransaction(); } /** * Clears a case from persistence * @param id the case id to clear * @throws YPersistenceException if there's a problem clearing the case */ protected void clearCaseFromPersistence(YIdentifier id) throws YPersistenceException { _logger.debug("--> clearCaseFromPersistence: CaseID = ", id.get_idString()); if (_persisting) { try { List<YIdentifier> list = id.get_children(); for (YIdentifier child : list) { if (child != null) clearCaseFromPersistence(child); } synchronized (_pmgr) { Object obj = _pmgr.getSession().get(YNetRunner.class, id.toString()); if (obj == null) { obj = _pmgr.getSession().get(YIdentifier.class, id.toString()); } if (obj != null) _pmgr.deleteObject(obj); } } catch (Exception e) { throw new YPersistenceException("Failure whilst clearing case", e); } } _logger.debug("<-- clearCaseFromPersistence"); } public static YPersistenceManager getPersistenceManager() { return _pmgr; } /** * Removes the workitems of the runner from persistence (after a case cancellation). * * @param items the set of work items to delete from persistnece * @throws YPersistenceException if there's some persistence problem */ private void clearWorkItemsFromPersistence(Set<YWorkItem> items) throws YPersistenceException { // clear child items first (to avoid foreign key constraint exceptions) for (YWorkItem item : items) { if (!item.getStatus().equals(YWorkItemStatus.statusIsParent)) _pmgr.deleteObject(item); } // now clear any parents for (YWorkItem item : items) { if (item.getStatus().equals(YWorkItemStatus.statusIsParent)) _pmgr.deleteObject(item); } } public void writeAudit(YAuditEvent event) { try { storeObject(event); } catch (YPersistenceException ype) { LogManager.getLogger(YEngine.class).warn("Unable to write audit event to log."); } } /** sets the URI passed as an listener for exception events */ public boolean addInterfaceXListener(String observerURI) { _announcer.addInterfaceXListener(observerURI); return true; } /** removes an exception event listener */ public boolean removeInterfaceXListener(String uri) { return _announcer.removeInterfaceXListener(uri); } /****************************************************************************/ public void dump() { if (!_logger.isDebugEnabled()) return; _logger.debug("*** DUMP OF ENGINE STARTS ***"); Set<YSpecificationID> specids = _specifications.getSpecIDs(); _logger.debug("\n*** DUMPING " + specids.size(), " SPECIFICATIONS ***"); int i = 0; for (YSpecificationID specid : specids) { YSpecification spec = _specifications.getSpecification(specid); if (spec != null) { _logger.debug("Entry " + i++ + ":"); _logger.debug(" ID " + spec.getURI()); _logger.debug(" Name " + spec.getName()); _logger.debug(" Version " + spec.getMetaData().getVersion()); } } _logger.debug("*** DUMP OF SPECIFICATIONS ENDS ***"); _netRunnerRepository.dump(_logger); _logger.debug("*** DUMP OF RUNNING CASES TO SPEC MAP STARTS ***"); int sub = 0; for (YIdentifier key : _runningCaseIDToSpecMap.keySet()) { if (key != null) { YSpecification spec = _runningCaseIDToSpecMap.get(key); if (spec != null) { _logger.debug("Entry " + sub++ + " Key=" + key); _logger.debug(" ID " + spec.getURI()); _logger.debug(" Version " + spec.getMetaData().getVersion()); } } else _logger.debug("key is NULL !!!"); } _logger.debug("*** DUMP OF RUNNING CASES TO SPEC MAP ENDS ***"); if (getWorkItemRepository() != null) { getWorkItemRepository().dump(_logger); } _logger.debug("*** DUMP OF ENGINE ENDS ***"); } public void setHibernateStatisticsEnabled(boolean enabled) { _pmgr.setStatisticsEnabled(enabled); } public boolean isHibernateStatisticsEnabled() { return _pmgr.isStatisticsEnabled(); } public String getHibernateStatistics() { return _pmgr.getStatistics(); } public void disableProcessLogging() { _yawllog.disable(); } }