How to refactor XSD so that unmarshalling does not return JAXBElement

心已入冬 提交于 2019-12-08 16:07:44

问题


I have the following schema:

<xsd:schema xmlns:bar="http://www.foo.org/bar"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:annox="http://annox.dev.java.net"
        xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
        targetNamespace="http://www.foo.org/bar"
        jaxb:extensionBindingPrefixes="annox" jaxb:version="2.1" elementFormDefault="qualified">

    <xsd:element name="unit" type="bar:unit" />

    <xsd:complexType name="unit">
        <xsd:annotation>
            <xsd:appinfo>
                <annox:annotate>@javax.xml.bind.annotation.XmlRootElement(name="unit")</annox:annotate>
            </xsd:appinfo>
        </xsd:annotation>
            <xsd:sequence>
            <xsd:any processContents="skip" />
        </xsd:sequence>
    </xsd:complexType>

</xsd:schema>

When I unmarshall this XML

<unit xmlns="http://www.foo.org/bar">
    <text>Name</text>
</unit>

the returned object is javax.xml.bind.JAXBElement<Unit>, however I would like to get org.foo.bar.Unit back. I need this because unmarshalling in my case happens implicitly by JAX-RS provider or SpringWeb.

Observations:

  • If I remove/replace <xsd:any processContents="skip" /> declaration, JAXB starts to return org.foo.bar.Unit.
  • If I remove <xsd:element name="unit" type="bar:unit" /> declaration, JAXB starts to return org.foo.bar.Unit (although one need to disable validation during unmarshalling).

Thus I would say that given XSD is the smallest XSD that demonstrates the problem.

Questions: Why JAXB wraps org.foo.bar.Unit into JAXBElement for above combination? From what I see, there is no way the XSD type unit can have tag name different from unit, so why JAXB needs this factory method?

@XmlElementDecl(namespace = "http://www.foo.org/bar", name = "unit")
public JAXBElement<Unit> createUnit(Unit value) { ... }

The project demonstrating the problem for JAXB 2.2.7 is here. When run it outputs the following:

Running org.foo.bar.UnitTest
>>> Class is: javax.xml.bind.JAXBElement
>>> XML is: <?xml version="1.0" encoding="UTF-8" standalone="yes"?><unit xmlns="http://www.foo.org/bar"><text>Name</text></unit>
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.318 sec <<< FAILURE!

回答1:


Adding to Ian's answer, Any complex element that is named by root element will have factory method annotated with @XmlElementDecl().

You can resolve this, by moving the complex type declaration inline like below.

<xsd:schema xmlns= "http://www.foo.org/bar" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:annox="http://annox.dev.java.net" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
    targetNamespace="http://www.foo.org/bar" jaxb:extensionBindingPrefixes="annox"
    jaxb:version="2.1" elementFormDefault="qualified">
    <xsd:element name="unit">
        <xsd:complexType>
            <xsd:annotation>
                <xsd:appinfo>
                    <annox:annotate>@javax.xml.bind.annotation.XmlRootElement(name="unit")
                    </annox:annotate>
                </xsd:appinfo>
            </xsd:annotation>
            <xsd:sequence>
                <xsd:any processContents="skip" />
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

ObjectFactory.class (no JAXBElement factory method generated here)

@XmlRegistry
public class ObjectFactory {


    /**
     * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: org.foo.bar
     * 
     */
    public ObjectFactory() {
    }

    /**
     * Create an instance of {@link Unit }
     * 
     */
    public Unit createUnit() {
        return new Unit();
    }

}

Test class:

@Test
public void testUnmarshalling() throws JAXBException, SAXException {
    JAXBContext context = JAXBContext.newInstance(Unit.class);

    Unmarshaller unmarshaller = context.createUnmarshaller();

    unmarshaller.setSchema(SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
            .newSchema(new StreamSource(getClass().getClassLoader().getResourceAsStream("common.xsd"))));

    Object unit = unmarshaller.unmarshal(getClass().getResourceAsStream("unit.xml"));

    System.out.println(">>> Class is: " + unit.getClass().getName());

    StringWriter writer = new StringWriter();
    context.createMarshaller().marshal(unit, writer);

    System.out.println(">>> XML is: " + writer.toString());

    //assertTrue(unit instanceof Unit);
}

