问题
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 returnorg.foo.bar.Unit
. - If I remove
<xsd:element name="unit" type="bar:unit" />
declaration, JAXB starts to returnorg.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