Bug 357088 - XML Serialization: incorrect order of children
Summary: XML Serialization: incorrect order of children
Status: NEW
Alias: None
Product: MDT.BPMN2
Classification: Modeling
Component: Core (show other bugs)
Version: unspecified   Edit
Hardware: All All
: P3 normal (vote)
Target Milestone: ---   Edit
Assignee: Project Inbox CLA
QA Contact:
URL: http://www.eclipse.org/forums/index.p...
Whiteboard:
Keywords:
Depends on:
Blocks: 323167
  Show dependency tree
 
Reported: 2011-09-08 10:11 EDT by Henning Heitkoetter CLA
Modified: 2011-11-22 12:56 EST (History)
2 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Henning Heitkoetter CLA 2011-09-08 10:11:54 EDT
At least for elements of type Process, EMF's XML serialization stores children elements not in the order prescribed by the XML schema.

In our Ecore file (modeled after XMI), Process inherits from CallableElement, FlowElementsContainer (in this order).
Thus, EMF stores contained elements of super types in order of the super type collection first (1. CallableElement, 2. FlowElementsContainer), then the contained elements defined for the type itself.

Instead of multi inheritance, which is not possible in XML Schema, the XML type Process only inherits from CallableElement, whose children elements therefore go before children of Process itself. The elements defined by FlowElementsContainer have been pulled down to the type definition of Process.
Sadly, Semantic.xsd defines a different order as EMF and inserts the children defined by FlowElementsContainer in between the children defined by Process itself.

Therefore, the default order of EMF's XML serialization does not match the XML schema and needs to be adapted. See http://www.eclipse.org/forums/index.php/mv/msg/236798/719960/#msg_719960 for a possible solution, involving OPTION_USE_CACHED_LOOKUP_TABLE and a custom XMLSaveImpl.Lookup implementation.

The same might occur in other cases with multi inheritance.
Comment 1 tsurdilo surdilovic CLA 2011-10-02 14:13:22 EDT
Hi Henning, here is description of a fix. I can create a diff for it so it can be merged, just let me know if I am supposed to create a diff off of the github repo or some other repository. Thanks:

1) Update org.eclipse.bpmn2.util.Bpmn2ResourceFactoryImpl:
add:
result.getDefaultSaveOptions().put(XMLResource.OPTION_USE_CACHED_LOOKUP_TABLE, new ArrayList<Object>());
to the createResource method

