JAXBでネームスペース付加・XMLスキーマ検証もできました

JAXB（Java Architecture for XML Binding）で、ネームスペースを付加したり、XMLスキーマ検証をしてみました。少し悩んだところもありましたが、問題なく動作確認できました。

# ネームスペース付加
## オブジェクト→XML変換
変換元データを保持するクラスは、特別なことはありません。ネームスペースを付加しない場合と同じ作りです。

```java:変換元データを保持するクラス
@XmlRootElement(name = "person")
public class PersonWithXmlns {
    // Getter/Setterを省略して、publicにしてあります。
    public String name;
    public int age;
}
```

ポイントは、package-info.javaを作成することです。上記クラスを配置してあるパッケージに、package-info.javaを作成し、次のように@XmlSchemaアノテーションで、ネームスペースを指定してあげます。

```java:package-info.java
@XmlSchema(namespace = "http://www.example.org/person/", elementFormDefault = XmlNsForm.QUALIFIED)
package jp.co.tryjava.jaxb.schema;

import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
```

変換処理は、ネームスペース付加しない場合と同じ処理です。下記の「期待値」に記述したように、personタグにxmlns属性が付加されるようになりました。

```java:オブジェクト→XML変換処理
// 入力オブジェクト
PersonWithXmlns person = new PersonWithXmlns();
person.name = "山田 太郎";
person.age = 20;

// 出力先
StringWriter actual = new StringWriter();

// 実行
JAXB.marshal(person, actual);

// 期待値
// xmlns属性が付くのがポイント！
String expected = "" //
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" //
+ "<person xmlns=\"http://www.example.org/person/\">\n" //
+ "    <name>山田 太郎</name>\n" //
+ "    <age>20</age>\n" //
+ "</person>\n" //
;

// 検証
assertThat(actual.toString(), is(expected));
```

## XML→オブジェクト変換
XML→オブジェクトの変換処理は次のようになりました。xmlns属性が付加されたXMLを入力データとして渡しています。

```java:XML→オブジェクト変換処理
// 入力XML
String input = "" //
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" //
+ "<person xmlns=\"http://www.example.org/person/\">\n" //
+ "    <name>山田 太郎</name>\n" //
+ "    <age>20</age>\n" //
+ "</person>\n" //
;
StringReader sr = new StringReader(input);

// 実行
PersonWithXmlns actual = JAXB.unmarshal(sr, PersonWithXmlns.class);

// 検証
assertThat(actual.name, is("山田 太郎"));
assertThat(actual.age, is(20));
```

# XMLスキーマ検証
## オブジェクト→XML変換

まずは、次のようなXMLスキーマファイルを用意しました。

※ここで「elementFormDefault="qualified"」が無いと、「org.xml.sax.SAXParseException; lineNumber: 0; columnNumber: 0; cvc-complex-type.2.4.a: 要素'name'で始まる無効なコンテンツが見つかりました。'{name}'のいずれかが必要です。」というエラーが発生してしまいました。

```xml:person.xsd
<?xml version="1.0" encoding="UTF-8"?>
<schema
  xmlns="http://www.w3.org/2001/XMLSchema"
  xmlns:tns="http://www.example.org/person/"
  targetNamespace="http://www.example.org/person/"
  elementFormDefault="qualified">

  <element name="person" type="tns:personType"></element>
  <complexType name="personType">
    <sequence>
      <element name="name" type="string"></element>
      <element name="age" type="int"></element>
    </sequence>
  </complexType>

</schema>
```

オブジェクト→XMLの変換処理は、次のようになりました。JavaオブジェクトとXMLスキーマの整合性が取れていない場合は、MarshalExceptionが発生しました。

```java:オブジェクト→XML変換処理（XMLスキーマ検証あり）
// 入力オブジェクト
PersonWithXmlns person = new PersonWithXmlns();
person.name = "山田 太郎";
person.age = 20;

// 出力先
StringWriter actual = new StringWriter();

// スキーマインスタンス生成
SchemaFactory schemaFactory = SchemaFactory
        .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory.newSchema(getClass().getResource(
        "/jp/co/tryjava/jaxb/person.xsd"));

// Marshallerインスタンス生成
JAXBContext context = JAXBContext.newInstance(person.getClass());
Marshaller marshaller = context.createMarshaller();
marshaller.setSchema(schema);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

// 実行
marshaller.marshal(person, actual);

// 期待値
String expected = "" //
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" //
+ "<person xmlns=\"http://www.example.org/person/\">\n" //
+ "    <name>山田 太郎</name>\n" //
+ "    <age>20</age>\n" //
+ "</person>\n" //
;

// 検証
assertThat(actual.toString(), is(expected));
```

## XML→オブジェクト変換
XML→オブジェクトの変換処理は、次のようになりました。JavaオブジェクトとXMLスキーマの整合性が取れていない場合は、UnmarshalExceptionが発生しました。

```java:XML→オブジェクト変換処理（XMLスキーマ検証あり）
// 入力XML
String input = "" //
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" //
+ "<person xmlns=\"http://www.example.org/person/\">\n" //
+ "    <name>山田 太郎</name>\n" //
+ "    <age>20</age>\n" //
+ "</person>\n" //
;
StringReader sr = new StringReader(input);

// スキーマインスタンス生成
SchemaFactory schemaFactory = SchemaFactory
        .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory.newSchema(getClass().getResource(
        "/jp/co/tryjava/jaxb/person.xsd"));

// Unmarshallerインスタンス生成
JAXBContext context = JAXBContext
        .newInstance(PersonWithXmlns.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setSchema(schema);

// 実行
PersonWithXmlns actual = (PersonWithXmlns) unmarshaller
        .unmarshal(sr);

// 検証
assertThat(actual.name, is("山田 太郎"));
assertThat(actual.age, is(20));
```

# 参考ページ
・[JAXBと名前空間と - Starlight](http://d.hatena.ne.jp/Kazuhira/20120716/1342441007)