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>
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>
日付の形式も自由自在。ただしおすすめはできないけど。
読みたいこともあるよね。
- 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+"));
- }