連載 役に立つXMLツール集(4)
Relaxerでデータバインディングに挑戦しよう

www.netpotlet.com
原田洋子
2004/2/4

読み込みアプリケーションの作成

主な内容
Relaxerの概要
Relaxerの入手とセットアップ
開発手順
スキーマの作成
スキーマのコンパイル
読み込みアプリケーションの作成
読み込みアプリケーションの実行
文書操作アプリケーションの作成
文書操作アプリケーションの実行
まとめ&サンプル・ダウンロード

 スキーマをコンパイルしてAPIが生成されたので、今度は簡単なアプリケーションを試してみます。ここでもこれまでのJAXB、Castorと比較しやすいようにXML文書を読み込むプログラムを同様のスタイルで作ってみます。スキーマをコンパイルするオプションによっては、アプリケーションのコンパイル時にRelaxerOrg.jarなどRelaxer付属のアーカイブが必要になりますが、通常はそのようなアーカイブを使いません。スキーマから自動生成されたAPIだけでアプリケーションを作れます。

 リスト5がXML文書を読み込むRelaxerReaderクラスです。JAXBやCastorはXML文書の読み込みをアンマーシャル(Unmarshal)といい、unmarshalメソッドを使いましたが、Relaxerは独自の名前を持つメソッドで読み込みます。また、RelaxerはXML文書のルート要素に当たるクラスをインスタンス化するという方法で読み込みを行います。コンストラクタの引数にはファイルやURLだけではなく、ストリームやDOMオブジェクトも指定できます。アンマーシャルにこだわりたい場合はjava.jaxbオプションを指定してスキーマをコンパイルするといいでしょう。JAXB APIが使えるようになるので、アンマーシャルもできるようになります。

 リスト5はパッケージcom.netpotlet.testに属するクラスとして定義しましたので、次のようにしてパッケージ/クラスを作ります。

 パッケージ・エクスプローラーでソース・フォルダー(src)選択 →
 右クリック → 新規 → パッケージ →
 パッケージ名(com.netpotlet.test) → 終了

 パッケージ・エクスプローラーでパッケージ(com.netpotlet.test)選択 →
 右クリック → 新規 → クラス → クラス名(RelaxerReader) → 終了

 1 package com.netpotlet.test;
 2
 3 import java.io.FileNotFoundException;
 4 import java.io.File;
 5 import java.io.IOException;
 6
 7 import javax.xml.parsers.ParserConfigurationException;
 8
 9 import org.xml.sax.SAXException;
10
11 import com.netpotlet.boat.ILabelMixed;
12 import com.netpotlet.boat.ISizeChoice;
13 import com.netpotlet.boat.Label;
14 import com.netpotlet.boat.LabelType1;
15 import com.netpotlet.boat.LabelType2;
16 import com.netpotlet.boat.StartKeyboard;
17 import com.netpotlet.boat.StartKeyboardKey;
18
19 public class RelaxerReader {
20     private StartKeyboard keyboard;
21
22     RelaxerReader(String filename)
23         throws
24             FileNotFoundException,
25             IOException,
26             SAXException,
27             ParserConfigurationException {

           //XML文書の読み込み
28         keyboard = new StartKeyboard(new File(filename));
29     }
30
31     StartKeyboard getKeyboard() {
32         return keyboard;
33     }
34
35     private void report() {
36         String newline = System.getProperty("line.separator");
37         String defaultSize = "small";
38

           //要素 key の配列を取り出す
39         StartKeyboardKey[] keys =
               keyboard.getStartKeyboardKey();
40         StringBuffer sb = new StringBuffer();
41         for (int i = 0; i < keys.length; i++) {
42             StartKeyboardKey key = keys[i];

               //属性か要素にセットされている size を取得する
43             sb.append("size: ");
44             ISizeChoice sizeChoice = key.getSize();
45             if (sizeChoice == null) {
46                 sb.append(defaultSize);
47             } else {
48                 sb.append(sizeChoice.getContent());
49             }

               //要素 label の配列を取り出す
50             Label[] labels = key.getLabel();
51             for (int j = 0; j < labels.length; j++) {
52                 sb.append(" [");
53                 Label label = labels[j];

                   //この label 要素が default かどうかをチェック
54                 if (label.getDefault()) {
55                     sb.append("(default)");
56                 }

                   //属性か要素にセットされている type を取得する
57                 ILabelMixed[] mixedLabels = label.getContent();
58                 for (int k = 0; k < mixedLabels.length; k++) {
59                     ILabelMixed labelMixed = mixedLabels[k];
60                     if ((labelMixed instanceof LabelType1)
61                         || (labelMixed instanceof LabelType2))
                       {
62                         sb.append("type: ");
63                         sb.append(labelMixed.getContent());
64                         sb.append(" ");
65                     } else {

                           //label要素のテキストを取得する
66                         String contents =
                               labelMixed.getContent();
67                         sb.append("label: ");
68                         sb.append(contents);
69                         sb.append(" ");
70                     }
71                 }
72                 sb.append("]");
73             }
74             sb.append(newline);
75         }
76         System.out.println(new String(sb));
77     }
78
79     public static void main(String[] args) throws Exception {
80         if (args.length < 1) {
81             throw new Exception("arguments are not enough");
82         }
83         RelaxerReader reader = new RelaxerReader(args[0]);
84         reader.report();
85     }
86 }
リスト5 RelaxerReader.java

