/**
* Copyright (C) 2015 Orange
* 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 com.francetelecom.clara.cloud.paas.activation.v1;
import com.francetelecom.clara.cloud.model.TechnicalDeploymentInstance;
import com.francetelecom.clara.cloud.model.XaasSubscription;
import com.francetelecom.clara.cloud.paas.activation.ActivationStepEnum;
import com.francetelecom.clara.cloud.paas.activation.v1.ActivitiProcessUtils.NodeTask;
import org.activiti.bpmn.model.*;
import org.activiti.bpmn.model.Process;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.xml.bind.JAXBException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Activiti process factory
*/
public class ActivitiProcessFactory {
private static Logger logger = LoggerFactory.getLogger(ActivitiProcessFactory.class.getName());
static final String TASK_COUNT_KEY = "[TCK]";
static final String NODE_SUFFIX_CONDITIONNAL = "-okornot";
@Autowired
private ActivationPluginStrategy pluginStrategy;
private boolean canParrallel = false;
protected Process createActivateProcess(final TechnicalDeploymentInstance tdi) throws JAXBException {
// Creates the root element
Process process = new Process();
process.setId("TDI-" + tdi.getId() + "-" + System.currentTimeMillis());
process.setName("Activate virtual applicance " + tdi.getTechnicalDeployment().getName());
process.setExecutable(Boolean.TRUE);
// Start node
StartEvent start = new StartEvent();
start.setId("start");
process.addFlowElement(start);
// End node
EndEvent end = new EndEvent();
end.setId("end");
process.addFlowElement(end);
ParallelGateway afterStartGw = new ParallelGateway();
afterStartGw.setId("afterStartGw");
afterStartGw.getIncomingFlows().add(new SequenceFlow(start.getId(), afterStartGw.getId()));
ParallelGateway middleGw = new ParallelGateway();
middleGw.setId("middleGw");
ParallelGateway middleBisGw = new ParallelGateway();
middleBisGw.setId("middleBisGw");
ParallelGateway beforeEndGw = new ParallelGateway();
beforeEndGw.setId("beforeEndGw");
beforeEndGw.getOutgoingFlows().add(new SequenceFlow(beforeEndGw.getId(), end.getId()));
int taskCount = generateProcessServerService(tdi, ActivationStepEnum.INIT.getName(), process, afterStartGw, middleGw, ActivationStepEnum.INIT, 1);
taskCount += generateProcessServerService(tdi, ActivationStepEnum.ACTIVATE.getName(), process, middleGw, middleBisGw, ActivationStepEnum.ACTIVATE, taskCount + 1);
taskCount += generateProcessServerService(tdi, ActivationStepEnum.FIRSTSTART.getName(), process, middleBisGw, beforeEndGw, ActivationStepEnum.FIRSTSTART, taskCount + 1);
process.addFlowElement(afterStartGw);
process.addFlowElement(beforeEndGw);
process.addFlowElement(middleGw);
process.addFlowElement(middleBisGw);
ActivitiProcessUtils.simplifyProcess(process, taskCount);
return process;
}
private Process createVappProcess(final TechnicalDeploymentInstance tdi, ActivationStepEnum processType) throws JAXBException {
// Creates the root element
//ProcessDefinition definitions = new ProcessDefinitionEntity();
//definitions.setTargetNamespace("Activation");
Process process = new Process();
process.setId("TDI-" + tdi.getId() + "-" + System.currentTimeMillis());
process.setName("Start virtual applicance " + tdi.getName());
//definitions.getRootElement().add(factory.createProcess(process));
// Start node
StartEvent start = new StartEvent();
start.setId("start");
process.addFlowElement(start);
// End node
EndEvent end = new EndEvent();
end.setId("end");
process.addFlowElement(end);
ParallelGateway afterStartGw = new ParallelGateway();
afterStartGw.setId("afterStartGw");
afterStartGw.getIncomingFlows().add(new SequenceFlow(start.getId(), afterStartGw.getId()));
ParallelGateway beforeEndGw = new ParallelGateway();
beforeEndGw.setId("beforeEndGw");
beforeEndGw.getOutgoingFlows().add(new SequenceFlow(beforeEndGw.getId(), end.getId()));
int taskCount;
if (processType == ActivationStepEnum.ACTIVATE || processType == ActivationStepEnum.START) {
taskCount = generateProcessServerService(tdi, processType.getName(), process, afterStartGw, beforeEndGw, processType, 1);
} else {
taskCount = generateProcessServerServiceReverse(tdi, processType.getName(), process, afterStartGw, beforeEndGw, processType, 1);
}
process.addFlowElement(afterStartGw);
process.addFlowElement(beforeEndGw);
ActivitiProcessUtils.simplifyProcess(process, taskCount);
return process;
}
/**
* @param processType
* @param tdi
* @return
* @throws JAXBException
* @throws IOException
*/
Process generateProcessFromTDI(ActivationStepEnum processType, TechnicalDeploymentInstance tdi) throws JAXBException, IOException {
// Creates the root element
//TDefinitions definitions = factory.createTDefinitions();
//definitions.setTargetNamespace("Activation");
Process process;
if (processType == ActivationStepEnum.ACTIVATE) {
process = createActivateProcess(tdi);
} else {
process = createVappProcess(tdi, processType);
}
addBoundaryErrorEvent(tdi, process, processType);
//definitions.getRootElement().add(factory.createProcess(process));
/*
writeProcess(definitions, outputFile);
boolean dumpWorkflowXml = false;
if (dumpWorkflowXml) {
logger.debug("*** workflow bpmn20 XML :\n{}", FileUtils.readFileToString(outputFile));
}
logger.debug("*** workflow id=" + process.getId() + " is done***");
return process.getId();
*/
return process;
}
/**
* Generate BPMN 2.0 XML
* @param definitions Java representation of process
* @param outputFile File to write
*/
/*
void writeProcess(TDefinitions definitions, File outputFile) throws JAXBException {
ObjectFactory factory = new ObjectFactory();
JAXBContext jc = JAXBContext.newInstance( "com.francetelecom.clara.cloud.paas.activation.bpmn20" );
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.marshal( factory.createDefinitions(definitions), outputFile);
}
*/
/**
* Transform the process into a subprocess so errors can be catched and redirected to
* a failure task.
*
* @param tdi The TDI
* @param process The process
*/
void addBoundaryErrorEvent(final TechnicalDeploymentInstance tdi, Process process, ActivationStepEnum processType) {
// Subprocess
SubProcess subprocess = new SubProcess();
subprocess.setId("sub-" + process.getId());
subprocess.getFlowElements().addAll(process.getFlowElements());
process.getFlowElements().clear();
process.addFlowElement(subprocess);
// Start node
StartEvent start = new StartEvent();
start.setId("startglobal");
process.addFlowElement(start);
// End node
EndEvent end = new EndEvent();
end.setId("endglobal");
process.addFlowElement(end);
// End error node
EndEvent enderror = new EndEvent();
enderror.setId("enderror");
process.addFlowElement(enderror);
// Boundary (catch)
BoundaryEvent boundary = new BoundaryEvent();
boundary.setId("catch-error");
boundary.setAttachedToRefId(subprocess.getId());
boundary.setAttachedToRef(subprocess);
ErrorEventDefinition failureEvent = new ErrorEventDefinition();
failureEvent.setErrorCode("failure");
boundary.getEventDefinitions().add(failureEvent);
process.addFlowElement(boundary);
// Create the service task that manage the failure
ServiceTask failureTask = new ServiceTask();
failureTask.setId("failureTask");
failureTask.setName("Process failure");
//failureTask.setExpression("#{wrapper.execute(activationStep, '"+failureTask.getId()+"', entityId, entityClass, errMessage)}");
failureTask.setImplementation("#{wrapper.failed(execution.processInstanceId, '" + processType.getName() + "', '" + tdi.getId() + "', errMessage)}");
failureTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_EXPRESSION);
process.addFlowElement(failureTask);
// Create the service task that manage the success
ServiceTask successTask = new ServiceTask();
successTask.setId("successTask");
successTask.setName("Process success");
successTask.setImplementation("#{wrapper.success(execution.processInstanceId, '" + processType.getName() + "', '" + tdi.getId() + "')}");
successTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_EXPRESSION);
process.addFlowElement(successTask);
// Link : start -> subprocess
SequenceFlow start2sub = new SequenceFlow();
start2sub.setId("start2sub");
start2sub.setSourceRef(start.getId());
start2sub.setTargetRef(subprocess.getId());
process.addFlowElement(start2sub);
// Link : subprocess -> successTask
SequenceFlow sub2successTask = new SequenceFlow();
sub2successTask.setId("sub2successTask");
sub2successTask.setSourceRef(subprocess.getId());
sub2successTask.setTargetRef(successTask.getId());
process.addFlowElement(sub2successTask);
// Link : successTask -> end
SequenceFlow successTask2end = new SequenceFlow();
successTask2end.setId("successTask2end");
successTask2end.setSourceRef(successTask.getId());
successTask2end.setTargetRef(end.getId());
process.addFlowElement(successTask2end);
// Link : boundary -> failureTask
SequenceFlow catch2failed = new SequenceFlow();
catch2failed.setId("catch2failed");
catch2failed.setSourceRef(boundary.getId());
catch2failed.setTargetRef(failureTask.getId());
process.addFlowElement(catch2failed);
// Link : failureTask -> endError
SequenceFlow failed2end = new SequenceFlow();
failed2end.setId("failed2end");
failed2end.setSourceRef(failureTask.getId());
failed2end.setTargetRef(enderror.getId());
process.addFlowElement(failed2end);
}
private int generateProcessServerService(final TechnicalDeploymentInstance tdi, String serviceName, Process process, ParallelGateway startGW, ParallelGateway endGW, ActivationStepEnum activationStep, int taskStartIndex) throws JAXBException {
// Compute Server dependancies
Map<String, NodeTask> nodes = new HashMap<String, NodeTask>();
Map<String, Set<NodeTask>> cache = new HashMap<String, Set<NodeTask>>();
for (XaasSubscription subs : tdi.getTechnicalDeployment().listXaasSubscriptionTemplates()) {
ActivitiProcessUtils.addService(nodes, cache, subs, canParrallel, activationStep, pluginStrategy);
}
ActivitiProcessUtils.logSequence(nodes, activationStep);
int index = taskStartIndex;
for (NodeTask node : nodes.values()) {
generateTask(tdi, serviceName, process, node, false, index);
index++;
}
HashMap<String, FlowElement> elements = new HashMap<String, FlowElement>();
for (FlowElement flowElement : process.getFlowElements()) {
elements.put(flowElement.getId(), flowElement);
}
for (NodeTask node : nodes.values()) {
for (NodeTask nextNode : node.dependOnMe) {
SequenceFlow link = new SequenceFlow();
link.setId(node.outputId + "-to-" + nextNode.inputId);
link.setSourceRef(elements.get(node.outputId).getId());
link.setTargetRef(elements.get(nextNode.inputId).getId());
if (node.outputId.endsWith(NODE_SUFFIX_CONDITIONNAL)) {
link.setConditionExpression("${errCode == 0}");
}
process.addFlowElement(link);
}
if (node.dependOn.size() == 0) {
// There is no dependancy so link to the start node
startGW.getOutgoingFlows().add(new SequenceFlow(startGW.getId(), node.inputId));
}
if (node.dependOnMe.size() == 0) {
// No server depends on this server so link to the end node
endGW.getIncomingFlows().add(new SequenceFlow(node.outputId, endGW.getId()));
}
}
return nodes.size();
}
private int generateProcessServerServiceReverse(final TechnicalDeploymentInstance tdi, String serviceName, Process process, ParallelGateway startGW, ParallelGateway endGW, ActivationStepEnum activationStep, int taskStartIndex) throws JAXBException {
// Compute Server dependancies
HashMap<String, NodeTask> nodes = new HashMap<String, NodeTask>();
Map<String, Set<NodeTask>> cache = new HashMap<String, Set<NodeTask>>();
for (XaasSubscription subs : tdi.getTechnicalDeployment().listXaasSubscriptionTemplates()) {
ActivitiProcessUtils.addService(nodes, cache, subs, canParrallel, activationStep, pluginStrategy);
}
ActivitiProcessUtils.logSequence(nodes, activationStep);
int index = taskStartIndex;
for (NodeTask node : nodes.values()) {
generateTask(tdi, serviceName, process, node, true, index);
index++;
}
for (NodeTask node : nodes.values()) {
for (NodeTask nextNode : node.dependOn) {
SequenceFlow link = new SequenceFlow();
link.setId(node.outputId + "-to-" + nextNode.inputId);
link.setSourceRef(node.outputId);
link.setTargetRef(nextNode.inputId);
if (nextNode.outputId.endsWith(NODE_SUFFIX_CONDITIONNAL)) {
link.setConditionExpression("${errCode == 0}");
}
process.addFlowElement(link);
}
if (node.dependOnMe.size() == 0) {
// There is no dependancy so link to the start node
startGW.getOutgoingFlows().add(new SequenceFlow(startGW.getId(), node.inputId));
}
if (node.dependOn.size() == 0) {
// No server depends on this server so link to the end node
endGW.getIncomingFlows().add(new SequenceFlow(node.outputId, endGW.getId()));
}
}
return nodes.size();
}
private void generateTask(final TechnicalDeploymentInstance tdi, String serviceName, Process process, NodeTask node, boolean reverse, int indexTask) {
logger.debug("Generating task " + serviceName + " for node " + node.item.getClass().getSimpleName() + "#" + node.item.getId() + " in=" + node.dependOn.size() + "out=" + node.dependOnMe.size());
// Create the service task
ServiceTask task = new ServiceTask();
task.setId(ActivitiProcessUtils.generateId(serviceName, node));
task.setName(serviceName);
// Item task
task.setName(serviceName + " " + node.item.getClass().getSimpleName());
task.setImplementation("${wrapper.execute(execution.processInstanceId, '" + serviceName + "', '" + task.getId() + "', " + tdi.getId() + ", " + node.item.getId() + ", '" + node.item.getClass().getName() + "', " + indexTask + ", " + TASK_COUNT_KEY + ")}");
task.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_EXPRESSION);
process.addFlowElement(task);
// Create the task that will wait until task is finished correctly or not
ReceiveTask receiveTask = new ReceiveTask();
receiveTask.setId(task.getId() + "-end");
process.addFlowElement(receiveTask);
// Link : ServiceTask -> ReceiveTask
SequenceFlow task2wait = new SequenceFlow();
task2wait.setId(task.getId() + "2wait");
task2wait.setSourceRef(task.getId());
task2wait.setTargetRef(receiveTask.getId());
process.addFlowElement(task2wait);
// ExclusiveGateway : success or error?
ExclusiveGateway succesOrError = new ExclusiveGateway();
succesOrError.setId(task.getId() + NODE_SUFFIX_CONDITIONNAL);
process.addFlowElement(succesOrError);
// Link : ReceiveTask -> ExclusiveGateway
SequenceFlow wait2test = new SequenceFlow();
wait2test.setId(task.getId() + "2test");
wait2test.setSourceRef(receiveTask.getId());
wait2test.setTargetRef(succesOrError.getId());
process.addFlowElement(wait2test);
// End node (error)
EndEvent endError = new EndEvent();
endError.setId(task.getId() + "-error-event");
ErrorEventDefinition errEvent = new ErrorEventDefinition();
errEvent.setErrorCode("failure");
endError.addEventDefinition(errEvent);
process.addFlowElement(endError);
// Link : error
SequenceFlow errorLink = new SequenceFlow();
errorLink.setId(task.getId() + "-ko-link");
errorLink.setSourceRef(succesOrError.getId());
errorLink.setTargetRef(endError.getId());
errorLink.setConditionExpression("${errCode > 0}");
process.addFlowElement(errorLink);
boolean multipleInput = node.dependOn.size() > 1;
boolean multipleOutput = node.dependOnMe.size() > 1;
if (reverse) {
multipleInput = node.dependOnMe.size() > 1;
multipleOutput = node.dependOn.size() > 1;
}
if (multipleOutput) {
// Parallele gateway : success
ParallelGateway succesNode = new ParallelGateway();
succesNode.setId(task.getId() + "-ok");
process.addFlowElement(succesNode);
// Link: success (with multiple output nodes)
SequenceFlow successLink = new SequenceFlow();
successLink.setId(task.getId() + "-ok-link");
successLink.setSourceRef(succesOrError.getId());
successLink.setTargetRef(succesNode.getId());
successLink.setConditionExpression("${errCode == 0}");
process.addFlowElement(successLink);
// Set the outputId of the node, here the parallele gateway
node.outputId = succesNode.getId();
} else {
// Set the outputId of the node, here the exclusive gateway
node.outputId = succesOrError.getId();
}
if (multipleInput) {
// Parallele gateway : input
ParallelGateway inputNode = new ParallelGateway();
inputNode.setId(task.getId() + "-in");
process.addFlowElement(inputNode);
// Link: in -> task (with multiple input nodes)
SequenceFlow inputLink = new SequenceFlow();
inputLink.setId(task.getId() + "-in-link");
inputLink.setSourceRef(inputNode.getId());
inputLink.setTargetRef(task.getId());
process.addFlowElement(inputLink);
// Set the inputId of the node, here the parallele gateway
node.inputId = inputNode.getId();
} else {
// Set the inputId of the node, here the service task
node.inputId = task.getId();
}
}
public void setCanParrallel(boolean canParrallel) {
this.canParrallel = canParrallel;
}
}