Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.67.x] [JBPM-10185] Handling sla timer for processes #2304

Merged
merged 1 commit into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public AuditEvent buildEvent(ProcessCompletedEvent pce, Object log) {
logEvent.setDuration(logEvent.getEnd().getTime() - logEvent.getStart().getTime());
logEvent.setProcessInstanceDescription( pi.getDescription() );
logEvent.setSlaCompliance(pi.getSlaCompliance());
logEvent.setSlaDueDate(pi.getSlaDueDate());
return logEvent;
}

Expand Down
79 changes: 78 additions & 1 deletion jbpm-bpmn2/src/test/java/org/jbpm/bpmn2/SLAComplianceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.jbpm.bpmn2;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
Expand All @@ -31,11 +32,17 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.drools.core.command.SingleSessionCommandService;
import org.drools.core.command.impl.CommandBasedStatefulKnowledgeSession;
import org.drools.core.impl.StatefulKnowledgeSessionImpl;
import org.jbpm.bpmn2.objects.TestWorkItemHandler;
import org.jbpm.process.audit.NodeInstanceLog;
import org.jbpm.process.audit.ProcessInstanceLog;
import org.jbpm.process.instance.InternalProcessRuntime;
import org.jbpm.process.instance.command.UpdateTimerCommand;
import org.jbpm.process.instance.impl.demo.SystemOutWorkItemHandler;
import org.jbpm.process.instance.timer.TimerInstance;
import org.jbpm.process.instance.timer.TimerManager;
import org.jbpm.workflow.instance.node.HumanTaskNodeInstance;
import org.junit.After;
import org.junit.BeforeClass;
Expand Down Expand Up @@ -126,6 +133,49 @@ public void afterSLAViolated(SLAViolatedEvent event) {
ksession.dispose();
}

@Test
public void testSLAonProcessUpdated() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
KieBase kbase = createKnowledgeBase("BPMN2-UserTaskWithSLA.bpmn2");
KieSession ksession = createKnowledgeSession(kbase);

ProcessInstance processInstance = ksession.startProcess("UserTask");
assertTrue(processInstance.getState() == ProcessInstance.STATE_ACTIVE);

Date firstSlaDueDate = getSLADueDateForProcessInstance(processInstance.getId(),
(org.jbpm.workflow.instance.WorkflowProcessInstance) processInstance);

Collection<TimerInstance> timers = getTimerManager(ksession).getTimers();
assertThat(timers.size()).isEqualTo(1);

ksession.execute(new UpdateTimerCommand(processInstance.getId(), timers.iterator().next().getId(), 7));

boolean slaViolated = latch.await(5, TimeUnit.SECONDS);
assertFalse("Process SLA was violated while it is not expected after update SLA", slaViolated);

processInstance = ksession.getProcessInstance(processInstance.getId());
assertTrue(processInstance.getState() == ProcessInstance.STATE_ACTIVE);

int slaCompliance = getSLAComplianceForProcessInstance(processInstance);
assertEquals(ProcessInstance.SLA_PENDING, slaCompliance);

slaViolated = latch.await(5, TimeUnit.SECONDS);
assertFalse("Process SLA was not violated while it is expected after 10s", slaViolated);

slaCompliance = getSLAComplianceForProcessInstance(processInstance);
assertEquals(ProcessInstance.SLA_VIOLATED, slaCompliance);

ksession.abortProcessInstance(processInstance.getId());
Date updatedSlaDueDate = getSLADueDateForProcessInstance(processInstance.getId(),
(org.jbpm.workflow.instance.WorkflowProcessInstance) processInstance);

assertTrue(String.format("updatedSlaDueDate '%tc' should be around 4-5 seconds after firstSlaDueDate '%tc'",
updatedSlaDueDate, firstSlaDueDate),
updatedSlaDueDate.after(firstSlaDueDate));

ksession.dispose();
}

