package com.constellio.model.services.workflows.bpmn;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
import com.constellio.model.entities.workflows.definitions.AllUsersSelector;
import com.constellio.model.entities.workflows.definitions.BPMNProperty;
import com.constellio.model.entities.workflows.definitions.GroupSelector;
import com.constellio.model.entities.workflows.definitions.RoleSelector;
import com.constellio.model.entities.workflows.definitions.UserSelector;
import com.constellio.model.entities.workflows.definitions.WorkflowAction;
import com.constellio.model.entities.workflows.definitions.WorkflowCondition;
import com.constellio.model.entities.workflows.definitions.WorkflowConfiguration;
import com.constellio.model.entities.workflows.definitions.WorkflowDefinition;
import com.constellio.model.entities.workflows.definitions.WorkflowRouting;
import com.constellio.model.entities.workflows.definitions.WorkflowRoutingDestination;
import com.constellio.model.entities.workflows.definitions.WorkflowServiceTask;
import com.constellio.model.entities.workflows.definitions.WorkflowTask;
import com.constellio.model.entities.workflows.definitions.WorkflowUserTask;
import com.constellio.model.entities.workflows.execution.WorkflowExecution;
import com.constellio.model.services.workflows.bpmn.BPMNParserRuntimeException.BPMNParserRuntimeException_InvalidCondition;
import com.constellio.model.services.workflows.general.WorkflowConditions;
import com.constellio.model.utils.InstanciationUtils;
public class BPMNParser {
public static final String SEQUENCE_FLOW = "sequenceFlow";
public static final String ID = "id";
public static final String USER_PREFIX = "user:";
public static final String GROUP_PREFIX = "group:";
public static final String ROLE_PREFIX = "role:";
private static final String EXCLUSIVE_GATEWAY = "exclusiveGateway";
Namespace activitiNamespace;
Document document;
Namespace namespace;
Map<String, String> mapping;
WorkflowConfiguration workflowConfiguration;
public BPMNParser(Document document, Map<String, String> mapping, WorkflowConfiguration workflowConfiguration) {
this.document = document;
this.mapping = mapping;
namespace = document.getRootElement().getNamespace();
activitiNamespace = document.getRootElement().getNamespace("activiti");
this.workflowConfiguration = workflowConfiguration;
}
public WorkflowDefinition build() {
Map<String, WorkflowTask> tasks = new HashMap<>();
Element rootElement = document.getRootElement().getChild("process", namespace);
Map<String, WorkflowRouting> routings = new HashMap<>();
for (Element gateway : rootElement.getChildren(EXCLUSIVE_GATEWAY, namespace)) {
routings.put(gateway.getAttributeValue(ID), null);
}
routings.put(WorkflowRoutingDestination.DESTINATION_START, startRouting());
Map<String, WorkflowRoutingDestination> routingDestinations = new HashMap<>();
for (Element sequenceFlow : rootElement.getChildren(SEQUENCE_FLOW, namespace)) {
String id = sequenceFlow.getAttributeValue(ID);
String sourceRef = sequenceFlow.getAttributeValue("sourceRef");
String targetRef = sequenceFlow.getAttributeValue("targetRef");
Element conditionElement = sequenceFlow.getChild("conditionExpression", namespace);
WorkflowRoutingDestination routingDestination;
if (conditionElement != null) {
routingDestination = createRoutingDestinationWithCondition(targetRef, sourceRef, conditionElement);
} else {
routingDestination = new WorkflowRoutingDestination(WorkflowConditions.directCondition(), targetRef, sourceRef);
}
if (routings.containsKey(sourceRef)) {
addDestinationToSourceRouting(routings, sourceRef, routingDestination);
}
routingDestinations.put(id, routingDestination);
}
parseServiceTasks(tasks, rootElement, routingDestinations);
parseUserTasks(tasks, rootElement, routingDestinations);
String collection = workflowConfiguration.getCollection();
return new WorkflowDefinition(workflowConfiguration.getId(), tasks, true, routings, collection);
}
private WorkflowRouting startRouting() {
return new WorkflowRouting(WorkflowRoutingDestination.DESTINATION_START);
}
private void parseUserTasks(Map<String, WorkflowTask> tasks, Element rootElement,
Map<String, WorkflowRoutingDestination> routingDestinations) {
for (Element userTaskElement : rootElement.getChildren("userTask", namespace)) {
String id = userTaskElement.getAttributeValue(ID);
String usersString = userTaskElement.getAttributeValue("candidateUsers", activitiNamespace);
String groupsString = userTaskElement.getAttributeValue("candidateGroups", activitiNamespace);
AllUsersSelector userSelector = parseUserSelector(usersString, groupsString);
List<BPMNProperty> fields = parseFields(userTaskElement);
String taskSchema = userTaskElement.getAttributeValue("formKey", activitiNamespace);
int dueDate = Integer.parseInt(userTaskElement.getAttributeValue("dueDate", activitiNamespace));
tasks.put(id,
new WorkflowUserTask(id, taskSchema, getRoutingsForTaskId(routingDestinations, id), userSelector, dueDate,
fields));
}
}
private List<BPMNProperty> parseFields(Element userTaskElement) {
List<BPMNProperty> fields = new ArrayList<>();
for (Element propertyElement : userTaskElement.getChild("extensionElements", namespace)
.getChildren("formProperty", activitiNamespace)) {
String id = propertyElement.getAttributeValue(ID);
String variableCode = propertyElement.getAttributeValue("variable");
String expressionValue = propertyElement.getAttributeValue("expression");
fields.add(new BPMNProperty(id, expressionValue, variableCode));
}
return fields;
}
private AllUsersSelector parseUserSelector(String usersString, String groupsString) {
List<String> parsedUsers = new ArrayList<>();
List<String> parsedGroups = new ArrayList<>();
List<String> parsedRoles = new ArrayList<>();
if (usersString != null) {
for (String username : usersString.split(",")) {
if (username.startsWith(USER_PREFIX)) {
if (StringUtils.substringAfter(username, USER_PREFIX).startsWith("${")) {
parsedUsers.add(StringUtils.substringAfter(username, USER_PREFIX));
} else {
parsedUsers.add(mapping.get(username));
}
} else {
parsedUsers.add(username);
}
}
}
for (String groupName : groupsString.split(",")) {
if (groupName.startsWith(GROUP_PREFIX)) {
parsedGroups.add(StringUtils.substringAfter(groupName, GROUP_PREFIX));
} else if (groupName.startsWith(ROLE_PREFIX)) {
if (StringUtils.substringAfter(groupName, ROLE_PREFIX).startsWith("${")) {
parsedRoles.add(StringUtils.substringAfter(groupName, ROLE_PREFIX));
} else {
parsedRoles.add(mapping.get(groupName));
}
}
}
return new AllUsersSelector(new RoleSelector(parsedRoles), new GroupSelector(parsedGroups),
new UserSelector(parsedUsers));
}
private void parseServiceTasks(Map<String, WorkflowTask> tasks, Element rootElement,
Map<String, WorkflowRoutingDestination> routingDestinations) {
for (Element serviceTask : rootElement.getChildren("serviceTask", namespace)) {
String id = serviceTask.getAttributeValue(ID);
String className = serviceTask.getAttributeValue("class", activitiNamespace);
WorkflowAction action = (WorkflowAction) new InstanciationUtils().instanciate(className);
List<WorkflowRoutingDestination> taskRoutings = getRoutingsForTaskId(routingDestinations, id);
tasks.put(id, new WorkflowServiceTask(id, action, taskRoutings));
}
}
private List<WorkflowRoutingDestination> getRoutingsForTaskId(Map<String, WorkflowRoutingDestination> routingDestinations,
String id) {
List<WorkflowRoutingDestination> taskRoutings = new ArrayList<>();
for (WorkflowRoutingDestination routingDestination : routingDestinations.values()) {
if (routingDestination.getSource().equals(id)) {
taskRoutings.add(routingDestination);
}
}
return taskRoutings;
}
private void addDestinationToSourceRouting(Map<String, WorkflowRouting> routings, String sourceRef,
WorkflowRoutingDestination routingDestination) {
if (routings.get(sourceRef) != null) {
routings.get(sourceRef).addDestination(routingDestination);
} else {
WorkflowRouting workflowRouting = new WorkflowRouting(sourceRef);
workflowRouting.addDestination(routingDestination);
routings.put(sourceRef, workflowRouting);
}
}
private WorkflowRoutingDestination createRoutingDestinationWithCondition(String targetRef, String sourceRef,
Element conditionElement) {
WorkflowCondition condition;
String conditionValue = conditionElement.getText();
String extractedCondition = StringUtils.substringBetween(conditionValue, "${", "}");
if (extractedCondition.contains("==")) {
final String[] parsedCondition = extractedCondition.split("==");
condition = new WorkflowCondition() {
@Override
public boolean isTrue(WorkflowExecution execution) {
return execution.getVariable(parsedCondition[0].trim()).equals(parsedCondition[1].trim().replace("\"", ""));
}
};
} else if (extractedCondition.contains("!=")) {
final String[] parsedCondition = extractedCondition.split("!=");
condition = new WorkflowCondition() {
@Override
public boolean isTrue(WorkflowExecution execution) {
return !execution.getVariable(parsedCondition[0].trim()).equals(parsedCondition[1].trim().replace("\"", ""));
}
};
} else {
throw new BPMNParserRuntimeException_InvalidCondition(extractedCondition);
}
return new WorkflowRoutingDestination(condition, targetRef, sourceRef);
}
}