Generate JAXB class with constant from XML schema file and XJB binding - java

Currently i have to hard-code the schema version (or parse it) manually in java code when working with generated JAXB classes. This can easily lead to mistakes when changing the XML schema version and feels wrong.
What i want is to specify the schema version in the schema and let xjc generate a constant in the corresponding root element class.
I haven't found a JAXB plugin or binding mechanism which i can use to meet these requirements.
example.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:namespace:v1"
targetNamespace="my:namespace:v1"
xmlns="my:namespace:v1"
elementFormDefault="qualified"
attributeFormDefault="unqualified"
<!-- Use this version information in generated class! -->
version="1.0">
<xs:element name="root" type="RootType"/>
<xs:complexType name="RootType">
<xs:sequence>
<xs:element name="name" type="xs:string" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
<xs:attribute name="xsdVersion" type="xs:string" use="required"/>
[...]
</xs:schema>
Generated RootType.class:
[...]
public class RootType {
protected String name;
protected String xsdVersion;
// This shall be generated too
public static final GENERATED_WITH_VERSION = "1.0";
[...]
}

Related

xjc - Error resolving component 'xsi:type'

I am writing an XSD file to automatically generate Java classes to parse an XML file like this:
<pipeline:Pipeline
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:pipeline="http://www.intershop.de/pipeline/2010"
name="ViewCheckoutReview" type="view"
>
<nodes xsi:type="pipeline:Text" nodeID="Text0"/>
</pipeline:Pipeline>
I'm having issues when unmarshalling the xsi:type attribute on the <nodes> element.
These are my XSD files:
schema0.xsd:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:pipeline="http://www.intershop.de/pipeline/2010"
attributeFormDefault="unqualified"
elementFormDefault="qualified"
targetNamespace="http://www.intershop.de/pipeline/2010"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
>
<xs:import schemaLocation="schema1.xsd" />
<xs:element name="Pipeline">
<xs:complexType>
<xs:sequence>
<xs:element ref="nodes" />
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="type" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
</xs:schema>
schema1.xsd:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema
attributeFormDefault="unqualified"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>
<xs:element name="nodes">
<xs:complexType>
<xs:attribute name="nodeID" type="xs:string" use="required" />
<xs:attribute ref="xsi:type" use="required" />
</xs:complexType>
</xs:element>
</xs:schema>
When I execute xjc using these XSD I get this error:
[ant:jaxb] parsing a schema...
[ant:jaxb] [ERROR] src-resolve.4.2: Error resolving component 'xsi:type'. It was detected that 'xsi:type' is in namespace 'http://www.w3.org/2001/XMLSchema-instance', but components from this namespace are not referenceable from schema document 'file:/C:/Projects/test/xsd/schema1.xsd'. If this is the incorrect namespace, perhaps the prefix of 'xsi:type' needs to be changed. If this is the correct namespace, then an appropriate 'import' tag should be added to 'file:/C:/Projects/test/xsd/schema1.xsd'.
[ant:jaxb] line 12 of file:/C:/Projects/test/xsd/schema1.xsd
[ant:jaxb]
[ant:jaxb] [ERROR] src-resolve: Cannot resolve the name 'xsi:type' to a(n) 'attribute declaration' component.
[ant:jaxb] line 12 of file:/C:/Projects/test/xsd/schema1.xsd
[ant:jaxb]
[ant:jaxb] Failed to parse a schema.
I have found a possible solution here on StackOverflow, which consists in adding the namespace attribute in the #XmlAttribute annotation of the generated Java class' field:
#XmlAttribute(name = "type", required = true, namespace = "http://www.w3.org/2001/XMLSchema-instance")
protected String type;
and changing schema1.xsd like this:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema
attributeFormDefault="unqualified"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>
<xs:element name="nodes">
<xs:complexType>
<xs:attribute name="nodeID" type="xs:string" use="required" />
<!-- 'type' attribute is not referenced anymore -->
<xs:attribute name="type" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
</xs:schema>
This solution works but I don't like it, because I would need to change manually, every time I run xjc, the generated Java classes to add that namespace attribute.
What I would like instead is to put something in the XSD file (or in a XJB file maybe?) which adds that namespace attribute automatically when I run xjc.
If I don't add that namespace attribute on the #XmlAttribute annotation, value of type attribute is always null:
public static void main(String[] args) throws JAXBException
{
File xmlFile = new File("D:\\temp\\test.xml");
JAXBContext jaxbContext = JAXBContext.newInstance(Pipeline.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Pipeline pipeline = (Pipeline) unmarshaller.unmarshal(xmlFile);
Nodes node = pipeline.getNodes();
System.out.println(node.getType()); // prints null
}

referencing/including xsd files in a jar

I want to add an xsd file to our project that relies on types defined in another xsd that is in a jar. We use jaxb to generate Java classes from the xsds.
How do i refer to SchemaContainingFooTypeIsInaJAR.xsd so that 'FooType' resolves correctly and the proper Java classes get generated
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:include schemaLocation="SchemaContainingFooTypeIsInaJAR.xsd"/>
<xs:complexType name="FooMoreType">
<xs:complexContent>
<xs:extension base="FooType">
<xs:sequence>
<xs:element name="something" type="xs:boolean" minOccurs="0">
<xs:annotation>
<xs:documentation xml:lang="en">
something something
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
You might be interested in these features:
http://confluence.highsource.org/display/MJIIP/User+Guide#UserGuide-CompilingaschemafromaMavenartifact
http://confluence.highsource.org/display/MJIIP/User+Guide#UserGuide-Usingcatalogs
In short, you can write a catalog file which would point to your schema in a JAR file:
REWRITE_SYSTEM "some/url/a.xsd" "maven:org.jvnet.jaxb2.maven2:maven-jaxb2-plugin-tests-episodes-a!/a.xsd"
DISCLAIMER: I am the principal dev of maven-jaxb2-plugin.
See XML Schema reference and JAXB SchemaFactory source order must follow import order between schemas?
The consensus appears to be: you need to supply your own resolver.
Replace your line:
<xs:include schemaLocation="SchemaContainingFooTypeIsInaJAR.xsd"/>
with
<xs:include schemaLocation="jar:file:///path/to/jar/thing.jar!/path/inside/jar/to/file.xsd"/>

JAXB Unmarshall error

I'm using JAXB on my project, but from time to time, I face some problems that I can't solve. I have setup my environment like this:
Armor Class
package com.fortresswars.entity.component;
#XmlType(name = "armor", namespace = "http://fortresswars.com")
public class ArmorComponent extends AbstractComponent
package-info.java
#XmlSchema(xmlns = #XmlNs(namespaceURI = "http://fortresswars.com", prefix = "fw"), elementFormDefault = XmlNsForm.UNQUALIFIED, namespace = "http://fortresswars.com")
package com.fortresswars.entity.component;
The generated schema header is almost correct:
<xs:schema elementFormDefault="unqualified" version="1.0" targetNamespace="http://fortresswars.com" xmlns:fw="http://fortresswars.com" xmlns:tns="http://fortresswars.com" xmlns:xs="http://www.w3.org/2001/XMLSchema">
The only thing I didn't like is that TNS prefix that JAXB puts there and I can't remove. I'm using an ant task (com.sun.tools.jxc.SchemaGenTask), and I remember reading somewhere that this was the problem.
The rest of the generated scheme follows below. I'll show only the relevant part about armor:
<xs:complexType name="armor">
<xs:complexContent>
<xs:extension base="tns:abstractComponent">
<xs:sequence>
<xs:element name="value" type="xs:short" minOccurs="0"/>
<xs:element name="type" type="tns:armor-type" minOccurs="0"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
And the element that is using the armor component:
<xs:complexType name="character">
<xs:complexContent>
<xs:extension base="tns:thing">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="armor" type="tns:armor"/>
<xs:element name="model" type="tns:model"/>
<xs:element name="status" type="tns:status"/>
<xs:element name="costs" type="tns:costs"/>
</xs:choice>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
To test this, I created a XML Document (see the TNS prefix, I need to put it, or the fw prefix, along with the xmlns:fw also).
<?xml version="1.0" encoding="UTF-8"?><tns:character xmlns:tns="http://fortresswars.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="/home/shirkit/jMonkeyProjects/Fortress Wars/Core/schema/full.xsd">
<armor>
<value>5</value>
<type>NORMAL</type>
</armor>
</tns:character>
But when I'm unmarshalling this document, here's the error I get:
Exception: unexpected element (uri:"", local:"armor"). Expected elements are <{http://fortresswars.com}armor>,<{http://fortresswars.com}attacks>,<{http://fortresswars.com}costs>,<{http://fortresswars.com}model>,<{http://fortresswars.com}abilities>,<{http://fortresswars.com}status>,<{http://fortresswars.com}movement>
I have setup elementFormDefault to UNQUALIFIED, and even though this doesn't work. Why I'm getting this exception? And can I remove TNS prefix from the generated schema?
when you define a namespace prefix for elements of http://fortresswars.com you need to prefix all elements with it, not only character. So this should work
<?xml version="1.0" encoding="UTF-8"?>
<tns:character
xmlns:tns="http://fortresswars.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="/home/shirkit/jMonkeyProjects/Fortress Wars/Core/schema/full.xsd">
<tns:armor>
<tns:value>5</tns:value>
<tns:type>NORMAL</tns:type>
</tns:armor>
</tns:character>
The tns prefix used in the schema is unrelated to what you use as prefix for XML text that you unmarshal. You may choose any other in xmlns:whatyoulike="http://fortresswars.com". The key that connects the elements in the XML document to the definitions in the schema file is the namespace URI, in your case "http://fortresswars.com". If you define your namespace as default namespace, you can omit the prefix on every element:
<?xml version="1.0" encoding="UTF-8"?>
<character
xmlns="http://fortresswars.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="/home/shirkit/jMonkeyProjects/Fortress Wars/Core/schema/full.xsd">
<armor>
<value>5</value>
<type>NORMAL</type>
</armor>
</character>
The namespace prefix in the XSD file helps to avoid name clashes if you wan't to use the schema file together with other schema files that define types or elements with the same name. It does not force you to use it in the xml files you want to unmarshal.
On the other hand, when you marhshal objects to XML, the file package-info.java defines what prefix JAXB uses, but this only works in recent versions of JAXB and it's not always easy to assure that the correct version is in use when your code runs. But you may use a NamespacePrefixMapper to control that.

Generating JAXB class from XSDs with similar attribute names

I use maven-jaxb2-plugin to generate jaxb annotated classes from xsd. I have many xsd files like those:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="A3">
<xs:complexType>
<xs:sequence>
<xs:element name="loginPartner">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="login"/>
<xs:element type="xs:string" name="password"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="A3">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="errorCode"/>
<xs:element type="xs:string" name="errorDescription"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
When I run maven plugin it gives me an error:
[ERROR] Error while parsing schema(s).Location [
file:schema1.xsd{10,16}]. org.xml.sax.SAXParseException: 'A3' is already
defined
Is there any way to fix this?
Actually I have many XSDs representing request/response messages to/from server. I want to simplify creating, validating, parsing messages. Maybe is there another solution for this?
I had a similar problem; I had two separate and independent WSDL (each with several schema definitions in each) that I was running through JAXB (via the maven-jaxb2-plugin) to generate mapping classes.
My WSDL's shared a duplicate schema definition that was causing XJC to choke.
Because they were both independent, I was able to generate JAXB mappings by running two separate executions of the maven-jaxb2-plugin - one for each WSDL (covered here - How can I tell jaxb / Maven to generate multiple schema packages?).
You cannot have conflicting element definitions within the same namespace. This is same as not allowing multiple classes with the same name in a given package in Java. Your best bet is to define them with different names or in different namespaces.
you can rename the second or the first A3 of your xsd in your jaxb binding file
<jaxb:bindings schemaLocation="filePath.xsd" node="/xs:schema">
<jaxb:bindings node="//xs:element[#name='A3']">
<jaxb:property name="SecondA3"/>
</jaxb:bindings>
</jaxb:bindings>

JAXB, xs:any and targetNamespace

I have an XSD that defines the following schema:
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="http://example.com/2010/aa/"
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
xmlns:aa="http://example.com/2010/aa/"
targetNamespace="http://example.com/2010/aa/"
elementFormDefault="qualified">
...
<xs:element name="user" type="aa:User"/>
<xs:complexType name="User">
<xs:sequence>
<xs:element ref="aa:firstName" minOccurs="0" maxOccurs="1"/>
<xs:element ref="aa:lastName" minOccurs="0" maxOccurs="1"/>
...
<xs:any namespace="##targetNamespace" processContents="skip" maxOccurs="unbounded" />
</xs:sequence>
<xs:anyAttribute processContents="skip" />
</xs:complexType>
<xs:element name="profile" type="aa:Profile"/>
<xs:complexType name="Profile">
<xs:sequence>
<xs:element ref="aa:username" minOccurs="0" maxOccurs="1"/>
<xs:element ref="aa:accountStatus" minOccurs="0" maxOccurs="1" />
<xs:element ref="aa:roleid" minOccurs="0" maxOccurs="1"/>
...
<xs:element ref="aa:userid"/>
</xs:sequence>
<xs:anyAttribute processContents="skip" />
</xs:complexType>
When JAXB is marshalling the generated Objects it defines the following:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<user xmlns:ns2="http://example.com/2010/aa/">...</user>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<profile xmlns="http://example.com/2010/aa/">...</profile>
See how one namespace is xmlns:ns2 and the other one is xmlns. This is because all the elements of user are qualified for the aa namespace but the ones defined by the xs:any tag, hence the need to define two namespaces.
Profile doesn't have a xs:any tag and doesn't need to define more than one namespaces. This is my interpretation, since if I remove the xs:any from the user definition it will remove the ns2 from the generated XML.
How can I tell JAXB that both the targetNamespace and aa are the same namespace so it doesn't include both?
You could try to use a NamespacePrefixMapper to override how the prefixes are generated in the first place:
NamespacePrefixMapper mapper = new NamespacePrefixMapper() {
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
return "";
}
};
marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", mapper);
I'm returning "" there, so there will be only a default prefix; implement a more sophisticated version as required.
This does create a dependency on a Sun class, which is a problem caused by JAXB. Please review this other post. The answer at the bottom shows how to modify package-info.java to achieve the same.
Alternatively, instead of using a proprietary Metro JAXB extension, you could use MOXy JAXB. MOXy will use the namespace prefixing defined in the #XmlSchema package level annotation.
For more information see:
JAXB marshalling problem - probably namespace related
Jaxb2Marshaller creating JAXBContext with empty namespace URI
Define Spring JAXB namespaces without using NamespacePrefixMapper

Categories