content: /*
   * Copyright 2010-2025 Hyland Software, Inc. and its affiliates.
   *
   * 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.IOException;
  import java.io.InputStreamReader;
  import java.io.OutputStreamWriter;
  import java.io.UnsupportedEncodingException;
  import java.net.URISyntaxException;
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import javax.xml.XMLConstants;
  import javax.xml.stream.XMLInputFactory;
  import javax.xml.stream.XMLOutputFactory;
  import javax.xml.stream.XMLStreamException;
  import javax.xml.stream.XMLStreamReader;
  import javax.xml.stream.XMLStreamWriter;
  import javax.xml.transform.stax.StAXSource;
  import javax.xml.transform.stream.StreamSource;
  import javax.xml.validation.Schema;
  import javax.xml.validation.SchemaFactory;
  import javax.xml.validation.Validator;
  import org.activiti.bpmn.constants.BpmnXMLConstants;
  import org.activiti.bpmn.converter.alfresco.AlfrescoStartEventXMLConverter;
  import org.activiti.bpmn.converter.alfresco.AlfrescoUserTaskXMLConverter;
  import org.activiti.bpmn.converter.child.DocumentationParser;
  import org.activiti.bpmn.converter.child.IOSpecificationParser;
  import org.activiti.bpmn.converter.child.multi.instance.MultiInstanceParser;
  import org.activiti.bpmn.converter.export.ActivitiListenerExport;
  import org.activiti.bpmn.converter.export.BPMNDIExport;
  import org.activiti.bpmn.converter.export.CollaborationExport;
  import org.activiti.bpmn.converter.export.DataStoreExport;
  import org.activiti.bpmn.converter.export.DefinitionsRootExport;
  import org.activiti.bpmn.converter.export.ErrorExport;
  import org.activiti.bpmn.converter.export.MultiInstanceExport;
  import org.activiti.bpmn.converter.export.ProcessExport;
  import org.activiti.bpmn.converter.export.SignalAndMessageDefinitionExport;
  import org.activiti.bpmn.converter.parser.BpmnEdgeParser;
  import org.activiti.bpmn.converter.parser.BpmnShapeParser;
  import org.activiti.bpmn.converter.parser.DataStoreParser;
  import org.activiti.bpmn.converter.parser.DefinitionsParser;
  import org.activiti.bpmn.converter.parser.ExtensionElementsParser;
  import org.activiti.bpmn.converter.parser.ImportParser;
  import org.activiti.bpmn.converter.parser.InterfaceParser;
  import org.activiti.bpmn.converter.parser.ItemDefinitionParser;
  import org.activiti.bpmn.converter.parser.LaneParser;
  import org.activiti.bpmn.converter.parser.MessageFlowParser;
  import org.activiti.bpmn.converter.parser.MessageParser;
  import org.activiti.bpmn.converter.parser.ParticipantParser;
  import org.activiti.bpmn.converter.parser.PotentialStarterParser;
  import org.activiti.bpmn.converter.parser.ProcessParser;
  import org.activiti.bpmn.converter.parser.ResourceParser;
  import org.activiti.bpmn.converter.parser.SignalParser;
  import org.activiti.bpmn.converter.parser.SubProcessParser;
  import org.activiti.bpmn.converter.util.BpmnXMLUtil;
  import org.activiti.bpmn.converter.util.InputStreamProvider;
  import org.activiti.bpmn.exceptions.XMLException;
  import org.activiti.bpmn.model.Activity;
  import org.activiti.bpmn.model.AdhocSubProcess;
  import org.activiti.bpmn.model.Artifact;
  import org.activiti.bpmn.model.Association;
  import org.activiti.bpmn.model.BaseElement;
  import org.activiti.bpmn.model.BooleanDataObject;
  import org.activiti.bpmn.model.BoundaryEvent;
  import org.activiti.bpmn.model.BpmnModel;
  import org.activiti.bpmn.model.DateDataObject;
  import org.activiti.bpmn.model.DoubleDataObject;
  import org.activiti.bpmn.model.EventSubProcess;
  import org.activiti.bpmn.model.FlowElement;
  import org.activiti.bpmn.model.FlowNode;
  import org.activiti.bpmn.model.IntegerDataObject;
  import org.activiti.bpmn.model.LongDataObject;
  import org.activiti.bpmn.model.Pool;
  import org.activiti.bpmn.model.Process;
  import org.activiti.bpmn.model.SequenceFlow;
  import org.activiti.bpmn.model.StringDataObject;
  import org.activiti.bpmn.model.SubProcess;
  import org.activiti.bpmn.model.TextAnnotation;
  import org.activiti.bpmn.model.Transaction;
  import org.apache.commons.lang3.StringUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.xml.sax.SAXException;
  
  /**
  
  
   */
  public class BpmnXMLConverter implements BpmnXMLConstants {
  
      protected static final Logger LOGGER = LoggerFactory.getLogger(BpmnXMLConverter.class);
  
      protected static final String BPMN_XSD = "org/activiti/impl/bpmn/parser/BPMN20.xsd";
      protected static final String DEFAULT_ENCODING = "UTF-8";
  
      protected static Map<String, BaseBpmnXMLConverter> convertersToBpmnMap = new HashMap<
          String,
          BaseBpmnXMLConverter
      >();
      protected static Map<Class<? extends BaseElement>, BaseBpmnXMLConverter> convertersToXMLMap = new HashMap<
          Class<? extends BaseElement>,
          BaseBpmnXMLConverter
      >();
  
      protected ClassLoader classloader;
      protected List<String> userTaskFormTypes;
      protected List<String> startEventFormTypes;
  
      protected BpmnEdgeParser bpmnEdgeParser = new BpmnEdgeParser();
      protected BpmnShapeParser bpmnShapeParser = new BpmnShapeParser();
      protected DefinitionsParser definitionsParser = new DefinitionsParser();
      protected DocumentationParser documentationParser = new DocumentationParser();
      protected ExtensionElementsParser extensionElementsParser = new ExtensionElementsParser();
      protected ImportParser importParser = new ImportParser();
      protected InterfaceParser interfaceParser = new InterfaceParser();
      protected ItemDefinitionParser itemDefinitionParser = new ItemDefinitionParser();
      protected IOSpecificationParser ioSpecificationParser = new IOSpecificationParser();
      protected DataStoreParser dataStoreParser = new DataStoreParser();
      protected LaneParser laneParser = new LaneParser();
      protected MessageParser messageParser = new MessageParser();
      protected MessageFlowParser messageFlowParser = new MessageFlowParser();
      protected MultiInstanceParser multiInstanceParser = new MultiInstanceParser();
      protected ParticipantParser participantParser = new ParticipantParser();
      protected PotentialStarterParser potentialStarterParser = new PotentialStarterParser();
      protected ProcessParser processParser = new ProcessParser();
      protected ResourceParser resourceParser = new ResourceParser();
      protected SignalParser signalParser = new SignalParser();
      protected SubProcessParser subProcessParser = new SubProcessParser();
  
      static {
          // events
          addConverter(new EndEventXMLConverter());
          addConverter(new StartEventXMLConverter());
  
          // tasks
          addConverter(new BusinessRuleTaskXMLConverter());
          addConverter(new ManualTaskXMLConverter());
          addConverter(new ReceiveTaskXMLConverter());
          addConverter(new ScriptTaskXMLConverter());
          addConverter(new ServiceTaskXMLConverter());
          addConverter(new SendTaskXMLConverter());
          addConverter(new UserTaskXMLConverter());
          addConverter(new TaskXMLConverter());
          addConverter(new CallActivityXMLConverter());
  
          // gateways
          addConverter(new EventGatewayXMLConverter());
          addConverter(new ExclusiveGatewayXMLConverter());
          addConverter(new InclusiveGatewayXMLConverter());
          addConverter(new ParallelGatewayXMLConverter());
          addConverter(new ComplexGatewayXMLConverter());
  
          // connectors
          addConverter(new SequenceFlowXMLConverter());
  
          // catch, throw and boundary event
          addConverter(new CatchEventXMLConverter());
          addConverter(new ThrowEventXMLConverter());
          addConverter(new BoundaryEventXMLConverter());
  
          // artifacts
          addConverter(new TextAnnotationXMLConverter());
          addConverter(new AssociationXMLConverter());
  
          // data store reference
          addConverter(new DataStoreReferenceXMLConverter());
  
          // data objects
          addConverter(new ValuedDataObjectXMLConverter(), StringDataObject.class);
          addConverter(new ValuedDataObjectXMLConverter(), BooleanDataObject.class);
          addConverter(new ValuedDataObjectXMLConverter(), IntegerDataObject.class);
          addConverter(new ValuedDataObjectXMLConverter(), LongDataObject.class);
          addConverter(new ValuedDataObjectXMLConverter(), DoubleDataObject.class);
          addConverter(new ValuedDataObjectXMLConverter(), DateDataObject.class);
  
          // Alfresco types
          addConverter(new AlfrescoStartEventXMLConverter());
          addConverter(new AlfrescoUserTaskXMLConverter());
      }
  
      public static void addConverter(BaseBpmnXMLConverter converter) {
          addConverter(converter, converter.getBpmnElementType());
      }
  
      public static void addConverter(BaseBpmnXMLConverter converter, Class<? extends BaseElement> elementType) {
          convertersToBpmnMap.put(converter.getXMLElementName(), converter);
          convertersToXMLMap.put(elementType, converter);
      }
  
      public void setClassloader(ClassLoader classloader) {
          this.classloader = classloader;
      }
  
      public void setUserTaskFormTypes(List<String> userTaskFormTypes) {
          this.userTaskFormTypes = userTaskFormTypes;
      }
  
      public void setStartEventFormTypes(List<String> startEventFormTypes) {
          this.startEventFormTypes = startEventFormTypes;
      }
  
      public void validateModel(InputStreamProvider inputStreamProvider) throws Exception {
          Schema schema = createSchema();
  
          Validator validator = schema.newValidator();
          validator.validate(new StreamSource(inputStreamProvider.getInputStream()));
      }
  
      public void validateModel(XMLStreamReader xmlStreamReader) throws Exception {
          Schema schema = createSchema();
  
          Validator validator = schema.newValidator();
          validator.validate(new StAXSource(xmlStreamReader));
      }
  
      protected Schema createSchema() throws SAXException {
          SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
          Schema schema = null;
          if (classloader != null) {
              schema = createSchema(factory, classloader.getResource(BPMN_XSD));
          }
  
          if (schema == null) {
              schema = createSchema(factory, BpmnXMLConverter.class.getClassLoader().getResource(BPMN_XSD));
          }
  
          if (schema == null) {
              throw new XMLException("BPMN XSD could not be found");
          }
          return schema;
      }
  
      protected Schema createSchema(SchemaFactory factory, URL schemaUrl) throws SAXException {
          try {
              return factory.newSchema(new StreamSource(schemaUrl.toURI().toASCIIString()));
          } catch (URISyntaxException e) {
              throw new XMLException("BPMN XSD could not be found", e);
          }
      }
  
      public BpmnModel convertToBpmnModel(
          InputStreamProvider inputStreamProvider,
          boolean validateSchema,
          boolean enableSafeBpmnXml
      ) {
          return convertToBpmnModel(inputStreamProvider, validateSchema, enableSafeBpmnXml, DEFAULT_ENCODING);
      }
  
      public BpmnModel convertToBpmnModel(
          InputStreamProvider inputStreamProvider,
          boolean validateSchema,
          boolean enableSafeBpmnXml,
          String encoding
      ) {
          XMLInputFactory xif = XMLInputFactory.newInstance();
  
          if (xif.isPropertySupported(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES)) {
              xif.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false);
          }
  
          if (xif.isPropertySupported(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES)) {
              xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
          }
  
          if (xif.isPropertySupported(XMLInputFactory.SUPPORT_DTD)) {
              xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
          }
  
          InputStreamReader in = null;
          try {
              in = new InputStreamReader(inputStreamProvider.getInputStream(), encoding);
              XMLStreamReader xtr = xif.createXMLStreamReader(in);
  
              try {
                  if (validateSchema) {
                      if (!enableSafeBpmnXml) {
                          validateModel(inputStreamProvider);
                      } else {
                          validateModel(xtr);
                      }
  
                      // The input stream is closed after schema validation
                      in = new InputStreamReader(inputStreamProvider.getInputStream(), encoding);
                      xtr = xif.createXMLStreamReader(in);
                  }
              } catch (Exception e) {
                  throw new XMLException(e.getMessage(), e);
              }
  
              // XML conversion
              return convertToBpmnModel(xtr);
          } catch (UnsupportedEncodingException e) {
              throw new XMLException("The bpmn 2.0 xml is not UTF8 encoded", e);
          } catch (XMLStreamException e) {
              throw new XMLException("Error while reading the BPMN 2.0 XML", e);
          } finally {
              if (in != null) {
                  try {
                      in.close();
                  } catch (IOException e) {
                      LOGGER.debug("Problem closing BPMN input stream", e);
                  }
              }
          }
      }
  
      public BpmnModel convertToBpmnModel(XMLStreamReader xtr) {
          BpmnModel model = new BpmnModel();
          model.setStartEventFormTypes(startEventFormTypes);
          model.setUserTaskFormTypes(userTaskFormTypes);
          try {
              Process activeProcess = null;
              List<SubProcess> activeSubProcessList = new ArrayList<SubProcess>();
              while (xtr.hasNext()) {
                  try {
                      xtr.next();
                  } catch (Exception e) {
                      LOGGER.debug("Error reading XML document", e);
                      throw new XMLException("Error reading XML", e);
                  }
  
                  if (
                      xtr.isEndElement() &&
                      (ELEMENT_SUBPROCESS.equals(xtr.getLocalName()) ||
                          ELEMENT_TRANSACTION.equals(xtr.getLocalName()) ||
                          ELEMENT_ADHOC_SUBPROCESS.equals(xtr.getLocalName()))
                  ) {
                      activeSubProcessList.remove(activeSubProcessList.size() - 1);
                  }
  
                  if (!xtr.isStartElement()) {
                      continue;
                  }
  
                  if (ELEMENT_DEFINITIONS.equals(xtr.getLocalName())) {
                      definitionsParser.parse(xtr, model);
                  } else if (ELEMENT_RESOURCE.equals(xtr.getLocalName())) {
                      resourceParser.parse(xtr, model);
                  } else if (ELEMENT_SIGNAL.equals(xtr.getLocalName())) {
                      signalParser.parse(xtr, model);
                  } else if (ELEMENT_MESSAGE.equals(xtr.getLocalName())) {
                      messageParser.parse(xtr, model);
                  } else if (ELEMENT_ERROR.equals(xtr.getLocalName())) {
                      if (StringUtils.isNotEmpty(xtr.getAttributeValue(null, ATTRIBUTE_ID))) {
                          model.addError(
                              xtr.getAttributeValue(null, ATTRIBUTE_ID),
                              xtr.getAttributeValue(null, ATTRIBUTE_NAME),
                              xtr.getAttributeValue(null, ATTRIBUTE_ERROR_CODE)
                          );
                      }
                  } else if (ELEMENT_IMPORT.equals(xtr.getLocalName())) {
                      importParser.parse(xtr, model);
                  } else if (ELEMENT_ITEM_DEFINITION.equals(xtr.getLocalName())) {
                      itemDefinitionParser.parse(xtr, model);
                  } else if (ELEMENT_DATA_STORE.equals(xtr.getLocalName())) {
                      dataStoreParser.parse(xtr, model);
                  } else if (ELEMENT_INTERFACE.equals(xtr.getLocalName())) {
                      interfaceParser.parse(xtr, model);
                  } else if (ELEMENT_IOSPECIFICATION.equals(xtr.getLocalName())) {
                      ioSpecificationParser.parseChildElement(xtr, activeProcess, model);
                  } else if (ELEMENT_PARTICIPANT.equals(xtr.getLocalName())) {
                      participantParser.parse(xtr, model);
                  } else if (ELEMENT_MESSAGE_FLOW.equals(xtr.getLocalName())) {
                      messageFlowParser.parse(xtr, model);
                  } else if (ELEMENT_PROCESS.equals(xtr.getLocalName())) {
                      Process process = processParser.parse(xtr, model);
                      if (process != null) {
                          activeProcess = process;
                      }
                  } else if (ELEMENT_POTENTIAL_STARTER.equals(xtr.getLocalName())) {
                      potentialStarterParser.parse(xtr, activeProcess);
                  } else if (ELEMENT_LANE.equals(xtr.getLocalName())) {
                      laneParser.parse(xtr, activeProcess, model);
                  } else if (ELEMENT_DOCUMENTATION.equals(xtr.getLocalName())) {
                      BaseElement parentElement = null;
                      if (!activeSubProcessList.isEmpty()) {
                          parentElement = activeSubProcessList.get(activeSubProcessList.size() - 1);
                      } else if (activeProcess != null) {
                          parentElement = activeProcess;
                      }
                      documentationParser.parseChildElement(xtr, parentElement, model);
                  } else if (activeProcess == null && ELEMENT_TEXT_ANNOTATION.equals(xtr.getLocalName())) {
                      String elementId = xtr.getAttributeValue(null, ATTRIBUTE_ID);
                      TextAnnotation textAnnotation =
                          (TextAnnotation) new TextAnnotationXMLConverter().convertXMLToElement(xtr, model);
                      textAnnotation.setId(elementId);
                      model.getGlobalArtifacts().add(textAnnotation);
                  } else if (activeProcess == null && ELEMENT_ASSOCIATION.equals(xtr.getLocalName())) {
                      String elementId = xtr.getAttributeValue(null, ATTRIBUTE_ID);
                      Association association = (Association) new AssociationXMLConverter().convertXMLToElement(
                          xtr,
                          model
                      );
                      association.setId(elementId);
                      model.getGlobalArtifacts().add(association);
                  } else if (ELEMENT_EXTENSIONS.equals(xtr.getLocalName())) {
                      extensionElementsParser.parse(xtr, activeSubProcessList, activeProcess, model);
                  } else if (
                      ELEMENT_SUBPROCESS.equals(xtr.getLocalName()) ||
                      ELEMENT_TRANSACTION.equals(xtr.getLocalName()) ||
                      ELEMENT_ADHOC_SUBPROCESS.equals(xtr.getLocalName())
                  ) {
                      subProcessParser.parse(xtr, activeSubProcessList, activeProcess);
                  } else if (ELEMENT_COMPLETION_CONDITION.equals(xtr.getLocalName())) {
                      if (!activeSubProcessList.isEmpty()) {
                          SubProcess subProcess = activeSubProcessList.get(activeSubProcessList.size() - 1);
                          if (subProcess instanceof AdhocSubProcess) {
                              AdhocSubProcess adhocSubProcess = (AdhocSubProcess) subProcess;
                              adhocSubProcess.setCompletionCondition(xtr.getElementText());
                          }
                      }
                  } else if (ELEMENT_DI_SHAPE.equals(xtr.getLocalName())) {
                      bpmnShapeParser.parse(xtr, model);
                  } else if (ELEMENT_DI_EDGE.equals(xtr.getLocalName())) {
                      bpmnEdgeParser.parse(xtr, model);
                  } else {
                      if (!activeSubProcessList.isEmpty() && ELEMENT_MULTIINSTANCE.equalsIgnoreCase(xtr.getLocalName())) {
                          multiInstanceParser.parseChildElement(
                              xtr,
                              activeSubProcessList.get(activeSubProcessList.size() - 1),
                              model
                          );
                      } else if (convertersToBpmnMap.containsKey(xtr.getLocalName())) {
                          if (activeProcess != null) {
                              BaseBpmnXMLConverter converter = convertersToBpmnMap.get(xtr.getLocalName());
                              converter.convertToBpmnModel(xtr, model, activeProcess, activeSubProcessList);
                          }
                      }
                  }
              }
  
              for (Process process : model.getProcesses()) {
                  for (Pool pool : model.getPools()) {
                      if (process.getId().equals(pool.getProcessRef())) {
                          pool.setExecutable(process.isExecutable());
                      }
                  }
                  processFlowElements(process.getFlowElements(), process);
              }
          } catch (XMLException e) {
              throw e;
          } catch (Exception e) {
              LOGGER.error("Error processing BPMN document", e);
              throw new XMLException("Error processing BPMN document", e);
          }
          return model;
      }
  
      protected void processFlowElements(Collection<FlowElement> flowElementList, BaseElement parentScope) {
          for (FlowElement flowElement : flowElementList) {
              if (flowElement instanceof SequenceFlow) {
                  SequenceFlow sequenceFlow = (SequenceFlow) flowElement;
                  FlowNode sourceNode = getFlowNodeFromScope(sequenceFlow.getSourceRef(), parentScope);
                  if (sourceNode != null) {
                      sourceNode.getOutgoingFlows().add(sequenceFlow);
                      sequenceFlow.setSourceFlowElement(sourceNode);
                  }
  
                  FlowNode targetNode = getFlowNodeFromScope(sequenceFlow.getTargetRef(), parentScope);
                  if (targetNode != null) {
                      targetNode.getIncomingFlows().add(sequenceFlow);
                      sequenceFlow.setTargetFlowElement(targetNode);
                  }
              } else if (flowElement instanceof BoundaryEvent) {
                  BoundaryEvent boundaryEvent = (BoundaryEvent) flowElement;
                  FlowElement attachedToElement = getFlowNodeFromScope(boundaryEvent.getAttachedToRefId(), parentScope);
                  if (attachedToElement instanceof Activity) {
                      Activity attachedActivity = (Activity) attachedToElement;
                      boundaryEvent.setAttachedToRef(attachedActivity);
                      attachedActivity.getBoundaryEvents().add(boundaryEvent);
                  }
              } else if (flowElement instanceof SubProcess) {
                  SubProcess subProcess = (SubProcess) flowElement;
                  processFlowElements(subProcess.getFlowElements(), subProcess);
              }
          }
      }
  
      protected FlowNode getFlowNodeFromScope(String elementId, BaseElement scope) {
          FlowNode flowNode = null;
          if (StringUtils.isNotEmpty(elementId)) {
              if (scope instanceof Process) {
                  flowNode = (FlowNode) ((Process) scope).getFlowElement(elementId);
              } else if (scope instanceof SubProcess) {
                  flowNode = (FlowNode) ((SubProcess) scope).getFlowElement(elementId);
              }
          }
          return flowNode;
      }
  
      public byte[] convertToXML(BpmnModel model) {
          return convertToXML(model, DEFAULT_ENCODING);
      }
  
      public byte[] convertToXML(BpmnModel model, String encoding) {
          try {
              ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
  
              XMLOutputFactory xof = XMLOutputFactory.newInstance();
              OutputStreamWriter out = new OutputStreamWriter(outputStream, encoding);
  
              XMLStreamWriter writer = xof.createXMLStreamWriter(out);
              XMLStreamWriter xtw = new IndentingXMLStreamWriter(writer);
  
              DefinitionsRootExport.writeRootElement(model, xtw, encoding);
              CollaborationExport.writePools(model, xtw);
              DataStoreExport.writeDataStores(model, xtw);
              SignalAndMessageDefinitionExport.writeSignalsAndMessages(model, xtw);
  
              for (Process process : model.getProcesses()) {
                  if (process.getFlowElements().isEmpty() && process.getLanes().isEmpty()) {
                      // empty process, ignore it
                      continue;
                  }
  
                  ProcessExport.writeProcess(process, xtw);
  
                  for (FlowElement flowElement : process.getFlowElements()) {
                      createXML(flowElement, model, xtw);
                  }
  
                  for (Artifact artifact : process.getArtifacts()) {
                      createXML(artifact, model, xtw);
                  }
  
                  // end process element
                  xtw.writeEndElement();
              }
              ErrorExport.writeError(model, xtw);
              BPMNDIExport.writeBPMNDI(model, xtw);
  
              // end definitions root element
              xtw.writeEndElement();
              xtw.writeEndDocument();
  
              xtw.flush();
  
              outputStream.close();
  
              xtw.close();
  
              return outputStream.toByteArray();
          } catch (Exception e) {
              LOGGER.error("Error writing BPMN XML", e);
              throw new XMLException("Error writing BPMN XML", e);
          }
      }
  
      protected void createXML(FlowElement flowElement, BpmnModel model, XMLStreamWriter xtw) throws Exception {
          if (flowElement instanceof SubProcess) {
              SubProcess subProcess = (SubProcess) flowElement;
              if (flowElement instanceof Transaction) {
                  xtw.writeStartElement(ELEMENT_TRANSACTION);
              } else if (flowElement instanceof AdhocSubProcess) {
                  xtw.writeStartElement(ELEMENT_ADHOC_SUBPROCESS);
              } else {
                  xtw.writeStartElement(BPMN2_PREFIX, ELEMENT_SUBPROCESS, BPMN2_NAMESPACE);
              }
  
              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, ATTRIBUTE_VALUE_TRUE);
              } else if (!(subProcess instanceof Transaction)) {
                  if (subProcess.isAsynchronous()) {
                      BpmnXMLUtil.writeQualifiedAttribute(ATTRIBUTE_ACTIVITY_ASYNCHRONOUS, ATTRIBUTE_VALUE_TRUE, xtw);
                      if (subProcess.isNotExclusive()) {
                          BpmnXMLUtil.writeQualifiedAttribute(ATTRIBUTE_ACTIVITY_EXCLUSIVE, ATTRIBUTE_VALUE_FALSE, xtw);
                      }
                  }
              } else if (subProcess instanceof AdhocSubProcess) {
                  AdhocSubProcess adhocSubProcess = (AdhocSubProcess) subProcess;
                  BpmnXMLUtil.writeDefaultAttribute(
                      ATTRIBUTE_CANCEL_REMAINING_INSTANCES,
                      String.valueOf(adhocSubProcess.isCancelRemainingInstances()),
                      xtw
                  );
                  if (StringUtils.isNotEmpty(adhocSubProcess.getOrdering())) {
                      BpmnXMLUtil.writeDefaultAttribute(ATTRIBUTE_ORDERING, adhocSubProcess.getOrdering(), xtw);
                  }
              }
  
              if (StringUtils.isNotEmpty(subProcess.getDocumentation())) {
                  xtw.writeStartElement(BPMN2_PREFIX, ELEMENT_DOCUMENTATION, BPMN2_NAMESPACE);
                  xtw.writeCharacters(subProcess.getDocumentation());
                  xtw.writeEndElement();
              }
  
              boolean didWriteExtensionStartElement = ActivitiListenerExport.writeListeners(subProcess, false, xtw);
  
              didWriteExtensionStartElement = BpmnXMLUtil.writeExtensionElements(
                  subProcess,
                  didWriteExtensionStartElement,
                  model.getNamespaces(),
                  xtw
              );
              if (didWriteExtensionStartElement) {
                  // closing extensions element
                  xtw.writeEndElement();
              }
  
              MultiInstanceExport.writeMultiInstance(subProcess, xtw);
  
              if (subProcess instanceof AdhocSubProcess) {
                  AdhocSubProcess adhocSubProcess = (AdhocSubProcess) subProcess;
                  if (StringUtils.isNotEmpty(adhocSubProcess.getCompletionCondition())) {
                      xtw.writeStartElement(ELEMENT_COMPLETION_CONDITION);
                      xtw.writeCData(adhocSubProcess.getCompletionCondition());
                      xtw.writeEndElement();
                  }
              }
  
              for (FlowElement subElement : subProcess.getFlowElements()) {
                  createXML(subElement, model, xtw);
              }
  
              for (Artifact artifact : subProcess.getArtifacts()) {
                  createXML(artifact, model, xtw);
              }
  
              xtw.writeEndElement();
          } else {
              BaseBpmnXMLConverter converter = convertersToXMLMap.get(flowElement.getClass());
  
              if (converter == null) {
                  throw new XMLException("No converter for " + flowElement.getClass() + " found");
              }
  
              converter.convertToXML(xtw, flowElement, model);
          }
      }
  
      protected void createXML(Artifact artifact, BpmnModel model, XMLStreamWriter xtw) throws Exception {
          BaseBpmnXMLConverter converter = convertersToXMLMap.get(artifact.getClass());
  
          if (converter == null) {
              throw new XMLException("No converter for " + artifact.getClass() + " found");
          }
  
          converter.convertToXML(xtw, artifact, model);
      }
  }
  