/* 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.kickstart.service;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import org.activiti.kickstart.bpmn20.model.Definitions;
import org.activiti.kickstart.bpmn20.model.Documentation;
import org.activiti.kickstart.bpmn20.model.FlowElement;
import org.activiti.kickstart.bpmn20.model.FormalExpression;
import org.activiti.kickstart.bpmn20.model.Process;
import org.activiti.kickstart.bpmn20.model.activity.resource.HumanPerformer;
import org.activiti.kickstart.bpmn20.model.activity.resource.PotentialOwner;
import org.activiti.kickstart.bpmn20.model.activity.resource.ResourceAssignmentExpression;
import org.activiti.kickstart.bpmn20.model.activity.type.ScriptTask;
import org.activiti.kickstart.bpmn20.model.activity.type.ServiceTask;
import org.activiti.kickstart.bpmn20.model.activity.type.UserTask;
import org.activiti.kickstart.bpmn20.model.bpmndi.BPMNDiagram;
import org.activiti.kickstart.bpmn20.model.bpmndi.BPMNPlane;
import org.activiti.kickstart.bpmn20.model.connector.SequenceFlow;
import org.activiti.kickstart.bpmn20.model.event.EndEvent;
import org.activiti.kickstart.bpmn20.model.event.StartEvent;
import org.activiti.kickstart.bpmn20.model.extension.ExtensionElements;
import org.activiti.kickstart.bpmn20.model.extension.activiti.ActivitFieldExtensionElement;
import org.activiti.kickstart.bpmn20.model.extension.activiti.ActivitiFormProperty;
import org.activiti.kickstart.bpmn20.model.gateway.ParallelGateway;
import org.activiti.kickstart.dto.KickstartFormProperty;
import org.activiti.kickstart.dto.KickstartMailTask;
import org.activiti.kickstart.dto.KickstartMailTask.Field;
import org.activiti.kickstart.dto.KickstartScriptTask;
import org.activiti.kickstart.dto.KickstartServiceTask;
import org.activiti.kickstart.dto.KickstartTask;
import org.activiti.kickstart.dto.KickstartTaskBlock;
import org.activiti.kickstart.dto.KickstartUserTask;
import org.activiti.kickstart.dto.KickstartWorkflow;
import org.activiti.kickstart.util.ExpressionUtil;
/**
*
* @author jbarrez
*/
public class MarshallingServiceImpl implements Bpmn20MarshallingService {
public String marshallWorkflow(KickstartWorkflow kickstartWorkflow) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Definitions.class);
Marshaller marshaller = jaxbContext.createMarshaller();
StringWriter writer = new StringWriter();
marshaller.marshal(convertToBpmn(kickstartWorkflow), writer);
return writer.toString();
} catch (JAXBException e) {
throw new RuntimeException("Could not marshal workflow", e);
}
}
/**
* Generate the JAXB version of this workflow.
*
* Extremely important: the flowelements are added in topological order,
* from left to right and top to bottom.
*/
public Definitions convertToBpmn(KickstartWorkflow kickstartWorkflow) {
Definitions definitions = new Definitions();
definitions.setTargetNamespace("kickstart");
String processName = kickstartWorkflow.getName().replace(" ", "_");
// Process
org.activiti.kickstart.bpmn20.model.Process process = new org.activiti.kickstart.bpmn20.model.Process();
process.setId(kickstartWorkflow.getId());
process.setName(kickstartWorkflow.getName());
process.setExecutable(true);
Documentation processDocumentation = new Documentation();
processDocumentation.setId(process.getId() + "_documentation");
processDocumentation.setText(kickstartWorkflow.getDescription());
process.getDocumentation().add(processDocumentation);
definitions.getRootElement().add(process);
// BPMNDiagram
BPMNDiagram diagram = new BPMNDiagram();
diagram.setId(processName + "_diagram");
definitions.getDiagram().add(diagram);
BPMNPlane plane = new BPMNPlane();
plane.setId(processName + "_plane");
plane.setBpmnElement(process);
diagram.setBPMNPlane(plane);
// Start
StartEvent startEvent = new StartEvent();
startEvent.setId(KickstartWorkflow.START_NAME);
// Initiator
String initiatorName = "ks_initiator";
startEvent.setInitiator(initiatorName);
// Give all user tasks that use 'initiator' the same assignee
for (KickstartTask kickstartTask : kickstartWorkflow.getTasks()) {
if (kickstartTask instanceof KickstartUserTask) {
KickstartUserTask kickstartUserTask = (KickstartUserTask) kickstartTask;
if (kickstartUserTask.isAssigneeInitiator()) {
kickstartUserTask.setAssignee("${" + initiatorName + "}");
}
}
}
// TODO: For now, fixed start-task is used instead of adhoc-created one.
startEvent.setFormKey("wf:submitAdhocTask"); // used to be ks:genericStartTask (joram)
// TODO: end of hack by frederik :-)
process.getFlowElement().add(startEvent);
// We'll group tasks by each 'task block' that is to be executed in parallel
List<List<FlowElement>> TaskBlockList = new ArrayList<List<FlowElement>>();
int index = 1;
List<KickstartTaskBlock> taskBlocks = kickstartWorkflow.getTaskBlocks();
for (KickstartTaskBlock taskBlock : taskBlocks) {
List<FlowElement> TaskBlock = new ArrayList<FlowElement>();
TaskBlockList.add(TaskBlock);
for (KickstartTask kickstartTask : taskBlock.getTasks()) {
FlowElement generatedTask = convertToBPMN(kickstartTask);
generatedTask.setId("task_" + index++);
generatedTask.setName(kickstartTask.getName());
// TODO: disabled until I figure out how to do CDATA with JaxB
// // Description
// if (kickstartTask.getDescription() != null) {
// Documentation taskDocumentation = new Documentation(ExpressionUtil.replaceWhiteSpaces(kickstartTask.getDescription()));
// taskDocumentation.setId(generatedTask.getId() + "_documentation");
// generatedTask.getDocumentation().add(taskDocumentation);
// }
// process.getFlowElement().add(userTask);
TaskBlock.add(generatedTask);
}
}
// Sequence flow generation
AtomicInteger flowIndex = new AtomicInteger(1); // Hacky hacky, Integer doesnt have an increment() function ... lazy me
AtomicInteger gatewayIndex = new AtomicInteger(1);
List<FlowElement> lastFlowElementOfBlockStack = new ArrayList<FlowElement>();
lastFlowElementOfBlockStack.add(startEvent);
// All tasks blocks
for (int i = 0; i < taskBlocks.size(); i++) {
convertTaskBlockToBpmn20(process, flowIndex, gatewayIndex, TaskBlockList.get(i), lastFlowElementOfBlockStack);
}
// End
EndEvent endEvent = new EndEvent();
endEvent.setId(KickstartWorkflow.END_NAME);
process.getFlowElement().add(endEvent);
// Seq flow lastTask -> end
createSequenceFlow(process, flowIndex, getLast(lastFlowElementOfBlockStack), endEvent);
return definitions;
}
public FlowElement convertToBPMN(KickstartTask kickstartTask) {
if (kickstartTask instanceof KickstartUserTask) {
return convertToBPMN((KickstartUserTask) kickstartTask);
} else if (kickstartTask instanceof KickstartServiceTask) {
return convertToBPMN((KickstartServiceTask) kickstartTask);
} else if (kickstartTask instanceof KickstartScriptTask) {
return convertToBPMN((KickstartScriptTask) kickstartTask);
} else if (kickstartTask instanceof KickstartMailTask) {
return convertToBPMN((KickstartMailTask) kickstartTask);
} else {
throw new RuntimeException("Unknown task type: " + kickstartTask.getClass());
}
}
public UserTask convertToBPMN(KickstartUserTask kickstartUserTask) {
UserTask userTask = new UserTask();
// assignee
if (kickstartUserTask.getAssignee() != null && !"".equals(kickstartUserTask.getAssignee())) {
HumanPerformer humanPerformer = new HumanPerformer();
humanPerformer.setId(userTask.getId() + "_humanPerformer");
ResourceAssignmentExpression assignmentExpression = new ResourceAssignmentExpression();
assignmentExpression.setId(userTask.getId() + "_humanPerformer_assignmentExpression");
FormalExpression formalExpression = new FormalExpression(kickstartUserTask.getAssignee());
formalExpression.setId(userTask.getId() + "_humanPerformer_formalExpressions");
assignmentExpression.setExpression(formalExpression);
humanPerformer.setResourceAssignmentExpression(assignmentExpression);
userTask.getActivityResource().add(humanPerformer);
}
// groups
if (kickstartUserTask.getGroups() != null && !"".equals(kickstartUserTask.getGroups())) {
PotentialOwner potentialOwner = new PotentialOwner();
potentialOwner.setId(userTask.getId() + "_potentialOwner");
ResourceAssignmentExpression assignmentExpression = new ResourceAssignmentExpression();
assignmentExpression.setId(userTask.getId() + "_potentialOwner_assignmentExpression");
StringBuilder groups = new StringBuilder();
for (String group : kickstartUserTask.getGroups().split(",")) {
groups.append(group + ",");
}
groups.deleteCharAt(groups.length() - 1);
FormalExpression formalExpression = new FormalExpression(groups.toString());
formalExpression.setId(userTask.getId() + "_potentialOwner_formalExpressions");
assignmentExpression.setExpression(formalExpression);
potentialOwner.setResourceAssignmentExpression(assignmentExpression);
userTask.getActivityResource().add(potentialOwner);
}
// form
if (kickstartUserTask.getForm() != null) {
userTask.setFormKey(kickstartUserTask.getForm().getFormKey());
List<ActivitiFormProperty> formProperties = new ArrayList<ActivitiFormProperty>();
for (KickstartFormProperty formPropertyDto : kickstartUserTask.getForm().getFormProperties()) {
ActivitiFormProperty formProperty = new ActivitiFormProperty();
formProperty.setId(formPropertyDto.getProperty());
formProperty.setName(formPropertyDto.getProperty());
formProperty.setRequired(formPropertyDto.isRequired() ? "true" : "false");
String dtoType = formPropertyDto.getType();
String type = "string";
if ("number".equals(dtoType)) {
type = "long";
} else if ("date".equals(dtoType)) {
type = "date";
}
formProperty.setType(type);
formProperties.add(formProperty);
}
if (formProperties.size() > 0) {
userTask.setExtensionElements(new ExtensionElements());
for (ActivitiFormProperty formProperty : formProperties) {
userTask.getExtensionElements().add(formProperty);
}
}
}
return userTask;
}
public ServiceTask convertToBPMN(KickstartServiceTask kickstartServiceTask) {
ServiceTask serviceTask = new ServiceTask();
serviceTask.setDelegateExpression(kickstartServiceTask.getDelegateExpression());
serviceTask.setClassName(kickstartServiceTask.getClassName());
serviceTask.setExpression(kickstartServiceTask.getExpression());
return serviceTask;
}
public ScriptTask convertToBPMN(KickstartScriptTask kickstartScriptTask) {
ScriptTask task = new ScriptTask();
task.setScriptFormat(kickstartScriptTask.getScriptFormat());
task.setScript(kickstartScriptTask.getScript());
task.setResultVariableName(kickstartScriptTask.getResultVariableName());
return task;
}
public ServiceTask convertToBPMN(KickstartMailTask kickstartMailTask) {
ServiceTask serviceTask = new ServiceTask();
serviceTask.setType("mail");
ExtensionElements extensionElements = new ExtensionElements();
addIfFilled(extensionElements, kickstartMailTask.getTo());
addIfFilled(extensionElements, kickstartMailTask.getFrom());
addIfFilled(extensionElements, kickstartMailTask.getSubject());
addIfFilled(extensionElements, kickstartMailTask.getCc());
addIfFilled(extensionElements, kickstartMailTask.getBcc());
addIfFilled(extensionElements, kickstartMailTask.getHtml());
addIfFilled(extensionElements, kickstartMailTask.getText());
serviceTask.setExtensionElements(extensionElements);
return serviceTask;
}
private void addIfFilled(ExtensionElements extenstionElements, Field fieldToAdd) {
ActivitFieldExtensionElement element = prepareExtensionElement(fieldToAdd);
if (element != null) {
extenstionElements.add(element);
}
}
private ActivitFieldExtensionElement prepareExtensionElement(Field field) {
if (field.getStringValue() == null && field.getExpression() == null) {
return null;
}
ActivitFieldExtensionElement extensionElement = new ActivitFieldExtensionElement();
extensionElement.setName(field.getName());
extensionElement.setStringValue(field.getStringValue());
extensionElement.setExpression(field.getExpression());
return extensionElement;
}
protected void convertTaskBlockToBpmn20(Process process, AtomicInteger flowIndex, AtomicInteger gatewayIndex,
List<FlowElement> taskBlock, List<FlowElement> lastFlowElementOfBlockStack) {
SequenceFlow sequenceFlow = createSequenceFlow(process, flowIndex, getLast(lastFlowElementOfBlockStack), null);
if (taskBlock.size() == 1) {
FlowElement userTask = taskBlock.get(0);
sequenceFlow.setTargetRef(userTask);
lastFlowElementOfBlockStack.add(userTask);
process.getFlowElement().add(userTask);
} else {
ParallelGateway fork = new ParallelGateway();
fork.setId("parallel_gateway_fork_" + gatewayIndex.getAndIncrement());
process.getFlowElement().add(fork);
sequenceFlow.setTargetRef(fork);
ParallelGateway join = new ParallelGateway();
join.setId("parallel_gateway_join" + gatewayIndex.getAndIncrement());
// sequence flow to each task of the task block from the parallel gateway and back to the join
for (FlowElement taskInBlock : taskBlock) {
createSequenceFlow(process, flowIndex, fork, taskInBlock);
createSequenceFlow(process, flowIndex, taskInBlock, join);
process.getFlowElement().add(taskInBlock);
}
process.getFlowElement().add(join);
lastFlowElementOfBlockStack.add(join);
}
}
// Helper methods ////////////////////////////////////////////////////////////////////////////////////
/**
* Returns the last {@link FlowElement} from the list of elements
*/
protected FlowElement getLast(List<FlowElement> elements) {
if (elements.size() > 0) {
return elements.get(elements.size() - 1);
}
return null;
}
protected SequenceFlow createSequenceFlow(Process process, AtomicInteger flowIndex, FlowElement sourceRef, FlowElement targetRef) {
SequenceFlow sequenceFlow = new SequenceFlow();
sequenceFlow.setId("flow_" + flowIndex.getAndIncrement());
sequenceFlow.setSourceRef(sourceRef);
sequenceFlow.setTargetRef(targetRef);
process.getFlowElement().add(sequenceFlow);
return sequenceFlow;
}
}