From 57a654b345a2650cc18fdead6acb95642cc65ffd Mon Sep 17 00:00:00 2001 From: Maciej Swiderski Date: Wed, 26 Oct 2016 15:02:39 +0200 Subject: [PATCH] JBPM-4646 - Case management - improved handling of dynamic subprocesses and tasks (#653) --- .../BPMN2-BusinessRuleTaskWithDataInput.drl | 2 +- .../org/jbpm/casemgmt/api/CaseService.java | 12 ++++--- .../jbpm/casemgmt/impl/CaseServiceImpl.java | 16 +++++----- .../command/AddDynamicProcessCommand.java | 6 ++-- .../AddDynamicProcessToStageCommand.java | 6 ++-- .../casemgmt/impl/CaseServiceImplTest.java | 24 ++++++++++++-- .../workflow/instance/node/DynamicUtils.java | 32 +++++++++++++++++++ .../instance/node/SubProcessNodeInstance.java | 4 +++ 8 files changed, 81 insertions(+), 21 deletions(-) diff --git a/jbpm-bpmn2/src/test/resources/BPMN2-BusinessRuleTaskWithDataInput.drl b/jbpm-bpmn2/src/test/resources/BPMN2-BusinessRuleTaskWithDataInput.drl index 91683623cb..8260d6314c 100644 --- a/jbpm-bpmn2/src/test/resources/BPMN2-BusinessRuleTaskWithDataInput.drl +++ b/jbpm-bpmn2/src/test/resources/BPMN2-BusinessRuleTaskWithDataInput.drl @@ -21,7 +21,7 @@ import org.jbpm.bpmn2.objects.Person; rule "Person rule" ruleflow-group "MyRuleFlow" no-loop when - $p : Person() + $p : Person(name == null) then modify ($p){ setName("john") diff --git a/jbpm-case-mgmt/jbpm-case-mgmt-api/src/main/java/org/jbpm/casemgmt/api/CaseService.java b/jbpm-case-mgmt/jbpm-case-mgmt-api/src/main/java/org/jbpm/casemgmt/api/CaseService.java index 1781177c87..60bd976fee 100644 --- a/jbpm-case-mgmt/jbpm-case-mgmt-api/src/main/java/org/jbpm/casemgmt/api/CaseService.java +++ b/jbpm-case-mgmt/jbpm-case-mgmt-api/src/main/java/org/jbpm/casemgmt/api/CaseService.java @@ -190,18 +190,20 @@ public interface CaseService { * @param caseId unique case id in the format PREFIX-GENERATED_ID as described on startCase method * @param processId identifier of the process to be added * @param parameters optional parameters for the subprocess + * @return returns process instance id of the subprocess * @throws CaseNotFoundException thrown in case case was not found with given id */ - void addDynamicSubprocess(String caseId, String processId, Map parameters) throws CaseNotFoundException; + Long addDynamicSubprocess(String caseId, String processId, Map parameters) throws CaseNotFoundException; /** * Adds new subprocess (identified by process id) to case. * @param processInstanceId unique process instance id * @param processId identifier of the process to be added * @param parameters optional parameters for the subprocess + * @return returns process instance id of the subprocess * @throws CaseNotFoundException thrown in case case was not found with given id */ - void addDynamicSubprocess(Long processInstanceId, String processId, Map parameters) throws CaseNotFoundException; + Long addDynamicSubprocess(Long processInstanceId, String processId, Map parameters) throws CaseNotFoundException; /** * Adds new subprocess (identified by process id) to given process instance. Should be used when subprocess should be added to the @@ -211,9 +213,10 @@ public interface CaseService { * @param stageId id of the stage there the task should be added * @param processId identifier of the process to be added * @param parameters optional parameters for the subprocess + * @return returns process instance id of the subprocess * @throws CaseNotFoundException thrown in case case was not found with given id */ - void addDynamicSubprocessToStage(String caseId, String stageId, String processId, Map parameters) throws CaseNotFoundException; + Long addDynamicSubprocessToStage(String caseId, String stageId, String processId, Map parameters) throws CaseNotFoundException; /** * Adds new subprocess (identified by process id) to case. @@ -221,9 +224,10 @@ public interface CaseService { * @param stageId id of the stage there the task should be added * @param processId identifier of the process to be added * @param parameters optional parameters for the subprocess + * @return returns process instance id of the subprocess * @throws CaseNotFoundException thrown in case case was not found with given id */ - void addDynamicSubprocessToStage(Long processInstanceId, String stageId, String processId, Map parameters) throws CaseNotFoundException; + Long addDynamicSubprocessToStage(Long processInstanceId, String stageId, String processId, Map parameters) throws CaseNotFoundException; /** * Triggers given by fragmentName adhoc element (such as task, milestone) within given case. Should be used when fragment should be triggered diff --git a/jbpm-case-mgmt/jbpm-case-mgmt-impl/src/main/java/org/jbpm/casemgmt/impl/CaseServiceImpl.java b/jbpm-case-mgmt/jbpm-case-mgmt-impl/src/main/java/org/jbpm/casemgmt/impl/CaseServiceImpl.java index f0d1a8495a..a53d18d65b 100644 --- a/jbpm-case-mgmt/jbpm-case-mgmt-impl/src/main/java/org/jbpm/casemgmt/impl/CaseServiceImpl.java +++ b/jbpm-case-mgmt/jbpm-case-mgmt-impl/src/main/java/org/jbpm/casemgmt/impl/CaseServiceImpl.java @@ -269,40 +269,40 @@ public void addDynamicTaskToStage(Long processInstanceId, String stageId, TaskSp @Override - public void addDynamicSubprocess(String caseId, String processId, Map parameters) throws CaseNotFoundException { + public Long addDynamicSubprocess(String caseId, String processId, Map parameters) throws CaseNotFoundException { ProcessInstanceDesc pi = verifyCaseIdExists(caseId); if (pi == null || !pi.getState().equals(ProcessInstance.STATE_ACTIVE)) { throw new ProcessInstanceNotFoundException("No process instance found with id " + pi.getId() + " or it's not active anymore"); } - processService.execute(pi.getDeploymentId(), ProcessInstanceIdContext.get(pi.getId()), new AddDynamicProcessCommand(caseId, pi.getId(), processId, parameters)); + return processService.execute(pi.getDeploymentId(), ProcessInstanceIdContext.get(pi.getId()), new AddDynamicProcessCommand(caseId, pi.getId(), processId, parameters)); } @Override - public void addDynamicSubprocess(Long processInstanceId, String processId, Map parameters) throws CaseNotFoundException { + public Long addDynamicSubprocess(Long processInstanceId, String processId, Map parameters) throws CaseNotFoundException { ProcessInstanceDesc pi = runtimeDataService.getProcessInstanceById(processInstanceId); if (pi == null || !pi.getState().equals(ProcessInstance.STATE_ACTIVE)) { throw new ProcessInstanceNotFoundException("No process instance found with id " + processInstanceId + " or it's not active anymore"); } String caseId = pi.getCorrelationKey(); - processService.execute(pi.getDeploymentId(), ProcessInstanceIdContext.get(processInstanceId), new AddDynamicProcessCommand(caseId, pi.getId(), processId, parameters)); + return processService.execute(pi.getDeploymentId(), ProcessInstanceIdContext.get(processInstanceId), new AddDynamicProcessCommand(caseId, pi.getId(), processId, parameters)); } @Override - public void addDynamicSubprocessToStage(String caseId, String stageId, String processId, Map parameters) throws CaseNotFoundException { + public Long addDynamicSubprocessToStage(String caseId, String stageId, String processId, Map parameters) throws CaseNotFoundException { ProcessInstanceDesc pi = verifyCaseIdExists(caseId); - processService.execute(pi.getDeploymentId(), ProcessInstanceIdContext.get(pi.getId()), new AddDynamicProcessToStageCommand(caseId, pi.getId(), stageId, processId, parameters)); + return processService.execute(pi.getDeploymentId(), ProcessInstanceIdContext.get(pi.getId()), new AddDynamicProcessToStageCommand(caseId, pi.getId(), stageId, processId, parameters)); } @Override - public void addDynamicSubprocessToStage(Long processInstanceId, String stageId, String processId, Map parameters) throws CaseNotFoundException { + public Long addDynamicSubprocessToStage(Long processInstanceId, String stageId, String processId, Map parameters) throws CaseNotFoundException { ProcessInstanceDesc pi = runtimeDataService.getProcessInstanceById(processInstanceId); if (pi == null || !pi.getState().equals(ProcessInstance.STATE_ACTIVE)) { throw new ProcessInstanceNotFoundException("No process instance found with id " + processInstanceId + " or it's not active anymore"); } String caseId = pi.getCorrelationKey(); - processService.execute(pi.getDeploymentId(), ProcessInstanceIdContext.get(processInstanceId), new AddDynamicProcessToStageCommand(caseId, pi.getId(), stageId, processId, parameters)); + return processService.execute(pi.getDeploymentId(), ProcessInstanceIdContext.get(processInstanceId), new AddDynamicProcessToStageCommand(caseId, pi.getId(), stageId, processId, parameters)); } @Override diff --git a/jbpm-case-mgmt/jbpm-case-mgmt-impl/src/main/java/org/jbpm/casemgmt/impl/command/AddDynamicProcessCommand.java b/jbpm-case-mgmt/jbpm-case-mgmt-impl/src/main/java/org/jbpm/casemgmt/impl/command/AddDynamicProcessCommand.java index 67155ea9f1..4738b5fd89 100644 --- a/jbpm-case-mgmt/jbpm-case-mgmt-impl/src/main/java/org/jbpm/casemgmt/impl/command/AddDynamicProcessCommand.java +++ b/jbpm-case-mgmt/jbpm-case-mgmt-impl/src/main/java/org/jbpm/casemgmt/impl/command/AddDynamicProcessCommand.java @@ -29,7 +29,7 @@ /** * Adds subprocess (identified by processId) to selected ad hoc process instance with given parameters */ -public class AddDynamicProcessCommand extends CaseCommand { +public class AddDynamicProcessCommand extends CaseCommand { private static final long serialVersionUID = 6345222909719335953L; @@ -50,7 +50,7 @@ public AddDynamicProcessCommand(String caseId, Long processInstanceId, String pr } @Override - public Void execute(Context context) { + public Long execute(Context context) { KieSession ksession = ((KnowledgeCommandContext) context).getKieSession(); ProcessInstance processInstance = ksession.getProcessInstance(processInstanceId); @@ -63,7 +63,7 @@ public Void execute(Context context) { long subProcessInstanceId = DynamicUtils.addDynamicSubProcess(processInstance, ksession, processId, parameters); caseEventSupport.fireAfterDynamicProcessAdded(caseId, processInstanceId, processId, parameters, subProcessInstanceId); - return null; + return subProcessInstanceId; } } diff --git a/jbpm-case-mgmt/jbpm-case-mgmt-impl/src/main/java/org/jbpm/casemgmt/impl/command/AddDynamicProcessToStageCommand.java b/jbpm-case-mgmt/jbpm-case-mgmt-impl/src/main/java/org/jbpm/casemgmt/impl/command/AddDynamicProcessToStageCommand.java index 3d93f7be31..6024298f06 100644 --- a/jbpm-case-mgmt/jbpm-case-mgmt-impl/src/main/java/org/jbpm/casemgmt/impl/command/AddDynamicProcessToStageCommand.java +++ b/jbpm-case-mgmt/jbpm-case-mgmt-impl/src/main/java/org/jbpm/casemgmt/impl/command/AddDynamicProcessToStageCommand.java @@ -32,7 +32,7 @@ /** * Adds subprocess (identified by processId) to selected ad hoc process instance with given parameters */ -public class AddDynamicProcessToStageCommand extends CaseCommand { +public class AddDynamicProcessToStageCommand extends CaseCommand { private static final long serialVersionUID = 6345222909719335953L; @@ -55,7 +55,7 @@ public AddDynamicProcessToStageCommand(String caseId, Long processInstanceId, St } @Override - public Void execute(Context context) { + public Long execute(Context context) { KieSession ksession = ((KnowledgeCommandContext) context).getKieSession(); ProcessInstance processInstance = ksession.getProcessInstance(processInstanceId); @@ -77,7 +77,7 @@ public Void execute(Context context) { long subProcessInstanceId = DynamicUtils.addDynamicSubProcess(dynamicContext, ksession, processId, parameters); caseEventSupport.fireAfterDynamicProcessAdded(caseId, processInstanceId, processId, parameters, subProcessInstanceId); - return null; + return subProcessInstanceId; } } diff --git a/jbpm-case-mgmt/jbpm-case-mgmt-impl/src/test/java/org/jbpm/casemgmt/impl/CaseServiceImplTest.java b/jbpm-case-mgmt/jbpm-case-mgmt-impl/src/test/java/org/jbpm/casemgmt/impl/CaseServiceImplTest.java index 264c9be994..b0dd7ac9c6 100644 --- a/jbpm-case-mgmt/jbpm-case-mgmt-impl/src/test/java/org/jbpm/casemgmt/impl/CaseServiceImplTest.java +++ b/jbpm-case-mgmt/jbpm-case-mgmt-impl/src/test/java/org/jbpm/casemgmt/impl/CaseServiceImplTest.java @@ -316,7 +316,8 @@ public void testAddUserTaskToEmptyCase() { assertEquals(deploymentUnit.getIdentifier(), cInstance.getDeploymentId()); // add dynamic user task to empty case instance - first by case id - Map parameters = new HashMap<>(); + Map parameters = new HashMap<>(); + parameters.put("variable", "#{name}"); caseService.addDynamicTask(FIRST_CASE_ID, caseService.newHumanTaskSpec("First task", "test", "john", null, parameters)); List tasks = runtimeDataService.getTasksAssignedAsPotentialOwner("john", new QueryFilter()); @@ -327,6 +328,10 @@ public void testAddUserTaskToEmptyCase() { assertTask(task, "john", "First task", Status.Reserved); assertEquals("test", task.getDescription()); + Map inputs = userTaskService.getTaskInputContentByTaskId(task.getId()); + assertNotNull(inputs); + assertEquals("my first case", inputs.get("variable")); + String nameVar = (String)processService.getProcessInstanceVariable(task.getProcessInstanceId(), "name"); assertNotNull(nameVar); assertEquals("my first case", nameVar); @@ -564,12 +569,27 @@ public void testAddSubprocessToEmptyCase() { assertNotNull(casePI); assertEquals(FIRST_CASE_ID, casePI.getCorrelationKey()); + + Collection nodes = runtimeDataService.getProcessInstanceHistoryCompleted(casePI.getId(), new QueryContext()); + assertNotNull(nodes); + + assertEquals(3, nodes.size()); + + Map nodesByName = nodes.stream().collect(toMap(NodeInstanceDesc::getName, NodeInstanceDesc::getNodeType)); + assertTrue(nodesByName.containsKey("StartProcess")); + assertTrue(nodesByName.containsKey("EndProcess")); + assertTrue(nodesByName.containsKey("[Dynamic] Sub Process")); + + assertEquals("StartNode", nodesByName.get("StartProcess")); + assertEquals("EndNode", nodesByName.get("EndProcess")); + assertEquals("SubProcessNode", nodesByName.get("[Dynamic] Sub Process")); + caseService.addDynamicSubprocess(casePI.getId(), SUBPROCESS_P_ID, parameters); // let's verify that there are three process instances related to this case caseProcessInstances = caseRuntimeDataService.getProcessInstancesForCase(caseId, new QueryContext()); assertNotNull(caseProcessInstances); - assertEquals(3, caseProcessInstances.size()); + assertEquals(3, caseProcessInstances.size()); } catch (Exception e) { logger.error("Unexpected error {}", e.getMessage(), e); diff --git a/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/DynamicUtils.java b/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/DynamicUtils.java index d471d8be96..5b23939c1f 100644 --- a/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/DynamicUtils.java +++ b/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/DynamicUtils.java @@ -19,6 +19,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.drools.core.command.CommandService; import org.drools.core.command.impl.CommandBasedStatefulKnowledgeSession; @@ -29,11 +31,13 @@ import org.drools.core.impl.StatefulKnowledgeSessionImpl; import org.drools.core.process.instance.WorkItemManager; import org.drools.core.process.instance.impl.WorkItemImpl; +import org.drools.core.util.MVELSafeHelper; import org.jbpm.process.instance.InternalProcessRuntime; import org.jbpm.process.instance.ProcessInstance; import org.jbpm.process.instance.impl.ProcessInstanceImpl; import org.jbpm.workflow.instance.WorkflowProcessInstance; import org.jbpm.workflow.instance.impl.NodeInstanceImpl; +import org.jbpm.workflow.instance.impl.ProcessInstanceResolverFactory; import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl; import org.kie.api.definition.process.Process; import org.kie.api.runtime.EnvironmentName; @@ -52,6 +56,7 @@ public class DynamicUtils { private static final Logger logger = LoggerFactory.getLogger(DynamicUtils.class); + protected static final Pattern PARAMETER_MATCHER = Pattern.compile("#\\{([\\S&&[^\\}]]+)\\}", Pattern.DOTALL); public static void addDynamicWorkItem( final DynamicNodeInstance dynamicContext, KieRuntime ksession, @@ -75,6 +80,32 @@ private static void internalAddDynamicWorkItem( workItem.setDeploymentId( (String) ksession.getEnvironment().get(EnvironmentName.DEPLOYMENT_ID)); workItem.setName(workItemName); workItem.setParameters(parameters); + + for (Map.Entry entry: workItem.getParameters().entrySet()) { + if (entry.getValue() instanceof String) { + String s = (String) entry.getValue(); + Object variableValue = null; + Matcher matcher = PARAMETER_MATCHER.matcher(s); + while (matcher.find()) { + String paramName = matcher.group(1); + variableValue = processInstance.getVariable(paramName); + if (variableValue == null) { + try { + variableValue = MVELSafeHelper.getEvaluator().eval(paramName, new ProcessInstanceResolverFactory(processInstance)); + } catch (Throwable t) { + logger.error("Could not find variable scope for variable {}", paramName); + logger.error("when trying to replace variable in string for Dynamic Work Item {}", workItemName); + logger.error("Continuing without setting parameter."); + } + } + + } + if (variableValue != null) { + workItem.setParameter(entry.getKey(), variableValue); + } + } + } + final WorkItemNodeInstance workItemNodeInstance = new WorkItemNodeInstance(); workItemNodeInstance.internalSetWorkItem(workItem); workItemNodeInstance.setMetaData("NodeType", workItemName); @@ -145,6 +176,7 @@ public static long internalAddDynamicSubProcess( final SubProcessNodeInstance subProcessNodeInstance = new SubProcessNodeInstance(); subProcessNodeInstance.setNodeInstanceContainer(dynamicContext == null ? processInstance : dynamicContext); subProcessNodeInstance.setProcessInstance(processInstance); + subProcessNodeInstance.setMetaData("NodeType", "SubProcessNode"); if (ksession instanceof StatefulKnowledgeSessionImpl) { return executeSubProcess((StatefulKnowledgeSessionImpl) ksession, processId, parameters, processInstance, subProcessNodeInstance); } else if (ksession instanceof CommandBasedStatefulKnowledgeSession) { diff --git a/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/SubProcessNodeInstance.java b/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/SubProcessNodeInstance.java index b8c8a7852d..a09537f10f 100644 --- a/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/SubProcessNodeInstance.java +++ b/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/SubProcessNodeInstance.java @@ -322,6 +322,10 @@ public void processInstanceCompleted(ProcessInstance processInstance) { } } + // handle dynamic subprocess + if (getNode() == null) { + setMetaData("NodeType", "SubProcessNode"); + } // if there were no exception proceed normally triggerCompleted();