Test xml:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<unit xmlns="http://www.foo.org/bar">
    <text>Name</text>
</unit>

output :

>>> Class is: org.foo.bar.Unit
>>> XML is: <?xml version="1.0" encoding="UTF-8" standalone="yes"?><unit xmlns="http://www.foo.org/bar"><text>Name</text></unit>



回答2:


From what I see, there is no way the XSD type unit can have tag name different from unit, so why JAXB needs this factory method?

On the contrary - you will always get a JAXBElement when your schema uses a named complex type by reference. With a named complex type there's always the possibility that the type might be used for a different element (maybe in another importing schema) or that the element may use a subtype of the named type rather than the top type itself.

Unwrapped root elements are used when the global xsd:element declaration has a nested anonymous complexType, as in that scenario the unmarshaller knows that those kinds of substitutions can't happen.




回答3:


If you are doing something like this:

JAXBContext jaxbContext = JAXBContext.newInstance(Unit.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();

JAXBElement<Unit> root = jaxbUnmarshaller.unmarshal(new StreamSource(
        file), Unit.class);
Unit unit = root.getValue();

Try maybe:

Unit unit = JAXBIntrospector.getValue(jaxbUnmarshaller.unmarshal(new StreamSource(
        file), Unit.class);



回答4:


Use this method to work with, either a javax.xml.bind.JAXBElement instance or an instance of @XmlRootElement annotated Java class:

public <T> T unmarshal(Source queryResults, String modelPackages) {
        T resultObject = null;
        try {
            JAXBContext jc = JAXBContext.newInstance(modelPackages);
            Unmarshaller u = jc.createUnmarshaller();
            resultObject = (T) JAXBIntrospector.getValue(u.unmarshal(queryResults));
        } catch (JAXBException e) {
            LOG.error(e.getMessage(), e);
        } catch (ClassCastException e) {
            LOG.error(e.getMessage(), e);
        }
        return resultObject;
    }

Edit:

You have reason, I putted this code because is a code I did for a project and that I think that is more reusable.

So for you question:

Why JAXB wraps org.foo.bar.Unit into JAXBElement for above combination?

because you are telling it to do it with <xsd:any processContents="skip" /> :)

Well, when you put this in your XSD you acts like if you use the annotation:

@XmlAnyElement(lax=false)

With that tag / annotation you are saying to JAXB: 'here comes something, a bunch of nodes, (@XmlAnyElement) that you should not parse never; let it as DOM object, please (lax=false)' try with:

processContents=lax

This way it should try to parse your xml to domain object and will return Unit if it can find it or JAXBElement otherway or:

processContents=strict

Which will try to parse it to your domain object




回答5:


After some googling I have found an answer, which is basically given by Kohsuke Kawaguchi in Why does JAXB put @XmlRootElement sometimes but not always. It turned out that the decision to add @XmlRootElement annotation is antagonist to generation of helper factory method. One should enable <xjc:simple /> optimization and JAXB will assume that all elements are root elements:

<xsd:schema xmlns:bar="http://www.foo.org/bar"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
            xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
            targetNamespace="http://www.foo.org/bar"
            jaxb:extensionBindingPrefixes="xjc" jaxb:version="2.1" elementFormDefault="qualified">

    <xsd:annotation>
        <xsd:appinfo>
            <jaxb:globalBindings>
                <xjc:simple />
            </jaxb:globalBindings>
        </xsd:appinfo>
    </xsd:annotation>

    <xsd:element name="unit" type="bar:unit" />

    <xsd:complexType name="unit">
        <xsd:sequence>
            <xsd:any processContents="skip" />
        </xsd:sequence>
    </xsd:complexType>

</xsd:schema>


来源:https://stackoverflow.com/questions/39528236/how-to-refactor-xsd-so-that-unmarshalling-does-not-return-jaxbelement

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!