/** * */ package org.activiti.designer.export.bpmn20.export; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.OutputStreamWriter; import java.util.List; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamWriter; import org.activiti.designer.eclipse.common.ActivitiBPMNDiagramConstants; import org.activiti.designer.eclipse.extension.export.AbstractExportMarshaller; import org.activiti.designer.eclipse.extension.export.ExportMarshaller; import org.activiti.designer.eclipse.extension.validation.ProcessValidator; import org.activiti.designer.eclipse.preferences.PreferencesUtil; import org.activiti.designer.eclipse.util.ExtensionPointUtil; import org.activiti.designer.util.preferences.Preferences; import org.apache.commons.lang.StringUtils; import org.eclipse.bpmn2.AlfrescoScriptTask; import org.eclipse.bpmn2.BoundaryEvent; import org.eclipse.bpmn2.BusinessRuleTask; import org.eclipse.bpmn2.CallActivity; import org.eclipse.bpmn2.EndEvent; import org.eclipse.bpmn2.ErrorEventDefinition; import org.eclipse.bpmn2.ExclusiveGateway; import org.eclipse.bpmn2.FlowElement; import org.eclipse.bpmn2.FlowNode; import org.eclipse.bpmn2.FormalExpression; import org.eclipse.bpmn2.InclusiveGateway; import org.eclipse.bpmn2.IntermediateCatchEvent; import org.eclipse.bpmn2.MailTask; import org.eclipse.bpmn2.ManualTask; import org.eclipse.bpmn2.ParallelGateway; import org.eclipse.bpmn2.Process; import org.eclipse.bpmn2.ReceiveTask; import org.eclipse.bpmn2.ScriptTask; import org.eclipse.bpmn2.SequenceFlow; import org.eclipse.bpmn2.ServiceTask; import org.eclipse.bpmn2.StartEvent; import org.eclipse.bpmn2.SubProcess; import org.eclipse.bpmn2.TimerEventDefinition; import org.eclipse.bpmn2.UserTask; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; import org.eclipse.graphiti.mm.pictograms.Diagram; /** * @author Tiese Barrell * @since 0.5.1 * @version 2 * */ public class BPMN20ExportMarshaller extends AbstractExportMarshaller implements ActivitiNamespaceConstants { private static final String FILENAME_PATTERN = ExportMarshaller.PLACEHOLDER_ORIGINAL_FILENAME_WITHOUT_EXTENSION + ".bpmn20.xml"; private static final int WORK_VALIDATION = 40; private static final int WORK_EXPORT = 60; private static final int WORK_TOTAL = WORK_VALIDATION + WORK_EXPORT; private IProgressMonitor monitor; private Diagram diagram; private IndentingXMLStreamWriter xtw; /** * */ public BPMN20ExportMarshaller() { } @Override public String getMarshallerName() { return ActivitiBPMNDiagramConstants.BPMN_MARSHALLER_NAME; } @Override public String getFormatName() { return "Activiti BPMN 2.0"; } @Override public void marshallDiagram(Diagram diagram, IProgressMonitor monitor) { this.monitor = monitor; this.diagram = diagram; this.monitor.beginTask("Exporting to BPMN 2.0", WORK_TOTAL); // Clear problems for this diagram first clearMarkers(getResource(diagram.eResource().getURI())); boolean performMarshalling = true; // Retrieve validatorId to allow for overriding the default validator String validatorId = getValidatorId(); if (isValidationEnabled(validatorId)) { boolean validBpmn = invokeValidator(validatorId, diagram, new SubProgressMonitor(this.monitor, WORK_VALIDATION)); if (!validBpmn) { // Get the behavior required final String behavior = PreferencesUtil.getStringPreference(Preferences.SKIP_BPMN_MARSHALLER_ON_VALIDATION_FAILURE); // Flag marshalling to be skipped if the behavior is to skip or not // defined (mirrors default behavior) if (behavior == null || ActivitiBPMNDiagramConstants.BPMN_MARSHALLER_VALIDATION_SKIP.equals(behavior)) { performMarshalling = false; // add additional marker for user addProblemToDiagram(diagram, "Marshalling to " + getFormatName() + " format was skipped because validation of the diagram failed.", null); } } } else { this.monitor.worked(WORK_VALIDATION); } if (performMarshalling) { marshallBPMNDiagram(); this.monitor.worked(WORK_EXPORT); } this.monitor.done(); } /** * Gets the identifier of the {@link ProcessValidator} to be invoked from this * marshaller. This implementation provides the default value of * {@link ActivitiBPMNDiagramConstants#BPMN_VALIDATOR_ID}. Override this * method to use this export marshaller for all BPMN 2.0 marshalling, but use * your own {@link ProcessValidator} instead of the one provided by Activiti * Designer by default. * * @return The string identifier of the {@link ProcessValidator}, may not be * null or empty or validation will be skipped. */ public String getValidatorId() { return ActivitiBPMNDiagramConstants.BPMN_VALIDATOR_ID; } private boolean isValidationEnabled(String validatorId) { boolean result = true; if (ActivitiBPMNDiagramConstants.BPMN_VALIDATOR_ID.equals(validatorId)) { // Validate if the BPMN validator is checked in the preferences result = PreferencesUtil.getBooleanPreference(Preferences.VALIDATE_ACTIVITI_BPMN_FORMAT); } else { result = StringUtils.isNotBlank(validatorId) && ExtensionPointUtil.getProcessValidator(validatorId) != null; } return result; } private void marshallBPMNDiagram() { try { XMLOutputFactory xof = XMLOutputFactory.newInstance(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); OutputStreamWriter out = new OutputStreamWriter(baos, "UTF-8"); XMLStreamWriter writer = xof.createXMLStreamWriter(out); xtw = new IndentingXMLStreamWriter(writer); final EList<EObject> contents = diagram.eResource().getContents(); Process process = null; for (final EObject eObject : contents) { if (eObject instanceof Process) { process = (Process) eObject; break; } } if (process == null) { addProblemToDiagram(diagram, "Process cannot be null", null); } xtw.writeStartDocument("UTF-8", "1.0"); // start definitions root element xtw.writeStartElement("definitions"); xtw.setDefaultNamespace(BPMN2_NAMESPACE); xtw.writeDefaultNamespace(BPMN2_NAMESPACE); xtw.writeNamespace("xsi", 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("typeLanguage", SCHEMA_NAMESPACE); xtw.writeAttribute("expressionLanguage", XPATH_NAMESPACE); if (process != null && StringUtils.isNotEmpty(process.getNamespace())) { xtw.writeAttribute("targetNamespace", process.getNamespace()); } else { xtw.writeAttribute("targetNamespace", PROCESS_NAMESPACE); } // start process element xtw.writeStartElement("process"); xtw.writeAttribute("id", process.getId()); xtw.writeAttribute("name", process.getName()); ExtensionListenerExport.createExtensionListenerXML(process.getExecutionListeners(), true, EXECUTION_LISTENER, xtw); if (process.getDocumentation() != null && process.getDocumentation().size() > 0 && process.getDocumentation().get(0) != null && process.getDocumentation().get(0).getText() != null && process.getDocumentation().get(0).getText().length() > 0) { xtw.writeStartElement("documentation"); xtw.writeCharacters(process.getDocumentation().get(0).getText()); xtw.writeEndElement(); } for (EObject object : contents) { if (object instanceof FlowNode) { FlowNode node = (FlowNode) object; if (node.getIncoming().size() == 0 && node.getOutgoing().size() == 0 && node instanceof BoundaryEvent == false && node instanceof EndEvent == false) { continue; } } createXML(object); } // end process element xtw.writeEndElement(); BpmnDIExport.createDIXML(process, diagram, xtw); // end definitions root element xtw.writeEndElement(); xtw.writeEndDocument(); xtw.flush(); final byte[] bytes = baos.toByteArray(); final ByteArrayInputStream bais = new ByteArrayInputStream(bytes); saveResource(getRelativeURIForDiagram(diagram, FILENAME_PATTERN), bais, new NullProgressMonitor()); xtw.close(); } catch (Exception e) { e.printStackTrace(); addProblemToDiagram(diagram, "An exception occurred while creating the BPMN 2.0 XML: " + e.getMessage(), null); } } private void createXML(EObject object) throws Exception { if (object instanceof StartEvent) { StartEvent startEvent = (StartEvent) object; // start StartEvent element xtw.writeStartElement("startEvent"); xtw.writeAttribute("id", startEvent.getId()); xtw.writeAttribute("name", startEvent.getName()); if (startEvent.getFormKey() != null && startEvent.getFormKey().length() > 0) { xtw.writeAttribute(ACTIVITI_EXTENSIONS_PREFIX, ACTIVITI_EXTENSIONS_NAMESPACE, "formKey", startEvent.getFormKey()); } if (startEvent.getInitiator() != null && startEvent.getInitiator().length() > 0) { xtw.writeAttribute(ACTIVITI_EXTENSIONS_PREFIX, ACTIVITI_EXTENSIONS_NAMESPACE, "initiator", startEvent.getInitiator()); } if (startEvent.getEventDefinitions().size() > 0) { TimerEventDefinition timerDef = (TimerEventDefinition) startEvent.getEventDefinitions().get(0); if (timerDef.getTimeDuration() != null && ((((FormalExpression) timerDef.getTimeDuration()).getBody() != null && ((FormalExpression) timerDef.getTimeDuration()).getBody().length() > 0) || (((FormalExpression) timerDef.getTimeDate()).getBody() != null && ((FormalExpression) timerDef.getTimeDate()).getBody().length() > 0) || (((FormalExpression) timerDef.getTimeCycle()).getBody() != null && ((FormalExpression) timerDef.getTimeCycle()).getBody().length() > 0))) { xtw.writeStartElement("timerEventDefinition"); if (((FormalExpression) timerDef.getTimeDuration()).getBody() != null && ((FormalExpression) timerDef.getTimeDuration()).getBody().length() > 0) { xtw.writeStartElement("timeDuration"); xtw.writeCharacters(((FormalExpression) timerDef.getTimeDuration()).getBody()); xtw.writeEndElement(); } else if (((FormalExpression) timerDef.getTimeDate()).getBody() != null && ((FormalExpression) timerDef.getTimeDate()).getBody().length() > 0) { xtw.writeStartElement("timeDate"); xtw.writeCharacters(((FormalExpression) timerDef.getTimeDate()).getBody()); xtw.writeEndElement(); } else { xtw.writeStartElement("timeCycle"); xtw.writeCharacters(((FormalExpression) timerDef.getTimeCycle()).getBody()); xtw.writeEndElement(); } xtw.writeEndElement(); } } if (startEvent.getFormProperties().size() > 0) { xtw.writeStartElement("extensionElements"); FormPropertiesExport.createFormPropertiesXML(startEvent.getFormProperties(), xtw); xtw.writeEndElement(); } // end StartEvent element xtw.writeEndElement(); } else if (object instanceof EndEvent) { EndEvent endEvent = (EndEvent) object; // start EndEvent element xtw.writeStartElement("endEvent"); xtw.writeAttribute("id", endEvent.getId()); xtw.writeAttribute("name", endEvent.getName()); if (endEvent.getEventDefinitions().size() > 0) { ErrorEventDefinition errorDef = (ErrorEventDefinition) endEvent.getEventDefinitions().get(0); if (errorDef.getErrorCode() != null && errorDef.getErrorCode().length() > 0) { xtw.writeStartElement("errorEventDefinition"); xtw.writeAttribute("errorRef", errorDef.getErrorCode()); xtw.writeEndElement(); } } // end EndEvent element xtw.writeEndElement(); } else if (object instanceof SequenceFlow) { SequenceFlowExport.createSequenceFlow(object, xtw); } else if (object instanceof UserTask) { UserTaskExport.createUserTask(object, xtw); } else if (object instanceof ScriptTask) { ScriptTaskExport.createScriptTask(object, xtw); } else if (object instanceof ServiceTask) { ServiceTaskExport.createServiceTask(object, xtw); } else if (object instanceof MailTask) { MailTaskExport.createMailTask(object, xtw); } else if (object instanceof ManualTask) { ManualTaskExport.createManualTask(object, xtw); } else if (object instanceof ReceiveTask) { ReceiveTaskExport.createReceiveTask(object, xtw); } else if (object instanceof BusinessRuleTask) { BusinessRuleTaskExport.createBusinessRuleTask(object, xtw); } else if (object instanceof AlfrescoScriptTask) { AlfrescoScriptTaskExport.createScriptTask(object, xtw); } else if (object instanceof CallActivity) { CallActivityExport.createCallActivity(object, xtw); } else if (object instanceof ParallelGateway) { ParallelGateway parallelGateway = (ParallelGateway) object; // start ParallelGateway element xtw.writeStartElement("parallelGateway"); xtw.writeAttribute("id", parallelGateway.getId()); if (parallelGateway.getName() != null) { xtw.writeAttribute("name", parallelGateway.getName()); } // end ParallelGateway element xtw.writeEndElement(); } else if (object instanceof ExclusiveGateway) { ExclusiveGateway exclusiveGateway = (ExclusiveGateway) object; // start ExclusiveGateway element xtw.writeStartElement("exclusiveGateway"); xtw.writeAttribute("id", exclusiveGateway.getId()); if (exclusiveGateway.getName() != null) { xtw.writeAttribute("name", exclusiveGateway.getName()); } DefaultFlowExport.createDefaultFlow(object, xtw); // end ExclusiveGateway element xtw.writeEndElement(); } else if (object instanceof InclusiveGateway) { InclusiveGateway inclusiveGateway = (InclusiveGateway) object; // start InclusiveGateway element xtw.writeStartElement("inclusiveGateway"); xtw.writeAttribute("id", inclusiveGateway.getId()); if (inclusiveGateway.getName() != null) { xtw.writeAttribute("name", inclusiveGateway.getName()); } DefaultFlowExport.createDefaultFlow(object, xtw); // end InclusiveGateway element xtw.writeEndElement(); } else if (object instanceof IntermediateCatchEvent) { IntermediateCatchEventExport.createIntermediateEvent(object, xtw); } else if (object instanceof SubProcess) { SubProcess subProcess = (SubProcess) object; List<FlowElement> flowElementList = subProcess.getFlowElements(); if (flowElementList != null && flowElementList.size() > 0) { // start SubProcess element xtw.writeStartElement("subProcess"); xtw.writeAttribute("id", subProcess.getId()); if (subProcess.getName() != null) { xtw.writeAttribute("name", subProcess.getName()); } DefaultFlowExport.createDefaultFlow(object, xtw); AsyncActivityExport.createDefaultFlow(object, xtw); ExtensionListenerExport.createExtensionListenerXML(subProcess.getActivitiListeners(), true, EXECUTION_LISTENER, xtw); MultiInstanceExport.createMultiInstance(object, xtw); for (FlowElement flowElement : flowElementList) { createXML(flowElement); } // end SubProcess element xtw.writeEndElement(); if(subProcess.getBoundaryEventRefs().size() > 0) { for(BoundaryEvent event : subProcess.getBoundaryEventRefs()) { BoundaryEventExport.createBoundaryEvent(event, xtw); } } } } } }