JAXBとJoda-Timeを連携してみましょう。
以下のクラスとXMLをBindするのが目標です。
public class Foo { private DateTime bar; private DateTime baz; }
<foo> <bar>2011-2-3</bar> <baz>2010-3-4T05:06:07</baz> </foo>
JAXBでは面倒な点がいくつかありました。
1. @XmlType(propOrder)にフィールドを列挙する必要がある。
2. xs:dateはXmlGregorianCalendarに割り当てられている。
うーん、面倒ですね。GroovyだったらとかClojureだったらとか、思わずにはいられない。
それぞれの対策を書いていきます。
1. @XmlType(propOrder)にフィールド名を列挙する必要がある。
アノテーションでは順序が保たれないため、フィールドとpropOrderの双方に書く必要がある。
とりあえず生成するGroovyコードを書いたのですが、調べたらxjcなんてものがあったので、移植してみました。
かんたんなスキーマを書いてみます。
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="foo"> <xs:complextype> <xs:sequence> <xs:element name="bar" type="xs:date" /> <xs:element name="baz" type="xs:dateTime" /> </xs:sequence> </xs:complextype> </xs:element> </xs:schema>生成されたbar,bazの型は、見慣れないXmlGregorianCalendar なんだこれ。
2. xs:dateはXmlGregorianCalendarに割り当てられている。
http://weblogs.java.net/blog/kohsuke/archive/2006/03/how_do_i_map_xs.html
xjcの開発者であるkohsukeのブログでXmlGregorianCalendarを使わない方法があったので参考にしました。
<xs:schema elementFormDefault="qualified" version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="2.0" targetNamespace="calendar-schemalet"> <xs:annotation><xs:appinfo> <jaxb:globalBindings> <jaxb:javaType name="java.util.Calendar" xmlType="xs:date" parseMethod="javax.xml.bind.DatatypeConverter.parseDate" printMethod="javax.xml.bind.DatatypeConverter.printDate" /> </jaxb:globalBindings> </xs:appinfo></xs:annotation> </xs:schema>
なるほど。変換するメソッドを呼び出すことができるわけですね。
この例では準備されたメソッドですが、任意のメソッドを呼び出すことができます。
標準のはCalendarなので、java.util.Dateに変換するのを書けばいいだけ。
簡単ですね。
これでどんな型でも自由自在。
xs:appinfoを別のファイルに移し、xs:includeすると共通化できて便利です。
うまく名前空間を利用するとインクルードなしでもいけるんでしょうが。
ここで一つ別の問題が出てきました。
3. 余計なAdapterが生成される
Adapter1やAdapter2が勝手に生成されてしまいます。
できればこれらの名前も指定したいところ。
最終的にはこうなりました。
<xs:schema xmlns:xs="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" jaxb:extensionBindingPrefixes="xjc" jaxb:version="2.0"> <xs:annotation> <xs:documentation>custom datetime adapter</xs:documentation> <xs:appinfo> <jaxb:globalBindings> <xjc:javaType name="org.joda.time.DateTime" adapter="hikoz.xml.DateAdapter" xmlType="xs:date"/> <xjc:javaType name="org.joda.time.DateTime" adapter="hikoz.xml.DateTimeAdapter" xmlType="xs:dateTime"/> </jaxb:globalBindings> </xs:appinfo> </xs:annotation> <xs:element name="foo"> <xs:complexType> <xs:sequence> <xs:element name="bar" type="xs:date" /> <xs:element name="baz" type="xs:dateTime" /> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>Adapterはこんな感じ。
日付の形式も自由自在。ただしおすすめはできないけど。
読みたいこともあるよね。
public class DateTimeAdapter extends XmlAdapter<String, Datetime> { private static final DateTimeFormatter YMDHMS = DateTimeFormat.forPattern("yyyy/MM/dd HH:mm:ss"); @Override public DateTime unmarshal(String v) throws Exception { return YMDHMS.parseDateTime(v); } @Override public String marshal(DateTime v) throws Exception { return YMDHMS.print(v); } }
以下はテストコードと、xjcの実行。
Antタスクもあったんだけど、最近build.xmlを書いてないから構文を忘れた。
Gradleかわいいよ
@Test public void readwrite() throws Exception { StringWriter sw = new StringWriter(); Foo foo = new Foo(); foo.setBar(new DateTime(2011, 2, 3, 4, 5, 6, 7)); foo.setBaz(new DateTime(2012, 3, 4, 5, 6, 7, 8)); JAXB.marshal(foo, sw); StringReader sr = new StringReader(sw.toString()); Foo foo2 = JAXB.unmarshal(sr, Foo.class); assertThat(foo2.getBar(), is(new DateTime(2011, 2, 3, 0, 0, 0, 0))); assertThat(foo2.getBaz(), is(new DateTime(2012, 3, 4, 5, 6, 7, 0))); } public static void main(String[] args) throws Exception { com.sun.tools.xjc.Driver .main("-no-header -extension -d src/test/java -p hikoz.xml src/test/java/hikoz/xml/foo.xsd" .split("\\s+")); }