読み込みアプリケーションの実行

 XML文書の読み込みを行うアプリケーションを実行してみましょう。このアプリケーションはXML文書を実行時の引数から取得するので、次のように設定、実行します(図5、6参照)。なお、XML文書は図4のdocsディレクトリに置いてあります。

 実行 → 実行... → Javaアプリケーション → 新規 → メインタブ →
 プロジェクト名(gulf)/メイン・クラス(com.netpotlet.test.RelaxerReader)
 → 引き数タブ → プログラム引き数(docs/keyboard1.xml) →
 適用 → 実行

4 gulfプロジェクトのディレクトリ構成(クリックで拡大します)


5 実行アプリケーションの設定(クリックで拡大します)


6 実行時の引数指定(クリックで拡大します)

 XML文書が正しく書けていれば図7のように出力されます。

7 RelaxerReaderの実行結果(クリックで拡大します)

文書操作アプリケーションの作成

 JAXBとCastorで試したのと同様に、要素や属性を追加してXML文書を出力するアプリケーションも試してみましょう。リスト6 RelaxerCreatorがそのためのクラスです。

 標準ではRelaxerはXML文書を出力するときにCastorやJAXBのようにマーシャル(Marshal)のためのmarshalメソッドを使いません。代わりに、リスト6の27行目で実行しているように、XML文書のルート要素に当たるクラスのmakeTextDocument()メソッドを実行します。読み込みと同様、java.jaxbオプションを付けてスキーマをコンパイルすれば、JAXB APIを利用できるので、マーシャルによる出力も可能です。

 1 package com.netpotlet.test;
 2
 3 import java.io.FileNotFoundException;
 4 import java.io.IOException;
 5
 6 import javax.xml.parsers.ParserConfigurationException;
 7
 8 import org.xml.sax.SAXException;
 9
