JAXB 2.0

JavaXML処理のAPIは、はじのうちは、

を興味本位で勉強したり、仕事では、

  • JDOM
  • Apache Commons Digester(なつかしいStrutsの内部でも使われてたから)
  • Apache Commons Configuration(XPathXML設定ファイルの値を取得できる)

使ってみたり、特に、Axis2をつかったときは、

  • XMLBeans
  • JiBX

をちらっとやったり(結局は、Axis2独自のADBを使った)いわゆるXMLデータバインディングという類いのライブラリをつかったりしていた。

Java 6がでて随分たつけど、JAXBさわったことがなかった。今の職場でJAX-RSが使われてて、アノテーションを指定してXMLを処理するのに興味がでてきたので、ためしにJAXBをさわってみる。

ドキュメントはここなんだろうか。
http://java.sun.com/javase/ja/6/docs/ja/technotes/guides/xml/jaxb/index.html

Javadocは、
http://java.sun.com/javase/ja/6/docs/ja/api/javax/xml/bind/Marshaller.html
http://java.sun.com/javase/ja/6/docs/ja/api/javax/xml/bind/Unmarshaller.html
http://java.sun.com/javase/ja/6/docs/ja/api/javax/xml/bind/JAXB.html
あたりをよめばいけそうだ。
JAXBクラスが、便利クラスなわけか。

アノテーションの使い方など、例を使ったJAXBの説明はここが一番わかりやすかった気がする。
http://jaxb.java.net/guide/


ドキュメントを調べたので、実装してみよう。googleでJAXBを調べてみると、たいていは、XML Schemaを書いて、xjcコマンドで、Javaのクラスを生成してというのが多い。自分は、XML Schemaに明るくないので、手作業でJavaのクラスを書くことにしよう。

ちょっと準備

XMLJavaのオブジェクトにするクラスを準備する。
こいつは、共通。
XMLの読み込んでJavaマッピングしたい場合は、JAXBクラスのunmarshallを使う。
その逆は、marshall

package note.jaxb20;

import java.io.File;

import javax.xml.bind.JAXB;

public final class XmlBinder {

    private XmlBinder() {

    }

    public static <T> T xmlToObjcet(File file, Class<T> clazz) {
        return JAXB.unmarshal(file, clazz);
    }

}

XMLJavaのクラス

互いに対応するXMLJavaのクラスをそれぞれ用意する。

<?xml version="1.0" encoding="UTF-8"?>
<persons>
    <person no="1">
        <first-name>テスト名1</first-name>
        <family-name>テスト姓1</family-name>
        <!-- 日時は、W3C-DTFのフォーマットでXMLに記述してあれば、 勝手にjava.util.Dateに変換してくれるようだ。 -->
        <birthday>1980-01-23</birthday>
        <sex></sex>
        <favorite-things>
            <thing></thing>
            <thing></thing>
        </favorite-things>
    </person>
    <person no="2">
        <first-name>テスト名2</first-name>
        <family-name>テスト姓2</family-name>
        <!-- 日時は、W3C-DTFのフォーマットでXMLに記述してあれば、 勝手にjava.util.Dateに変換してくれるようだ。 -->
        <birthday>1990-01-23</birthday>
        <sex></sex>
        <favorite-things>
            <thing></thing>
            <thing>ファッション</thing>
            <thing>音楽</thing>
            <thing>旅行</thing>
        </favorite-things>
    </person>
</persons>
package note.jaxb20.example01;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;

@XmlRootElement(name = "persons")
public class Persons {

    private List<Person> personList = new ArrayList<Person>();

    @XmlElement(name = "person")
    public List<Person> getPersonList() {
        return personList;
    }

    public void setPersonList(List<Person> personList) {
        this.personList = personList;
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }

    @Override
    public boolean equals(Object obj) {
        return EqualsBuilder.reflectionEquals(this, obj);
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }

}
package note.jaxb20.example01;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlType;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;

@XmlType(name = "person")
public class Person {

    private Integer no;
    private String firstName;
    private String familyName;
    private Date birthDay;
    private String sex;
    private List<Thing> favoriteThings = new ArrayList<Thing>();

    @XmlAttribute
    public Integer getNo() {
        return no;
    }

    public void setNo(Integer no) {
        this.no = no;
    }

    @XmlElement(name = "first-name")
    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    @XmlElement(name = "family-name")
    public String getFamilyName() {
        return familyName;
    }

    public void setFamilyName(String familyName) {
        this.familyName = familyName;
    }

    @XmlElement(name = "birthday")
    public Date getBirthDay() {
        return birthDay;
    }

    public void setBirthDay(Date birthDay) {
        this.birthDay = birthDay;
    }

    @XmlElement
    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @XmlElement(name = "thing")
    @XmlElementWrapper(name = "favorite-things")
    public List<Thing> getFavoriteThings() {
        return favoriteThings;
    }

    public void setFavoriteThings(List<Thing> favoriteThings) {
        this.favoriteThings = favoriteThings;
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }

    @Override
    public boolean equals(Object obj) {
        return EqualsBuilder.reflectionEquals(this, obj);
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }

}
package note.jaxb20.example01;

