レベル: 上級 Brett D. McLaughlin, Sr. (brett@newInstance.com), Author and Editor, O'Reilly Media, Inc.
2008年 1月 29日 このシリーズの第 1 回と第 2 回を読んでいれば、Castor を使って XML から Java™ に変換することにも、またその逆の変換をすることにも慣れているはずです。この記事では、そうした機能に柔軟性を追加するために、Castor のマッピング・ファイルを使う方法を学びます。この方法を学ぶと、XML 文書の要素の名前や Java クラスのメンバー変数の名前に制約されることがなくなります。
前提条件
前回の記事と同じく、この記事の場合にも、システム上にセットアップされているものについて、また既に扱い方を知っているものについて、いくつかの前提があります。まず、このシリーズの第 1 回で説明したとおり、最新バージョンの Castor をダウンロードしてインストールし、現在設定されているクラスパスや関連の Java ライブラリーを使えるようにセットアップしておく必要があります (このシリーズの第 1 回を見てください。リンクは「参考文献」にあります)。次に、第 2 回で詳細に説明したように、Castor による基本的なマーシャリング機能とアンマーシャリング機能にも慣れている必要があります。
つまり皆さんは、XML 文書の中から Castor を使ってデータを抽出し、そのデータを皆さん独自の Java クラスを使って操作できるはずです。データ・バインディングの用語では、これはアンマーシャリングと呼ばれます。その逆はマーシャリングと呼ばれ、Java クラスのメンバー変数に保存されているデータを XML 文書に変換することができます。もしプログラムで Castor のアンマーシャラーとマーシャラーを使うことに慣れていない場合には、第 2 回を復習する必要があります (この記事も「参考文献」にリンクされています)。
理想的ではない世界でのデータ・バインディング
一見した限りでは、何をどうすればよいか、また Castor を効率的に使うための方法は既にわかっているように思えます。つまりセットアップし、マーシャリングし、そしてアンマーシャリングすればよいのです。しかし、これまで学んだことはすべて、いわゆる理想の世界でしか通用しません。理想の世界では、誰もが素晴らしい XML を作成します。要素名は「title」や「authorAddress」といった実用的な名前になっており、「t」や「aa」は使われていません。Java クラスは体系付けられた様式に従って作成され、クラス名は単数 (「Book」など) であり、またメンバー変数は「isbn」や「price」のように単数の名詞です。そしてデータ型も適切です。相変わらず C コードを作成しているつもりで price を float ではなく int にしたり、ストリング・データに char の配列を使ったりする開発者はいません。
しかし大部分のプログラマーは、そうした理想的な世界には住んでいません (私はまだ、そんな世界に連れて行ってくれる魔法のワードローブを探しています
)。大部分のプログラマーが住んでいる (月 2 回給料を受け取り、その中から住宅ローンを支払うような) 現実の世界では、XML 文書に恐ろしい名前の要素や属性が含まれていることがよくあり、しかも名前空間に関する山のような問題にも対処しなければなりません。要素データは属性の中に保存されており、さらには一部のデータがパイプ記号やセミコロンで区切られていることさえあります。
Java クラスは継承されますが、継承により作成された Java クラスを XML スキーマにマッピングするための時間的なコストと作業の手間は、それによって得られるメリットを超えています。こうした Java クラスは XML スキーマにうまくマッピングできないことが多く (XML とデータの論者と Java の専門家が仲良くすることも非常に稀です)、うまくマッピングできる場合であっても、すべてのクラスとデータがマッピングできるわけではありません。XML の要素名が不適切であるのと同様に、Java の変数名もその多くが不適切な名前です。例えば、誰かがハンガリアン記法を使ってコーディングした、すべてのメンバー変数が「m」で始まり mTitle のようになるクラスを見かけたことはないでしょうか。これは美しくない名前の付け方です。
このような場合では、これまでデータ・バインディングに関して学んだ手法では、あまり大したことはできません。せいぜいハンガリアン記法での要素名を持つ扱いにくい XML 文書や、あまり意味をなさない構造を持つ、その場しのぎの Java クラスが得られるだけです。これではとても使えません。思いどおりに XML 文書のデータを操作できないのであれば、Castor (あるいは他のすべてのデータ・バインディング・フレームワーク) の利点は一体何なのでしょう。
柔軟なデータ・バインディングという目標
まず、Castor の場合であれ、他のデータ・バインディング・フレームワークの場合であれ、マッピング・ファイルを使えるようになるには多少時間がかかることに注意してください。何よりもまず、いくつかの新しい構文を学ぶ必要があります。マッピング・ファイルが XML をフォーマットとして使用する場合であっても (ほとんどのフレームワークはまさにそうです)、新しい要素と属性を理解しなければなりません。また、XML から Java コードへの、あるいは Java コードから XML へのマッピングで望むとおりの結果が確実に得られようにするには、テストを数多く行わなければならないはずです。またさらに、データ・バインディングでは通常以上に大量のエラーが発生します。これはマッピングをフレームワークに処理させず、自分自身でマッピングを指定するためです。つまり、もし XML の fiddler 要素から Java コードの violin 属性にマッピングするようにフレームワークに指示したものの、誤ってこの属性が (Player クラスに対するものではなく) player クラスに対するものだとフレームワーク指示してしまうと、エラーになるということです。突然、スペリングや大文字小文字の区別、下線、単一引用符あるいは二重引用符が非常に重要になってきます。
こうした細かなニュアンスを詳しく調べる前に、詳しく調べるための理由がなければなりません。単にマッピング・ファイルを知っていると言えるようになるためにマッピング・ファイルを学ぶことは時間の無駄です。しかしマッピングには、いくつか実際にメリットがあるのです。
Java コードは XML の命名規則の制約を受けません
先ほど少し、XML から Java コードに変換する際に大文字小文字の区別が苦労の種になることについて触れました。XML を扱う場合、最も一般的には、すべての名前を小文字にし、ダッシュを使います (例えば first-name など)。場合によると、first_name のようなものさえあります。これらはどちらも、Java の属性名としてはかなり不適切なものになります。コードの中で getFirst-name() を呼び出したいと思う人はいません。実際、キャメルケースの命名規則 (firstName など) を使う大部分の文書はプログラマーによって作成されたものであり、XML を中心に開発している人やデータ・マネージャーが作成したものではありません。マッピング・ファイルを使うと、XML 流儀の名前 (first-name など) から Java 流儀の名前 (firstName) へのマッピングが非常に簡単になります。そして何よりも良いことに、XML の専門家に Java プログラマーが考えるように考えてもらう必要がなくなります (XML の専門家にとって、そういう考え方をすることは、いくつかの新しいマッピング構文を学ぶよりもずっと難しいことが多いものです)。
XML は Java の命名規則の制約を受けません
確かに、これは明白に思えます。XML から Java への名前の対応を調整できるなら、同じことが逆の場合にも可能であり、Java のクラスや属性に含まれるデータを XML に変換する際に Java で使われていた名前を変更することができます。しかしもっと重要な、ただし目立たない利点は、Java のクラス名やパッケージ名に関する制約も受けないということです。
これはほとんど構造に関する問題になります。XML の中のネスト要素の大部分はクラス構造に変換され、最も深くネストした要素がクラスのプロパティー (メンバー変数) になることを考えてみてください。例えばリスト 1 に示す XML について考えてみてください。
リスト 1. 本を表す XML
<?xml version="1.0" encoding="UTF-8"?>
<book>
<authors total-sales="0">
<last-name>Finder</last-name>
<first-name>Joseph</first-name>
</authors>
<isbn>9780312347482</isbn>
<title>Power Play</title>
</book>
|
おそらく Castor (あるいは他のすべてのデータ・バインディング・フレームワーク) は、(Author クラスのいくつかのインスタンスへの参照を持つ) Book クラスが必要と考えるでしょう。Author クラスは lastName と firstName というメンバー変数を持っているはずです (先ほどの命名方法の問題に注意してください。Author の中のメンバー変数は last-name なのでしょうか、それとも lastName なのでしょうか。同じ問題は first name にも当てはまります)。しかし、もし皆さんの求めているものが Book クラスではなかったらどうするのでしょう。例えば、すべての著者、会議での講演者、そして教授を、Person または Professional と呼ばれる 1 つのクラスに保存したらどうなるでしょう。こうなると本当に問題ですが、この問題を解決するために XML 要素を完全に再構成し、リネームする必要はないはずです。実際これは、マッピングこそ (Java プログラマーが行った選択のために変更を迫られることなく) XML をそのまま保持するための唯一の方法、という一例なのです。
マッピングによって、Java と XML のパイプラインの両側での命名規則を表現することができます。XML 文書の作成者のために Java コードを変更することを避けたいのと同様に、Java のクラスやメンバー変数の適当な構成に合わせて XML の構造を変更する必要はないはずです。そしてついでに、そこに Java パッケージも加わります。Castor ではパッケージはそれほど大きな問題ではありませんが、それでも Java のクラスとパッケージに関する情報を、マーシャリングされた XML に保存しなければなりません。これではビジネス・ロジック (Java クラス) とデータ (XML) とをうまく分離できません。こうした問題が、マッピングによってすべて解決できるのです。
マッピングによって既存環境にデータ・バインディングを追加することができます
上記の問題 (XML あるいは Java コードに課される制約) はどちらも、実際には、より大きな問題に関係しています。ほとんどの場合、一連の Java オブジェクトと 1 つか 2 つの XML 文書、という両方があります。そのため、前回の記事のような柔軟性を持つことができません (前回の記事では、Castor が自由に Java コードを XML にアンマーシャルしたり、あるいは XML 文書用の Java クラスを生成あるいはカスタム・ビルドしたりすることができました)。
今回はそれとは異なり、既存の構造に新しい技術 (データ・バインディング) を追加するという、もっと一般的な状況に置かれています。こうした場合、データ・バインディングを使うかどうかによって、マッピング・ファイルが変わってくる可能性があります。マッピング・ファイルによって、2 つの固定した「エンドポイント」、つまり現在のオブジェクト・モデルと現在の XML 構造を持つことができ、しかもこの 2 つの間で相変わらずデータのネゴシエーションを行うことができます。要約すれば、適切なマッピング・システムによってデータ・バインディングが現実の世界で有用なものになり、単なる理論的な演習ではなくなるのです。
マッピング・シナリオの例
まず、例として使用できる簡単なマッピング・シナリオを作成しましょう。前回の記事では、Book クラスと Author クラスを作成しました。これらを以下に示します。リスト 2 は Book クラスです。
リスト 2. Book クラス
package ibm.xml.castor;
import java.util.LinkedList;
import java.util.List;
public class Book {
/** The book's ISBN */
private String isbn;
/** The book's title */
private String title;
/** The authors' names */
private List<Author> authors;
public Book() { }
public Book(String isbn, String title, List<Author> authors) {
this.isbn = isbn;
this.title = title;
this.authors = authors;
}
public Book(String isbn, String title, Author author) {
this.isbn = isbn;
this.title = title;
this.authors = new LinkedList<Author>();
authors.add(author);
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public String getIsbn() {
return isbn;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setAuthors(List<Author> authors) {
this.authors = authors;
}
public List<Author> getAuthors() {
return authors;
}
public void addAuthor(Author author) {
authors.add(author);
}
} |
リスト 3 は Author クラスです
リスト 3. Author クラス
package ibm.xml.castor;
public class Author {
private String firstName, lastName;
public Author() { }
public Author(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
} |
注意: 前回の記事から唯一変更した点は、Author クラスの中の合計販売額 (totalSales) 変数を削除した点です。改めて考えてみると totalSales にはあまり意味がないため、今回のバージョンの Author クラスでは削除しました。
もっと面倒な XML 文書
前回の記事の XML を使う代わりに、あまりうまくマッピングできないものを使いましょう。リスト 4 は、リスト 2 とリスト 3 の Java クラスにバインドするための XML 文書です。
リスト 4. データ・バインディングのための XML
<?xml version="1.0" encoding="UTF-8"?>
<book>
<author>
<name first="Douglas" last="Preston" />
</author>
<author>
<name first="Lincoln" last="Child" />
</author>
<book-info>
<isbn>9780446618502</isbn>
<title>The Book of the Dead</title>
</book-info>
</book>
|
何をマッピングするのか
最初の課題、つまりマッピング・ファイルの動作や Castor の API、あるいは他の何かを気にする前に必要なことは、リスト 4 の XML のデータをリスト 2 と 3 に示す Java クラスに入れるために何をしなければならないかを判断することです。それについて少し考えてみましょう。
以下に示すのは、この XML をどのように Java クラスにマッピングするかを簡単に要約したものです。
-
book 要素を Book クラスのインスタンスにマッピングします。
- 各
author 要素を Author クラスのインスタンスにマッピングします。
-
各
Author インスタンスを Book インスタンスの authors リストの中に追加します。
- 各
Author インスタンスに対して、firstName を name 要素の first 属性の値に設定します。
-
各
Author インスタンスに対して、lastName を name 要素の last 属性の値に設定します。
-
Book インスタンスの ISBN を、book-info 要素の中にネストされている isbn 要素の値に設定します。
-
Book インスタンスの title を、book-info 要素の中にネストされている title 要素の値に設定します。
これらのうちの一部については、既に方法がわかっています。例えば、book を Book クラスのインスタンスにマッピングする方法は標準的であり、Castor はこの作業をデフォルトで処理します。しかし新たな問題がいくつかあります。例えば author の場合です。author 要素を Author インスタンスにマッピングする方法はそれほど面倒ではありませんが、各 author が Book インスタンスの 1 つのコレクションに属することを明確に示す、authors のようなグループ要素がありません。
また、要素と属性を Java のオブジェクト・モデルに直接マッピングできない部分がいくつかあります。Author クラスには名前 (first name) 用の変数と苗字 (last name) 用の変数がありますが、そのデータに対して XML の中で 2 つの属性を持つ要素は 1 つ (name) しかありません。また本のタイトルと ISBN は、どの Java オブジェクトにもマッピングされない book-info 要素の中にネストされています。
こうした状況には、マッピングが理想的です。マッピングを使うことで、この XML (必要なデータを持っていますが、構造は持っていません) を使うことができ、しかもこの文書内にあるデータを Java オブジェクトの中に入れることができます。また、このすぐ後に説明するように、マッピング・ファイル自体の作成は難しくありません。
基本的なマッピング・ファイル
Castorでのマッピングは、マッピング・ファイルを使うことで実現されます。マッピング・ファイルは、Java コードから XML に、そしてその逆の変換方法の情報を提供する、単なる XML 文書にすぎません。しかも皆さんは既に XML に慣れているため、マッピング文書の作成は非常に簡単に思えるはずです。実際、単純なマッピングの場合 (つまり単に要素の名前を Java のクラスやメンバー変数に変更するのみの場合) には、数秒でマッピング・ファイルを作成できてしまいます。
このマッピング・ファイルはマーシャリングとアンマーシャリングを行う際に Castor が使いますが、これをプログラムで行う方法は前回までの記事で学びました。他に必要なことは、Castor への API 呼び出しをもう 1 つ追加することのみであり、それ以外はすべて同じです。
マッピング要素
Castor のマッピング・ファイルのすべては、昔ながらの単純な XML 文書と、mapping ルート要素で始まります。また、Castor のマッピング DTD を参照する必要もあります。そうすることで文書の妥当性を検証することができ、構造と構文に何も問題がないことを確認することができます。それによって、マッピングをデバッグする際に作業が非常に容易になります。
リスト 5 は最も基本的なマッピング・ファイルを示しています。
リスト 5. Castor の基本的なマッピング・ファイル
<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.org/mapping.dtd">
<mapping>
<!-- All your mappings go here -->
</mapping>
|
もちろん、このファイルは何もしませんが、すべてのマッピング・ファイルがこれを出発点として使います。
クラス要素を使ってクラスをマッピングする
基本となるファイルが用意できたら、ほとんどの場合、最初に Java クラスを XML 要素にマッピングすることから始めます。この例の場合は、Book クラスを book 要素内のデータにマッピングする必要があります。マッピング・ファイルはまずクラスを調べます。そのため以下のように、class 要素を追加し、その class 要素の name 属性に Java の完全修飾クラス名を指定する必要があります。
<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.org/mapping.dtd">
<mapping>
<class name="ibm.xml.castor.Book">
</class>
</mapping>
|
今度は、このクラスのマッピング先となる XML 要素を、map-to 要素と xml 属性を使って指定します。map-to 要素は以下のように、この XML のマッピング先となる Java クラス (パッケージ名も含む完全修飾クラス) の class 要素の中にネストしています。
<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.org/mapping.dtd">
<mapping>
<class name="ibm.xml.castor.Book">
<map-to xml="book" />
</class>
</mapping>
|
これは非常に単純です。実際、このマッピング・ファイルはここまで、Castor がデフォルトで行うことを行っているにすぎません。この部分を省略し、Castor に作業を行わせることもできますが、そうしないのは以下の 2 つの理由からです。
-
Book の中のいくつかの特定のフィールド (例えばタイトルや ISBN など) にデータを追加する方法を指定しなければなりません。
- マッピング・ファイルを使用するのであれば、すべてのマッピング対象がどのようにマッピングされるのか示した方が適切です。それによってマッピング・ファイル自体がドキュメンテーションになり、XML と Java コードがどのように連携するのかを明確にすることができます。
フィールドを要素にマッピングする
クラスから要素への基本的なマッピングが用意できると、book クラスのさまざまなフィールドから XML 文書内の特定の要素へのマッピングを開始することができます。Castor のマッピング・ファイルは、どんな Java メンバー変数が使われているかを field 要素を使って示し、またその field 要素にネストされている bind-xml 要素を使って、マッピング先となる XML を示します。したがって、Book クラスの title 変数を、Book 要素にネストされている title という名前の XML 要素にマッピングする方法は、以下のようになります。
<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.org/mapping.dtd">
<mapping>
<class name="ibm.xml.castor.Book">
<map-to xml="book" />
<field name="Title" type="java.lang.String">
<bind-xml name="title" node="element" />
</field>
</class>
</mapping>
|
 |
プロパティー名によってコードを隔離する
プロパティー名 (マッピングによってメソッド名に変換されます) を使う本当の価値は、クラスの内部が隠されることです。クラスのメンバー変数の名前を title や book-title、mTitle、あるいは _title などにしたとしても、setTitle() メソッドがある限り、Castor は「title」という単純なプロパティー名を受け付けます。これによって、さまざまなコーディング・スタイルが許容され、そのクラスの処理を、そのクラスの公開 API (そのクラスのゲッターとセッター) のみをベースに行うことができます。
|
|
ここで、いくつかの点に注意してください。まず、プロパティーの名前が提供されています (この場合は「title」) が、これはプロパティー名であってメンバー変数名ではありません。つまり Castor は、プロパティー名を使うために set[PropertyName]() を呼び出します。もし「foo」をプロパティー名として提供すると、Castor は setfoo() を呼び出そうとしますが、これはおそらく望ましい動作ではないはずです。こうしたことから、大文字小文字に注意し、Java クラスの中の変数名ではなく、プロパティー名を使うようにします。
2 番目の注意点として、type 属性が使われています。この属性を使うことで、データに対してどんな型を想定して使用するのかを正確に Castor に指示します。この場合は単純ですが、XML のテキスト・データとして保存されている数字を、整数あるいは十進数として、さらには単なるストリングとして保存したい場合には、正しいデータ型を示すことが非常に重要です。また、java.lang.String のように、Java の完全修飾クラス名を使う必要があります。
bind-xml 要素の場合では、バインド先の XML 要素の名前 (この場合は「title」) と、バインド先が要素なのか属性なのかを、node 属性を使って指定します。こうすることで容易に要素データと属性データの両方を使うことができますが、これもマッピング・ファイルを使うことでデータ・バインディングが柔軟になる、もう 1 つの点です。
XML 要素の位置を指定する
しかしマッピングを使って一番最初に解決しなければならないのは、次のようなケースです。つまり本のタイトルと ISBN が book-info 要素の中にネストされており、book 要素の中に直接存在しているわけではないケースです。その場合、思ったように動作させるためには少し作業が必要です。
このようなケース、つまりクラス内のフィールドを、そのクラスに対応する XML 要素の中に直接入っていないデータにマッピングするケースに直面した場合には、bind-xml 要素にある location 属性を使う必要があります。この属性の値は、バインド先の対象データを含む XML 文書内の要素でなければなりません。もし要素データにバインドするのであれば、この値は対象要素の親要素でなければなりません。もし属性データにバインドするのであれば、この値はその属性を収容する要素でなければなりません。
そこでこの場合では、Book クラスの title プロパティーを、book-info 要素の中にある title 要素の値にバインドします。以下に示すのはそのための方法です。
<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.org/mapping.dtd">
<mapping>
<class name="ibm.xml.castor.Book">
<map-to xml="book" />
<field name="Title" type="java.lang.String">
<bind-xml name="title" node="element" location="book-info"
</field>
</class>
</mapping>
|
こうなると、この本の ISBN に対して別のフィールド・マッピングを追加するのは簡単です。
<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.org/mapping.dtd">
<mapping>
<class name="ibm.xml.castor.Book">
<map-to xml="book" />
<field name="Title" type="java.lang.String">
<bind-xml name="title" node="element" location="book-info" />
</field>
<field name="Isbn" type="java.lang.String">
<bind-xml name="isbn" node="element" location="book-info" />
</field>
</class>
</mapping>
|
これで Book クラスのプロパティーをすべて設定できました。そこで今度は Author クラスに進みます。
さらにクラスをマッピングする
さらにクラスをマッピングするための方法は、ベース・クラスをマッピングする方法と同じです。唯一の違いは、追加のクラスの中で map-to 要素を使う必要がないことです。
Author クラスをマッピングする
Author クラスのフィールドから author 要素へのマッピングを処理するために必要なものは、すべて揃っています。思い出して欲しいのですが、作業対象となる XML フラグメントは以下のとおりです。
<author>
<name first="Lincoln" last="Child" />
</author> |
唯一の問題は、名前と苗字を含む 1 つの要素の代わりに、2 つの属性を持つ 1 つの要素があることです。しかし皆さんは既に、location 属性 (データとして name 要素を調べるように Castor に指示するために必要です) と node 属性 (必要なデータが要素ではなく属性に関するものであることを示します) を使っています。従って、マッピング・ファイルに必要な内容は以下のようになります。
<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.org/mapping.dtd">
<mapping>
<class name="ibm.xml.castor.Book">
<map-to xml="book" />
<field name="Title" type="java.lang.String">
<bind-xml name="title" node="element" location="book-info" />
</field>
<field name="Isbn" type="java.lang.String">
<bind-xml name="isbn" node="element" location="book-info" />
</field>
</class>
<class name="ibm.xml.castor.Author">
<field name="FirstName" type="java.lang.String">
<bind-xml name="first" node="attribute" location="name" />
</field>
<field name="LastName" type="java.lang.String">
<bind-xml name="last" node="attribute" location="name" />
</field>
</class>
</mapping>
|
ここまで来ると、この XML は非常に単純に見えるはずです。これはデータのマップ先となるクラス (Author) と、そのクラス上にある、マップ対象のプロパティー (FirstName と LastName) を示しています。そして各プロパティーに対して、調べる対象となる要素 (両方の name) を指定し、また属性が必要であることを指定しています。
Book クラスと Author クラスをリンクする
上記の XML をよく見ると、Author クラスをどの XML 要素にマッピングするのか Castor に指示していないことに気付く人がいるかもしれません。これは問題です。Castor は皆さんの代わりに何かを想定してくれるわけではないからです。マッピング・ファイルを作成したら、Castor に対してあらゆることを指定しておくのが最善の方法です。
それぞれの本に対して著者が 1 人だったとすると、おそらく Book クラスの中に Author プロパティーが 1 つあるはずです。それを考慮すると、以下のような XML をマッピングに挿入することができます。
<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.org/mapping.dtd">
<mapping>
<class name="ibm.xml.castor.Book">
<map-to xml="book" />
<field name="Title" type="java.lang.String">
<bind-xml name="title" node="element" location="book-info" />
</field>
<field name="Isbn" type="java.lang.String">
<bind-xml name="isbn" node="element" location="book-info" />
</field>
<field name="Author" type="ibm.xml.castor.Author">
<bind-xml name="author" />
</field>
</class>
<class name="ibm.xml.castor.Author">
<field name="FirstName" type="java.lang.String">
<bind-xml name="first" node="attribute" location="name" />
</field>
<field name="LastName" type="java.lang.String">
<bind-xml name="last" node="attribute" location="name" />
</field>
</class>
</mapping>
|
この場合はマッピング・ファイルに book セクションのマッピングを含めていますが、これは Book クラスに属するプロパティーをマッピングするからです。マッピングの型は ibm.xml.castor.Author と指定され、また author という名前の XML 要素が指定されています。次に、Castor のマッピング・システムは、(class 要素の中で) Author クラスの定義を使って著者のプロパティーを処理しています。
ここでの問題は、当然ながら、Book クラスの中に 1 つも Author プロパティーがないことです。その代わり、Author インスタンスのコレクションを含む Authors プロパティーがあります。そこで Castor に対して、各 author 要素を Author の 1 つのインスタンスにマッピングするように (これはほとんど完了しています) 指示し、さらに各インスタンスを組み合わせて Book の Authors プロパティーにするようにも指示する必要があります。
要素をコレクションにマッピングする
book と author の場合では、いくつかの要素 (XML 文書の中の各 author 要素) をコレクションの中にマッピングし、次にそのコレクションを Book の Authors プロパティーに割り当てる必要があります。
実際に Book のフィールドにマッピングするので、まず field 要素から始めます。また、Book の中でバインド先となるプロパティーの名前は Authors なので、name 属性の値を「Authors」と指定します。
<field name="Authors">
</field>
|
次に、プロパティーの型を指定する必要があります。ちょっと考えると、これはコレクション型だと思いがちです。しかし実際には、コレクションの各メンバーの型を指定しなければならないのです (この後すぐに、このコレクションに関する難題を解決する方法を説明します)。そこで、この場合の型は ibm.xml.castor.Author でなければなりません。すると、Castor を使って Authors プロパティーの中に入れたい ibm.xml.castor.Author クラスのインスタンスとして、以下のようなものができます。
<field name="Authors" type="ibm.xml.castor.Author">
</field> |
さて、ここに難題を解く鍵があります。このプロパティーがコレクションであることを、collection 属性を使って示すのです。この属性の値はコレクション型です。Castor は現在、2 つしか値をサポートしていません。つまりリスト型に対する vector と配列型に対する array です。vector は next() のような呼び出しを使って標準的な Java コレクション API によりアクセスされ、array は Java の配列のように扱われ、ar[2] のように索引を使ってアクセスされます。ここでは Java 型は List なので、vector を使います。
<field name="Authors" type="ibm.xml.castor.Author" collection="vector">
</field> |
collection 属性が指定されると、Castor は type 属性に対応する値を使ってコレクションを作成するのだと認識します。つまりここでは、Authors プロパティーは ibm.xml.castor.Author という型のインスタンスのコレクションでなければなりません。
そうするとあとは、こうした Author のインスタンスを取得する元となるソースを示すことです。これはおなじみの要素、bind-xml を使って行います。
<field name="Authors" type="ibm.xml.castor.Author" collection="vector">
<bind-xml name="author" />
</field> |
これで終わりです。これでマッピング・ファイルが完成しました。最終的なファイルはリスト 6 のようになるはずです。
リスト 6. 完成したマッピング・ファイル
<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.org/mapping.dtd">
<mapping>
<class name="ibm.xml.castor.Book">
<map-to xml="book" />
<field name="Title" type="java.lang.String">
<bind-xml name="title" node="element" location="book-info" />
</field>
<field name="Isbn" type="java.lang.String">
<bind-xml name="isbn" node="element" location="book-info" />
</field>
<field name="Authors" type="ibm.xml.castor.Author" collection="vector">
<bind-xml name="author" />
</field>
</class>
<class name="ibm.xml.castor.Author">
<field name="FirstName" type="java.lang.String">
<bind-xml name="first" node="attribute" location="name" />
</field>
<field name="LastName" type="java.lang.String">
<bind-xml name="last" node="attribute" location="name" />
</field>
</class>
</mapping>
|
プログラムでマッピング・ファイルを使う
これで、あとはアンマーシャリング・プロセスの中でマッピング・ファイルを使うだけです。以前は Unmarshaller クラスを静的に使い、Unmarshaller.unmarshal() を呼び出して XML から Java コードへの変換を行いました。しかし今度はマッピング・ファイルを使っているので、Unmarshaller のインスタンスを作成し、いくつかのオプションを設定する必要があります。リスト 7 は、XML 文書から Java オブジェクトへのアンマーシャリングを処理するクラスを示しています。
リスト 7. マッピング・ファイルを使ってアンマーシャリングする
package ibm.xml.castor;
import java.io.FileReader;
import java.util.Iterator;
import java.util.List;
import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.xml.Unmarshaller;
public class BookMapUnmarshaller {
public static void main(String[] args) {
Mapping mapping = new Mapping();
try {
mapping.loadMapping("book-mapping.xml");
FileReader reader = new FileReader("book.xml");
Unmarshaller unmarshaller = new Unmarshaller(Book.class);
unmarshaller.setMapping(mapping);
Book book = (Book)unmarshaller.unmarshal(reader);
System.out.println("Book ISBN: " + book.getIsbn());
System.out.println("Book Title: " + book.getTitle());
List authors = book.getAuthors();
for (Iterator i = authors.iterator(); i.hasNext(); ) {
Author author = (Author)i.next();
System.out.println("Author: " + author.getFirstName() + " " +
author.getLastName());
}
} catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace(System.err);
}
}
} |
これまでの 2 回の記事で説明したアンマーシャラーと比較すると、ここで行われている変更は最小限です。まず、Unmarshaller のインスタンスが Book.class の引数を使って作成されます。これはアンマーシャラーに対して、アンマーシャル先となる最上位レベルのクラスを指示します。この最上位レベルの Java オブジェクトが、map-to 要素を使うマッピング要素に対応することに注目してください。次にマッピングが設定され、そしてようやく、非静的なバージョンの unmarshal() が呼び出されます。
これで終わりです。皆さんが既に知っていることと大幅に異なるものは何もありません。実際、演習として、Java コードから XML にマーシャリングするコードを皆さん自身で作成してみてはいかがでしょうか。前回の記事の BookMarshaller クラスを参照し、マッピング・ファイルを設定するための変更を行うと、XML から Java コードに、またその逆に、瞬時に変換することができます。
まとめ
データ・バインディングは、結局のところ、データが保存されているフォーマットに注目することではなく、データに注目することです。大部分の Java プログラマーにとって Java オブジェクトの操作は、単にさまざまなソースから (特に XML から) データを取得して Java オブジェクトの中に入れることにすぎません。データ・バインディングを使って Java オブジェクトを操作する場合もまったく同様ですが、データ・バインディングの世界で行うマッピングではそれをさらに一歩進め、Java オブジェクトにデータを入れる際に、データ・ソースのフォーマットを非常に柔軟に選択することができます。データ・バインディングを好きであれば、マッピングも好きになるはずです。マッピングを使うことで、必要な命名規則とうまく適合しない XML 文書や、Java オブジェクトとは少し異なる構造を使う XML 文書をバインドできるのです。
同じことはデータを操作する側の人にも当てはまります。Java のメソッドが呼び出され、おかしな名前で XML 流儀の変数の中に保存される場合や、1 つの XML 文書に同じクラスにマッピングされる複数の要素がある場合にも、中間処理を行うレイヤーを作成することなく、Java クラスから必要なデータを得られるのです。繰り返しますが、重要なことは柔軟性です。皆さんが望むようにデータを操作することができ、フレームワークやツールに制限されないことが重要なのです。
次回は
これまで、Castor が XML の世界に何を提供するのかを見てきました。ただしそれは、Castor の API の機能の、ほんの一部をかじったにすぎません。次回の記事では、単純な XML データ・バインディングを後にし、Castor の SQL データ・バインディング機能について調べます。Java クラスから SQL データベースに、そしてその逆に、JDBC を使わずに素早くデータを移動することができます。XML に慣れ、SQL の技術を磨き、来月、データ・バインディングの持つ、さらに大きな力を調べることにしましょう。
このシリーズの最終回となる次回の記事を読み終わると、XML、Java、そして SQL データベースの間で、同じ API を使って変換を行うことができます。それによって、マッピング・ファイルよりもさらに柔軟性を高めることができます。1 つの API を使って (どのデータ・ストレージ・フォーマットの場合も同様の呼び出し方法で)、データベース、Java オブジェクト、そして XML 文書を処理できるのです。実際、時おり C# の世界に忍び込んでいる人達にとって、これは Visual C# 2008 で最新の、そして最もホットな技術の 1 つである LINQ に非常に似ているように思えます。そしてそうしたものがすべて、Java 技術として、安定した API として利用できるのです。非常に素晴らしいではありませんか。では先に進み、データをバインドし、何ができるのかを調べてみましょう。そして次回、オンラインの記事で会いましょう。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Article sample code | x-xjavacastor3-Code.zip | 46KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | 
|  | Brett McLaughlin はノンフィクション作家としてベストセラーを書き、またいくつかの賞を受賞しています。彼が執筆したコンピューター・プログラミングやホーム・シアター、分析や設計に関する本は、これまで 10 万部以上売れています。彼は 10 年近く技術的な本を執筆し、編集し、そして製作してきており、ワープロの前に座っていることが好きですが、ギターを弾いたり、家で 2 人の息子を追いかけたり、彼の妻と Arrested Development (訳注: 米国で作成、放映されていたテレビ番組) の再放送を見て笑い転げることも好きです。彼の最新の本『Head First Object Oriented Analysis and Design』は 2007年の Jolt Technical Book award を受賞しています。彼の古典作『Java & XML』は、Java 言語での XML 技術の使い方に関する決定作の地位を保っています。 |
記事の評価
|