10 import com.netpotlet.boat.StartKeyboardKey;
11 import com.netpotlet.boat.StartKeyboard;
12 import com.netpotlet.boat.Label;
13 import com.netpotlet.boat.LabelType2;
14 import com.netpotlet.boat.RString;
15
16 public class RelaxerCreator {
17     private StartKeyboard keyboard;
18     
19     RelaxerCreator(String filename)
20         throws FileNotFoundException,
21                IOException,
22                SAXException,
23                ParserConfigurationException {

           //元文書を取得する
24         keyboard =
25             (new RelaxerReader(filename)).getKeyboard();

           //元文書にいくつかの要素を追加する
26         updateDocument();

           //標準出力に表示する
27         System.out.println(keyboard.makeTextDocument());
28     }
29
30     private void updateDocument() {
31         String[] labels1 = {
32             "う", "え", "お"
33         };
34         String[] labels2 = {
35             "な", "に", "ぬ", "ね", "の"
36         };

           //1つ目のkey要素にlabel要素を追加する
37         StartKeyboardKey key1 =
38             keyboard.getStartKeyboardKey(0);
39         updateKey(key1, labels1);

           //3番目のkey要素としてlabel要素を含む
           //key要素を生成して追加する
40         StartKeyboardKey key2 = createKey(labels2);
41         keyboard.addStartKeyboardKey(2, key2);
42     }
43     
44     private void updateKey(StartKeyboardKey key,
45                            String[] labels) {
46         for (int i=0; i<labels.length; i++) {
47             key.addLabel(
                   getLabel(LabelType2.CONTENT_CHARACTER,
                            labels[i]));
48         }
49     }
50     
51     private StartKeyboardKey createKey(String[] labels) {
52         StartKeyboardKey key = new StartKeyboardKey();
53         key.addLabel(getLabel(LabelType2.CONTENT_NUMBER,
                                 "5"));
54         for (int i=0; i<labels.length; i++) {
55             key.addLabel(
                   getLabel(LabelType2.CONTENT_CHARACTER,
                            labels[i]));
56         }
57         return key;
58     }
59     
60     private Label getLabel(String type,
61                            String text) {
62         Label label = new Label();
63         LabelType2 labelType = new LabelType2();
64         labelType.setContentByString(type);
65         label.addContent(labelType);
66         label.addContent(new RString(text));
67         return label;
68     }
69     
70     public static void main(String[] args) throws Exception {
71         if (args.length < 1) {
72             throw new Exception("arguments are not enough");
73         }
74         new RelaxerCreator(args[0]);
75     }
76 }
リスト6 RelaxerCreator.java


JAXPを利用した出力の整形

 RelaxerはCastor同様、特に何かをしない限り、出力するXML文書のインデントを行いません。これは前回のCastorでも説明しましたが、改行やインデントはXML的には

  終了タグ、改行、スペース、スペース、スペース、スペース、開始タグ

のようになるため、XML文書のサイズが大きくなるほどに改行やインデントが占める容量を無視できなくなることが理由でした。また、Canonical XML準拠という理由でもあります。JAXBやCastorには整形のための方法がありましたが、Relaxerには特に用意されていないので、ほかのAPIを利用します。最初に、JAXP(Java API for XML Processing ― DOM/SAXのAPI)による整形方法を紹介します。

 JAXPを利用して出力するにはXSLTによる変換を目的としている、javax.xml.transformパッケージを使います。例えば、リスト7のような変換のためのクラスJaxpMarshallerを用意し、リスト6 RelaxerCreatorクラスにimport文を2つ、コンストラクタにスローする例外を2つ、marshalメソッドを実行する行を1つ追加します。このようにすると、出力が改行されます。

 6 import javax.xml.parsers.ParserConfigurationException;
 7 import javax.xml.transform.TransformerConfigurationException;
 8 import javax.xml.transform.TransformerException;
   ………
   ………
21      RelaxerCreator(String filename)
22          throws FileNotFoundException,
23                 IOException,
24                 SAXException,
25                 ParserConfigurationException,
26                 TransformerConfigurationException,
27                 TransformerException {
28          keyboard =
29              (new RelaxerReader(filename)).getKeyboard();
30          updateDocument();
31          //System.out.println(keyboard.makeTextDocument());
32          JaxpMarshaller.marshal(keyboard.makeDocument());
33      }
RelaxerCreator.javaの変更点

 1 package com.netpotlet.test;
 2
 3 import javax.xml.transform.OutputKeys;
 4 import javax.xml.transform.Transformer;
 5 import javax.xml.transform.TransformerFactory;
 6 import javax.xml.transform.TransformerConfigurationException;
 7 import javax.xml.transform.TransformerException;
 8 import javax.xml.transform.dom.DOMSource;
 9 import javax.xml.transform.stream.StreamResult;
10
11 import org.w3c.dom.Document;
12
13 class JaxpMarshaller {
14     static void marshal(Document document)
15         throws TransformerConfigurationException,
16                TransformerException {
17         Transformer transformer = getTransformer();
18         transformer.transform(new DOMSource(document),
19                               new StreamResult(System.out));
20     }
21     
22     private static Transformer getTransformer()
23         throws TransformerConfigurationException {
24         TransformerFactory factory =
               TransformerFactory.newInstance();
25         Transformer transformer =
               factory.newTransformer();
26         transformer.setOutputProperty(OutputKeys.ENCODING,
                                         "EUC-JP");
27         transformer.setOutputProperty(OutputKeys.INDENT,
                                         "yes");
28         return transformer;
29     }
30 }
リスト7 JaxpMarshaller.java


