From 20f50059b5f13f60345695bfdb75c74c937823f7 Mon Sep 17 00:00:00 2001 From: Michael Adams Date: Tue, 16 Jul 2024 11:52:26 +1000 Subject: [PATCH] synced changes from adamsmj/yawl --- build/build.xml | 6 + .../yawl/stateless/YStatelessEngine.java | 168 ++++++++++++++++-- .../yawl/stateless/engine/YEngine.java | 12 +- .../yawl/stateless/engine/YWorkItem.java | 24 ++- .../stateless/engine/time/YWorkItemTimer.java | 24 ++- .../yawl/stateless/monitor/YCase.java | 31 +++- .../yawl/stateless/monitor/YCaseImporter.java | 2 +- .../yawl/stateless/monitor/YCaseMonitor.java | 32 +++- 8 files changed, 259 insertions(+), 40 deletions(-) diff --git a/build/build.xml b/build/build.xml index d23fec2d7..e1ad50423 100644 --- a/build/build.xml +++ b/build/build.xml @@ -2843,6 +2843,8 @@ org/yawlfoundation/yawl/elements/YAttributeMap.class org/yawlfoundation/yawl/elements/YVerifiable.class org/yawlfoundation/yawl/elements/YNetElement.class + org/yawlfoundation/yawl/elements/data/YParameter.class + org/yawlfoundation/yawl/elements/data/YVariable.class org/yawlfoundation/yawl/elements/e2wfoj/* org/yawlfoundation/yawl/engine/YSpecificationID.class org/yawlfoundation/yawl/engine/YNetData.class @@ -2861,10 +2863,12 @@ org/yawlfoundation/yawl/exceptions/* org/yawlfoundation/yawl/logging/YLogDataItem.class org/yawlfoundation/yawl/logging/YLogDataItemList.class + org/yawlfoundation/yawl/resourcing/codelets/* org/yawlfoundation/yawl/util/DynamicValue.class org/yawlfoundation/yawl/util/JDOMUtil.class org/yawlfoundation/yawl/util/YNetElementDocoParser.class org/yawlfoundation/yawl/util/SaxonErrorListener.class + org/yawlfoundation/yawl/util/SaxonUtil.class org/yawlfoundation/yawl/util/PluginLoaderUtil.class org/yawlfoundation/yawl/util/YVerificationHandler.class org/yawlfoundation/yawl/util/YPluginLoader.class @@ -2878,6 +2882,8 @@ value="${app.version} ${TODAY}"/> + diff --git a/src/org/yawlfoundation/yawl/stateless/YStatelessEngine.java b/src/org/yawlfoundation/yawl/stateless/YStatelessEngine.java index 1e34b5010..02d3ad9fd 100644 --- a/src/org/yawlfoundation/yawl/stateless/YStatelessEngine.java +++ b/src/org/yawlfoundation/yawl/stateless/YStatelessEngine.java @@ -28,6 +28,7 @@ */ public class YStatelessEngine { + private final YEngine _engine; private YCaseMonitor _caseMonitor; // watches for idle cases @@ -334,9 +335,16 @@ public void resumeCase(YNetRunner runner) */ public YWorkItem suspendWorkItem(YWorkItem workItem) throws YStateException { checkIsLoadedCase(workItem, "suspend work item"); - return _engine.suspendWorkItem(workItem); + try { + return _engine.suspendWorkItem(workItem); + } + catch (Exception e) { + resumeCaseIdleTimer(workItem); + throw e; + } } + /** * Resume a suspended work item * @param workItem the work item to suspend @@ -345,19 +353,33 @@ public YWorkItem suspendWorkItem(YWorkItem workItem) throws YStateException { */ public YWorkItem unsuspendWorkItem(YWorkItem workItem) throws YStateException { checkIsLoadedCase(workItem, "unsuspend work item"); - return _engine.unsuspendWorkItem(workItem) ; + try { + return _engine.unsuspendWorkItem(workItem) ; + } + catch (Exception e) { + resumeCaseIdleTimer(workItem); + throw e; + } } + /** * Roll back a work item from executing to enabled * @param workItem the work item to roll back - * @throws YStateException + * @throws YStateException if the item cannot be rolled back */ - public void rollbackWorkItem(YWorkItem workItem) throws YStateException { + public YWorkItem rollbackWorkItem(YWorkItem workItem) throws YStateException { checkIsLoadedCase(workItem, "rollback work item"); - _engine.rollbackWorkItem(workItem); + try { + return _engine.rollbackWorkItem(workItem); + } + catch (Exception e) { + resumeCaseIdleTimer(workItem); + throw e; + } } + /** * Complete a currently executing work item @@ -370,13 +392,20 @@ public void rollbackWorkItem(YWorkItem workItem) throws YStateException { * @throws YQueryException if the data extraction query is malformed * @throws YDataStateException if the data state cannot be initialised */ - public void completeWorkItem(YWorkItem workItem, String data, + public YWorkItem completeWorkItem(YWorkItem workItem, String data, String logPredicate, WorkItemCompletion completionType ) throws YEngineStateException, YStateException, YQueryException, YDataStateException { checkIsLoadedCase(workItem, "complete work item"); - _engine.completeWorkItem(workItem, data, logPredicate, completionType); + try { + return _engine.completeWorkItem(workItem, data, logPredicate, completionType); + } + catch (Exception e) { + resumeCaseIdleTimer(workItem); + throw e; + } } + /** * Complete a currently executing work item * @param workItem the work item to complete @@ -387,11 +416,17 @@ public void completeWorkItem(YWorkItem workItem, String data, * @throws YQueryException if the data extraction query is malformed * @throws YDataStateException if the data state cannot be initialised */ - public void completeWorkItem(YWorkItem workItem, String data, + public YWorkItem completeWorkItem(YWorkItem workItem, String data, String logPredicate) throws YEngineStateException, YStateException, YQueryException, YDataStateException { checkIsLoadedCase(workItem, "complete work item"); - _engine.completeWorkItem(workItem, data, logPredicate, WorkItemCompletion.Normal); + try { + return _engine.completeWorkItem(workItem, data, logPredicate, WorkItemCompletion.Normal); + } + catch (Exception e) { + resumeCaseIdleTimer(workItem); + throw e; + } } @@ -406,9 +441,16 @@ public void completeWorkItem(YWorkItem workItem, String data, public YWorkItem startWorkItem(YWorkItem workItem) throws YEngineStateException, YStateException, YQueryException, YDataStateException { checkIsLoadedCase(workItem, "start work item"); - return _engine.startWorkItem(workItem); + try { + return _engine.startWorkItem(workItem); + } + catch (Exception e) { + resumeCaseIdleTimer(workItem); + throw e; + } } + /** * Skip an enabled work item (immediately completes) * @param workItem the work item to skip @@ -420,7 +462,13 @@ public YWorkItem startWorkItem(YWorkItem workItem) public YWorkItem skipWorkItem(YWorkItem workItem) throws YEngineStateException, YStateException, YQueryException, YDataStateException { checkIsLoadedCase(workItem, "skip work item"); - return _engine.skipWorkItem(workItem); + try { + return _engine.skipWorkItem(workItem); + } + catch (Exception e) { + resumeCaseIdleTimer(workItem); + throw e; + } } @@ -436,7 +484,13 @@ public YWorkItem skipWorkItem(YWorkItem workItem) public YWorkItem createNewInstance(YWorkItem workItem, String paramValueForMICreation) throws YStateException { checkIsLoadedCase(workItem, "create new work item instance"); - return _engine.createNewInstance(workItem, paramValueForMICreation); + try { + return _engine.createNewInstance(workItem, paramValueForMICreation); + } + catch (Exception e) { + resumeCaseIdleTimer(workItem); + throw e; + } } @@ -449,7 +503,7 @@ public YWorkItem createNewInstance(YWorkItem workItem, String paramValueForMICre * or if current number of instances is not less than the maxInstances * for the task. */ - public void checkElegibilityToAddInstances(YWorkItem workItem) throws YStateException { + public void checkEligibilityToAddInstances(YWorkItem workItem) throws YStateException { _engine.checkEligibilityToAddInstances(workItem); } @@ -475,7 +529,7 @@ public String unloadCase(YIdentifier caseID) throws YStateException { throw new YStateException("This engine is not monitoring idle cases"); } YCase yCase = _caseMonitor.unloadCase(caseID); // notnull guaranteed - yCase.cancelWorkItemTimers(); + yCase.removeWorkItemTimers(); String caseXML = yCase.marshal(); // ditto _engine.getAnnouncer().announceCaseEvent( new YCaseEvent(YEventType.CASE_UNLOADED, yCase.getRunner())); @@ -507,6 +561,8 @@ public String marshalCase(YNetRunner runner) throws YStateException { */ public YNetRunner restoreCase(String caseXML) throws YSyntaxException, YStateException { List runners = new YCaseImporter().unmarshal(caseXML, _engine.getAnnouncer()); + + // collect events and identify 'top' net runner in the runner hierarchy List events = new ArrayList<>(); YNetRunner topRunner = null; for (YNetRunner runner : runners) { @@ -516,12 +572,73 @@ public YNetRunner restoreCase(String caseXML) throws YSyntaxException, YStateExc } } if (topRunner == null) throw new YStateException("Failed to restore case runner"); - events.add(0, new YCaseEvent(YEventType.CASE_RESTORED, topRunner)); + + // add the 'restored' event to the list of generated events + YCaseEvent restoredEvent = new YCaseEvent(YEventType.CASE_RESTORED, topRunner); + events.add(0, restoredEvent); + + // ensure case is added to the case monitor + if (isCaseMonitoringEnabled()) { + _caseMonitor.addCase(restoredEvent); + } + _engine.getAnnouncer().announceEvents(events); return topRunner; } + /** + * Check if the engine is currently executing code for a monitored case of which this + * workitem is a member. + * @param workItem the workitem to check + * @return true if case monitoring is enabled and the case is currently in idle + * state (i.e. it has no current executing code associated with it), or false if + * case monitoring is enabled and there is code executing for the case. + * @throws YStateException when the case is unknown to the engine, or when case + * monitoring is disabled for the engine + */ + public boolean isIdleCase(YWorkItem workItem) throws YStateException { + return isIdleCase(workItem.getNetRunner().getTopRunner().getCaseID()); + } + + + /** + * Check if the engine is currently executing code for a monitored case of which + * this net runner is a member. + * @param runner the net runner to check + * @return true if case monitoring is enabled and the case is currently in idle + * state (i.e. it has no current executing code associated with it), or false if + * case monitoring is enabled and there is code executing for the case. + * @throws YStateException when the case is unknown to the engine, or when case + * monitoring is disabled for the engine + */ + public boolean isIdleCase(YNetRunner runner) throws YStateException { + return isIdleCase(runner.getTopRunner().getCaseID()); + } + + + /** + * Check if the engine is currently executing code for a monitored case. + * @param caseID the id of the case to check + * @return true if case monitoring is enabled and the case is currently in idle + * state (i.e. it has no current executing code associated with it), or false if + * case monitoring is enabled and there is code executing for the case. + * @throws YStateException when the case is unknown to the engine, or when case + * monitoring is disabled for the engine + */ + public boolean isIdleCase(YIdentifier caseID) throws YStateException { + if (isCaseMonitoringEnabled()) { + if (_caseMonitor.hasCase(caseID)) { + return _caseMonitor.isIdleCase(caseID); + } + else { + throw new YStateException(String.format("Case '%s' is unknown" + + " to this engine - perhaps it has been unloaded?", caseID)); + } + } + else throw new YStateException("Case monitoring is disabled for this engine"); + } + /** * Throws a YStateException if cases are being monitored AND the case is unknown to * this engine @@ -529,20 +646,37 @@ public YNetRunner restoreCase(String caseXML) throws YSyntaxException, YStateExc * @param errMsg to be inserted if an exception is thrown * @throws YStateException if the condition described above evaluates to true */ - private void isLoadedCase(YIdentifier caseID, String errMsg) throws YStateException { + private boolean isLoadedCase(YIdentifier caseID, String errMsg) throws YStateException { if (isCaseMonitoringEnabled() && ! _caseMonitor.hasCase(caseID)) { throw new YStateException(String.format("Unable to %s; case '%s' is unknown" + " to this engine - perhaps it has been unloaded?", errMsg, caseID)); } + return true; } private void checkIsLoadedCase(YWorkItem item, String msg) throws YStateException { - isLoadedCase(item.getNetRunner().getTopRunner().getCaseID(), msg); + YIdentifier caseID = item.getNetRunner().getTopRunner().getCaseID(); + if (isCaseMonitoringEnabled() && isLoadedCase(caseID, msg)) { + + // pause any idle timer while the workitem action is processed + _caseMonitor.pauseIdleTimer(caseID); + } } + private void checkIsLoadedCase(YNetRunner runner, String msg) throws YStateException { isLoadedCase(runner.getTopRunner().getCaseID(), msg); } + + // for all successful workitem method completions above, timer will resume. + // this is called from catch blocks above for cases when an exception is thrown. + private void resumeCaseIdleTimer(YWorkItem workItem) { + if (isCaseMonitoringEnabled()) { + YIdentifier caseID = workItem.getNetRunner().getTopRunner().getCaseID(); + _caseMonitor.resumeIdleTimer(caseID); + } + } + } diff --git a/src/org/yawlfoundation/yawl/stateless/engine/YEngine.java b/src/org/yawlfoundation/yawl/stateless/engine/YEngine.java index b4f57bdcc..e3369163e 100644 --- a/src/org/yawlfoundation/yawl/stateless/engine/YEngine.java +++ b/src/org/yawlfoundation/yawl/stateless/engine/YEngine.java @@ -435,7 +435,7 @@ private YWorkItem startFiredWorkItem(YNetRunner netRunner, YWorkItem workItem) * 'force' (forced completion) or 'fail' (forced fail) completion * @throws YStateException */ - public void completeWorkItem(YWorkItem workItem, String data, String logPredicate, + public YWorkItem completeWorkItem(YWorkItem workItem, String data, String logPredicate, WorkItemCompletion completionType) throws YStateException, YDataStateException, YQueryException, YEngineStateException{ if (_logger.isDebugEnabled()) { @@ -469,6 +469,8 @@ public void completeWorkItem(YWorkItem workItem, String data, String logPredicat // } _logger.debug("<-- completeWorkItem"); + + return workItem; } @@ -687,7 +689,7 @@ public YWorkItem unsuspendWorkItem(YWorkItem workItem) throws YStateException { // rolls back a workitem from executing to fired - public void rollbackWorkItem(YWorkItem workItem) throws YStateException { + public YWorkItem rollbackWorkItem(YWorkItem workItem) throws YStateException { if ((workItem != null) && workItem.getStatus().equals(YWorkItemStatus.statusExecuting)) { workItem.rollBackStatus(); YNetRunner netRunner = workItem.getTask().getNetRunner().getRunnerWithID( @@ -699,10 +701,12 @@ public void rollbackWorkItem(YWorkItem workItem) throws YStateException { } } else throw new YStateException("Work Item[" + workItem.getIDString() + "] not found."); + + return workItem; } - public void cancelWorkItem(YNetRunner caseRunner, YWorkItem workItem) throws YStateException { + public YWorkItem cancelWorkItem(YNetRunner caseRunner, YWorkItem workItem) throws YStateException { try { if ((workItem != null) && workItem.getStatus().equals(YWorkItemStatus.statusExecuting)) { YNetRunner runner = caseRunner.getRunnerWithID(workItem.getCaseID().getParent()); @@ -721,6 +725,8 @@ public void cancelWorkItem(YNetRunner caseRunner, YWorkItem workItem) throws YSt catch (Exception e) { throw new YStateException("Failure whilst cancelling workitem: " + e.getMessage()); } + + return workItem; } diff --git a/src/org/yawlfoundation/yawl/stateless/engine/YWorkItem.java b/src/org/yawlfoundation/yawl/stateless/engine/YWorkItem.java index c5292c74b..1c61d38e6 100644 --- a/src/org/yawlfoundation/yawl/stateless/engine/YWorkItem.java +++ b/src/org/yawlfoundation/yawl/stateless/engine/YWorkItem.java @@ -99,6 +99,8 @@ public class YWorkItem { private String _externalLogPredicate; // set by services on checkin private Element _data; + private YWorkItemTimer _timer = null ; + private final Logger _log = LogManager.getLogger(YWorkItem.class); @@ -422,25 +424,24 @@ public void checkStartTimer(YNetData data) { // if current workitem status equals trigger status, start the timer if (_timerParameters.triggerMatchesStatus(_status)) { - YWorkItemTimer timer = null ; switch (_timerParameters.getTimerType()) { case Expiry: { - timer = new YWorkItemTimer(this, + _timer = new YWorkItemTimer(this, _timerParameters.getDate()) ; break; } case Duration: { - timer = new YWorkItemTimer(this, + _timer = new YWorkItemTimer(this, _timerParameters.getWorkDayDuration()); break; } case Interval: { - timer = new YWorkItemTimer(this, + _timer = new YWorkItemTimer(this, _timerParameters.getTicks(), _timerParameters.getTimeUnit()) ; } } - if (timer != null) { - _timerExpiry = timer.getEndTime(); + if (_timer != null) { + _timerExpiry = _timer.getEndTime(); setTimerActive(); _timerStarted = true ; } @@ -448,6 +449,10 @@ public void checkStartTimer(YNetData data) { } } + public YWorkItemTimer getTimer() { return _timer; } + + public void setTimer(YWorkItemTimer timer) { _timer = timer; } + public void cancelTimer() { if (hasTimerStarted()) { @@ -463,6 +468,12 @@ public void cancelTimer() { } } } + + // if suppressing timer cancel events, do it in the parent too + YWorkItemTimer parentTimer = parent.getTimer(); + if (! (parentTimer == null || _timer == null) ) { + parentTimer.enableAnnouncements(_timer.announcementsEnabled()); + } YTimer.getInstance().cancelTimerTask(parent.getIDString()); } } @@ -831,6 +842,7 @@ public String getDocumentation() { return (_parent != null) ? _parent.getDocumentation() : _documentation; } + public String toXML() { StringBuilder xml = new StringBuilder(" 0; } }