先週に引きつづき,今週もJAXBで使われるアノテーションを紹介していきます。今週は列挙型を修飾するアノテーションと,フィールド/プロパティを修飾するアノテーションです。
その後,実際にJavaのクラスからスキーマを生成させる方法を紹介します。
列挙型を修飾するアノテーション
列挙型は特殊なクラスなので,クラスを修飾するアノテーションはすべて使用することができます。それ以外に@XmlEnumと@XmlEnumValueが使用できます。
表1 列挙型を修飾するアノテーション
アノテーション名 | 説明/デフォルト値 |
---|
@XmlEnum | クラスをスキーマの型に対応づける |
@XmlEnum(
value=String.class
) |
@XmlEnumValue | 値をスキーマの型に対応づける |
なし |
@XmlType | クラスをスキーマの型に対応づける |
@XmlType(
name="##default",
propOrder={""},
namespace="##default",
factoryClass=DEFAULT.class,
factoryMethod=""
) |
@XmlRootElement | クラスをスキーマの宣言に対応づける |
@XmlRootElement(
name="##default",
namespace="##default"
) |
@XmlAccessorType | フィールド,プロパティの対応を決定する |
@XmlAccesorType(
namespace="http://www.w3.org/2001/XMLSchema",
type=DEFAULT.class
) |
@XmlAccessorOrder | フィールド,プロパティの対応順序を決める |
なし |
@XmlEnum
@XmlEnumは,列挙型をXML Schemaで定義された型に対応づけるために使用します。デフォルトは文字列です。
例として血液型を表す列挙型を使用します。
@XmlEnum(String.class)
@XmlType
public enum BloodType {
A, B, O, AB
}
@XmlEnumの要素に使用するクラスオブジェクトを指定します。上記のソースでは文字列(String.class)を使用しています。
文字列の場合,XML Schemaに対応するのはnameメソッドの戻り値です。たとえば,BloodType.AはBloodType.A.name()がスキーマで使用されます。
<xs:simpleType name="bloodType">
<xs:restriction base="xs:string">
<xs:enumeration value="A"/>
<xs:enumeration value="B"/>
<xs:enumeration value="O"/>
<xs:enumeration value="AB"/>
</xs:restriction>
</xs:simpleType>
列挙型は,simpleTypeとenumerationで表されます。ここでは,@XmlEnum(String.class)と指定したので,文字列で表されています。
@XmlEnumの要素に文字列以外を使用したい場合,@XmlEnumValueと組み合わせて使用します。
たとえば,血液型を整数で表す場合には次のようにします。
@XmlEnum(Integer.class)
@XmlType
public enum BloodType {
@XmlEnumValue("1") A,
@XmlEnumValue("2") B,
@XmlEnumValue("3") O,
@XmlEnumValue("4") AB
}
@XmlEnumで使用するクラスを指定し,値をそのクラスの文字列表現で修飾します。
この列挙型に対応するXML Schemaを次に示します。
<xs:simpleType name="bloodType">
<xs:restriction base="xs:int">
<xs:enumeration value="1"/>
<xs:enumeration value="2"/>
<xs:enumeration value="3"/>
<xs:enumeration value="4"/>
</xs:restriction>
</xs:simpleType>
型がintになり,@XmlEnumValueで指定した値が使用されていることが分かります。
フィールド/プロパティを修飾するアノテーション
フィールドもしくはプロパティを修飾するアノテーションを表2に示しました。以下,よく使用されるアノテーションを少し詳しく説明していきます。
表2 フィールド/プロパティを修飾するアノテーション
アノテーション名 | 説明/デフォルト値 |
---|
@XmlElement | フィールド/プロパティを,その名前に由来するXML要素に対応づける |
@XmlElement (
name = "##default",
nillable = false,
namespace = "##default",
type = DEFAULT.class,
defaultValue = "\u0000"
) |
@XmlElements | @XmlElementのコンテナアノテーション |
なし |
@XmlElementRef | フィールド/プロパティを,その型に由来するXML要素に対応づける |
@XmlElementRef (
name = "##default",
namespace = "##default",
type = DEFAULT.class,
) |
@XmlElementRefs | @XmlElementRefのコンテナアノテーション |
なし |
@XmlElementWrapper | XML表現の周りにラッパー要素を生成する |
@XmlElementWrapper (
name = "##default",
namespace = "##default",
nillable = false
) |
@XmlAnyElement | フィールド,プロパティをanyに対応づける |
@XmlAnyElement (
lax = false,
value = W3CDomHandler.class
) |
@XmlAttribute | フィールド,プロパティを属性に対応づける |
@XmlAttribute (
name = ##default,
required = false,
namespace = "##default"
) |
@XmlAnyAttribute | フィールド,プロパティをワイルドカード属性に対応づける |
なし |
@XmlTransient | フィールド,プロパティをXMLに対応づけないようにする |
なし |
@XmlValue | フィールド,プロパティをcomplexTypeのsimpleContentもしくはsimpleTypeに対応づける |
なし |
@XmlIDREF | フィールド,プロパティをXML IDREFに対応づけます |
なし |
@XmlList | フィールド,プロパティをリストsimpleTypeに対応づける |
なし |
@XmlMixed | ミックスコンテンツをサポートするために,複数の値をとるフィールド,プロパティをマークする |
なし |
@XmlMimeType | フィールド,プロパティをMIME型に対応づける |
なし |
@XmlAttachmentRef | MIMEコンテンツへのURI参照であることを示すため,フィールド,プロパティをマークする |
なし |
@XmlInlineBinaryData | base64エンコードされたバイナリデータにバインドされるデータ型に対して,XOPエンコーディングの考慮を無効にする |
なし |
@XmlSchemaType | フィールド,プロパティの対応順序を決める |
@XmlSchemaType(
namespace="http://www.w3.org/2001/XMLSchema",
type = DEFAULT.class
) |
@XmlElementで修飾されたfirst,lastはXMLの要素に対応づけられます。
<xs:element name="name">
<xs:complexType>
<xs:sequence>
<xs:element name="first" type="xs:string" minOccurs="0"/>
<xs:element name="last" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
ここで,firstがnullの場合があるとしましょう。そのような場合,@XmlElementのnillable要素を使用します。nullを使えるようにする場合はnillableの値をtrue,nullを許容しない場合はfalseにします。nillabel要素を指定しない場合,nillableの値はfalseになります。
@XmlRootElement
@XmlType(name="")
public class Name {
@XmlElement(nillable = true)
String first;
@XmlElement(nillable = false)
String last;
}
ここでは,firstはnillableをtrueに,lastはfalseにしてみました。これに対応するXML Schemaは次のようになります。
<xs:element name="name">
<xs:complexType>
<xs:sequence>
<xs:element name="first" type="xs:string" nillable="true" minOccurs="0"/>
<xs:element name="last" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
firstのみ,要素の定義にnillable="true"が付加されました。
要素に必ず値を代入しなくてはならない場合は,required要素で指定します。required要素もnillable要素と同じように,指定しない場合はfalseと見なされます。
@XmlRootElement
@XmlType(name="")
public class Name {
@XmlElement(nillable = true)
String first;
@XmlElement(required = true)
String last;
}
対応するXML Schemaを次に示します。
<xs:element name="name">
<xs:complexType>
<xs:sequence>
<xs:element name="first" type="xs:string" nillable="true" minOccurs="0"/>
<xs:element name="last" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
minOccursが指定されていないことが分かります。
プロパティのデフォルト値を指定するには,defaultValue要素で指定します。
@XmlRootElement
@XmlType(name="")
public class Name {
@XmlElement(defaultValue = "Yuichi")
String first;
@XmlElement(defaultValue = "Sakuraba")
String last;
}
ここでは,firstのデフォルト値をYuichi,lastのデフォルト値をSakurabaにしました。
<xs:element name="name">
<xs:complexType>
<xs:sequence>
<xs:element name="first" type="xs:string" default="Yuichi" minOccurs="0"/>
<xs:element name="last" type="xs:string" default="Sakuraba" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
defaultValueで指定した値がdefault属性に対応していることが分かります。
@XmlElements
@XmlElementsは,複数の要素をXML SchemaにおけるcomplexTypeのchoiceに対応させます。
たとえば,座標を表すPointクラスで,座標値を整数と倍精度浮動小数で表せる場合を考えてみます。
@XmlRootElement
@XmlType(name="")
public class Point {
@XmlElements({
@XmlElement(name="ix", type=Integer.class),
@XmlElement(name="dx", type=Double.class)
})
Number x;
@XmlElements({
@XmlElement(name="iy", type=Integer.class),
@XmlElement(name="dy", type=Double.class)
})
Number y;
}
@XmlElementsの要素は,@XmlElementの配列です。xは整数の場合,ixというXMLの要素で表します。同様に,倍精度浮動小数の場合,dxというXML要素です。ixでもdxでも同じように扱うため,xの型はNumberクラスにしてあります。
これに対応するXML Schemaを次に示します。
<xs:element name="point">
<xs:complexType>
<xs:sequence>
<xs:choice minOccurs="0">
<xs:element name="ix" type="xs:int"/>
<xs:element name="dx" type="xs:double"/>
</xs:choice>
<xs:choice minOccurs="0">
<xs:element name="iy" type="xs:int"/>
<xs:element name="dy" type="xs:double"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
</xs:element>
x,yがともにchoiceに対応づけられます。xは,要素名をixとしたときは整数,dxとしたときは倍精度浮動小数となります。
プロパティがコレクションの場合も同様に,@XmlEmementsで修飾することができます。
@XmlRootElement
@XmlType(name="")
public class Point {
@XmlElements({
@XmlElement(name="ix", type=Integer.class),
@XmlElement(name="dx", type=Double.class)
})
List<? extends Number> listX;
@XmlElements({
@XmlElement(name="iy", type=Integer.class),
@XmlElement(name="dy", type=Double.class)
})
List<? extends Number> listY;
}
listX,listYの要素がIntegerオブジェクトとDoubleオブジェクトの場合があるため,赤字で示したようにジェネリクスでどちらでも扱えるように指定してあります。
XML Schemaを次に示します。
<xs:element name="point">
<xs:complexType>
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="ix" type="xs:int"/>
<xs:element name="dx" type="xs:double"/>
</xs:choice>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="iy" type="xs:int"/>
<xs:element name="dy" type="xs:double"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
</xs:element>
maxOccurs属性が指定されたことが分かります。
来週説明するXmlAdapterを使用すると,ユーザーが定義したクラスでも同様のことを行なうことができます。
@XmlElementWrapper
@XmlElementWrapperは,フィールド/プロパティに対応したXML要素を含むラッパー要素を生成します。このアノテーションは,コレクションや配列で使用することが可能です。
たとえば,アルバムの中にソングが複数あるクラスを考えます。
@XmlRootElement
@XmlType(name="")
public class Album {
@XmlElement(name = "song")
List<String> songs;
}
このクラスに対応するXMLドキュメントは次のようになります。
<album>
<song>Tell Me Why</song>
<song>After the Gold Rush</song>
......
</album>
これに対し,次のように<song>タグを子要素として持つ,<songs>タグを挿入したいと考えます。
<album>
<songs>
<song>Tell Me Why</song>
<song>After the Gold Rush</song>
......
</songs>
</album>
この<songs>タグがラッパー要素にあたります。このラッパー要素に対応づけたクラスは次のようになります。
@XmlRootElement
@XmlType(name="")
public class Album {
@XmlElementWrapper(name = "songs")
@XmlElement(name = "song")
List<String> songs;
}
赤字で示したように,@XmlElementWrapperを付け加えています。name要素でラッパー要素の名前を指定します。デフォルトでは,name要素はプロパティ名に対応づけられます。したがって,上記の例ではname要素を指定する必要はないのですが,分かりやすさのために書いてみました。
これに対応するXML Schemaを以下に示します。
<xs:element name="album">
<xs:complexType>
<xs:sequence>
<xs:element name="songs" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="song" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
song要素を子要素として持つ,songs要素が追加されています。
@XmlAttribute
@XmlAttributeは,フィールド/アトリビュートをXMLの属性に対応づけます。
@XmlRootElement
@XmlType(name="")
public class Name {
@XmlAttribute
String first;
@XmlAttribute
String last;
}
このクラスに対応するXML Schemaを次に示します。
<xs:element name="name">
<xs:complexType>
<xs:sequence/>
<xs:attribute name="first" type="xs:string"/>
<xs:attribute name="last" type="xs:string"/>
</xs:complexType>
</xs:element>
firstとlastがattributeで定義されていることが分かります。
では,コレクションの場合はどうなるでしょうか。次のクラスを考えてみましょう。
@XmlRootElement
@XmlType(name="")
public class Album {
@XmlAttribute
String title;
@XmlAttribute(name = "song")
List<String> songs;
}
titleが単一の文字列であるのに対し,songsは複数の要素を保持することができます。これをXML Schemaにすると,次のようになります。
<xs:element name="album">
<xs:complexType>
<xs:sequence/>
<xs:attribute name="title" type="xs:string"/>
<xs:attribute name="song">
<xs:simpleType>
<xs:list itemType="xs:string"/>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
songは,simpleTypeで表された属性に対応づけられています。
@XmlTransient
@XmlTransientは,フィールド/プロパティをXMLに対応づけないようにします。
@XmlRootElement
@XmlType(name="")
public class Name {
@XmlTransient
String first;
@XmlElement
String last;
}
このクラスではfirstを@XmlTransientで修飾し,lastを@XmlElementで修飾しています。このクラスから生成したXML Schemaを次に示します。
<xs:element name="name">
<xs:complexType>
<xs:sequence>
<xs:element name="last" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
スキーマにはfirstの定義が含まれていません。このように,@XmlTransientで修飾することにより,XMLへの対応付けを抑制できます。
@XmlValue
@XmlValueはフィールド/プロパティをcomplexTypeにおけるsimpleContent,もしくはsimpleTypeに対応づけます。
なお,@XmlValueで修飾できるフィールド/プロパティは1つだけです。また,@XmlValueで修飾されたフィールド/プロパティを持つクラスは,@XmlElementを使うことはできません。
@XmlRootElement
@XmlType(name="")
public class Album {
@XmlValue
String title;
}
ここでは,titleを@XmlValueで修飾しています。XML Schemaは次のようになります。
<xs:element name="album">
<xs:simpleType>
<xs:restriction base="xs:string"/>
</xs:simpleType>
</xs:element>
album要素がsimpleTypeで定義されました。
では,@XmlValueで修飾する対象がコレクションもしくは配列の場合はどうなるでしょうか。
@XmlRootElement
@XmlType(name="")
public class Album {
@XmlAttribute
String title;
@XmlValue
List<String> songs;
}
対応するXML Schemaを次に示します。
<xs:element name="album">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="title" type="xs:string"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
赤字で示したようにalbum要素がsimpleContentで定義されました。
ここで紹介した以外にも,XmlAdapterやObjectFactoryで使用するアノテーションがありますが,ここでは省略します。
Javaクラスからスキーマを生成する
先週から紹介してきたアノテーションを使用し,クラスからスキーマを生成してみましょう。
スキーマの生成にはschemagenコマンドを使用します。
例として,次のクラスからスキーマを生成してみます。
package net.javainthebox.music;
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.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
@XmlRootElement
@XmlType(name="")
public class Album {
@XmlAttribute
String title;
@XmlElementWrapper(name = "songs")
@XmlElement(name = "song")
List<String> songs;
}
名前空間を扱うために,パッケージの定義も行ないます。パッケージの定義は先週解説したように,package-info.javaで行ないます。
// package-info.java
@javax.xml.bind.annotation.XmlSchema(
namespace = "http://www.javainthebox.net/music",
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED
)
package net.javainthebox.music;
これで準備は整いました。schemagenでスキーマを生成してみましょう。
schemagenの使い方は簡単です。オプションとして,スキーマを生成させるためのソースファイルを並べていくだけです。
schemagenは名前空間ごとにスキーマを出力します。現状ではschemagenはschema1.xsd,schema2.xsdのようなファイル名となり,これを変更することはできません。
まずはLinuxでやってみましょう。
[sakuraba]$ schmagen net/javainthebox/music/*.java
注:Writing schema1.xsd
[sakuraba]$
生成したスキーマschema1.xsdは次のようになりました。なお,見やすくするために,整形をほどこしてあります。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema elementFormDefault="qualified" version="1.0"
targetNamespace="http://www.javainthebox.net/music"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="album">
<xs:complexType>
<xs:sequence>
<xs:element name="songs" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="song" type="xs:string"
minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="title" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:schema>
このようにスキーマを生成することができました。
同じようにWindowsでもやってみましょう。
C:\jaxb>schmagen net\javainthebox\music\*.java
アプリケーションで例外が発生しました (1.6.0_06)。Bug Parade に同じバグが登録され
ていないことをご確認の上、Java Developer Connection (http://java.sun.com/webapps
/bugreport) にてバグの登録をお願いいたします。 レポートには、そのプログラムと下
記の診断内容を含めてください。ご協力ありがとうございます。
java.lang.NullPointerException
at com.sun.tools.apt.main.CommandLine.parse(CommandLine.java:42)
at com.sun.tools.apt.main.Main.compile(Main.java:775)
<<以下、省略>>
NullPointerException例外が発生してしまいました。
schemagenは内部でjavacをコールして,ソースのコンパイルを行なっています。しかし,コンパイルに必要なtools.jarがクラスパスに含まれていないため,このような例外が発生してしまいます注1。
これを解決するためには,-cpもしくは-classpathオプションを使用して,tools.jarをクラスパスに含めるようにします。tools.jarは,JDKをインストールしたディレクトリの直下にあるlibディレクトリに配備されています。
したがって,次のようにschemagenを実行します。
C:\jaxb>schmagen -cp "C:\Program Files\Java\jdk1.6.0_06\lib\tools.jar;. net\jav
ainthebox\music\*.java
注:Writing C:\jaxb\schema1.xsd
schemagenのコマンド行が2行になっていますが,実際には1行で入力します。
このようにtools.jarを含めることで,正しくスキーマを生成することが可能になります。
これで,JavaのクラスからXMLのスキーマを生成できるようになりました。今まで紹介したことを含めると,JavaのクラスとXMLのスキーマの相互変換が可能になったわけです。
さて,来週はJAXBのバインディングをカスタマイズする方法を紹介します。お楽しみに。
注1 このバグはすでにBug Databaseに登録されているのですが(BugID: 6510966),まだ修正されていないようです。