JAXBの利用

 JAXPを利用する方法は仮のマーシャラーとmarshalメソッドを用意しただけなので、JAXB APIを使って本物のマーシャルも試してみます。JAXB APIを利用する場合、java.jaxbオプションを指定してスキーマをコンパイルします。Relaxer EclipseプラグインではRelaxer.propertiesの「XML1」でjava.jaxbをtrueに指定します(図8)。Antでビルドする場合はリスト4 build.xmlの18行目の次に「-java.jaxb」オプションを追加します。

17       <arg line="-validation:relax -java.package:com.
netpotlet.boat"/>
18       <arg line="${schema.dir}/${schema} -verbose"/>
              <arg value="-java.jaxb"/>
19     </java>
build.xmlの変更点

8 java.jaxbオプションの指定

 java.jaxbオプションを指定するときは、RelaxerとJAXBのAPIも必要になるので、次のようにRelaxerのRelaxerOrg.jarとJAXBのjaxb-api.jarをJavaプロジェクトのビルド・パスに追加します。

 パッケージ・エクスプローラーでプロジェクト(gulf)選択 → 右クリック →
 プロパティー → Javaのビルド・パス(左ペイン) → ライブラリー選択 →
 外部JARの追加
  → RelaxerOrg.jar/jaxb-api.jar アーカイブを追加する(画面)

 以上の準備をした後にビルドすると、表1のクラス、インターフェイスに加わえて、IKeyboardFactory.java、AbstractKeyboardFactory.java、DefaultKeyboardFactory.java、KeyboardFactory.javaの4つが生成されます。ここで、例えば、リスト8のようなJAXB APIによるマーシャルのクラスJaxbMarshallerを用意し、RelaxerCreatorにimport文を1つ、コンストラクタにスローする例外を1つ、marshalメソッドを実行する行を1つ追加します。これは、第2回のリスト6で紹介したマーシャル方法と同じです。

 ここでは出力をインデントする目的でJAXB APIを利用しましたが、JAXBにしかない機能を使いたい場合には、リスト8のgetContext()メソッドで行っているようにjavax.xml.bind.JAXBContext型のオブジェクトを取得すれば、JAXBも使えるようになります。

 6  import javax.xml.bind.JAXBException;
 7  import javax.xml.parsers.ParserConfigurationException;
    ........
    ........
22      RelaxerCreator(String filename)
23          throws FileNotFoundException,
24                 IOException,
25                 SAXException,
26                 ParserConfigurationException,
27                 TransformerConfigurationException,
28                 TransformerException,
29                 JAXBException {
30          keyboard =
31              (new RelaxerReader(filename)).getKeyboard();
32          updateDocument();
33          //System.out.println(keyboard.makeTextDocument());
34          JaxpMarshaller.marshal(keyboard.makeDocument());
35          JaxbMarshaller.marshal(getClass().getClassLoader(),
36                                 keyboard.makeDocument());
37      }
RelaxerCreator.javaの変更点

 1 package com.netpotlet.test;
 2
 3 import javax.xml.bind.JAXBContext;
 4 import javax.xml.bind.Marshaller;
 5 import javax.xml.bind.Unmarshaller;
 6 import javax.xml.bind.JAXBException;
 7
 8 import org.w3c.dom.Document;
 9
