Skip to content

Commit

Permalink
BPMSPL-55 - [GSS-RFE] Able to use 'Task Variables' as expressions in …
Browse files Browse the repository at this point in the history
…Task properties / JBPM-4147 - Able to use 'Task Variables' as expressions in Task properties
  • Loading branch information
mswiderski committed May 28, 2015
1 parent e7a641e commit aafba25
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 13 deletions.
18 changes: 18 additions & 0 deletions jbpm-bpmn2/src/test/java/org/jbpm/bpmn2/ActivityTest.java
Expand Up @@ -1715,4 +1715,22 @@ public void afterNodeTriggered(ProcessNodeTriggeredEvent event) {
assertProcessInstanceCompleted(processInstance); assertProcessInstanceCompleted(processInstance);
assertEquals(2, list.size()); assertEquals(2, list.size());
} }

@Test
public void testUserTaskParametrizedInput() throws Exception {
KieBase kbase = createKnowledgeBase("BPMN2-UserTaskWithParametrizedInput.bpmn2");
KieSession ksession = createKnowledgeSession(kbase);
TestWorkItemHandler workItemHandler = new TestWorkItemHandler();
ksession.getWorkItemManager().registerWorkItemHandler("Human Task", workItemHandler);
ProcessInstance processInstance = ksession.startProcess("UserTask");
assertTrue(processInstance.getState() == ProcessInstance.STATE_ACTIVE);
ksession = restoreSession(ksession, true);
WorkItem workItem = workItemHandler.getWorkItem();
assertNotNull(workItem);
assertEquals("Executing task of process instance " + processInstance.getId() + " as work item with Hello",
workItem.getParameter("Description").toString().trim());
ksession.getWorkItemManager().completeWorkItem(workItem.getId(), null);
assertProcessInstanceFinished(processInstance, ksession);
ksession.dispose();
}
} }
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions id="Definition"
targetNamespace="http://www.example.org/MinimalExample"
typeLanguage="http://www.java.com/javaTypes"
expressionLanguage="http://www.mvel.org/2.0"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
xmlns:tns="http://www.jboss.org/drools">

<itemDefinition id="_sItem" structureRef="String" />
<process processType="Private" isExecutable="true" id="UserTask" name="User Task" >
<property id="s" itemSubjectRef="_sItem"/>
<!-- nodes -->
<startEvent id="_1" name="StartProcess" />
<userTask id="_2" name="Hello" >

