/* 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 com.artfess.activiti.bpmn.converter.export.HtDefinitionsRootExport;
import com.artfess.base.util.BeanUtils;
import com.artfess.base.util.StringUtil;
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.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.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.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.EndEvent;
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.ServiceTask;
import org.activiti.bpmn.model.StartEvent;
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.activiti.bpmn.model.UserTask;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

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 java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Tijs Rademakers
 * @author Joram Barrez
 */
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/HtBpmExtend.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 = factory.newSchema(classloader.getResource(BPMN_XSD));
		}

		if (schema == null) {
			schema = factory.newSchema(BpmnXMLConverter.class.getClassLoader().getResource(BPMN_XSD));
		}

		if (schema == null) {
			throw new XMLException("BPMN XSD could not be found");
		}
		return schema;
	}

	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 {
				validateSchema = false;
				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())) {
					activeSubProcessList.remove(activeSubProcessList.size() - 1);
				}

				if (xtr.isEndElement()  && ELEMENT_TRANSACTION.equals(xtr.getLocalName())) {
					activeSubProcessList.remove(activeSubProcessList.size() - 1);
				}

				if (xtr.isStartElement() == false) {
					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_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())) {
					subProcessParser.parse(xtr, activeSubProcessList, activeProcess);

				} else if (ELEMENT_TRANSACTION.equals(xtr.getLocalName())) {
					subProcessParser.parse(xtr, activeSubProcessList, activeProcess);

				} 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;
	}

	private 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);
				}
				FlowNode targetNode = getFlowNodeFromScope(sequenceFlow.getTargetRef(), parentScope);
				if (targetNode != null) {
					targetNode.getIncomingFlows().add(sequenceFlow);
				}
			} else if (flowElement instanceof BoundaryEvent) {
				BoundaryEvent boundaryEvent = (BoundaryEvent) flowElement;
				FlowElement attachedToElement = getFlowNodeFromScope(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 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);

			HtDefinitionsRootExport.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();
			}

			BPMNDIExport.writeBPMNDI(model, xtw);

			//artfess add
			writeExtElement(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);
		}
	}

	/**
	 * 添加ext element by artfess
	 * @param model
	 * @param xtw
	 * @throws XMLStreamException 
	 */
	private void writeExtElement(BpmnModel model, XMLStreamWriter xtw) throws Exception{
		xtw.writeStartElement("ext:extProcess");
		xtw.writeStartElement("ext:extNodes");
		for(Process processe :model.getProcesses()){
			for(FlowElement flowElement :  processe.getFlowElements()){
				wirteExtFlowElement(flowElement,xtw);
			}
		}
		xtw.writeEndElement();
		xtw.writeStartElement("ext:extProperties");
		xtw.writeEmptyElement("ext:subjectRule");
		xtw.writeEmptyElement("ext:description");
		xtw.writeEmptyElement("ext:startNotifyType");
		xtw.writeEmptyElement("ext:archiveNotifyType");
		xtw.writeEmptyElement("ext:skipFirstNode");
		xtw.writeEmptyElement("ext:firstNodeUserAssign");
		xtw.writeEmptyElement("ext:skipSameUser");
		xtw.writeEmptyElement("ext:allowCopyTo");
		xtw.writeEmptyElement("ext:allowTransTo");
		xtw.writeEmptyElement("ext:useMainForm");
		xtw.writeEmptyElement("ext:allowReference");
		xtw.writeEmptyElement("ext:allowRefCounts");
		xtw.writeEndElement();
		xtw.writeEndElement();
	}

	private void wirteExtFlowElement(FlowElement flowElement,XMLStreamWriter xtw) throws Exception{
		if(flowElement instanceof StartEvent || flowElement instanceof EndEvent || flowElement instanceof ServiceTask){
			xtw.writeStartElement("ext:baseNode");
			xtw.writeAttribute("nodeType", "common");
			xtw.writeAttribute("name", flowElement.getName());
			xtw.writeAttribute("bpmnElement", flowElement.getId());
			xtw.writeStartElement("ext:extPlugins");
			xtw.writeEndElement();
			xtw.writeEndElement();
		}else if(flowElement instanceof UserTask){
			UserTask task = (UserTask)flowElement;
			if(!BeanUtils.isEmpty(task.getLoopCharacteristics())){
				xtw.writeStartElement("ext:signNode");
				xtw.writeAttribute("nodeType", "common");
				xtw.writeAttribute("name", flowElement.getName());
				xtw.writeAttribute("bpmnElement", flowElement.getId());
				if(StringUtil.isEmpty(task.getAttributeValue(null, "signType"))) {
					xtw.writeStartElement("ext:signSetting");
					xtw.writeStartElement("ext:signRule");
					xtw.writeAttribute("decideType", "refuse");
					xtw.writeAttribute("voteType", "amount");
					xtw.writeAttribute("voteAmount", "1");
					xtw.writeAttribute("followMode", "complete");
					xtw.writeEndElement();
					xtw.writeEndElement();
				}
				xtw.writeStartElement("ext:extPlugins");
				xtw.writeEndElement();
				xtw.writeEndElement();
			}else{
				xtw.writeStartElement("ext:userNode");
				xtw.writeAttribute("nodeType", "common");
				xtw.writeAttribute("name", flowElement.getName());
				xtw.writeAttribute("bpmnElement", flowElement.getId());
				xtw.writeStartElement("ext:extPlugins");
				xtw.writeEndElement();
				xtw.writeEndElement();
			}
		}else if(flowElement instanceof SubProcess){
			for(FlowElement f : ((SubProcess) flowElement).getFlowElements()){
				wirteExtFlowElement(f,xtw);
			}
		}
	}

	private 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 {
				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, ATTRIBUTE_VALUE_TRUE);

			} else if (subProcess instanceof Transaction == false) {
				if (subProcess.isAsynchronous()) {
					BpmnXMLUtil.writeQualifiedAttribute(ATTRIBUTE_ACTIVITY_ASYNCHRONOUS, ATTRIBUTE_VALUE_TRUE, xtw);
					if (subProcess.isNotExclusive()) {
						BpmnXMLUtil.writeQualifiedAttribute(ATTRIBUTE_ACTIVITY_EXCLUSIVE, ATTRIBUTE_VALUE_FALSE, xtw);
					}
				}
			}

			if (StringUtils.isNotEmpty(subProcess.getDocumentation())) {

				xtw.writeStartElement(ELEMENT_DOCUMENTATION);
				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);

			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);
		}
	}

	private 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);
	}
}