10 import com.netpotlet.boat.KeyboardFactory;
11
12 public class JaxbMarshaller {
13
14     static void marshal(ClassLoader loader,
                           Document document)
15         throws JAXBException {
16         JAXBContext context = getContext("", loader);
17         Unmarshaller unmarshaller =
               context.createUnmarshaller();
18         Object obj = unmarshaller.unmarshal(document);
19         Marshaller marshaller = context.createMarshaller();
20         marshaller.setProperty(
               Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
21         marshaller.setProperty("jaxb.encoding", "euc-jp");
22         marshaller.marshal(obj, System.out);
23     }
24
25     static JAXBContext getContext(String path,
                                     ClassLoader loader) {
26         return KeyboardFactory.createContext(path, loader);
27     }
28 }
リスト8 JaxbMarshaller.java

文書操作アプリケーションの実行

 アプリケーションを作りましたので実行してみましょう。今度もXML文書を実行時の引数から取得するので、次のように設定、実行します。XML文書は図4のdocsディレクトリに置いてあります。

 実行 → 実行... → Javaアプリケーション → 新規 → メインタブ →
 プロジェクト名(gulf)/メイン・クラス(com.netpotlet.test.RelaxerCreator)
 → 引き数タブ →
 プログラム引き数(docs/keyboard2.xml) → 適用 → 実行

 問題なく実行されれば、図9(JAXP)、図10(JAXB)のように出力されます。

9 RelaxerCreatorの実行結果(JAXPによるインデント)


10 RelaxerCreatorの実行結果(JAXBによるインデント)



まとめ&サンプル・ダウンロード

 今回は日本発のデータバインディングツールRelaxerを取り上げましたがいかがだったでしょうか。Castor同様、JAXBという標準仕様に直接対応していないものの、要素がクラスにマップされたものをインスタンス化できるので、分かりやすいのではないかと思います。また、Relaxer Eclipseプラグインもとても使いやすく、便利なので、楽に開発を進められます。ただし、まだできたてのツールなのでヘルプが実装されていなかったり、プラットフォームによっては不具合があるかもしれません。

 Relaxerは多機能と説明しましたが、これはプラグインのプロパティの画面を見ると一目瞭然でしょう。数々のオプションの中にはデザインパターンを構成するクラスを自動生成するもの、データベース利用機能に関係するものまであります。Relaxerは単なるデータバインディングツールではなく、スキーマコンパイラと呼べるツールになっているのではないかと思います。

 ドキュメントについてはやや不足している感じですが、1.0リリース版には詳細なドキュメントが付属しているのでRelaxerの使い方についてこれを参照するといいでしょう(今回のテストに使った1.1 beta版には付属していません)。また、日本語のメーリングリストは作者の浅海氏も購読していますので、バグや不具合の報告で英語に悩まされることはありません。

 最後に、Relaxerの日本語による主な資料を紹介します。

 今回使用したプログラムやファイル類は以下からダウンロードできます。

 Windows環境で利用される場合、euc-jpをWindows-31Jなど適当なエンコーディングに変更してください。また、日本語を含むファイルはEUC-JPになっていますので、あらかじめ文字コードを変えてから利用されるといいでしょう。

 次回はデータバインディングツールが提供するO/Rマッピング機能を取り上げる予定です。(次回に続く)

======== my confessions ========

前回のCastorの記事について……

「2003年12月時点で最新版は0.9.5.2ですが、このバージョンはW3C XML SchemaからJavaのソースコードを生成するSourceGeneratorがうまく動かなかったため、本記事では1つ前のバージョンである0.9.5.1をftp://ftp.exolab.org/pub/castor/から取得して使いました。」

と書いたくだりについて、前回の記事で紹介した書籍『Javaオープンソース徹底攻略』の執筆者である吉田さんからコメントがありました。

http://yoshi.homelinux.org/~yoshi/?date=20040108#p01

この方法を試したところ、確かにコードは生成されたのですが、XML文書をネームスペースを使って書いていなかった(手抜き (^^; )ため、実行できませんでした。次のバージョンではフィックスされるそうですので、待つことにした次第です。
#まだ、リリースされていないもよう……。
#2004年3月9日にリリースされたバージョン0.9.5.3でこの不具合が修正されました(2004年4月7日追記)。



前編 開発環境の準備 2/2  

Index
連載 役に立つXMLツール集(4)
Relaxerでデータバインディングに挑戦しよう
 

前編 開発環境の準備
 Relaxerの概要
 Relaxerの入手とセットアップ
 開発手順
 スキーマの作成
 スキーマのコンパイル

後編 アプリケーションの作成
 読み込みアプリケーションの作成
 読み込みアプリケーションの実行
 文書操作アプリケーションの作成
 文書操作アプリケーションの実行
 まとめ&サンプル・ダウンロード

関連記事

 ・SEのためのXML Schema入門
 ・XMLテクニック集
 ・Javaで実現するDOM/SAXプログラミング



「連載 役に立つXMLツール集」


XML & SOA フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

HTML5+UX 記事ランキング

本日月間