<ioSpecification>
<dataInput id="_067D221E-E8BD-4AD5-9A90-FB204A0E89F9_UrlInputX" name="Description"/>
<inputSet>
<dataInputRefs>_067D221E-E8BD-4AD5-9A90-FB204A0E89F9_UrlInputX</dataInputRefs>
</inputSet>
<outputSet>
</outputSet>
</ioSpecification>
<dataInputAssociation id="_mZWjtlOZEeSBDLLXGrwEZw">
<targetRef>_067D221E-E8BD-4AD5-9A90-FB204A0E89F9_UrlInputX</targetRef>
<assignment id="_mZWjt1OZEeSBDLLXGrwEZw">
<from xsi:type="tFormalExpression" id="_mZWjuFOZEeSBDLLXGrwEZw">
<![CDATA[Executing task of process instance #{processInstance.id} as work item with #{nodeInstance.nodeName}]]>
</from>
<to xsi:type="tFormalExpression" id="_mZWjuVOZEeSBDLLXGrwEZw">_067D221E-E8BD-4AD5-9A90-FB204A0E89F9_UrlInputX</to>
</assignment>
</dataInputAssociation>
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>john</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
<endEvent id="_3" name="EndProcess" >
<terminateEventDefinition/>
</endEvent>

<!-- connections -->
<sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2" />
<sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3" />

</process>

<bpmndi:BPMNDiagram>
<bpmndi:BPMNPlane bpmnElement="UserTask" >
<bpmndi:BPMNShape bpmnElement="_1" >
<dc:Bounds x="16" y="16" width="48" height="48" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_2" >
<dc:Bounds x="96" y="16" width="100" height="48" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_3" >
<dc:Bounds x="228" y="16" width="48" height="48" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="_1-_2" >
<di:waypoint x="40" y="40" />
<di:waypoint x="146" y="40" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_2-_3" >
<di:waypoint x="146" y="40" />
<di:waypoint x="252" y="40" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>

</definitions>
Expand Up @@ -16,6 +16,9 @@


package org.jbpm.workflow.instance.impl; package org.jbpm.workflow.instance.impl;


import java.util.HashMap;
import java.util.Map;

import org.jbpm.process.core.context.variable.VariableScope; import org.jbpm.process.core.context.variable.VariableScope;
import org.jbpm.process.instance.context.variable.VariableScopeInstance; import org.jbpm.process.instance.context.variable.VariableScopeInstance;
import org.jbpm.workflow.instance.NodeInstance; import org.jbpm.workflow.instance.NodeInstance;
Expand All @@ -29,16 +32,32 @@ public class NodeInstanceResolverFactory extends ImmutableDefaultFactory {


private NodeInstance nodeInstance; private NodeInstance nodeInstance;


private Map<String, Object> extraParameters = new HashMap<String, Object>();

public NodeInstanceResolverFactory(NodeInstance nodeInstance) { public NodeInstanceResolverFactory(NodeInstance nodeInstance) {
this.nodeInstance = nodeInstance; this.nodeInstance = nodeInstance;
this.extraParameters.put("nodeInstance", nodeInstance);
if (nodeInstance.getProcessInstance() != null) {
this.extraParameters.put("processInstance", nodeInstance.getProcessInstance());
}
} }


public boolean isResolveable(String name) { public boolean isResolveable(String name) {
return nodeInstance.resolveContextInstance(VariableScope.VARIABLE_SCOPE, name) != null; boolean found = nodeInstance.resolveContextInstance(VariableScope.VARIABLE_SCOPE, name) != null;
if (!found) {
return extraParameters.containsKey(name);
}

return found;
} }



public VariableResolver getVariableResolver(String name) { public VariableResolver getVariableResolver(String name) {
Object value = ((VariableScopeInstance) if (extraParameters.containsKey(name)) {
return new SimpleValueResolver(extraParameters.get(name));
}

Object value = ((VariableScopeInstance)
nodeInstance.resolveContextInstance( nodeInstance.resolveContextInstance(
VariableScope.VARIABLE_SCOPE, name)).getVariable(name); VariableScope.VARIABLE_SCOPE, name)).getVariable(name);
return new SimpleValueResolver(value); return new SimpleValueResolver(value);
Expand Down
Expand Up @@ -6,15 +6,23 @@


import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
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.process.instance.WorkItem;
import org.drools.core.util.MVELSafeHelper;
import org.jbpm.process.core.context.variable.VariableScope;
import org.jbpm.process.instance.context.variable.VariableScopeInstance;
import org.jbpm.services.task.commands.TaskCommand; import org.jbpm.services.task.commands.TaskCommand;
import org.jbpm.services.task.commands.TaskContext; import org.jbpm.services.task.commands.TaskContext;
import org.jbpm.services.task.events.TaskEventSupport; import org.jbpm.services.task.events.TaskEventSupport;
import org.jbpm.services.task.internals.lifecycle.LifeCycleManager; import org.jbpm.services.task.internals.lifecycle.LifeCycleManager;
import org.jbpm.services.task.utils.ClassUtil; import org.jbpm.services.task.utils.ClassUtil;
import org.jbpm.services.task.utils.ContentMarshallerHelper; import org.jbpm.services.task.utils.ContentMarshallerHelper;
import org.jbpm.workflow.instance.impl.NodeInstanceResolverFactory;
import org.kie.api.command.Command; import org.kie.api.command.Command;
import org.kie.api.runtime.Environment; import org.kie.api.runtime.Environment;
import org.kie.api.task.model.Content; import org.kie.api.task.model.Content;
Expand Down Expand Up @@ -45,6 +53,8 @@ public class TaskInstanceServiceImpl implements TaskInstanceService {


private static final Logger logger = LoggerFactory.getLogger(TaskInstanceServiceImpl.class); private static final Logger logger = LoggerFactory.getLogger(TaskInstanceServiceImpl.class);


protected static final Pattern PARAMETER_MATCHER = Pattern.compile("\\$\\{([\\S&&[^\\}]]+)\\}", Pattern.DOTALL);

private LifeCycleManager lifeCycleManager; private LifeCycleManager lifeCycleManager;


private org.kie.internal.task.api.TaskContext context; private org.kie.internal.task.api.TaskContext context;
Expand Down Expand Up @@ -83,30 +93,39 @@ public void setPersistenceContext(TaskPersistenceContext persistenceContext) {
public long addTask(Task task, Map<String, Object> params) { public long addTask(Task task, Map<String, Object> params) {
taskEventSupport.fireBeforeTaskAdded(task, context); taskEventSupport.fireBeforeTaskAdded(task, context);


persistenceContext.persistTask(task);

resolveTaskDetailsForTaskProperties(task);
resolveTaskDetails(params, task);

This comment has been minimized.

Copy link
@salaboy

salaboy Jun 1, 2015

Contributor

@mswiderski This looks wrong to me. We are using the params before checking if they are null. If Params is null, the resolveTaskDetails() method will blow up! :)

This comment has been minimized.

Copy link
@mswiderski

mswiderski Jun 1, 2015

Author Contributor

we can add null check here, but do we have situation when we create task without parameters?

This comment has been minimized.

Copy link
@salaboy

salaboy Jun 1, 2015

Contributor

The null check is already one line below.. and yes.. I had one situation for ad-hoc tasks, that's why I found the issue.


if (params != null) { if (params != null) {
ContentData contentData = ContentMarshallerHelper.marshal(params, environment); ContentData contentData = ContentMarshallerHelper.marshal(params, TaskContentRegistry.get().getMarshallerContext(task).getEnvironment());
Content content = TaskModelProvider.getFactory().newContent(); Content content = TaskModelProvider.getFactory().newContent();
((InternalContent) content).setContent(contentData.getContent()); ((InternalContent) content).setContent(contentData.getContent());
persistenceContext.persistContent(content); persistenceContext.persistContent(content);
((InternalTaskData) task.getTaskData()).setDocument( ((InternalTaskData) task.getTaskData()).setDocument(content.getId(), contentData);
content.getId(), contentData);
} }


persistenceContext.persistTask(task);
taskEventSupport.fireAfterTaskAdded(task, context); taskEventSupport.fireAfterTaskAdded(task, context);
return task.getId(); return task.getId();
} }


public long addTask(Task task, ContentData contentData) { public long addTask(Task task, ContentData contentData) {
taskEventSupport.fireBeforeTaskAdded(task, context); taskEventSupport.fireBeforeTaskAdded(task, context);
if (contentData != null) {
persistenceContext.persistTask(task);

resolveTaskDetailsForTaskProperties(task);

if (contentData != null) {
Content content = TaskModelProvider.getFactory().newContent(); Content content = TaskModelProvider.getFactory().newContent();
((InternalContent) content).setContent(contentData.getContent()); ((InternalContent) content).setContent(contentData.getContent());
persistenceContext.persistContent(content); persistenceContext.persistContent(content);
((InternalTaskData) task.getTaskData()).setDocument(content.getId(), contentData); ((InternalTaskData) task.getTaskData()).setDocument(content.getId(), contentData);
} }


persistenceContext.persistTask(task);
taskEventSupport.fireAfterTaskAdded(task, context); taskEventSupport.fireAfterTaskAdded(task, context);
return task.getId(); return task.getId();
} }
Expand Down Expand Up @@ -357,4 +376,48 @@ protected List<String> toGroups(List<String> groups) {


return groups; return groups;
} }

protected Map<String, Object> resolveTaskDetails(Map<String, Object> parameters, Task task) {
for (Map.Entry<String, Object> entry: parameters.entrySet()) {
if (entry.getValue() != null && entry.getValue() instanceof String) {
String s = (String) entry.getValue();
Map<String, String> replacements = new HashMap<String, String>();
Matcher matcher = PARAMETER_MATCHER.matcher(s);
while (matcher.find()) {
String paramName = matcher.group(1);
if (replacements.get(paramName) == null) {

try {
Object variableValue = MVELSafeHelper.getEvaluator().eval(paramName, new TaskResolverFactory(task));
String variableValueString = variableValue == null ? "" : variableValue.toString();
replacements.put(paramName, variableValueString);
} catch (Throwable t) {

logger.error("Continuing without setting parameter.");
}
}

}
for (Map.Entry<String, String> replacement: replacements.entrySet()) {
s = s.replace("${" + replacement.getKey() + "}", replacement.getValue());
}
parameters.put(entry.getKey(), s);
}
}
return parameters;
}

protected void resolveTaskDetailsForTaskProperties(Task task) {
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put("name", task.getName());
parameters.put("description", task.getDescription());
parameters.put("subject", task.getSubject());
parameters.put("formName", ((InternalTask)task).getFormName());

Map<String, Object> replacements = resolveTaskDetails(parameters, task);
((InternalTask)task).setName((String) replacements.get("name"));
((InternalTask)task).setDescription((String) replacements.get("description"));
((InternalTask)task).setSubject((String) replacements.get("subject"));
((InternalTask)task).setFormName((String) replacements.get("formName"));
}
} }
@@ -0,0 +1,27 @@
package org.jbpm.services.task.impl;

import org.kie.api.task.model.Task;
import org.mvel2.integration.VariableResolver;
import org.mvel2.integration.impl.ImmutableDefaultFactory;
import org.mvel2.integration.impl.SimpleValueResolver;


public class TaskResolverFactory extends ImmutableDefaultFactory {

private static final long serialVersionUID = 8019024969834990593L;
private Task task;

public TaskResolverFactory(Task task) {
this.task = task;
}
public boolean isResolveable(String name) {
return "task".equals(name);
}


public VariableResolver getVariableResolver(String name) {

return new SimpleValueResolver(task);
}

}
Expand Up @@ -19,6 +19,7 @@
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;


import org.drools.core.process.instance.impl.WorkItemImpl; import org.drools.core.process.instance.impl.WorkItemImpl;
import org.jbpm.process.core.timer.DateTimeUtils; import org.jbpm.process.core.timer.DateTimeUtils;
Expand Down Expand Up @@ -195,6 +196,18 @@ protected ContentData createTaskContentBasedOnWorkItemParams(KieSession session,
return content; return content;
} }


protected Map<String, Object> createTaskDataBasedOnWorkItemParams(KieSession session, WorkItem workItem) {
Map<String, Object> data = new HashMap<String, Object>();
Object contentObject = workItem.getParameter("Content");
if (contentObject == null) {
data = new HashMap<String, Object>(workItem.getParameters());
} else {
data.put("Content", contentObject);
}

return data;
}

protected boolean isAutoClaim(WorkItem workItem, Task task) { protected boolean isAutoClaim(WorkItem workItem, Task task) {
String swimlaneUser = (String) workItem.getParameter("SwimlaneActorId"); String swimlaneUser = (String) workItem.getParameter("SwimlaneActorId");
if (swimlaneUser != null && !"".equals(swimlaneUser) && task.getTaskData().getStatus() == Status.Ready) { if (swimlaneUser != null && !"".equals(swimlaneUser) && task.getTaskData().getStatus() == Status.Ready) {
Expand Down
Expand Up @@ -16,6 +16,7 @@
package org.jbpm.services.task.wih; package org.jbpm.services.task.wih;


import java.util.Date; import java.util.Date;
import java.util.Map;


import org.jbpm.services.task.exception.PermissionDeniedException; import org.jbpm.services.task.exception.PermissionDeniedException;
import org.jbpm.services.task.utils.OnErrorAction; import org.jbpm.services.task.utils.OnErrorAction;
Expand Down Expand Up @@ -56,7 +57,8 @@ public void executeWorkItem(WorkItem workItem, WorkItemManager manager) {
KieSession ksessionById = runtime.getKieSession(); KieSession ksessionById = runtime.getKieSession();


Task task = createTaskBasedOnWorkItemParams(ksessionById, workItem); Task task = createTaskBasedOnWorkItemParams(ksessionById, workItem);
ContentData content = createTaskContentBasedOnWorkItemParams(ksessionById, workItem); // ContentData content = createTaskContentBasedOnWorkItemParams(ksessionById, workItem);
Map<String, Object> content = createTaskDataBasedOnWorkItemParams(ksessionById, workItem);
try { try {
long taskId = ((InternalTaskService) runtime.getTaskService()).addTask(task, content); long taskId = ((InternalTaskService) runtime.getTaskService()).addTask(task, content);
if (isAutoClaim(workItem, task)) { if (isAutoClaim(workItem, task)) {
Expand Down
Expand Up @@ -770,6 +770,41 @@ public void testTaskCompleteGroupActors() throws Exception {
assertEquals("Luke Cage", actualOwner); assertEquals("Luke Cage", actualOwner);
} }


@Test
public void testTaskWithVariables() throws Exception {
TestWorkItemManager manager = new TestWorkItemManager();
ksession.setWorkItemManager(manager);
WorkItemImpl workItem = new WorkItemImpl();
workItem.setName("Human Task");
workItem.setParameter("NodeName", "TaskName ${task.taskData.processInstanceId}");
workItem.setParameter("Comment", "Comment for task ${task.id}");
workItem.setParameter("Priority", "10");
workItem.setParameter("ActorId", "Darth Vader");
workItem.setProcessInstanceId(10);
handler.executeWorkItem(workItem, manager);


List<TaskSummary> tasks = taskService.getTasksAssignedAsPotentialOwner("Darth Vader", "en-UK");
assertEquals(1, tasks.size());
TaskSummary task = tasks.get(0);
assertEquals("TaskName " + task.getProcessInstanceId(), task.getName());
assertEquals(10, task.getPriority().intValue());
assertEquals("Comment for task " + task.getId(), task.getDescription());
assertEquals(Status.Reserved, task.getStatus());
assertEquals("Darth Vader", task.getActualOwner().getId());
assertEquals(10, task.getProcessInstanceId().intValue());

taskService.start(task.getId(), "Darth Vader");
taskService.complete(task.getId(), "Darth Vader", null);

assertTrue(manager.waitTillCompleted(MANAGER_COMPLETION_WAIT_TIME));

String actualOwner = (String) manager.getResults().get("ActorId");
assertNotNull(actualOwner);
assertEquals("Darth Vader", actualOwner);

}

public void setHandler(WorkItemHandler handler) { public void setHandler(WorkItemHandler handler) {
this.handler = handler; this.handler = handler;
} }
Expand Down
Expand Up @@ -174,7 +174,7 @@ private MedicalRecord getTaskContent(RuntimeEngine runtimeEngine, TaskSummary su
runtimeEngine.getKieSession().getEnvironment()); runtimeEngine.getKieSession().getEnvironment());


logger.info(" >>> Object = {}", readObject); logger.info(" >>> Object = {}", readObject);
return (MedicalRecord)readObject; return (MedicalRecord)((Map)readObject).get("Content");
} }




Expand Down

0 comments on commit aafba25

Please sign in to comment.