/* * Copyright (c) 2004-2020 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.commons.lang.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jdom2.Document; import org.jdom2.Element; import org.yawlfoundation.yawl.elements.*; import org.yawlfoundation.yawl.elements.state.YIdentifier; import org.yawlfoundation.yawl.elements.state.YInternalCondition; import org.yawlfoundation.yawl.engine.announcement.YAnnouncement; import org.yawlfoundation.yawl.engine.announcement.YEngineEvent; import org.yawlfoundation.yawl.engine.time.YTimer; import org.yawlfoundation.yawl.engine.time.YTimerVariable; import org.yawlfoundation.yawl.engine.time.YWorkItemTimer; import org.yawlfoundation.yawl.exceptions.YDataStateException; import org.yawlfoundation.yawl.exceptions.YPersistenceException; import org.yawlfoundation.yawl.exceptions.YQueryException; import org.yawlfoundation.yawl.exceptions.YStateException; 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.util.JDOMUtil; import java.net.URL; import java.util.*; /** * * * @author Lachlan Aldred * Date: 16/04/2003 * Time: 16:08:01 * * @author Michael Adams (for v2) */ public class YNetRunner { public enum ExecutionStatus { Normal, Suspending, Suspended, Resuming } private static final Logger _logger = LogManager.getLogger(YNetRunner.class); protected YNet _net; private YWorkItemRepository _workItemRepository; private Set<YTask> _netTasks; private Set<YTask> _enabledTasks = new HashSet<YTask>(); private Set<YTask> _busyTasks = new HashSet<YTask>(); private final Set<YTask> _deadlockedTasks = new HashSet<YTask>(); private YIdentifier _caseIDForNet; private YSpecificationID _specID; private YCompositeTask _containingCompositeTask; private YEngine _engine; private YAnnouncer _announcer; private boolean _cancelling; private Set<String> _enabledTaskNames = new HashSet<String>(); private Set<String> _busyTaskNames = new HashSet<String>(); private String _caseID = null; private String _containingTaskID = null; private YNetData _netdata = null; private YAWLServiceReference _caseObserver; private long _startTime; private Map<String, String> _timerStates; private ExecutionStatus _executionStatus; private Set<YAnnouncement> _announcements; // used to persist observers private String _caseObserverStr = null ; // Constructors // protected YNetRunner() { _logger.debug("YNetRunner: <init>"); _engine = YEngine.getInstance(); init(); } public YNetRunner(YPersistenceManager pmgr, YNet netPrototype, Element paramsData, String caseID) throws YStateException, YDataStateException, YPersistenceException { this(); // initialise and persist case identifier - if caseID is null, a new one is supplied _caseIDForNet = new YIdentifier(caseID); if (pmgr != null) pmgr.storeObject(_caseIDForNet); // get case data from external data gateway, if set for this specification Element externalData = netPrototype.getCaseDataFromExternal(_caseIDForNet.toString()); if (externalData != null) paramsData = externalData; initialise(pmgr, netPrototype, _caseIDForNet, paramsData) ; } /** * Constructor called by a composite task (creating a sub-net runner) * @param pmgr * @param netPrototype * @param container * @param caseIDForNet * @param incomingData * @throws YDataStateException * @throws YPersistenceException */ public YNetRunner(YPersistenceManager pmgr, YNet netPrototype, YCompositeTask container, YIdentifier caseIDForNet, Element incomingData) throws YDataStateException, YPersistenceException { this(); initialise(pmgr, netPrototype, caseIDForNet, incomingData) ; _containingCompositeTask = container; setContainingTaskID(container.getID()); if (pmgr != null) pmgr.storeObject(this); } private void init() { _workItemRepository = _engine.getWorkItemRepository(); _announcer = _engine.getAnnouncer(); } private void initialise(YPersistenceManager pmgr, YNet netPrototype, YIdentifier caseIDForNet, Element incomingData) throws YDataStateException, YPersistenceException { _caseIDForNet = caseIDForNet; _caseID = _caseIDForNet.toString(); _netdata = new YNetData(_caseID); _net = (YNet) netPrototype.clone(); _net.initializeDataStore(pmgr, _netdata); _netTasks = new HashSet<YTask>(_net.getNetTasks()); _specID = _net.getSpecification().getSpecificationID(); _startTime = System.currentTimeMillis(); prepare(pmgr); if (incomingData != null) _net.setIncomingData(pmgr, incomingData); initTimerStates(); refreshAnnouncements(); _executionStatus = ExecutionStatus.Normal; } private void prepare(YPersistenceManager pmgr) throws YPersistenceException { YInputCondition inputCondition = _net.getInputCondition(); inputCondition.add(pmgr, _caseIDForNet); _net.initialise(pmgr); } public Set<YAnnouncement> refreshAnnouncements() { Set<YAnnouncement> current = new HashSet<>(); if (_announcements != null) { current.addAll(_announcements); } _announcements = new HashSet<>(); return current; } public boolean equals(Object other) { return (other instanceof YNetRunner) && // instanceof = false if other is null ((getCaseID() != null) ? getCaseID().equals(((YNetRunner) other).getCaseID()) : super.equals(other)); } public int hashCode() { return (getCaseID() != null) ? getCaseID().hashCode() : super.hashCode(); } /******************************************************************************/ public void setContainingTask(YCompositeTask task) { _containingCompositeTask = task; } public String getContainingTaskID() { return _containingTaskID; } public void setContainingTaskID(String taskid) { _containingTaskID = taskid; } public void setNet(YNet net) { _net = net; _specID = net.getSpecification().getSpecificationID(); _net.restoreData(_netdata); _netTasks = new HashSet<YTask>(_net.getNetTasks()); } public YNet getNet() { return _net; } public void setEngine(YEngine engine) { _engine = engine; init(); } public YSpecificationID getSpecificationID() { return _specID; } public void setSpecificationID(YSpecificationID id) { _specID = id; } public YNetData getNetData() { return _netdata; } public void setNetData(YNetData data) { _netdata = data; } public YIdentifier get_caseIDForNet() { return _caseIDForNet; } public void set_caseIDForNet(YIdentifier id) { this._caseIDForNet = id; _caseID = _caseIDForNet.toString(); } public void addBusyTask(YTask ext) { _busyTasks.add(ext); } public void addEnabledTask(YTask ext) { _enabledTasks.add(ext); } public void removeActiveTask(YPersistenceManager pmgr, YTask task) throws YPersistenceException { _busyTasks.remove(task); _busyTaskNames.remove(task.getID()); _enabledTasks.remove(task); _enabledTaskNames.remove(task.getID()); if (pmgr != null) pmgr.updateObject(this); } public String get_caseID() { return this._caseID; } public void set_caseID(String ID) { this._caseID = ID; } public Set<String> getEnabledTaskNames() { return _enabledTaskNames; } public Set<String> getBusyTaskNames() { return _busyTaskNames; } protected void setEnabledTaskNames(Set<String> names) { _enabledTaskNames = names; } protected void setBusyTaskNames(Set<String> names) { _busyTaskNames = names; } public long getStartTime() { return _startTime; } public void setStartTime(long time) { _startTime = time ; } /************************************************/ public void start(YPersistenceManager pmgr) throws YPersistenceException, YDataStateException, YQueryException, YStateException { kick(pmgr); } public boolean isAlive() { return ! _cancelling; } /** * Assumption: this will only get called AFTER a workitem has been progressed? * Because if it is called any other time then it will cause the case to stop. * @param pmgr * @throws YPersistenceException */ public synchronized void kick(YPersistenceManager pmgr) throws YPersistenceException, YDataStateException, YQueryException, YStateException { _logger.debug("--> YNetRunner.kick"); if (! continueIfPossible(pmgr)) { _logger.debug("YNetRunner not able to continue"); // if root net can't continue it means a case completion if ((_engine != null) && isRootNet()) { announceCaseCompletion(); if (endOfNetReached() && warnIfNetNotEmpty()) { _cancelling = true; // flag its not a deadlock } // call the external data source, if its set for this specification _net.postCaseDataToExternal(getCaseID().toString()); _logger.debug("Asking engine to finish case"); _engine.removeCaseFromCaches(_caseIDForNet); // log it YLogPredicate logPredicate = getNet().getLogPredicate(); YLogDataItemList logData = null; if (logPredicate != null) { String predicate = logPredicate.getParsedCompletionPredicate(getNet()); if (predicate != null) { logData = new YLogDataItemList(new YLogDataItem("Predicate", "OnCompletion", predicate, "string")); } } YEventLogger.getInstance().logNetCompleted(_caseIDForNet, logData); } if (! _cancelling && deadLocked()) notifyDeadLock(pmgr); cancel(pmgr); if ((_engine != null) && isRootNet()) _engine.clearCaseFromPersistence(_caseIDForNet); } _logger.debug("<-- YNetRunner.kick"); } private void announceCaseCompletion() { _announcer.announceCaseCompletion(_caseObserver, _caseIDForNet, _net.getOutputData()); // notify exception checkpoint to listeners if any (post's for case end) if (_announcer.hasInterfaceXListeners()) { Document data = _net.getInternalDataDocument(); _announcer.announceCheckCaseConstraints(_specID, _caseID, JDOMUtil.documentToString(data), false); } } private boolean isRootNet() { return _caseIDForNet.getParent() == null; } private void notifyDeadLock(YPersistenceManager pmgr) throws YPersistenceException { for (Object o : _caseIDForNet.getLocations()) { if (o instanceof YExternalNetElement) { YExternalNetElement element = (YExternalNetElement) o; if (_net.getNetElements().values().contains(element)) { if (element instanceof YTask) { createDeadlockItem(pmgr, (YTask) element); } Set<YExternalNetElement> postset = element.getPostsetElements(); for (YExternalNetElement postsetElement : postset) { createDeadlockItem(pmgr, (YTask) postsetElement); } } } } _announcer.announceDeadlock(_caseIDForNet, _deadlockedTasks); } private void createDeadlockItem(YPersistenceManager pmgr, YTask task) throws YPersistenceException { if (! _deadlockedTasks.contains(task)) { _deadlockedTasks.add(task); // create a new deadlocked workitem so that the deadlock can be logged new YWorkItem(pmgr, _net.getSpecification().getSpecificationID(), task, new YWorkItemID(_caseIDForNet, task.getID()), false, true); YProblemEvent event = new YProblemEvent(_net, "Deadlocked", YProblemEvent.RuntimeError); event.logProblem(pmgr); } } /** * Assumption: there are no enabled tasks * @return if deadlocked */ private boolean deadLocked() { for (YNetElement location : _caseIDForNet.getLocations()) { if (location instanceof YExternalNetElement) { if (((YExternalNetElement) location).getPostsetElements().size() > 0) { return true; } } } return false; } private synchronized void processCompletedSubnet(YPersistenceManager pmgr, YIdentifier caseIDForSubnet, YCompositeTask busyCompositeTask, Document rawSubnetData) throws YDataStateException, YStateException, YQueryException, YPersistenceException { _logger.debug("--> processCompletedSubnet"); if (caseIDForSubnet == null) throw new RuntimeException(); if (busyCompositeTask.t_complete(pmgr, caseIDForSubnet, rawSubnetData)) { _busyTasks.remove(busyCompositeTask); if (pmgr != null) pmgr.updateObject(this); logCompletingTask(caseIDForSubnet, busyCompositeTask); //check to see if completing this task resulted in completing the net. if (endOfNetReached()) { if (_containingCompositeTask != null) { YNetRunner parentRunner = _engine.getNetRunner(_caseIDForNet.getParent()); if ((parentRunner != null) && _containingCompositeTask.t_isBusy()) { parentRunner.setEngine(_engine); // added to avoid NPE Document dataDoc = _net.usesSimpleRootData() ? _net.getInternalDataDocument() : _net.getOutputData() ; parentRunner.processCompletedSubnet(pmgr, _caseIDForNet, _containingCompositeTask, dataDoc); } } } kick(pmgr); } _logger.debug("<-- processCompletedSubnet"); } public List<YIdentifier> attemptToFireAtomicTask(YPersistenceManager pmgr, String taskID) throws YDataStateException, YStateException, YQueryException, YPersistenceException { YAtomicTask task = (YAtomicTask) _net.getNetElement(taskID); if (task.t_enabled(_caseIDForNet)) { List<YIdentifier> newChildIdentifiers = task.t_fire(pmgr); _enabledTasks.remove(task); _enabledTaskNames.remove(task.getID()); _busyTasks.add(task); _busyTaskNames.add(task.getID()); if (pmgr != null) pmgr.updateObject(this); _logger.debug("NOTIFYING RUNNER"); kick(pmgr); return newChildIdentifiers; } throw new YStateException("Task is not (or no longer) enabled: " + taskID); } public synchronized YIdentifier addNewInstance(YPersistenceManager pmgr, String taskID, YIdentifier aSiblingInstance, Element newInstanceData) throws YDataStateException, YStateException, YQueryException, YPersistenceException { YAtomicTask task = (YAtomicTask) _net.getNetElement(taskID); if (task.t_isBusy()) { return task.t_add(pmgr, aSiblingInstance, newInstanceData); } return null; } public void startWorkItemInTask(YPersistenceManager pmgr, YWorkItem workItem) throws YDataStateException, YPersistenceException, YQueryException, YStateException { startWorkItemInTask(pmgr, workItem.getCaseID(), workItem.getTaskID()); } public synchronized void startWorkItemInTask(YPersistenceManager pmgr, YIdentifier caseID, String taskID) throws YDataStateException, YPersistenceException, YQueryException, YStateException { YAtomicTask task = (YAtomicTask) _net.getNetElement(taskID); task.t_start(pmgr, caseID); } public boolean completeWorkItemInTask(YPersistenceManager pmgr, YWorkItem workItem, Document outputData) throws YDataStateException, YStateException, YQueryException, YPersistenceException { return completeWorkItemInTask(pmgr, workItem, workItem.getCaseID(), workItem.getTaskID(), outputData); } public synchronized boolean completeWorkItemInTask(YPersistenceManager pmgr, YWorkItem workItem, YIdentifier caseID, String taskID, Document outputData) throws YDataStateException, YStateException, YQueryException, YPersistenceException { _logger.debug("--> completeWorkItemInTask"); YAtomicTask task = (YAtomicTask) _net.getNetElement(taskID); boolean success = completeTask(pmgr, workItem, task, caseID, outputData); // notify exception checkpoint to service if available if (_announcer.hasInterfaceXListeners()) { _announcer.announceCheckWorkItemConstraints( workItem, _net.getInternalDataDocument(), false); } _logger.debug("<-- completeWorkItemInTask"); return success; } public synchronized boolean continueIfPossible(YPersistenceManager pmgr) throws YDataStateException, YStateException, YQueryException, YPersistenceException { _logger.debug("--> continueIfPossible"); // Check if we are suspending (or suspended?) and if so exit out as we // shouldn't post new workitems if (isInSuspense()) { _logger.debug("Aborting runner continuation as case is currently suspending/suspended"); return true; } // don't continue if the net has already finished if (isCompleted()) return false; // storage for the running set of enabled tasks YEnabledTransitionSet enabledTransitions = new YEnabledTransitionSet(); // iterate through the full set of tasks for the net for (YTask task : _netTasks) { // if this task is an enabled 'transition' if (task.t_enabled(_caseIDForNet)) { if (! (_enabledTasks.contains(task) || _busyTasks.contains(task))) enabledTransitions.add(task) ; } else { // if the task is not (or no longer) an enabled transition, and it // has been previously enabled by the engine, then it must be withdrawn if (_enabledTasks.contains(task)) { withdrawEnabledTask(task, pmgr); } } if (task.t_isBusy() && !_busyTasks.contains(task)) { _logger.error("Throwing RTE for lists out of sync"); throw new RuntimeException("Busy task list out of synch with a busy task: " + task.getID() + " busy tasks: " + _busyTasks); } } // fire the set of enabled 'transitions' (if any) if (! enabledTransitions.isEmpty()) fireTasks(enabledTransitions, pmgr); _busyTasks = _net.getBusyTasks(); _logger.debug("<-- continueIfPossible"); return hasActiveTasks(); } private void fireTasks(YEnabledTransitionSet enabledSet, YPersistenceManager pmgr) throws YDataStateException, YStateException, YQueryException, YPersistenceException { Set<YTask> enabledTasks = new HashSet<YTask>(); // A TaskGroup is a group of tasks that are all enabled by a single condition. // If the group has more than one task, it's a deferred choice, in which case: // 1. If any are composite, fire one (chosen randomly) - rest are withdrawn // 2. Else, if any are empty, fire one (chosen randomly) - rest are withdrawn // 3. Else, fire and announce all enabled atomic tasks to the environment for (YEnabledTransitionSet.TaskGroup group : enabledSet.getAllTaskGroups()) { if (group.hasCompositeTasks()) { YCompositeTask composite = group.getRandomCompositeTaskFromGroup(); if (! (enabledTasks.contains(composite) || endOfNetReached())) { fireCompositeTask(composite, pmgr); enabledTasks.add(composite); } } else if (group.hasEmptyTasks()) { YAtomicTask atomic = group.getRandomEmptyTaskFromGroup(); if (! (enabledTasks.contains(atomic) || endOfNetReached())) { processEmptyTask(atomic, pmgr); } } else { String groupID = group.getDeferredChoiceID(); // null if <2 tasks for (YAtomicTask atomic : group.getAtomicTasks()) { if (! (enabledTasks.contains(atomic) || endOfNetReached())) { YAnnouncement announcement = fireAtomicTask(atomic, groupID, pmgr); if (announcement != null) { _announcements.add(announcement); } enabledTasks.add(atomic) ; } } } } } private YAnnouncement fireAtomicTask(YAtomicTask task, String groupID, YPersistenceManager pmgr) throws YDataStateException, YStateException, YQueryException, YPersistenceException { _enabledTasks.add(task); _enabledTaskNames.add(task.getID()); YWorkItem item = createEnabledWorkItem(pmgr, _caseIDForNet, task); if (groupID != null) item.setDeferredChoiceGroupID(groupID); if (pmgr != null) pmgr.updateObject(this); YAWLServiceGateway wsgw = (YAWLServiceGateway) task.getDecompositionPrototype(); YAnnouncement announcement = _announcer.createAnnouncement(wsgw.getYawlService(), item, YEngineEvent.ITEM_ADD); if (_announcer.hasInterfaceXListeners()) { _announcer.announceCheckWorkItemConstraints(item, _net.getInternalDataDocument(), true); } return announcement; } private void fireCompositeTask(YCompositeTask task, YPersistenceManager pmgr) throws YDataStateException, YStateException, YQueryException, YPersistenceException { if (! _busyTasks.contains(task)) { // don't proceed if task already started _busyTasks.add(task); _busyTaskNames.add(task.getID()); if (pmgr != null) pmgr.updateObject(this); List<YIdentifier> caseIDs = task.t_fire(pmgr); for (YIdentifier id : caseIDs) { try { task.t_start(pmgr, id); } catch (YDataStateException ydse) { task.rollbackFired(id, pmgr); ydse.rethrow(); } } } } // fire, start and complete a decomposition-less atomic task in situ protected void processEmptyTask(YAtomicTask task,YPersistenceManager pmgr) throws YDataStateException, YStateException, YQueryException, YPersistenceException { try { if (task.t_enabled(_caseIDForNet)) { // may be already processed YIdentifier id = task.t_fire(pmgr).get(0); task.t_start(pmgr, id); _busyTasks.add(task); // pre-req for completeTask completeTask(pmgr, null, task, id, null); } } catch (YStateException yse) { // ignore - task already removed due to alternate path or case completion } } private void withdrawEnabledTask(YTask task, YPersistenceManager pmgr) throws YPersistenceException { _enabledTasks.remove(task); _enabledTaskNames.remove(task.getID()); // remove the withdrawn task from persistence YWorkItem wItem = _workItemRepository.get(_caseID, task.getID()); if (wItem != null) { //may already have been removed by task.cancel //announce all cancelled work items YAnnouncement announcement = _announcer.createAnnouncement(wItem, YEngineEvent.ITEM_CANCEL); if (announcement != null) _announcements.add(announcement); // log it YEventLogger.getInstance().logWorkItemEvent(wItem, YWorkItemStatus.statusWithdrawn, null); // cancel any live timer if (wItem.hasTimerStarted()) { YTimer.getInstance().cancelTimerTask(wItem.getIDString()); } if (pmgr != null) { pmgr.deleteObject(wItem); pmgr.updateObject(this); } } } /** * Creates an enabled work item. * * @param caseIDForNet the caseid for the net * @param atomicTask the atomic task that contains it. */ private YWorkItem createEnabledWorkItem(YPersistenceManager pmgr, YIdentifier caseIDForNet, YAtomicTask atomicTask) throws YPersistenceException, YDataStateException, YQueryException { _logger.debug("--> createEnabledWorkItem: Case={}, Task={}", caseIDForNet.get_idString(), atomicTask.getID()); boolean allowDynamicCreation = atomicTask.getMultiInstanceAttributes() != null && YMultiInstanceAttributes.CREATION_MODE_DYNAMIC.equals( atomicTask.getMultiInstanceAttributes().getCreationMode()); //creating a new work item puts it into the work item repository automatically. YWorkItem workItem = new YWorkItem(pmgr, atomicTask.getNet().getSpecification().getSpecificationID(), atomicTask, new YWorkItemID(caseIDForNet, atomicTask.getID()), allowDynamicCreation, false); if (atomicTask.getDataMappingsForEnablement().size() > 0) { Element data = atomicTask.prepareEnablementData(); workItem.setData(pmgr, data); } // copy in relevant data from the task's decomposition YDecomposition decomp = atomicTask.getDecompositionPrototype(); if (decomp != null) { workItem.setRequiresManualResourcing(decomp.requiresResourcingDecisions()); workItem.setCodelet(decomp.getCodelet()); workItem.setAttributes(decomp.getAttributes()); } // set timer params and start timer if required YTimerParameters timerParams = atomicTask.getTimerParameters(); if (timerParams != null) { workItem.setTimerParameters(timerParams); workItem.checkStartTimer(pmgr, _netdata); } // set custom form for workitem if specified URL customFormURL = atomicTask.getCustomFormURL(); if (customFormURL != null) workItem.setCustomFormURL(customFormURL) ; // persist the changes if (pmgr != null) pmgr.updateObject(workItem); return workItem; } /** * Completes a work item inside an atomic task. * * @param workItem The work item. If null is supplied, this work item cannot be * removed from the work items repository (hack) * @param atomicTask the atomic task * @param identifier the identifier of the work item * @param outputData the document containing output data * @return whether or not the task exited * @throws YDataStateException */ private synchronized boolean completeTask(YPersistenceManager pmgr, YWorkItem workItem, YAtomicTask atomicTask, YIdentifier identifier, Document outputData) throws YDataStateException, YStateException, YQueryException, YPersistenceException { _logger.debug("--> completeTask: {}", atomicTask.getID()); boolean taskExited = atomicTask.t_complete(pmgr, identifier, outputData); if (taskExited) { if (workItem != null) { for (YWorkItem removed : _workItemRepository.removeWorkItemFamily(workItem)) { if (! (removed.hasCompletedStatus() || removed.isParent())) { // MI fired or incomplete _announcer.announceCancelledWorkItem(removed); } } updateTimerState(workItem.getTask(), YWorkItemTimer.State.closed); } // check if completing this task resulted in completing the net. if (endOfNetReached()) { // check if the completed net is a subnet. if (_containingCompositeTask != null) { YNetRunner parentRunner = _engine.getNetRunner(_caseIDForNet.getParent()); if (parentRunner != null) { synchronized (parentRunner) { if (_containingCompositeTask.t_isBusy()) { warnIfNetNotEmpty(); Document dataDoc = _net.usesSimpleRootData() ? _net.getInternalDataDocument() : _net.getOutputData() ; parentRunner.processCompletedSubnet(pmgr, _caseIDForNet, _containingCompositeTask, dataDoc); _logger.debug("YNetRunner::completeTask() finished local task: {}," + " composite task: {}, caseid for decomposed net: {}" + atomicTask, _containingCompositeTask, _caseIDForNet); } } } } } continueIfPossible(pmgr); _busyTasks.remove(atomicTask); _busyTaskNames.remove(atomicTask.getID()); if ((pmgr != null) && _engine.getRunningCaseIDs().contains(_caseIDForNet)) { pmgr.updateObject(this); } _logger.debug("NOTIFYING RUNNER"); kick(pmgr); } _logger.debug("<-- completeTask: {}, Exited={}", atomicTask.getID(), taskExited); return taskExited; } public synchronized void cancel(YPersistenceManager pmgr) throws YPersistenceException { _logger.debug("--> NetRunner cancel {}", getCaseID().get_idString()); _cancelling = true; for (YExternalNetElement netElement : _net.getNetElements().values()) { if (netElement instanceof YTask) { YTask task = ((YTask) netElement); if (task.t_isBusy()) { task.cancel(pmgr); } } else if (((YCondition) netElement).containsIdentifier()) { ((YCondition) netElement).removeAll(pmgr); } } _enabledTasks = new HashSet<YTask>(); _busyTasks = new HashSet<YTask>(); if (_containingCompositeTask != null) { YEventLogger.getInstance().logNetCancelled( getSpecificationID(), this, _containingCompositeTask.getID(), null); } if (isRootNet()) _workItemRepository.removeWorkItemsForCase(_caseIDForNet); _engine.getNetRunnerRepository().remove(_caseIDForNet); } public void removeFromPersistence(YPersistenceManager pmgr) throws YPersistenceException { if (pmgr != null) { pmgr.deleteObject(this); } } public synchronized boolean rollbackWorkItem(YPersistenceManager pmgr, YIdentifier caseID, String taskID) throws YPersistenceException { YAtomicTask task = (YAtomicTask) _net.getNetElement(taskID); return task.t_rollBackToFired(pmgr, caseID); } private void logCompletingTask(YIdentifier caseIDForSubnet, YCompositeTask busyCompositeTask) { YLogPredicate logPredicate = busyCompositeTask.getDecompositionPrototype().getLogPredicate(); YLogDataItemList logData = null; if (logPredicate != null) { String predicate = logPredicate.getParsedCompletionPredicate( busyCompositeTask.getDecompositionPrototype()); if (predicate != null) { logData = new YLogDataItemList(new YLogDataItem("Predicate", "OnCompletion", predicate, "string")); } } YEventLogger.getInstance().logNetCompleted(caseIDForSubnet, logData); } //############################################################################### // accessors //############################################################################### public YExternalNetElement getNetElement(String id) { return _net.getNetElement(id); } public YIdentifier getCaseID() { return _caseIDForNet; } public boolean isCompleted() { return endOfNetReached() || isEmpty(); } public boolean endOfNetReached() { return _net.getOutputCondition().containsIdentifier(); } public boolean isEmpty() { for (YExternalNetElement element : _net.getNetElements().values()) { if (element instanceof YCondition) { if (((YCondition) element).containsIdentifier()) return false; } else { if (((YTask) element).t_isBusy()) return false; } } return true; } protected Set<YTask> getBusyTasks() { return _busyTasks; } protected Set<YTask> getEnabledTasks() { return _enabledTasks; } protected Set<YTask> getActiveTasks() { Set<YTask> activeTasks = new HashSet<YTask>(); activeTasks.addAll(_busyTasks); activeTasks.addAll(_enabledTasks); return activeTasks; } protected boolean hasActiveTasks() { return _enabledTasks.size() > 0 || _busyTasks.size() > 0; } public boolean isAddEnabled(String taskID, YIdentifier childID) { YAtomicTask task = (YAtomicTask) _net.getNetElement(taskID); return task.t_addEnabled(childID); } public void setObserver(YAWLServiceReference observer) { _caseObserver = observer; _caseObserverStr = observer.getURI(); // for persistence } private boolean warnIfNetNotEmpty() { List<YExternalNetElement> haveTokens = new ArrayList<YExternalNetElement>(); for (YExternalNetElement element : _net.getNetElements().values()) { if (! (element instanceof YOutputCondition)) { // ignore end condition tokens if ((element instanceof YCondition) && ((YCondition) element).containsIdentifier()) { haveTokens.add(element); } else if ((element instanceof YTask) && ((YTask) element).t_isBusy()) { haveTokens.add(element); // flag and announce any executing workitems YInternalCondition exeCondition = ((YTask) element).getMIExecuting(); for (YIdentifier id : exeCondition.getIdentifiers()) { YWorkItem executingItem = _workItemRepository.get( id.toString(), element.getID()); if (executingItem != null) executingItem.setStatusToDiscarded(); } } } } if (! haveTokens.isEmpty()) { StringBuilder msg = new StringBuilder(100); msg.append("Although Net [") .append(_net.getID()) .append("] of case [") .append(_caseIDForNet.toString()) .append("] has successfully completed, there were one or more ") .append("tokens remaining in the net, within these elements: ["); msg.append(StringUtils.join(haveTokens, ", ")); msg.append("], which usually indicates that the net is unsound. Those ") .append("tokens were removed when the net completed."); _logger.warn(msg.toString()); } return (! haveTokens.isEmpty()); } public String toString() { return String.format("CaseID: %s; Enabled: %s; Busy: %s", _caseIDForNet.toString(), _enabledTaskNames.toString(), _busyTaskNames.toString()); } public void dump() { dump(_enabledTasks, "ENABLED"); dump(_busyTasks, "BUSY"); } private void dump(Set<YTask> tasks, String label) { _logger.debug("*** DUMP OF NETRUNNER {} TASKS ***", label); for (YTask t : tasks) { _logger.debug("Type = {}", t.getClass().getName()); } _logger.debug("*** END OF DUMP OF NETRUNNER {} TASKS ***", label); } /***************************************************************************/ /** The following methods have been added to support the exception service */ /** restores the IB and IX observers on session startup (via persistence) */ public void restoreObservers() { if(_caseObserverStr != null) { YAWLServiceReference caseObserver = _engine.getRegisteredYawlService(_caseObserverStr); if (caseObserver != null) setObserver(caseObserver); } } /** these two methods are here to support persistence of the IB Observer */ protected String get_caseObserverStr() { return _caseObserverStr ; } protected void set_caseObserverStr(String obStr) { _caseObserverStr = obStr ; } /** cancels the specified task */ public synchronized void cancelTask(YPersistenceManager pmgr, String taskID) { YAtomicTask task = (YAtomicTask) getNetElement(taskID); try { task.cancel(pmgr, this.getCaseID()); _busyTasks.remove(task); _busyTaskNames.remove(task.getID()); } catch (YPersistenceException ype) { _logger.fatal("Failure whilst cancelling task: " + taskID, ype); } } /** returns true if the specified workitem is registered with the Time Service */ public boolean isTimeServiceTask(YWorkItem item) { YTask task = (YTask) getNetElement(item.getTaskID()); if ((task != null) && (task instanceof YAtomicTask)) { YAWLServiceGateway wsgw = (YAWLServiceGateway) task.getDecompositionPrototype(); if (wsgw != null) { YAWLServiceReference ys = wsgw.getYawlService(); if (ys != null) { return ys.getServiceID().indexOf("timeService") > -1 ; } } } return false ; } /** returns a list of all workitems executing in parallel to the time-out workitem passed (the list includes the time-out task) */ public List<String> getTimeOutTaskSet(YWorkItem item) { YTask timeOutTask = (YTask) getNetElement(item.getTaskID()); String nextTaskID = getFlowsIntoTaskID(timeOutTask); ArrayList<String> result = new ArrayList<String>() ; if (nextTaskID != null) { for (YTask task : _netTasks) { String nextTask = getFlowsIntoTaskID(task); if (nextTask != null) { if (nextTask.equals(nextTaskID)) result.add(task.getID()); } } } if (result.isEmpty()) result = null ; return result; } /** returns the task id of the task that the specified task flows into In other words, gets the id of the next task in the process flow */ private String getFlowsIntoTaskID(YTask task) { if ((task != null) && (task instanceof YAtomicTask)) { Element eTask = JDOMUtil.stringToElement(task.toXML()); return eTask.getChild("flowsInto").getChild("nextElementRef").getAttributeValue("id"); } return null ; } // **** TIMER STATE VARIABLES **********// // returns all the tasks in this runner's net that have timers public void initTimerStates() { _timerStates = new Hashtable<String, String>(); for (YTask task : _netTasks) { if (task.getTimerVariable() != null) { updateTimerState(task, YWorkItemTimer.State.dormant); } } } public void restoreTimerStates() { if (! _timerStates.isEmpty()) { for (String timerKey : _timerStates.keySet()) { for (YTask task : _netTasks) { String taskName = task.getName(); if (taskName == null) taskName = task.getID(); if (taskName != null && taskName.equals(timerKey)) { String stateStr = _timerStates.get(timerKey); YTimerVariable timerVar = task.getTimerVariable(); timerVar.setState(YWorkItemTimer.State.valueOf(stateStr), true); break; } } } } } public void updateTimerState(YTask task, YWorkItemTimer.State state) { YTimerVariable timerVar = task.getTimerVariable(); if (timerVar != null) { timerVar.setState(state); _timerStates.put(task.getName(), timerVar.getStateString()); } } public Map<String, String> get_timerStates() { return _timerStates; } public void set_timerStates(Map<String, String> states) { _timerStates = states; } public boolean evaluateTimerPredicate(String predicate) throws YQueryException { predicate = predicate.trim(); int pos = predicate.indexOf(')'); if (pos > -1) { String taskName = predicate.substring(6, pos); // 6 = 'timer(' YTimerVariable timerVar = getTimerVariable(taskName); if (timerVar != null) { return timerVar.evaluatePredicate(predicate); } else throw new YQueryException("Unable to find timer state for task named " + "in predicate: " + predicate); } else throw new YQueryException("Malformed timer predicate: " + predicate); } private YTimerVariable getTimerVariable(String taskName) { for (YTask task : _netTasks) { if (task.getName().equals(taskName)) { return task.getTimerVariable(); } } return null; } public boolean isSuspending() { return _executionStatus == ExecutionStatus.Suspending; } public boolean isSuspended() { return _executionStatus == ExecutionStatus.Suspended; } public boolean isResuming() { return _executionStatus == ExecutionStatus.Resuming; } public boolean isInSuspense() { return isSuspending() || isSuspended(); } public boolean hasNormalState() { return _executionStatus == ExecutionStatus.Normal; } public void setStateSuspending() { _executionStatus = ExecutionStatus.Suspending; } public void setStateSuspended() { _executionStatus = ExecutionStatus.Suspended; } public void setStateResuming() { _executionStatus = ExecutionStatus.Resuming; } public void setStateNormal() { _executionStatus = ExecutionStatus.Normal; } public void setExecutionStatus(String status) { _executionStatus = (status != null) ? ExecutionStatus.valueOf(status) : ExecutionStatus.Normal; } public String getExecutionStatus() { return _executionStatus.name(); } }