import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;


@XmlType(name = "thing")
public class Thing {

    private String name;

    @XmlValue
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }

    @Override
    public boolean equals(Object obj) {
        return EqualsBuilder.reflectionEquals(this, obj);
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }

}
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import java.io.File;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import note.jaxb20.XmlBinder;
import note.jaxb20.example01.Person;
import note.jaxb20.example01.Persons;
import note.jaxb20.example01.Thing;
import note.jaxb20.example02.Code;
import note.jaxb20.example02.CodeList;

import org.apache.commons.lang.time.DateUtils;
import org.junit.Test;

public class XmlBinderTest {

    private File dir = new File("test-data");

    public void setUp() {

    }

    @Test
    public void testExample01() {
        Persons actual = XmlBinder.xmlToObjcet(new File(dir, "persons.xml"),
                Persons.class);

        List<Person> personList = actual.getPersonList();
        assertNotNull(personList);
        assertEquals(2, personList.size());

        Person person0 = personList.get(0);
        assertNotNull(person0);
        assertEquals(Integer.valueOf(1), person0.getNo());

        Person person1 = personList.get(1);
        assertNotNull(person1);
        assertEquals(Integer.valueOf(2), person1.getNo());
        assertEquals(parse("1990-01-23", "yyyy-MM-dd"),
                person1.getBirthDay());

        List<Thing> favoriteThings = person1.getFavoriteThings();
        assertNotNull(favoriteThings);
        assertEquals(4, favoriteThings.size());
        assertEquals("金", favoriteThings.get(0).getName());
    }

    private static Date parse(String dateAsString, String pattern) {
        try {
            return DateUtils.parseDate(dateAsString, new String[] { pattern });
        }
        catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

アノテーションの使い方で、多少、試行錯誤が必要だったものの、
あっさりバインディングできた。
らくちん!XML Schemaからツールで自動生成したのでない手書きのクラスやアノテーションでもちゃんと動くもんだなー。

こんなのもできる

entry, key, valueという要素名を使えば、デフォルトでもMapにXMLを読み込んでくれる。
ただし、型はMapインタフェースでなくて、HashMapのようにクラスじゃないとだめなようだ。

<?xml version="1.0" encoding="UTF-8"?>
<code-list>
    <code name="sex">
        <entries>
            <entry>
                <key>1</key>
                <value></value>
            </entry>
            <entry>
                <key>2</key>
                <value></value>
            </entry>
        </entries>
    </code>
    <code name="size">
        <entries>
            <entry>
                <key>1</key>
                <value></value>
            </entry>
            <entry>
                <key>2</key>
                <value></value>
            </entry>
            <entry>
                <key>3</key>
                <value></value>
            </entry>
        </entries>
    </code>
</code-list>
package note.jaxb20.example02;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;

@XmlRootElement(name = "code-list")
public class CodeList {

    private List<Code> codeList = new ArrayList<Code>();

    @XmlElement(name = "code")
    public List<Code> getCodeList() {
        return codeList;
    }

    public void setCodeList(List<Code> codeList) {
        this.codeList = codeList;
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }

    @Override
    public boolean equals(Object obj) {
        return EqualsBuilder.reflectionEquals(this, obj);
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }

}
package note.jaxb20.example02;

import java.util.HashMap;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlType;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;

@XmlType(name = "code")
public class Code {

    private String name;

    private HashMap<Integer, String> entries = new HashMap<Integer, String>();

    @XmlAttribute
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @XmlElementWrapper
    public HashMap<Integer, String> getEntries() {
        return entries;
    }

    public void setEntries(HashMap<Integer, String> entries) {
        this.entries = entries;
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }

    @Override
    public boolean equals(Object obj) {
        return EqualsBuilder.reflectionEquals(this, obj);
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }

}
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import java.io.File;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import note.jaxb20.XmlBinder;
import note.jaxb20.example01.Person;
import note.jaxb20.example01.Persons;
import note.jaxb20.example01.Thing;
import note.jaxb20.example02.Code;
import note.jaxb20.example02.CodeList;

import org.apache.commons.lang.time.DateUtils;
import org.junit.Test;

public class XmlBinderTest {

    private File dir = new File("test-data");

    public void setUp() {

    }

    @Test
    public void testExample02() {

        HashMap<Integer, String> entries0 = new HashMap<Integer, String>();
        entries0.put(1, "女");
        entries0.put(2, "男");
        Code code0 = new Code();
        code0.setName("sex");
        code0.setEntries(entries0);

        HashMap<Integer, String> entries1 = new HashMap<Integer, String>();
        entries1.put(1, "小");
        entries1.put(2, "中");
        entries1.put(3, "大");
        Code code1 = new Code();
        code1.setName("size");
        code1.setEntries(entries1);

        List<Code> codeList = new ArrayList<Code>();
        codeList.add(code0);
        codeList.add(code1);

        CodeList expected = new CodeList();
        expected.setCodeList(codeList);

        CodeList actual = XmlBinder.xmlToObjcet(new File(dir, "code-list.xml"),
                CodeList.class);

        assertEquals(expected, actual);

    }
}