/* 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.bpmn.converter;
import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import org.activiti.bpmn.constants.BpmnXMLConstants;
import org.activiti.bpmn.converter.child.DocumentationParser;
import org.activiti.bpmn.converter.child.ExecutionListenerParser;
import org.activiti.bpmn.converter.child.MultiInstanceParser;
import org.activiti.bpmn.converter.parser.BpmnEdgeParser;
import org.activiti.bpmn.converter.parser.BpmnShapeParser;
import org.activiti.bpmn.converter.parser.SubProcessParser;
import org.activiti.bpmn.exceptions.XMLException;
import org.activiti.bpmn.model.Activity;
import org.activiti.bpmn.model.AssociationModel;
import org.activiti.bpmn.model.BaseElement;
import org.activiti.bpmn.model.BoundaryEvent;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.Event;
import org.activiti.bpmn.model.EventDefinition;
import org.activiti.bpmn.model.EventSubProcess;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.GraphicInfo;
import org.activiti.bpmn.model.Lane;
import org.activiti.bpmn.model.Message;
import org.activiti.bpmn.model.MessageEventDefinition;
import org.activiti.bpmn.model.Pool;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.bpmn.model.Signal;
import org.activiti.bpmn.model.SignalEventDefinition;
import org.activiti.bpmn.model.SubProcess;
import org.apache.commons.lang.StringUtils;
/**
* @author Tijs Rademakers
*/
public class BpmnXMLConverter implements BpmnXMLConstants {
protected static final Logger LOGGER = Logger.getLogger(BpmnXMLConverter.class.getName());
public List<AssociationModel> associationModels = new ArrayList<AssociationModel>();
public Map<String, GraphicInfo> labelLocationMap = new HashMap<String, GraphicInfo>();
private static Map<String, Class<? extends BaseBpmnXMLConverter>> convertersToBpmnMap =
new HashMap<String, Class<? extends BaseBpmnXMLConverter>>();
private static Map<Class<? extends BaseElement>, Class<? extends BaseBpmnXMLConverter>> convertersToXMLMap =
new HashMap<Class<? extends BaseElement>, Class<? extends BaseBpmnXMLConverter>>();
static {
// events
addConverter(EndEventXMLConverter.getXMLType(), EndEventXMLConverter.getBpmnElementType(), EndEventXMLConverter.class);
addConverter(StartEventXMLConverter.getXMLType(), StartEventXMLConverter.getBpmnElementType(), StartEventXMLConverter.class);
// tasks
addConverter(BusinessRuleTaskXMLConverter.getXMLType(), BusinessRuleTaskXMLConverter.getBpmnElementType(), BusinessRuleTaskXMLConverter.class);
addConverter(ManualTaskXMLConverter.getXMLType(), ManualTaskXMLConverter.getBpmnElementType(), ManualTaskXMLConverter.class);
addConverter(ReceiveTaskXMLConverter.getXMLType(), ReceiveTaskXMLConverter.getBpmnElementType(), ReceiveTaskXMLConverter.class);
addConverter(ScriptTaskXMLConverter.getXMLType(), ScriptTaskXMLConverter.getBpmnElementType(), ScriptTaskXMLConverter.class);
addConverter(ServiceTaskXMLConverter.getXMLType(), ServiceTaskXMLConverter.getBpmnElementType(), ServiceTaskXMLConverter.class);
addConverter(UserTaskXMLConverter.getXMLType(), UserTaskXMLConverter.getBpmnElementType(), UserTaskXMLConverter.class);
addConverter(CallActivityXMLConverter.getXMLType(), CallActivityXMLConverter.getBpmnElementType(), CallActivityXMLConverter.class);
// gateways
addConverter(EventGatewayXMLConverter.getXMLType(), EventGatewayXMLConverter.getBpmnElementType(), EventGatewayXMLConverter.class);
addConverter(ExclusiveGatewayXMLConverter.getXMLType(), ExclusiveGatewayXMLConverter.getBpmnElementType(), ExclusiveGatewayXMLConverter.class);
addConverter(InclusiveGatewayXMLConverter.getXMLType(), InclusiveGatewayXMLConverter.getBpmnElementType(), InclusiveGatewayXMLConverter.class);
addConverter(ParallelGatewayXMLConverter.getXMLType(), ParallelGatewayXMLConverter.getBpmnElementType(), ParallelGatewayXMLConverter.class);
// connectors
addConverter(SequenceFlowXMLConverter.getXMLType(), SequenceFlowXMLConverter.getBpmnElementType(), SequenceFlowXMLConverter.class);
// catch, throw and boundary event
addConverter(CatchEventXMLConverter.getXMLType(), CatchEventXMLConverter.getBpmnElementType(), CatchEventXMLConverter.class);
addConverter(ThrowEventXMLConverter.getXMLType(), ThrowEventXMLConverter.getBpmnElementType(), ThrowEventXMLConverter.class);
addConverter(BoundaryEventXMLConverter.getXMLType(), BoundaryEventXMLConverter.getBpmnElementType(), BoundaryEventXMLConverter.class);
}
private static void addConverter(String elementName, Class<? extends BaseElement> elementClass,
Class<? extends BaseBpmnXMLConverter> converter) {
convertersToBpmnMap.put(elementName, converter);
convertersToXMLMap.put(elementClass, converter);
}
public BpmnModel convertToBpmnModel(XMLStreamReader xtr) {
BpmnModel model = new BpmnModel();
try {
boolean processExtensionAvailable = false;
Process activeProcess = null;
List<SubProcess> activeSubProcessList = new ArrayList<SubProcess>();
while (xtr.hasNext()) {
try {
xtr.next();
} catch(Exception e) {
LOGGER.log(Level.SEVERE, "Error reading XML document", e);
return model;
}
if (xtr.isEndElement() && ELEMENT_SUBPROCESS.equalsIgnoreCase(xtr.getLocalName())) {
activeSubProcessList.remove(activeSubProcessList.size() - 1);
}
if (xtr.isStartElement() == false)
continue;
if ("definitions".equalsIgnoreCase(xtr.getLocalName())) {
model.setTargetNamespace(xtr.getAttributeValue(null, TARGET_NAMESPACE_ATTRIBUTE));
} else if (ELEMENT_SIGNAL.equalsIgnoreCase(xtr.getLocalName())) {
if (StringUtils.isNotEmpty(xtr.getAttributeValue(null, ATTRIBUTE_ID))) {
String signalId = xtr.getAttributeValue(null, ATTRIBUTE_ID);
String signalName = xtr.getAttributeValue(null, ATTRIBUTE_NAME);
model.addSignal(signalId, signalName);
}
} else if (ELEMENT_MESSAGE.equalsIgnoreCase(xtr.getLocalName())) {
if (StringUtils.isNotEmpty(xtr.getAttributeValue(null, ATTRIBUTE_ID))) {
String messageId = xtr.getAttributeValue(null, ATTRIBUTE_ID);
String messageName = xtr.getAttributeValue(null, ATTRIBUTE_NAME);
model.addMessage(messageId, messageName);
}
} else if (ELEMENT_PARTICIPANT.equalsIgnoreCase(xtr.getLocalName())) {
if (StringUtils.isNotEmpty(xtr.getAttributeValue(null, ATTRIBUTE_ID))) {
Pool pool = new Pool();
pool.setId(xtr.getAttributeValue(null, ATTRIBUTE_ID));
pool.setName(xtr.getAttributeValue(null, ATTRIBUTE_NAME));
pool.setProcessRef(xtr.getAttributeValue(null, ATTRIBUTE_PROCESS_REF));
model.getPools().add(pool);
}
} else if (ELEMENT_PROCESS.equalsIgnoreCase(xtr.getLocalName())) {
if (StringUtils.isNotEmpty(xtr.getAttributeValue(null, ATTRIBUTE_ID))) {
String processId = xtr.getAttributeValue(null, ATTRIBUTE_ID);
processExtensionAvailable = true;
Process process = new Process();
process.setId(processId);
process.setName(xtr.getAttributeValue(null, ATTRIBUTE_NAME));
model.getProcesses().add(process);
activeProcess = process;
}
} else if ("lane".equalsIgnoreCase(xtr.getLocalName())) {
Lane lane = new Lane();
lane.setId(xtr.getAttributeValue(null, ATTRIBUTE_ID));
lane.setName(xtr.getAttributeValue(null, ATTRIBUTE_NAME));
lane.setParentProcess(activeProcess);
activeProcess.getLanes().add(lane);
while (xtr.hasNext()) {
xtr.next();
if (xtr.isStartElement() && "flowNodeRef".equalsIgnoreCase(xtr.getLocalName())) {
lane.getFlowReferences().add(xtr.getElementText());
} else if(xtr.isEndElement() && "lane".equalsIgnoreCase(xtr.getLocalName())) {
break;
}
}
} else if (ELEMENT_DOCUMENTATION.equalsIgnoreCase(xtr.getLocalName())) {
BaseElement parentElement = null;
if(activeSubProcessList.size() > 0) {
parentElement = activeSubProcessList.get(activeSubProcessList.size() - 1);
} else if(activeProcess != null) {
parentElement = activeProcess;
}
new DocumentationParser().parseChildElement(xtr, parentElement);
} else if (ELEMENT_SUBPROCESS.equalsIgnoreCase(xtr.getLocalName())) {
new SubProcessParser().parse(xtr, activeSubProcessList, activeProcess);
} else if (ELEMENT_DI_SHAPE.equalsIgnoreCase(xtr.getLocalName())) {
new BpmnShapeParser().parse(xtr, model);
} else if (ELEMENT_DI_EDGE.equalsIgnoreCase(xtr.getLocalName())) {
new BpmnEdgeParser().parse(xtr, model);
} else if (processExtensionAvailable == true && ELEMENT_EXECUTION_LISTENER.equalsIgnoreCase(xtr.getLocalName())) {
new ExecutionListenerParser().parseChildElement(xtr, activeProcess);
} else {
processExtensionAvailable = false;
if (activeSubProcessList.size() > 0 && ELEMENT_EXTENSIONS.equalsIgnoreCase(xtr.getLocalName())) {
new ExecutionListenerParser().parseChildElement(xtr, activeSubProcessList.get(activeSubProcessList.size() - 1));
} else if (activeSubProcessList.size() > 0 && ELEMENT_MULTIINSTANCE.equalsIgnoreCase(xtr.getLocalName())) {
new MultiInstanceParser().parseChildElement(xtr, activeSubProcessList.get(activeSubProcessList.size() - 1));
} else if (convertersToBpmnMap.containsKey(xtr.getLocalName())) {
Class<? extends BaseBpmnXMLConverter> converter = convertersToBpmnMap.get(xtr.getLocalName());
converter.newInstance().convertToBpmnModel(xtr, model, activeProcess, activeSubProcessList);
}
}
}
processFlowElements(model.getMainProcess().getFlowElements(), model.getMainProcess());
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error processing BPMN document", e);
}
return model;
}
private void processFlowElements(Collection<FlowElement> flowElementList, BaseElement parentScope) {
for (FlowElement flowElement : flowElementList) {
if (flowElement instanceof SequenceFlow) {
SequenceFlow sequenceFlow = (SequenceFlow) flowElement;
FlowElement sourceElement = getFlowElementFromScope(sequenceFlow.getSourceRef(), parentScope);
if (sourceElement != null) {
sourceElement.addOutgoingFlow(sequenceFlow);
}
} else if (flowElement instanceof BoundaryEvent) {
BoundaryEvent boundaryEvent = (BoundaryEvent) flowElement;
FlowElement attachedToElement = getFlowElementFromScope(boundaryEvent.getAttachedToRefId(), parentScope);
if(attachedToElement != null) {
boundaryEvent.setAttachedToRef((Activity) attachedToElement);
((Activity) attachedToElement).getBoundaryEvents().add(boundaryEvent);
}
} else if(flowElement instanceof SubProcess) {
SubProcess subProcess = (SubProcess) flowElement;
processFlowElements(subProcess.getFlowElements(), subProcess);
}
}
}
private FlowElement getFlowElementFromScope(String elementId, BaseElement scope) {
FlowElement flowElement = null;
if (scope instanceof Process) {
flowElement = ((Process) scope).getFlowElement(elementId);
} else if (scope instanceof SubProcess) {
flowElement = ((SubProcess) scope).getFlowElement(elementId);
}
return flowElement;
}
public byte[] convertToXML(BpmnModel model) {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
XMLOutputFactory xof = XMLOutputFactory.newInstance();
OutputStreamWriter out = new OutputStreamWriter(outputStream, "UTF-8");
XMLStreamWriter writer = xof.createXMLStreamWriter(out);
XMLStreamWriter xtw = new IndentingXMLStreamWriter(writer);
xtw.writeStartDocument("UTF-8", "1.0");
// start definitions root element
xtw.writeStartElement("definitions");
xtw.setDefaultNamespace(BPMN2_NAMESPACE);
xtw.writeDefaultNamespace(BPMN2_NAMESPACE);
xtw.writeNamespace(XSI_PREFIX, XSI_NAMESPACE);
xtw.writeNamespace(ACTIVITI_EXTENSIONS_PREFIX, ACTIVITI_EXTENSIONS_NAMESPACE);
xtw.writeNamespace(BPMNDI_PREFIX, BPMNDI_NAMESPACE);
xtw.writeNamespace(OMGDC_PREFIX, OMGDC_NAMESPACE);
xtw.writeNamespace(OMGDI_PREFIX, OMGDI_NAMESPACE);
xtw.writeAttribute(TYPE_LANGUAGE_ATTRIBUTE, SCHEMA_NAMESPACE);
xtw.writeAttribute(EXPRESSION_LANGUAGE_ATTRIBUTE, XPATH_NAMESPACE);
xtw.writeAttribute(TARGET_NAMESPACE_ATTRIBUTE, PROCESS_NAMESPACE);
for (FlowElement flowElement : model.getMainProcess().getFlowElements()) {
if (flowElement instanceof Event) {
Event event = (Event) flowElement;
if (event.getEventDefinitions().size() > 0) {
EventDefinition eventDefinition = event.getEventDefinitions().get(0);
if (eventDefinition instanceof SignalEventDefinition) {
SignalEventDefinition signalEvent = (SignalEventDefinition) eventDefinition;
if (model.containsSignalId(signalEvent.getSignalRef()) == false) {
model.addSignal(signalEvent.getSignalRef(), signalEvent.getSignalRef());
}
} else if (eventDefinition instanceof MessageEventDefinition) {
MessageEventDefinition messageEvent = (MessageEventDefinition) eventDefinition;
if (model.containsMessageId(messageEvent.getMessageRef()) == false) {
model.addMessage(messageEvent.getMessageRef(), messageEvent.getMessageRef());
}
}
}
}
}
for (Signal signal : model.getSignals()) {
xtw.writeStartElement(ELEMENT_SIGNAL);
xtw.writeAttribute(ATTRIBUTE_ID, signal.getId());
xtw.writeAttribute(ATTRIBUTE_NAME, signal.getName());
xtw.writeEndElement();
}
for (Message message : model.getMessages()) {
xtw.writeStartElement(ELEMENT_MESSAGE);
xtw.writeAttribute(ATTRIBUTE_ID, message.getId());
xtw.writeAttribute(ATTRIBUTE_NAME, message.getName());
xtw.writeEndElement();
}
if(model.getPools().size() > 0) {
xtw.writeStartElement(ELEMENT_COLLABORATION);
xtw.writeAttribute(ATTRIBUTE_ID, "Collaboration");
for (Pool pool : model.getPools()) {
xtw.writeStartElement(ELEMENT_PARTICIPANT);
xtw.writeAttribute(ATTRIBUTE_ID, pool.getId());
if(StringUtils.isNotEmpty(pool.getName())) {
xtw.writeAttribute(ATTRIBUTE_NAME, pool.getName());
}
xtw.writeAttribute(ATTRIBUTE_PROCESS_REF, pool.getProcessRef());
xtw.writeEndElement();
}
xtw.writeEndElement();
}
// start process element
xtw.writeStartElement(ELEMENT_PROCESS);
xtw.writeAttribute(ATTRIBUTE_ID, model.getMainProcess().getId());
if(StringUtils.isNotEmpty(model.getMainProcess().getName())) {
xtw.writeAttribute(ATTRIBUTE_NAME, model.getMainProcess().getName());
}
xtw.writeAttribute(ATTRIBUTE_PROCESS_EXECUTABLE, ATTRIBUTE_VALUE_TRUE);
if (StringUtils.isNotEmpty(model.getMainProcess().getDocumentation())) {
xtw.writeStartElement(ELEMENT_DOCUMENTATION);
xtw.writeCharacters(model.getMainProcess().getDocumentation());
xtw.writeEndElement();
}
for (FlowElement flowElement : model.getMainProcess().getFlowElements()) {
createXML(flowElement, xtw);
}
// end process element
xtw.writeEndElement();
// BPMN DI information
xtw.writeStartElement(BPMNDI_PREFIX, ELEMENT_DI_DIAGRAM, BPMNDI_NAMESPACE);
xtw.writeAttribute(ATTRIBUTE_ID, "BPMNDiagram_" + model.getMainProcess().getId());
xtw.writeStartElement(BPMNDI_PREFIX, ELEMENT_DI_PLANE, BPMNDI_NAMESPACE);
xtw.writeAttribute(ATTRIBUTE_DI_BPMNELEMENT, model.getMainProcess().getId());
xtw.writeAttribute(ATTRIBUTE_ID, "BPMNPlane_" + model.getMainProcess().getId());
for (String elementId : model.getLocationMap().keySet()) {
xtw.writeStartElement(BPMNDI_PREFIX, ELEMENT_DI_SHAPE, BPMNDI_NAMESPACE);
xtw.writeAttribute(ATTRIBUTE_DI_BPMNELEMENT, elementId);
xtw.writeAttribute(ATTRIBUTE_ID, "BPMNShape_" + elementId);
GraphicInfo graphicInfo = model.getGraphicInfo(elementId);
xtw.writeStartElement(OMGDC_PREFIX, ELEMENT_DI_BOUNDS, OMGDC_NAMESPACE);
xtw.writeAttribute(ATTRIBUTE_DI_HEIGHT, "" + graphicInfo.height);
xtw.writeAttribute(ATTRIBUTE_DI_WIDTH, "" + graphicInfo.width);
xtw.writeAttribute(ATTRIBUTE_DI_X, "" + graphicInfo.x);
xtw.writeAttribute(ATTRIBUTE_DI_Y, "" + graphicInfo.y);
xtw.writeEndElement();
xtw.writeEndElement();
}
for (String elementId : model.getFlowLocationMap().keySet()) {
xtw.writeStartElement(BPMNDI_PREFIX, ELEMENT_DI_EDGE, BPMNDI_NAMESPACE);
xtw.writeAttribute(ATTRIBUTE_DI_BPMNELEMENT, elementId);
xtw.writeAttribute(ATTRIBUTE_ID, "BPMNEdge_" + elementId);
List<GraphicInfo> graphicInfoList = model.getFlowLocationGraphicInfo(elementId);
for (GraphicInfo graphicInfo : graphicInfoList) {
xtw.writeStartElement(OMGDI_PREFIX, ELEMENT_DI_WAYPOINT, OMGDI_NAMESPACE);
xtw.writeAttribute(ATTRIBUTE_DI_X, "" + graphicInfo.x);
xtw.writeAttribute(ATTRIBUTE_DI_Y, "" + graphicInfo.y);
xtw.writeEndElement();
}
xtw.writeEndElement();
}
// end BPMN DI elements
xtw.writeEndElement();
xtw.writeEndElement();
// end definitions root element
xtw.writeEndElement();
xtw.writeEndDocument();
xtw.flush();
outputStream.close();
xtw.close();
return outputStream.toByteArray();
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error writing BPMN XML", e);
throw new XMLException("Error writing BPMN XML", e);
}
}
private void createXML(FlowElement flowElement, XMLStreamWriter xtw) throws Exception {
if (flowElement instanceof SubProcess) {
SubProcess subProcess = (SubProcess) flowElement;
xtw.writeStartElement(ELEMENT_SUBPROCESS);
xtw.writeAttribute(ATTRIBUTE_ID, subProcess.getId());
if (StringUtils.isNotEmpty(subProcess.getName())) {
xtw.writeAttribute(ATTRIBUTE_NAME, subProcess.getName());
} else {
xtw.writeAttribute(ATTRIBUTE_NAME, "subProcess");
}
if (subProcess instanceof EventSubProcess) {
xtw.writeAttribute(ATTRIBUTE_TRIGGERED_BY, "true");
}
if (StringUtils.isNotEmpty(subProcess.getDocumentation())) {
xtw.writeStartElement(ELEMENT_DOCUMENTATION);
xtw.writeCharacters(subProcess.getDocumentation());
xtw.writeEndElement();
}
for (FlowElement subElement : subProcess.getFlowElements()) {
createXML(subElement, xtw);
}
xtw.writeEndElement();
} else {
Class<? extends BaseBpmnXMLConverter> converter = convertersToXMLMap.get(flowElement.getClass());
if (converter == null) {
throw new XMLException("No converter for " + flowElement.getClass() + " found");
}
converter.newInstance().convertToXML(xtw, flowElement);
}
}
}