While there are plenty of trivial examples of using an XmlAdapter to convert a non-mappable Java object to XML all the ones I read utilized a complete handcoded JAXB object model and not one generated from XJC. After much research and head banging I came across two methods of introducing a XmlAdapter or any other valid hand coded JAXB object into the XJC generate code model using JAXB XJC customizations in an external binding file.
Complete Simple or Complex Type Replacement using jaxb:class ref="..."
The standard <jaxb:class> custom binding actually supports a ref attribute so that a Simple or Complex type can be directly mapped to an existing Java class. The class must be a valid JAXB annotated object or mapped to an XmlAdapter via an XmlJavaTypeAdapter annotation at the package or class level. This technique is actually used extensively inside JAXB episodes for multi schema separate compilation. This seems to be an intuitive means of mapping hand coded JAXB objects into XJC generated code but surprisingly enough there is little documentation on it. If one does a google search on "JAXB bind customization" the first link to the Oracle documentation site completely omits all references to the ref attribute. Below is an example of how I used the jaxb:class ref attribute to bind interfaces to the XJC object model:
XSD:
<simpleType name="srcIdType">
<restriction base="ID">
<pattern value="s\d+" />
</restriction>
</simpleType>
<simpleType name="srcIdRefType">
<restriction base="IDREF">
<pattern value="s\d+" />
</restriction>
</simpleType>
<complexType name="sourceType">
<attribute name="src" type="tns:srcIdType" use="required" />
</complexType>
XJB:
<jxb:bindings node="//xs:simpleType[@name='srcIdType']">
<jxb:class ref="somepackage.SrcId" />
</jxb:bindings>
<jxb:bindings node="//xs:simpleType[@name='srcIdRefType']">
<jxb:class ref="somepackage.SrcIdRef" />
</jxb:bindings>
Custom Java:
@XmlJavaTypeAdapter(SrcIdAdapter.class)
public interface SrcId {
String id();
}
@XmlJavaTypeAdapter(SrcIdRefAdapter.class)
public interface SrcIdRef {
String id();
}
public class SourceId implements SrcId, SrcIdRef {
public SourceId() {
}
public SourceId(String id) {
this.id = id;
}
private String id;
@Override
public String id() {
return id;
}
public static class SrcIdAdapter extends XmlAdapter<String, SrcId> {
@Override
public String marshal(SrcId id) throws Exception {
if (id != null) {
return id.id();
}
return null;
}
@Override
public SrcId unmarshal(String id) throws Exception {
return new SourceId(id);
}
}
public static class SrcIdRefAdapter extends XmlAdapter<String, SrcIdRef> {
@Override
public String marshal(SrcIdRef id) throws Exception {
if (id != null) {
return id.id();
}
return null;
}
@Override
public SrcIdRef unmarshal(String id) throws Exception {
return new SourceId(id);
}
}
}
XJC Generated Java:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "sourceType", namespace = "somenamespace")
public class Source {
...
@XmlAttribute(name = "src", required = true)
protected SrcId src;
Using the custom code:
Source s = new Source();
s.setSrcId(new SourceId("b0i0"));
Here is an example of binding a default java class to a simpletype and using a package level annotation to associate the XMLAdapter
XSD:
<simpleType name="bondURIType">
<restriction base="anyURI" />
</simpleType>
<complexType name="executionConfigType" abstract="true">
<attribute name="uri" type="tns:bondURIType" use="required" />
</complexType>
XJB:
<jxb:bindings node="//xs:simpleType[@name='bondURIType']">
<jxb:class ref="java.net.URI" />
</jxb:bindings>
Custom Java:
//URIAdapter.java
public class URIAdapter extends XmlAdapter<String, URI> {
@Override
public String marshal(URI uri) throws Exception {
if (uri != null) {
return uri.toString();
}
return null;
}
@Override
public URI unmarshal(String uri) throws Exception {
return URI.create(uri);
}
}
//package-info.java
@XmlJavaTypeAdapter(URIAdapter.class)
@XmlSchema(namespace = "somenamespace", xmlns = { @XmlNs(namespaceURI = "somenamespace", prefix = "myprefix") }, elementFormDefault = XmlNsForm.QUALIFIED)
package somepackage; //needs to match XJC package declaration, XJC -npa flag should be set to not generate a package-info.java file
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import somepackage.URIAdapter;
XJC Generated Java:
@XmlAttribute(name = "uri", required = true)
protected URI uri;
Property Replacement using jaxb:property/jaxb:baseType name=".."
Another scenario to consider is when one would like to use XJC to generate JAXB objects from a schema but then use a XmlAdapter to map a non-compliant Java object to the XJC generated classes. For example, I wanted to bind a Java Map to a custom complexType I wrote. It turns out that if one wants to change the Java type of a property one can use the property/baseType combination. Again, this was not very intuitive and I didn't find many examples on the Internet of this use case.
XSD:
<element name="Root">
<complexType>
<sequence>
<element ref="tns:TestVariables" minOccurs="1" maxOccurs="1"/>
</sequence>
</complexType>
</element>
<element name="TestVariables">
<complexType>
<sequence>
<element name="TestVariable" minOccurs="0" maxOccurs="unbounded">
<complexType>
<attribute name="name" type="string" />
<attribute name="value" type="string" />
</complexType>
</element>
</sequence>
</complexType>
</element>
XJB:
<jxb:bindings node="//xs:element[@name='Root']//xs:element[@ref='tns:TestVariables']">
<jxb:property>
<jxb:baseType name="test.TestMap" />
</jxb:property>
</jxb:bindings>
Custom Java:
@XmlJavaTypeAdapter(TestAdapter.class)
public class TestMap extends LinkedHashMap<String,String>{
}
public class TestAdapter extends XmlAdapter<TestVariables, TestMap> {
@Override
public TestMap unmarshal(TestVariables value) {
TestMap map = new TestMap();
for (TestVariable var : value.getTestVariable())
map.put(var.name, var.value);
return map;
}
@Override
public TestVariables marshal(TestMap map) {
TestVariables vars = new TestVariables();
for (Map.Entry entry : map.entrySet()) {
TestVariable var = new TestVariable();
var.name = entry.getKey();
var.value = entry.getValue();
vars.getTestVariable().add(var);
}
return vars;
}
}
XJC Generated Java:
@XmlRootElement(name = "Root", namespace = "urn:test")
public class Root {
@XmlElement(name = "TestVariables", namespace = "urn:test", required = true, type = TestVariables.class)
protected TestMap testVariables;
Using the custom code:
Root root = new Root();
TestMap map = new TestMap();
root.setTestVariables(map);
..
m.marshal(root, writer);
There it is, two ways of injecting your own custom JAXB objects into the XJC generated object model. Now one can enjoy the full benefit of using a XML schema first development approach utilizing XJC to rebuild corresponding Java objects all the while having the flexibility to drop down to hand crafted JAXB objects when appropriate.
thanks a lot! i struggled a lot with trying to make xjc to generate map instead of list and this is exactly what i needed. not sure if there is a simpler solution for my particular case, but at least it's working :). thanks again!
ReplyDeleteI'm trying to copy your example, but I see an error when compiling this:
ReplyDeletesomepackage/SourceId.java:7: illegal start of type
public class SourceId implements {
^
Is that a typo? Looks like the interface is missing...
it didn't come out right in my last post:
ReplyDeletepublic class SourceId implements <SrcId, SrcIdRef> {
Yes, you are correct, it should have been
Deletepublic class SourceId implements SrcId, SrcIdRef{
I corrected the post. Thanks!
This comment has been removed by the author.
ReplyDeleteDear Aaron,
ReplyDeletethank you for your time writing this blog. I studied your 3 examples carefully, got them up and running with NetBeans 8 and Java 7-JAXB and learned something out of it!
However I also noticed behaviours that I think you might have overlooked:
1) The 3rd example ("Property Replacement using jaxb:property/jaxb:baseType...") has 2 issues: a) the Adapter never gets called when annotated on class TestMap. Only when it is annotated at the actual field of Type TestMap is the Adapter called. b) Because of a) and because there apparently exists a default mapping for Maps in JAXB the marshalled XML is not schema-conform. As far as I can tell, the only way to have the adapter called is by annotating the field in the genrated code.
2) The second example ("binding a default java class to a simpletype and using a package level annotation..." a) doesn't need an adapter. If xs:anyURI is bound to a java.net.URI then it will be marshalled/unmarshalled withour further ado. b) again, annotating the adapter in package-info.java results in the adapter NOT being called.
Since I need JAXB customization for SOAP-Webservices-WSDLs it is crucial that the marshaled XML validates. Therefore I'll have to bite the bullet and annotate xjc-generated code :-( Bt I see no other way. If I am missing something, I'll be glad to learn. If on the other hand you are interested in my sample-Projects (NetBeans-Projects with javax.xml.validation.Schema ...) I'll be glad to send them to you.
sincerely
Chris457