MOXy order of fields in the target Java object matters when unmarshalling

579 views Asked by At

It seems there is a bug in the MOXy. The piece of code below works perfectly when fields in class Request declared as metaInfo and then content, but test fails on unmarshalling with exception when fields declared in reverse order (content first and metaInfo second).

The exception thrown is:
Going with type: APPLICATION_XML
Original request = {content=Payload = {[one, two, three]}, metaInfo=requestMetaInfo = {confirmation=false}}
Marshaled as application/xml: <?xml version="1.0" encoding="UTF-8"?><request><collection><collection xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string">one</collection><collection xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string">two</collection><collection xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string">three</collection></collection><metaInfo><confirmation>false</confirmation></metaInfo></request>
Local Exception Stack: 
Exception [EclipseLink-32] (Eclipse Persistence Services - 2.6.0.v20150309-bf26070): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: Trying to set value [[one, two, three]] for instance variable [collection] of type [java.util.Collection] in the object.  The specified object is not an instance of the class or interface declaring the underlying field, or an unwrapping conversion has failed.
Internal Exception: java.lang.IllegalArgumentException: Can not set java.util.Collection field test2.TestCase2$Payload.collection to test2.TestCase2$RequestMetaInfo
Mapping: org.eclipse.persistence.oxm.mappings.XMLCompositeCollectionMapping[collection]
Descriptor: XMLDescriptor(test2.TestCase2$Payload --> [DatabaseTable(collection)])
    at org.eclipse.persistence.exceptions.DescriptorException.illegalArgumentWhileSettingValueThruInstanceVariableAccessor(DescriptorException.java:703)
    at org.eclipse.persistence.internal.descriptors.InstanceVariableAttributeAccessor.setAttributeValueInObject(InstanceVariableAttributeAccessor.java:188)
    at org.eclipse.persistence.mappings.DatabaseMapping.setAttributeValueInObject(DatabaseMapping.java:1652)
    at org.eclipse.persistence.oxm.mappings.XMLCompositeCollectionMapping.setAttributeValueInObject(XMLCompositeCollectionMapping.java:741)
    at org.eclipse.persistence.internal.oxm.XMLCompositeCollectionMappingNodeValue.setContainerInstance(XMLCompositeCollectionMappingNodeValue.java:265)
    at org.eclipse.persistence.internal.oxm.record.UnmarshalRecordImpl.endDocument(UnmarshalRecordImpl.java:628)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endDocument(AbstractSAXParser.java:745)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:515)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:848)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:777)
    at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1213)
    at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:648)
    at org.eclipse.persistence.internal.oxm.record.XMLReader.parse(XMLReader.java:243)
    at org.eclipse.persistence.internal.oxm.record.SAXUnmarshaller.unmarshal(SAXUnmarshaller.java:978)
    at org.eclipse.persistence.internal.oxm.record.SAXUnmarshaller.unmarshal(SAXUnmarshaller.java:425)
    at org.eclipse.persistence.internal.oxm.record.SAXUnmarshaller.unmarshal(SAXUnmarshaller.java:635)
    at org.eclipse.persistence.internal.oxm.record.SAXUnmarshaller.unmarshal(SAXUnmarshaller.java:706)
    at org.eclipse.persistence.internal.oxm.XMLUnmarshaller.unmarshal(XMLUnmarshaller.java:643)
    at org.eclipse.persistence.jaxb.JAXBUnmarshaller.unmarshal(JAXBUnmarshaller.java:339)
    at test2.TestCase2.main(TestCase2.java:67)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: java.lang.IllegalArgumentException: Can not set java.util.Collection field test2.TestCase2$Payload.collection to test2.TestCase2$RequestMetaInfo
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:164)
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:168)
    at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:55)
    at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:75)
    at java.lang.reflect.Field.set(Field.java:741)
    at org.eclipse.persistence.internal.descriptors.InstanceVariableAttributeAccessor.setAttributeValueInObject(InstanceVariableAttributeAccessor.java:141)
    ... 24 more

Here is a test to reproduce the problem.

package test2;

import org.eclipse.persistence.jaxb.JAXBContext;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
import org.eclipse.persistence.oxm.MediaType;

import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.*;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;

/**
 * Test that fails if Request.content field declared before than Request.metaInfo, but works well if
 * Request.metaInfo goes first in declaration
 *
 * MOXy 2.6.0
 */
