Skip to content

Commit

Permalink
JBPM-4646 - Case management - improved handling of dynamic subprocess…
Browse files Browse the repository at this point in the history
…es and tasks (#653)
  • Loading branch information
mswiderski committed Oct 26, 2016
1 parent 0fe889e commit 57a654b
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 21 deletions.
Expand Up @@ -21,7 +21,7 @@ import org.jbpm.bpmn2.objects.Person;
rule "Person rule" ruleflow-group "MyRuleFlow" rule "Person rule" ruleflow-group "MyRuleFlow"
no-loop no-loop
when when
$p : Person() $p : Person(name == null)
then then
modify ($p){ modify ($p){
setName("john") setName("john")
Expand Down
Expand Up @@ -190,18 +190,20 @@ public interface CaseService {
* @param caseId unique case id in the format PREFIX-GENERATED_ID as described on startCase method * @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 processId identifier of the process to be added
* @param parameters optional parameters for the subprocess * @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 * @throws CaseNotFoundException thrown in case case was not found with given id
*/ */
void addDynamicSubprocess(String caseId, String processId, Map<String, Object> parameters) throws CaseNotFoundException; Long addDynamicSubprocess(String caseId, String processId, Map<String, Object> parameters) throws CaseNotFoundException;


/** /**
* Adds new subprocess (identified by process id) to case. * Adds new subprocess (identified by process id) to case.
* @param processInstanceId unique process instance id * @param processInstanceId unique process instance id
* @param processId identifier of the process to be added * @param processId identifier of the process to be added
* @param parameters optional parameters for the subprocess * @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 * @throws CaseNotFoundException thrown in case case was not found with given id
*/ */
void addDynamicSubprocess(Long processInstanceId, String processId, Map<String, Object> parameters) throws CaseNotFoundException; Long addDynamicSubprocess(Long processInstanceId, String processId, Map<String, Object> parameters) throws CaseNotFoundException;


/** /**
* Adds new subprocess (identified by process id) to given process instance. Should be used when subprocess should be added to the * Adds new subprocess (identified by process id) to given process instance. Should be used when subprocess should be added to the
Expand All @@ -211,19 +213,21 @@ public interface CaseService {
* @param stageId id of the stage there the task should be added * @param stageId id of the stage there the task should be added
* @param processId identifier of the process to be added * @param processId identifier of the process to be added
* @param parameters optional parameters for the subprocess * @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 * @throws CaseNotFoundException thrown in case case was not found with given id
*/ */
void addDynamicSubprocessToStage(String caseId, String stageId, String processId, Map<String, Object> parameters) throws CaseNotFoundException; Long addDynamicSubprocessToStage(String caseId, String stageId, String processId, Map<String, Object> parameters) throws CaseNotFoundException;


/** /**
* Adds new subprocess (identified by process id) to case. * Adds new subprocess (identified by process id) to case.
* @param processInstanceId unique process instance id * @param processInstanceId unique process instance id
* @param stageId id of the stage there the task should be added * @param stageId id of the stage there the task should be added
* @param processId identifier of the process to be added * @param processId identifier of the process to be added
* @param parameters optional parameters for the subprocess * @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 * @throws CaseNotFoundException thrown in case case was not found with given id
*/ */
void addDynamicSubprocessToStage(Long processInstanceId, String stageId, String processId, Map<String, Object> parameters) throws CaseNotFoundException; Long addDynamicSubprocessToStage(Long processInstanceId, String stageId, String processId, Map<String, Object> parameters) throws CaseNotFoundException;


/** /**
* Triggers given by fragmentName adhoc element (such as task, milestone) within given case. Should be used when fragment should be triggered * Triggers given by fragmentName adhoc element (such as task, milestone) within given case. Should be used when fragment should be triggered
Expand Down
Expand Up @@ -269,40 +269,40 @@ public void addDynamicTaskToStage(Long processInstanceId, String stageId, TaskSp




@Override @Override
public void addDynamicSubprocess(String caseId, String processId, Map<String, Object> parameters) throws CaseNotFoundException { public Long addDynamicSubprocess(String caseId, String processId, Map<String, Object> parameters) throws CaseNotFoundException {
ProcessInstanceDesc pi = verifyCaseIdExists(caseId); ProcessInstanceDesc pi = verifyCaseIdExists(caseId);
if (pi == null || !pi.getState().equals(ProcessInstance.STATE_ACTIVE)) { 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"); 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 @Override
public void addDynamicSubprocess(Long processInstanceId, String processId, Map<String, Object> parameters) throws CaseNotFoundException { public Long addDynamicSubprocess(Long processInstanceId, String processId, Map<String, Object> parameters) throws CaseNotFoundException {
ProcessInstanceDesc pi = runtimeDataService.getProcessInstanceById(processInstanceId); ProcessInstanceDesc pi = runtimeDataService.getProcessInstanceById(processInstanceId);
if (pi == null || !pi.getState().equals(ProcessInstance.STATE_ACTIVE)) { 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"); throw new ProcessInstanceNotFoundException("No process instance found with id " + processInstanceId + " or it's not active anymore");
} }


String caseId = pi.getCorrelationKey(); 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 @Override
public void addDynamicSubprocessToStage(String caseId, String stageId, String processId, Map<String, Object> parameters) throws CaseNotFoundException { public Long addDynamicSubprocessToStage(String caseId, String stageId, String processId, Map<String, Object> parameters) throws CaseNotFoundException {
ProcessInstanceDesc pi = verifyCaseIdExists(caseId); 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 @Override
public void addDynamicSubprocessToStage(Long processInstanceId, String stageId, String processId, Map<String, Object> parameters) throws CaseNotFoundException { public Long addDynamicSubprocessToStage(Long processInstanceId, String stageId, String processId, Map<String, Object> parameters) throws CaseNotFoundException {
ProcessInstanceDesc pi = runtimeDataService.getProcessInstanceById(processInstanceId); ProcessInstanceDesc pi = runtimeDataService.getProcessInstanceById(processInstanceId);
if (pi == null || !pi.getState().equals(ProcessInstance.STATE_ACTIVE)) { 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"); throw new ProcessInstanceNotFoundException("No process instance found with id " + processInstanceId + " or it's not active anymore");
} }
String caseId = pi.getCorrelationKey(); 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 @Override
Expand Down
Expand Up @@ -29,7 +29,7 @@
/** /**
* Adds subprocess (identified by processId) to selected ad hoc process instance with given parameters * Adds subprocess (identified by processId) to selected ad hoc process instance with given parameters
*/ */
public class AddDynamicProcessCommand extends CaseCommand<Void> { public class AddDynamicProcessCommand extends CaseCommand<Long> {


private static final long serialVersionUID = 6345222909719335953L; private static final long serialVersionUID = 6345222909719335953L;


Expand All @@ -50,7 +50,7 @@ public AddDynamicProcessCommand(String caseId, Long processInstanceId, String pr
} }


@Override @Override
public Void execute(Context context) { public Long execute(Context context) {
KieSession ksession = ((KnowledgeCommandContext) context).getKieSession(); KieSession ksession = ((KnowledgeCommandContext) context).getKieSession();


ProcessInstance processInstance = ksession.getProcessInstance(processInstanceId); ProcessInstance processInstance = ksession.getProcessInstance(processInstanceId);
Expand All @@ -63,7 +63,7 @@ public Void execute(Context context) {
long subProcessInstanceId = DynamicUtils.addDynamicSubProcess(processInstance, ksession, processId, parameters); long subProcessInstanceId = DynamicUtils.addDynamicSubProcess(processInstance, ksession, processId, parameters);


caseEventSupport.fireAfterDynamicProcessAdded(caseId, processInstanceId, processId, parameters, subProcessInstanceId); caseEventSupport.fireAfterDynamicProcessAdded(caseId, processInstanceId, processId, parameters, subProcessInstanceId);
return null; return subProcessInstanceId;
} }


} }
Expand Up @@ -32,7 +32,7 @@
/** /**
* Adds subprocess (identified by processId) to selected ad hoc process instance with given parameters * Adds subprocess (identified by processId) to selected ad hoc process instance with given parameters
*/ */
public class AddDynamicProcessToStageCommand extends CaseCommand<Void> { public class AddDynamicProcessToStageCommand extends CaseCommand<Long> {


private static final long serialVersionUID = 6345222909719335953L; private static final long serialVersionUID = 6345222909719335953L;


Expand All @@ -55,7 +55,7 @@ public AddDynamicProcessToStageCommand(String caseId, Long processInstanceId, St
} }


@Override @Override
public Void execute(Context context) { public Long execute(Context context) {
KieSession ksession = ((KnowledgeCommandContext) context).getKieSession(); KieSession ksession = ((KnowledgeCommandContext) context).getKieSession();


ProcessInstance processInstance = ksession.getProcessInstance(processInstanceId); ProcessInstance processInstance = ksession.getProcessInstance(processInstanceId);
Expand All @@ -77,7 +77,7 @@ public Void execute(Context context) {
long subProcessInstanceId = DynamicUtils.addDynamicSubProcess(dynamicContext, ksession, processId, parameters); long subProcessInstanceId = DynamicUtils.addDynamicSubProcess(dynamicContext, ksession, processId, parameters);


caseEventSupport.fireAfterDynamicProcessAdded(caseId, processInstanceId, processId, parameters, subProcessInstanceId); caseEventSupport.fireAfterDynamicProcessAdded(caseId, processInstanceId, processId, parameters, subProcessInstanceId);
return null; return subProcessInstanceId;
} }


} }
Expand Up @@ -316,7 +316,8 @@ public void testAddUserTaskToEmptyCase() {
assertEquals(deploymentUnit.getIdentifier(), cInstance.getDeploymentId()); assertEquals(deploymentUnit.getIdentifier(), cInstance.getDeploymentId());


// add dynamic user task to empty case instance - first by case id // add dynamic user task to empty case instance - first by case id
Map<String, Object> parameters = new HashMap<>(); Map<String, Object> parameters = new HashMap<>();
parameters.put("variable", "#{name}");
caseService.addDynamicTask(FIRST_CASE_ID, caseService.newHumanTaskSpec("First task", "test", "john", null, parameters)); caseService.addDynamicTask(FIRST_CASE_ID, caseService.newHumanTaskSpec("First task", "test", "john", null, parameters));


List<TaskSummary> tasks = runtimeDataService.getTasksAssignedAsPotentialOwner("john", new QueryFilter()); List<TaskSummary> tasks = runtimeDataService.getTasksAssignedAsPotentialOwner("john", new QueryFilter());
Expand All @@ -327,6 +328,10 @@ public void testAddUserTaskToEmptyCase() {
assertTask(task, "john", "First task", Status.Reserved); assertTask(task, "john", "First task", Status.Reserved);
assertEquals("test", task.getDescription()); assertEquals("test", task.getDescription());


Map<String, Object> inputs = userTaskService.getTaskInputContentByTaskId(task.getId());
assertNotNull(inputs);
assertEquals("my first case", inputs.get("variable"));

String nameVar = (String)processService.getProcessInstanceVariable(task.getProcessInstanceId(), "name"); String nameVar = (String)processService.getProcessInstanceVariable(task.getProcessInstanceId(), "name");
assertNotNull(nameVar); assertNotNull(nameVar);
assertEquals("my first case", nameVar); assertEquals("my first case", nameVar);
Expand Down Expand Up @@ -564,12 +569,27 @@ public void testAddSubprocessToEmptyCase() {
assertNotNull(casePI); assertNotNull(casePI);
assertEquals(FIRST_CASE_ID, casePI.getCorrelationKey()); assertEquals(FIRST_CASE_ID, casePI.getCorrelationKey());



Collection<NodeInstanceDesc> nodes = runtimeDataService.getProcessInstanceHistoryCompleted(casePI.getId(), new QueryContext());
assertNotNull(nodes);

assertEquals(3, nodes.size());

Map<String, String> 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); caseService.addDynamicSubprocess(casePI.getId(), SUBPROCESS_P_ID, parameters);


// let's verify that there are three process instances related to this case // let's verify that there are three process instances related to this case
caseProcessInstances = caseRuntimeDataService.getProcessInstancesForCase(caseId, new QueryContext()); caseProcessInstances = caseRuntimeDataService.getProcessInstancesForCase(caseId, new QueryContext());
assertNotNull(caseProcessInstances); assertNotNull(caseProcessInstances);
assertEquals(3, caseProcessInstances.size()); assertEquals(3, caseProcessInstances.size());


} catch (Exception e) { } catch (Exception e) {
logger.error("Unexpected error {}", e.getMessage(), e); logger.error("Unexpected error {}", e.getMessage(), e);
Expand Down
Expand Up @@ -19,6 +19,8 @@
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; 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.CommandService;
import org.drools.core.command.impl.CommandBasedStatefulKnowledgeSession; import org.drools.core.command.impl.CommandBasedStatefulKnowledgeSession;
Expand All @@ -29,11 +31,13 @@
import org.drools.core.impl.StatefulKnowledgeSessionImpl; import org.drools.core.impl.StatefulKnowledgeSessionImpl;
import org.drools.core.process.instance.WorkItemManager; import org.drools.core.process.instance.WorkItemManager;
import org.drools.core.process.instance.impl.WorkItemImpl; 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.InternalProcessRuntime;
import org.jbpm.process.instance.ProcessInstance; import org.jbpm.process.instance.ProcessInstance;
import org.jbpm.process.instance.impl.ProcessInstanceImpl; import org.jbpm.process.instance.impl.ProcessInstanceImpl;
import org.jbpm.workflow.instance.WorkflowProcessInstance; import org.jbpm.workflow.instance.WorkflowProcessInstance;
import org.jbpm.workflow.instance.impl.NodeInstanceImpl; import org.jbpm.workflow.instance.impl.NodeInstanceImpl;
import org.jbpm.workflow.instance.impl.ProcessInstanceResolverFactory;
import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl; import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl;
import org.kie.api.definition.process.Process; import org.kie.api.definition.process.Process;
import org.kie.api.runtime.EnvironmentName; import org.kie.api.runtime.EnvironmentName;
Expand All @@ -52,6 +56,7 @@
public class DynamicUtils { public class DynamicUtils {


private static final Logger logger = LoggerFactory.getLogger(DynamicUtils.class); private static final Logger logger = LoggerFactory.getLogger(DynamicUtils.class);
protected static final Pattern PARAMETER_MATCHER = Pattern.compile("#\\{([\\S&&[^\\}]]+)\\}", Pattern.DOTALL);


public static void addDynamicWorkItem( public static void addDynamicWorkItem(
final DynamicNodeInstance dynamicContext, KieRuntime ksession, final DynamicNodeInstance dynamicContext, KieRuntime ksession,
Expand All @@ -75,6 +80,32 @@ private static void internalAddDynamicWorkItem(
workItem.setDeploymentId( (String) ksession.getEnvironment().get(EnvironmentName.DEPLOYMENT_ID)); workItem.setDeploymentId( (String) ksession.getEnvironment().get(EnvironmentName.DEPLOYMENT_ID));
workItem.setName(workItemName); workItem.setName(workItemName);
workItem.setParameters(parameters); workItem.setParameters(parameters);

for (Map.Entry<String, Object> 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(); final WorkItemNodeInstance workItemNodeInstance = new WorkItemNodeInstance();
workItemNodeInstance.internalSetWorkItem(workItem); workItemNodeInstance.internalSetWorkItem(workItem);
workItemNodeInstance.setMetaData("NodeType", workItemName); workItemNodeInstance.setMetaData("NodeType", workItemName);
Expand Down Expand Up @@ -145,6 +176,7 @@ public static long internalAddDynamicSubProcess(
final SubProcessNodeInstance subProcessNodeInstance = new SubProcessNodeInstance(); final SubProcessNodeInstance subProcessNodeInstance = new SubProcessNodeInstance();
subProcessNodeInstance.setNodeInstanceContainer(dynamicContext == null ? processInstance : dynamicContext); subProcessNodeInstance.setNodeInstanceContainer(dynamicContext == null ? processInstance : dynamicContext);
subProcessNodeInstance.setProcessInstance(processInstance); subProcessNodeInstance.setProcessInstance(processInstance);
subProcessNodeInstance.setMetaData("NodeType", "SubProcessNode");
if (ksession instanceof StatefulKnowledgeSessionImpl) { if (ksession instanceof StatefulKnowledgeSessionImpl) {
return executeSubProcess((StatefulKnowledgeSessionImpl) ksession, processId, parameters, processInstance, subProcessNodeInstance); return executeSubProcess((StatefulKnowledgeSessionImpl) ksession, processId, parameters, processInstance, subProcessNodeInstance);
} else if (ksession instanceof CommandBasedStatefulKnowledgeSession) { } else if (ksession instanceof CommandBasedStatefulKnowledgeSession) {
Expand Down
Expand Up @@ -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 // if there were no exception proceed normally
triggerCompleted(); triggerCompleted();


Expand Down

0 comments on commit 57a654b

Please sign in to comment.