Search code examples
androidxmlxsdschemaxstream

How to annotation order field when using XStream on Android?


I need to generate XML from java objects on Android. XML nodes must be in definite sequence. Here is what I got(These fields as according to alphabetical order):

<soap:Envelope xmlns:cwmp="urn:dslforum-org:cwmp-1-0" 
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope" 
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soap:Body>
    <cwmp:Inform>
      <CurrentTime>2013-06-07 07:11:20.561 UTC</CurrentTime>
      <DeviceId>
        <OUI>002615</OUI>
        <Manufacturer>Conexant</Manufacturer>
        <ProductClass>ADSL2+ IAD</ProductClass>
        <SerialNumber>00261559a496</SerialNumber>
      </DeviceId>
      <Event soapenc:arrayType="cwmp:EventStruct[1]">
        <EventStruct>
          <CommandKey></CommandKey>
          <EventCode>0 BOOTSTRAP</EventCode>
        </EventStruct>
      </Event>
      <ParameterList soapenc:arrayType="cwmp:ParameterValueStruct[1]">
        <ParameterValueStruct>
          <name>InternetGatewayDevice.DeviceInfo.SpecVersion</name>
          <value>1.0</value>
        </ParameterValueStruct>
      </ParameterList>
      <MaxEnvelopes>1</MaxEnvelopes>
      <RetryCount>0</RetryCount>
    </cwmp:Inform>
  </soap:Body>
  <soap:Header>
    <ID soap:mustUnderstand="1">
      <string>00001</string>
    </ID>
  </soap:Header>
</soap:Envelope>

Here is what I want it to be:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soap:Header>
    <ID soap:mustUnderstand="1">
      <string>00001</string>
    </ID>
  </soap:Header>
  <soap:Body>
    <cwmp:Inform>
      <DeviceId>
        <Manufacturer>Conexant</Manufacturer>
        <OUI>002615</OUI>
        <ProductClass>ADSL2+ IAD</ProductClass>
        <SerialNumber>00261559a496</SerialNumber>
      </DeviceId>
      <Event soapenc:arrayType="cwmp:EventStruct[1]">
        <EventStruct>
          <EventCode>0 BOOTSTRAP</EventCode>
          <CommandKey></CommandKey>
        </EventStruct>
      </Event>
      <MaxEnvelopes>0</MaxEnvelopes>
      <CurrentTime>2013-06-07 07:16:54.128 UTC</CurrentTime>
      <RetryCount>0</RetryCount>
      <ParameterList soapenc:arrayType="cwmp:ParameterValueStruct[1]">
        <ParameterValueStruct>
          <name>InternetGatewayDevice.DeviceInfo.SpecVersion</name>
          <value>1.0</value>
        </ParameterValueStruct>
      </ParameterList>
    </cwmp:Inform>
  </soap:Body>
</soap:Envelope>

I've investigated this problem more. And I found that Xstream handles fields in wrong order on Android. Xstream handles fields well in Java project.

@XStreamAlias("cwmp:Inform")
public class Inform {
    @XStreamAlias("DeviceId")
    private DeviceIdStruct      deviceId;

    @XStreamAlias("Event")
    private EventList           event;

    @XStreamAlias("MaxEnvelopes")
    private int                 maxEnvelopes;

    @XStreamAlias("CurrentTime")
    private Date                currentTime;

    @XStreamAlias("RetryCount")
    private int                 retryCount;

    @XStreamAlias("ParameterList")
    private ParameterValueList  parameterList;
}

Solution

  • Here is my answer.
    I researched the issue and concluded that Android returns fields in alphabetical order of field types. You can inherit FieldKeySorter to set order.

    First you need create annotation that will define fields order:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface XMLSequence {
        String[] value();
    } 
    

    And example using:

    @XMLSequence({
            "accountIds",
            "addresses",
            "birthDate",
            "contact",
            "name",
            "status",
    }) 
    

    Then I've inherited FieldKeySorter :

    public class SequenceFieldKeySorter implements FieldKeySorter {
        @Override
        public Map sort(final Class type, final Map keyedByFieldKey) {
            Annotation sequence = type.getAnnotation(XMLSequence.class);
            if (sequence != null) {
                final String[] fieldsOrder = ((XMLSequence) sequence).value();
                Map result = new OrderRetainingMap();
                Set<Map.Entry<FieldKey, Field>> fields = keyedByFieldKey.entrySet();
                for (String fieldName : fieldsOrder) {
                    if (fieldName != null) {
                        for (Map.Entry<FieldKey, Field> fieldEntry : fields) {
                            if
    (fieldName.equals(fieldEntry.getKey().getFieldName())) {
                                result.put(fieldEntry.getKey(),
    fieldEntry.getValue());
                            }
                        }
                    }
                }
                return result;
            } else {
                return keyedByFieldKey;
            }
    
        }
    }
    

    And finally go:

    XStream x = new XStream(new PureJavaReflectionProvider(
    new FieldDictionary(new SequenceFieldKeySorter())));