public class TestCase2 {
    @XmlRootElement(name = "request")
    @XmlAccessorType(XmlAccessType.FIELD)
    public static class Request<T> {
        @XmlAnyElement(lax = true)
        private T content; // NB!: Causes test failure if declared before metaInfo, if declaration order is changed, things work

        @XmlElement
        private RequestMetaInfo metaInfo;

        public Request() {
        }

        public Request(T content, RequestMetaInfo metaInfo) {
            this.content = content;
            this.metaInfo = metaInfo;
        }

        @Override
        public String toString() {
            return "request = {" + "content=" + content + ", metaInfo=" + metaInfo +'}';
        }

    }
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public static class RequestMetaInfo {
        @XmlElement
        private Boolean confirmation = false;
        @Override
        public String toString() {
            return "requestMetaInfo = {" + "confirmation=" + confirmation +'}';
        }
    }

    @XmlRootElement(name = "collection")
    @XmlAccessorType(XmlAccessType.FIELD)
    public static class Payload<T> {
        @XmlElement
        private Collection collection = new ArrayList();

        public Payload(){}
        public Payload(Collection<T> collection){
            this.collection.addAll(collection);
        }

        public Collection<T> getCollection() {
            return collection;
        }

        @Override
        public String toString() {
            return "Payload = {" + getCollection()+"}";
        }
    }

    // Element name holding value of primitive types
    public static final String VALUE_ELEMENT = "value";
    // Attribute prefix in JSON
    public static final String ATTRIBUTE_PREFIX = "@";
    public static void main(String... args) {
        try {
            for (MediaType type : new MediaType[]{MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) {
                System.out.println("Going with type: " + type);
                JAXBContext context = (JAXBContext) JAXBContextFactory.createContext(
                    new Class[]{
                        Request.class,
                        RequestMetaInfo.class,
                        Payload.class
                    },
                    Collections.emptyMap());

                Marshaller marshaller = context.createMarshaller();
                marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, type);
                marshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
                marshaller.setProperty(MarshallerProperties.JSON_ATTRIBUTE_PREFIX, ATTRIBUTE_PREFIX);
                marshaller.setProperty(MarshallerProperties.JSON_VALUE_WRAPPER, VALUE_ELEMENT);
                marshaller.setProperty(UnmarshallerProperties.JSON_WRAPPER_AS_ARRAY_NAME, true);

                Request original = new Request(
                    new Payload(Arrays.asList("one","two","three")),
                    new RequestMetaInfo()
                );
                System.out.println("Original " + original.toString());

                StreamResult result = new StreamResult(new StringWriter());
                marshaller.marshal(original, result);
                String generated = result.getWriter().toString();
                System.out.println("Marshaled as " + type.getMediaType() + ": " + generated);

                Unmarshaller unmarshaller = context.createUnmarshaller();
                unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, type);
                unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
                unmarshaller.setProperty(UnmarshallerProperties.JSON_ATTRIBUTE_PREFIX, ATTRIBUTE_PREFIX);
                unmarshaller.setProperty(UnmarshallerProperties.JSON_VALUE_WRAPPER, VALUE_ELEMENT);
                unmarshaller.setProperty(UnmarshallerProperties.JSON_WRAPPER_AS_ARRAY_NAME, true);


                Request unmarshalled = unmarshaller.unmarshal(new StreamSource(new StringReader(generated)), Request.class).getValue();
                System.out.println("Unmarshaled " + unmarshalled.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Please any ideas what can be wrong?

2

There are 2 answers

0
Sla On

After some debugging I found out, that bug could be probably in org.eclipse.persistence.internal.oxm.record.UnmarshalRecordImpl.endDocument().

The collection populatedContainerValues didn't get nulled after Payload.collection was unmarshalled. Then, when moxy unmarshalls metaInfo element it tries to process it as it were Payload.collection, assigning collection to Request.metaInfo, which causes exception.

I did ugly workaround (since I can't fix it) and just changed order of fields declaration in Request object, but I believe it will be fixed one day in MOXy.

UPDATE: I filed a bug to MOXy bugzilla: https://bugs.eclipse.org/bugs/show_bug.cgi?id=468337

0
Michał Kozłowski On

I had the same problem. My solution:

@XmlMixed
@XmlAnyElement(lax = true)
private String content;

Enjoy ;)