XMLSchema (format.xsd)
The following is the XML schema that will be used for this example. The interesting portion is a type called NumberCodeValueType. This type has a specified pattern requiring it be a seven digit number. This number can have leading zeros which would not be marshalled by JAXB's default conversion of numbers.
<?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="root"> <xs:complexType> <xs:sequence> <xs:element name="number" type="NumberCodeValueType" /> </xs:sequence> </xs:complexType> </xs:element> <xs:simpleType name="NumberCodeValueType"> <xs:restriction base="xs:int"> <xs:pattern value="[0-7]{7}" /> </xs:restriction> </xs:simpleType> </xs:schema>
NumberFormatter
package blog.xmladapter.bindings; public class NumberFormatter { public static String printInt(Integer value) { String result = String.valueOf(value); for(int x=0, length = 7 - result.length(); x<length; x++) { result = "0" + result; } return result; } public static Integer parseInt(String value) { return Integer.valueOf(value); } }
bindings.xml
We will leverage a JAXB bindings file to reference the formatter:
<jxb:bindings xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:jxb="http://java.sun.com/xml/ns/jaxb" version="2.1"> <jxb:bindings schemaLocation="format.xsd"> <jxb:bindings node="//xs:element[@name='number']"> <jxb:property> <jxb:baseType> <jxb:javaType name="java.lang.Integer" parseMethod="blog.xmladapter.bindings.NumberFormatter.parseInt" printMethod="blog.xmladapter.bindings.NumberFormatter.printInt" /> </jxb:baseType> </jxb:property> </jxb:bindings> </jxb:bindings> </jxb:bindings>
XJC Call
The bindings file is referenced in the XJC call as:
xjc -d out -p blog.xmladapter.bindings -b bindings.xml format.xsd
Adapter1
This will cause an XmlAdapter to be created that leverages the formatter:
package blog.xmladapter.bindings; import javax.xml.bind.annotation.adapters.XmlAdapter; public class Adapter1 extends XmlAdapter<String, Integer> { public Integer unmarshal(String value) { return (blog.xmladapter.bindings.NumberFormatter.parseInt(value)); } public String marshal(Integer value) { return (blog.xmladapter.bindings.NumberFormatter.printInt(value)); } }
Root
The XmlAdapter will be referenced from the domain object using the @XmlJavaTypeAdapter annotation:
package blog.xmladapter.bindings; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "", propOrder = { "number" }) @XmlRootElement(name = "root") public class Root { @XmlElement(required = true, type = String.class) @XmlJavaTypeAdapter(Adapter1 .class) protected Integer number; public Integer getNumber() { return number; } public void setNumber(Integer value) { this.number = value; } }
Demo
Now if we run the following demo code:
package blog.xmladapter.bindings; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; public class Demo { public static void main(String[] args) throws Exception { JAXBContext jc = JAXBContext.newInstance(Root.class); Root root = new Root(); root.setNumber(4); Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(root, System.out); } }
Output
We will get the desired output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <root> <number>0000004</number> </root>
Further Reading
If you enjoyed this post, then you may also be interested in:
Hi Blaise, question, why is it necessary to add a "name" attribute to the jxb:javaType element in the bindings.xml file? What's it for? Thanks!
ReplyDeleteHi Glen,
ReplyDeleteThe "name" attribute is a required attribute on the jxb:javaType element. The schema to Java compiler will throw an exception if it is not present.
I don't have a good answer as to why the schema to Java compiler requires it, as it could by default derive the Java type from the XML schema type.
-Blaise
Thanks for sharing. Learning how to override parseMethod and printMethod was very useful for one of my project.
ReplyDelete