/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.activiti.workflow.simple.converter.step;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.activiti.bpmn.model.ActivitiListener;
import org.activiti.bpmn.model.BaseElement;
import org.activiti.bpmn.model.BoundaryEvent;
import org.activiti.bpmn.model.ExclusiveGateway;
import org.activiti.bpmn.model.FieldExtension;
import org.activiti.bpmn.model.FormProperty;
import org.activiti.bpmn.model.ImplementationType;
import org.activiti.bpmn.model.MultiInstanceLoopCharacteristics;
import org.activiti.bpmn.model.ParallelGateway;
import org.activiti.bpmn.model.Signal;
import org.activiti.bpmn.model.SignalEventDefinition;
import org.activiti.bpmn.model.ThrowEvent;
import org.activiti.bpmn.model.UserTask;
import org.activiti.workflow.simple.converter.ConversionConstants;
import org.activiti.workflow.simple.converter.WorkflowDefinitionConversion;
import org.activiti.workflow.simple.definition.FeedbackStepDefinition;
import org.activiti.workflow.simple.definition.StepDefinition;
import org.activiti.workflow.simple.util.JvmUtil;
/**
* @author Joram Barrez
*/
public class FeedbackStepDefinitionConverter extends BaseStepDefinitionConverter<FeedbackStepDefinition, Map<String, BaseElement>> {
private static final long serialVersionUID = 1L;
private static final String SELECT_PEOPLE_USER_TASK = "initiatorSelectPeopleTask";
private static final String FEEDBACK_FORK = "feedbackFork";
private static final String FEEDBACK_JOIN = "feedbackJoin";
private static final String FEEDBACK_USER_TASK = "gatherFeedback";
public static final String VARIABLE_FEEDBACK_PROVIDERS = "feedbackProviders";
public static final String VARIABLE_FEEDBACK_PROVIDER = "feedbackProvider";
@Override
public Class< ? extends StepDefinition> getHandledClass() {
return FeedbackStepDefinition.class;
}
@Override
protected Map<String, BaseElement> createProcessArtifact(FeedbackStepDefinition feedbackStepDefinition, WorkflowDefinitionConversion conversion) {
// See feedback-step.png in the resource folder to get a graphical understanding of the conversion below
Map<String, BaseElement> processElements = new HashMap<String, BaseElement>();
// The first user task, responsible for configuring the feedback
UserTask selectPeopleUserTask = createSelectPeopleUserTask(feedbackStepDefinition, conversion, processElements);
// Parallel gateways (forking/joining)
ParallelGateway fork = createForkParallelGateway(conversion, processElements);
addSequenceFlow(conversion, selectPeopleUserTask, fork);
// Gather feedback user task for the initiator of the feedback step
UserTask gatherFeedbackUserTask = createGatherFeedbackUserTask(feedbackStepDefinition, conversion, processElements);
addSequenceFlow(conversion, fork, gatherFeedbackUserTask);
// Global signal event
Signal signal = createSignalDeclaration(conversion);
// Signal throw event after the gather feedback task
ThrowEvent signalThrowEvent = createSignalThrow(conversion, signal);
addSequenceFlow(conversion, gatherFeedbackUserTask, signalThrowEvent);
// Povide feedback step
UserTask feedbackTask = createFeedbackUserTask(feedbackStepDefinition, conversion, processElements);
addSequenceFlow(conversion, fork, feedbackTask);
// Boundary signal catch to shut down all tasks if the 'gather feedback' task is completed
BoundaryEvent boundarySignalCatch = createBoundarySignalCatch(conversion, signal, feedbackTask);
// Exclusive gateway after the feedback task, needed to correctly merge the sequence flow
// such that the joining parallel gateway has exactly two incoming sequence flow
ExclusiveGateway mergingExclusiveGateway = createMergingExclusiveGateway(conversion);
addSequenceFlow(conversion, feedbackTask, mergingExclusiveGateway);
addSequenceFlow(conversion, boundarySignalCatch, mergingExclusiveGateway);
// Parallel gateway that will join it all together
ParallelGateway join = createJoinParallelGateway(conversion, processElements);
addSequenceFlow(conversion, signalThrowEvent, join);
addSequenceFlow(conversion, mergingExclusiveGateway, join);
// Set the last activity id, such that next steps can connect correctly
conversion.setLastActivityId(join.getId());
return processElements;
}
protected UserTask createSelectPeopleUserTask(FeedbackStepDefinition feedbackStepDefinition, WorkflowDefinitionConversion conversion,
Map<String, BaseElement> processElements) {
UserTask selectPeopleUserTask = new UserTask();
selectPeopleUserTask.setId(conversion.getUniqueNumberedId(ConversionConstants.USER_TASK_ID_PREFIX));
selectPeopleUserTask.setName(getSelectPeopleTaskName());
selectPeopleUserTask.setAssignee(feedbackStepDefinition.getFeedbackInitiator());
addFlowElement(conversion, selectPeopleUserTask, true);
processElements.put(SELECT_PEOPLE_USER_TASK, selectPeopleUserTask);
// TODO: work out form such that it can be used in Activiti Explorer, ie. add correct form properties
// The following is just a a bit of a dummy form property
FormProperty feedbackProvidersProperty = new FormProperty();
feedbackProvidersProperty.setId(VARIABLE_FEEDBACK_PROVIDERS);
feedbackProvidersProperty.setName("Who needs to provide feedback?");
feedbackProvidersProperty.setRequired(true);
feedbackProvidersProperty.setType("string"); // TODO: we need some kind of 'people' property type here
selectPeopleUserTask.setFormProperties(Arrays.asList(feedbackProvidersProperty));
// When the list of feedback providers is fixed up front, we need to add a script listener
// that injects these variables into the process (instead of having it provided by the end user in a form)
if (feedbackStepDefinition.getFeedbackProviders() != null && !feedbackStepDefinition.getFeedbackProviders()
.isEmpty()) {
if (selectPeopleUserTask.getTaskListeners() == null)
{
selectPeopleUserTask.setTaskListeners(new ArrayList<ActivitiListener>());
}
ActivitiListener taskListener = new ActivitiListener();
taskListener.setEvent("complete");
taskListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_CLASS);
taskListener.setImplementation("org.activiti.engine.impl.bpmn.listener.ScriptTaskListener");
FieldExtension languageField = new FieldExtension();
languageField.setFieldName("language");
languageField.setStringValue("JavaScript");
FieldExtension scriptField = new FieldExtension();
scriptField.setFieldName("script");
StringBuilder script = new StringBuilder();
if (JvmUtil.isJDK8()) {
script.append("load(\"nashorn:mozilla_compat.js\");");
}
script.append("importPackage (java.util); var feedbackProviders = new ArrayList();" + System.getProperty("line.separator"));
for (String feedbackProvider : feedbackStepDefinition.getFeedbackProviders()) {
script.append("feedbackProviders.add('" + feedbackProvider + "');" + System.getProperty("line.separator"));
}
script.append("task.getExecution().setVariable('" + VARIABLE_FEEDBACK_PROVIDERS + "', feedbackProviders);" + System.getProperty("line.separator"));
scriptField.setStringValue(script.toString());
taskListener.setFieldExtensions(Arrays.asList(languageField, scriptField));
selectPeopleUserTask.getTaskListeners().add(taskListener);
}
return selectPeopleUserTask;
}
protected ParallelGateway createForkParallelGateway(WorkflowDefinitionConversion conversion, Map<String, BaseElement> processElements) {
ParallelGateway fork = new ParallelGateway();
fork.setId(conversion.getUniqueNumberedId(ConversionConstants.GATEWAY_ID_PREFIX));
addFlowElement(conversion, fork);
processElements.put(FEEDBACK_FORK, fork);
return fork;
}
protected UserTask createGatherFeedbackUserTask(FeedbackStepDefinition feedbackStepDefinition, WorkflowDefinitionConversion conversion,
Map<String, BaseElement> processElements) {
UserTask gatherFeedbackUserTask = new UserTask();
gatherFeedbackUserTask.setId(conversion.getUniqueNumberedId(ConversionConstants.USER_TASK_ID_PREFIX));
gatherFeedbackUserTask.setName(getGatherFeedbackTaskName());
gatherFeedbackUserTask.setAssignee(feedbackStepDefinition.getFeedbackInitiator());
addFlowElement(conversion, gatherFeedbackUserTask);
processElements.put(SELECT_PEOPLE_USER_TASK, gatherFeedbackUserTask);
return gatherFeedbackUserTask;
}
protected Signal createSignalDeclaration(WorkflowDefinitionConversion conversion) {
Signal signal = new Signal();
String uniqueSignalId = "signal-" + UUID.randomUUID().toString();
signal.setId(uniqueSignalId);
signal.setName(uniqueSignalId);
signal.setScope(Signal.SCOPE_PROCESS_INSTANCE);
conversion.getBpmnModel().addSignal(signal);
return signal;
}
protected ThrowEvent createSignalThrow(WorkflowDefinitionConversion conversion, Signal signal) {
ThrowEvent signalThrowEvent = new ThrowEvent();
signalThrowEvent.setId(conversion.getUniqueNumberedId(ConversionConstants.EVENT_ID_PREFIX));
SignalEventDefinition signalThrowEventDefinition = new SignalEventDefinition();
signalThrowEventDefinition.setSignalRef(signal.getId());
signalThrowEvent.addEventDefinition(signalThrowEventDefinition);
addFlowElement(conversion, signalThrowEvent);
return signalThrowEvent;
}
protected ParallelGateway createJoinParallelGateway(WorkflowDefinitionConversion conversion, Map<String, BaseElement> processElements) {
ParallelGateway join = new ParallelGateway();
join.setId(conversion.getUniqueNumberedId(ConversionConstants.GATEWAY_ID_PREFIX));
addFlowElement(conversion, join);
processElements.put(FEEDBACK_JOIN, join);
return join;
}
protected UserTask createFeedbackUserTask(FeedbackStepDefinition feedbackStepDefinition, WorkflowDefinitionConversion conversion,
Map<String, BaseElement> processElements) {
UserTask feedbackTask = new UserTask();
feedbackTask.setId(conversion.getUniqueNumberedId(ConversionConstants.USER_TASK_ID_PREFIX));
feedbackTask.setName(getProvideFeedbackTaskName());
feedbackTask.setAssignee("${" + VARIABLE_FEEDBACK_PROVIDER + "}");
MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = new MultiInstanceLoopCharacteristics();
multiInstanceLoopCharacteristics.setSequential(false);
multiInstanceLoopCharacteristics.setInputDataItem(VARIABLE_FEEDBACK_PROVIDERS);
multiInstanceLoopCharacteristics.setElementVariable(VARIABLE_FEEDBACK_PROVIDER);
feedbackTask.setLoopCharacteristics(multiInstanceLoopCharacteristics);
addFlowElement(conversion, feedbackTask);
processElements.put(FEEDBACK_USER_TASK, feedbackTask);
return feedbackTask;
}
protected BoundaryEvent createBoundarySignalCatch(WorkflowDefinitionConversion conversion, Signal signal, UserTask feedbackTask) {
BoundaryEvent boundarySignalCatch = new BoundaryEvent();
boundarySignalCatch.setId(conversion.getUniqueNumberedId(ConversionConstants.BOUNDARY_ID_PREFIX));
boundarySignalCatch.setAttachedToRef(feedbackTask);
boundarySignalCatch.setAttachedToRefId(feedbackTask.getId());
boundarySignalCatch.setCancelActivity(true);
addFlowElement(conversion, boundarySignalCatch);
SignalEventDefinition signalCatchEventDefinition = new SignalEventDefinition();
signalCatchEventDefinition.setSignalRef(signal.getId());
boundarySignalCatch.addEventDefinition(signalCatchEventDefinition);
return boundarySignalCatch;
}
protected ExclusiveGateway createMergingExclusiveGateway(WorkflowDefinitionConversion conversion) {
ExclusiveGateway mergingExclusiveGateway = new ExclusiveGateway();
mergingExclusiveGateway.setId(conversion.getUniqueNumberedId(ConversionConstants.GATEWAY_ID_PREFIX));
addFlowElement(conversion, mergingExclusiveGateway);
return mergingExclusiveGateway;
}
// The following are default task names and can be overidden by subclasses
protected String getSelectPeopleTaskName() {
return "Choose people";
}
protected String getProvideFeedbackTaskName() {
return "Provide feedback";
}
protected String getGatherFeedbackTaskName() {
return "Gather feedback";
}
}