2) Update org.eclipse.bpmn2.util.Bpmn2ResourceImpl:
change:
return new XMLSaveImpl(createXMLHelper()) { ... 
to
return new Bpmn2XMLSaveImpl(createXMLHelper()) { ...

3) Create new class Bpmn2XMLSaveImpl:
You can use this implementation if you wish:

public class Bpmn2XMLSaveImpl extends XMLSaveImpl {
	
	public Bpmn2XMLSaveImpl(XMLHelper helper) {
		super(helper);
	}
	
	@Override
	protected void init(XMLResource resource, Map<?, ?> options) {
		super.init(resource, options);
		featureTable = new Bpmn2XMLSaveImpl.Bpmn2Lookup(map, extendedMetaData, elementHandler);
	}
	
	@Override
	public void traverse(List<? extends EObject> contents) {
		for(EObject e : contents) {
			if(e instanceof Definitions) {
				List<RootElement> roots = ((Definitions) e).getRootElements();
				Process p = null;
				for(RootElement root : roots) {
					if(root instanceof Process) {
						p = (Process) root;
					}
				}
				if(p != null) {
					((Definitions) e).getRootElements().remove(p);
					((Definitions) e).getRootElements().add(p);
				}
			}
		}
		super.traverse(contents);
	}
	
	public static class Bpmn2Lookup extends XMLSaveImpl.Lookup {
		public Bpmn2Lookup(XMLMap map, ExtendedMetaData extendedMetaData, ElementHandler elementHandler) {
			super(map, extendedMetaData, elementHandler);
		}
		
		@Override
		public EStructuralFeature[] getFeatures(EClass cls) {
			int index = getIndex(cls);
			EClass c = classes[index];

			if (c == cls) {
				return features[index];
			}

			EStructuralFeature[] featureList = listFeatures(cls);
			if (c == null) {
				classes[index] = cls;
				features[index] = featureList;
				featureKinds[index] = listKinds(featureList);
			}
			
			if(cls.getName().equalsIgnoreCase("Process")) {
				EStructuralFeature[] modifiedFeatureList =  getModifiedProcessFeatureSet(featureList);
				if (c == null) {
					classes[index] = cls;
					features[index] = modifiedFeatureList;
					featureKinds[index] = listKinds(modifiedFeatureList);
				}
				return modifiedFeatureList;
			}
			return featureList;
		}
	}
	
	private static EStructuralFeature[] getModifiedProcessFeatureSet(EStructuralFeature[] processFeatureList) {
		/**
		 Feature list for Process provided by eclipse.bpmn2:
    		- extensionDefinitions (0)
    		- id (1)
    		- anyAttribute (2)
    		- name (3)
    		- definitionalCollaborationRef (4)
    		- isClosed (5)
    		- isExecutable (6)
    		- processType (7) 
    		- extensionValues (8) 
    		- documentation (9) 
    		- supportedInterfaceRefs (10) 
    		- ioSpecification (11) 
    		- ioBinding (12)
    		- laneSets (13)
    		- flowElements (14)
    		- auditing (15)
    		- monitoring (16)
    		- properties (17)
    		- artifacts (18)
    		- resources (19) 
    		- correlationSubscriptions (20)
    		- supports (21) 
    	  Semantic.xsd sequence definition for Process:
    	  <xsd:sequence>
			<xsd:element ref="auditing" minOccurs="0" maxOccurs="1"/>
			<xsd:element ref="monitoring" minOccurs="0" maxOccurs="1"/>
			<xsd:element ref="property" minOccurs="0" maxOccurs="unbounded"/>
			<xsd:element ref="laneSet" minOccurs="0" maxOccurs="unbounded"/>
			<xsd:element ref="flowElement" minOccurs="0" maxOccurs="unbounded"/>
			<xsd:element ref="artifact" minOccurs="0" maxOccurs="unbounded"/>
			<xsd:element ref="resourceRole" minOccurs="0" maxOccurs="unbounded"/>
			<xsd:element ref="correlationSubscription" minOccurs="0" maxOccurs="unbounded"/>
			<xsd:element name="supports" type="xsd:QName" minOccurs="0" maxOccurs="unbounded"/>
		  </xsd:sequence>
		  
		  Moving auditing, monitoring, property above flowElements...
		 */
		
		EStructuralFeature[] retArray = new EStructuralFeature[processFeatureList.length];
		for(int i=0; i<13; i++) {
			retArray[i] = processFeatureList[i];
		}
		retArray[13] = processFeatureList[15]; // auditing
		retArray[14] = processFeatureList[16]; // monitoring
		retArray[15] = processFeatureList[17]; // properties
		retArray[16] = processFeatureList[13]; // lanesets
		retArray[17] = processFeatureList[14]; // flow elements
		retArray[18] = processFeatureList[18]; // artifacts
		retArray[19] = processFeatureList[19]; // resources
		retArray[20] = processFeatureList[20]; // correlationSubscriptions
		retArray[21] = processFeatureList[21]; // supports
		
		return retArray;
	}
}


Hope this helps.

Thanks.
Comment 2 Reiner Hille CLA 2011-11-22 11:24:00 EST
Thanks.
I'm aware of the problem, and the use of strict sequences in BPMN 2.0 XSD is really a problem. Not only for the EMF metamodel, but also for the XSLT transforms between XML and XMI. The XSLTs also simulate the multiple inheritance aspect of the CMOF metamodel and thus can produce invalid XMLs in the sense of the sequence of features.
About your proposal: It looks right, but I would prefer a more generic approach. I think we could store the order information in EMF annotations and let the serializer consider it from there. Ideally we generate the annotations from the original BPMN 2.0 XSD. This is possible, as I anyway used a merge tool to generate the Ecore. 
Unfortunately this will take some time - and my time for BPMN2 is currently very limmited. 

Reiner.
Comment 3 Reiner Hille CLA 2011-11-22 12:56:38 EST
Some addition:
I have checked my merge tool (in http://git.eclipse.org/c/bpmn2/tree/org.eclipse.bpmn2.tools.ecoremerger/src/org/eclipse/bpmn2/tools/ecoremerger/Processor.java ). I do consider feature sorting of the XSD, but not in cases of Multi Inheritance. I now plan to extend the merge tool that i writes an annotation to each class with the original XSD feature order. The XmlSave.traverse then can easily take this information and walk accordingly.