java - 解码时,目标 Java 对象中的 MOXy 字段顺序很重要

标签 java jaxb moxy xmlanyelement

MOXy 中似乎存在错误。当类 Request 中的字段声明为元信息然后是内容时,下面的代码片段可以正常工作,但是当字段以相反的顺序声明时(首先是内容,然后是元信息),测试会在解码时失败并出现异常。

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

这是重现该问题的测试。

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

请问有什么想法可能是错误的吗?

最佳答案

经过一些调试,我发现该错误可能位于 org.eclipse.persistence.internal.oxm.record.UnmarshalRecordImpl.endDocument() 中。

解码 Payload.collection 后,集合 populatedContainerValues 并未被清空。然后,当 moxy 解码 metaInfo 元素时,它会尝试像 Payload.collection 一样处理它,将集合分配给 Request.metaInfo,这会导致异常。

我做了一个丑陋的解决方法(因为我无法修复它),只是更改了 Request 对象中字段声明的顺序,但我相信有一天它会在 MOXy 中修复。

更新: 我向 MOXy bugzilla 提交了一个错误:https://bugs.eclipse.org/bugs/show_bug.cgi?id=468337

关于java - 解码时,目标 Java 对象中的 MOXy 字段顺序很重要,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30434207/

相关文章:

java - 当从另一个对话框调用对话框时,软键盘不会在对话框关闭时隐藏

java - 如何在 Java 中对返回 boolean 值的两个并行线程执行短路评估?

JAXB schemagen 如何为两个字段生成选择?

web-services - 没有无限循环的 JAX WS 服务上的 Jpa 实体

jaxb - 动态moxy中枚举中的ClassNotFound异常

java - 指向 Java 中的用户配置文件框架的指针

java - 如何在没有绑定(bind)异常的情况下打印一维数组中的额外元素?

java - XJC 插件定制

java - JAXB 中没有前缀的 namespace

java - 支持 MOXy @XmlPath 表达式吗?