@Test
public void testSLAonProcessMet() throws Exception {
KieBase kbase = createKnowledgeBase("BPMN2-UserTaskWithSLA.bpmn2");
Expand Down Expand Up @@ -306,7 +356,7 @@ class TimerIdListener extends DefaultProcessEventListener {
public TimerIdListener(CountDownLatch latch) {
this.latch = latch;
}

@Override
public void afterNodeTriggered(ProcessNodeTriggeredEvent event) {
if (event.getNodeInstance() instanceof HumanTaskNodeInstance) {
Expand Down Expand Up @@ -664,4 +714,31 @@ private Date getSLADueDateForNodeInstance(long processInstanceId, org.jbpm.workf

throw new RuntimeException("NodeInstanceLog not found for id "+nodeInstance.getId()+" and type "+logType);
}

private Date getSLADueDateForProcessInstance(long processInstanceId, org.jbpm.workflow.instance.WorkflowProcessInstance processInstance) {
if (!sessionPersistence) {
return processInstance.getSlaDueDate();
}

List<ProcessInstanceLog> logs = logService.findProcessInstances();
if (logs == null)
throw new RuntimeException("ProcessInstanceLog not found");

for (ProcessInstanceLog log : logs) {
if (log.getId() == processInstanceId) {
return log.getSlaDueDate();
}
}

throw new RuntimeException("ProcessInstanceLog not found for id "+processInstanceId);
}

private TimerManager getTimerManager(KieSession ksession) {
KieSession internal = ksession;
if (ksession instanceof CommandBasedStatefulKnowledgeSession) {
internal = ( (SingleSessionCommandService) ( (CommandBasedStatefulKnowledgeSession) ksession ).getRunner() ).getKieSession();;
}

return ((InternalProcessRuntime)((StatefulKnowledgeSessionImpl)internal).getProcessRuntime()).getTimerManager();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,17 @@ public UpdateTimerCommand(long processInstanceId, long timerId, long delay, long
this.repeatLimit = repeatLimit;
}

private TimerInstance handleSla(long slaTimerId, TimerManager tm, RuleFlowProcessInstance wfp) {
if (slaTimerId != -1 && slaTimerId == timerId) {
TimerInstance newTimer = rescheduleTimer(tm.getTimerMap().get(timerId), tm);
logger.debug("New SLA timer {} about to be registered", newTimer);
tm.registerTimer(newTimer, wfp);
logger.debug("New SLA timer {} successfully registered", newTimer);
return newTimer;
}
return null;
}

@Override
public Void execute(Context context ) {
logger.debug("About to cancel timer in process instance {} by name '{}' or id {}", processInstanceId, timerName, timerId);
Expand All @@ -119,32 +130,30 @@ public Void execute(Context context ) {
if (wfp == null) {
throw new IllegalArgumentException("Process instance with id " + processInstanceId + " not found");
}

TimerInstance newTimer = handleSla(wfp.getSlaTimerId(), tm, wfp);
if (newTimer != null) {
wfp.internalSetSlaTimerId(newTimer.getId());
wfp.internalSetSlaDueDate(new Date(System.currentTimeMillis() + newTimer.getDelay()));
return null;
}

for (NodeInstance nodeInstance : wfp.getNodeInstances(true)) {
long slaTimerId = ((NodeInstanceImpl) nodeInstance).getSlaTimerId();
if (slaTimerId != -1 && slaTimerId == timerId) {
TimerInstance timer = tm.getTimerMap().get(timerId);

TimerInstance newTimer = rescheduleTimer(timer, tm);
logger.debug("New SLA timer {} about to be registered", newTimer);
tm.registerTimer(newTimer, wfp);

newTimer = handleSla(((NodeInstanceImpl) nodeInstance).getSlaTimerId(), tm, wfp);
if (newTimer != null) {
((NodeInstanceImpl) nodeInstance).internalSetSlaTimerId(newTimer.getId());
((NodeInstanceImpl) nodeInstance).internalSetSlaDueDate(new Date(System.currentTimeMillis() + newTimer.getDelay()));
logger.debug("New SLA timer {} successfully registered", newTimer);
break;
}

if (nodeInstance instanceof TimerNodeInstance) {
TimerNodeInstance tni = (TimerNodeInstance) nodeInstance;
if (tni.getTimerId() == timerId || (tni.getNodeName() != null && tni.getNodeName().equals(timerName))) {
TimerInstance timer = tm.getTimerMap().get(tni.getTimerId());

TimerInstance newTimer = rescheduleTimer(timer, tm);
newTimer = rescheduleTimer(timer, tm);
logger.debug("New timer {} about to be registered", newTimer);
tm.registerTimer(newTimer, wfp);
tni.internalSetTimerId(newTimer.getId());
logger.debug("New timer {} successfully registered", newTimer);

break;
}
}
Expand All @@ -157,7 +166,7 @@ public Void execute(Context context ) {
if (timerList != null && timerList.size() == 1) {
TimerInstance timer = tm.getTimerMap().get(timerList.get(0));

TimerInstance newTimer = rescheduleTimer(timer, tm);
newTimer = rescheduleTimer(timer, tm);
logger.debug("New timer {} about to be registered", newTimer);
tm.registerTimer(newTimer, wfp);
timerList.clear();
Expand All @@ -174,7 +183,7 @@ public Void execute(Context context ) {
HumanTaskNodeInstance htni = (HumanTaskNodeInstance) nodeInstance;
if (htni.getSuspendUntilTimerId() != -1 && htni.getSuspendUntilTimerId() == this.timerId) {
TimerInstance timer = tm.getTimerMap().get(this.timerId);
TimerInstance newTimer = rescheduleTimer(timer, tm);
newTimer = rescheduleTimer(timer, tm);
logger.debug("New timer {} about to be registered", newTimer);
tm.registerTimer(newTimer, wfp);
htni.setSuspendUntilTimerId(newTimer.getId());
Expand Down