XMLを簡単にJavaオブジェクトにマッピング現場に活かすJakarta Project(4)

» 2003年03月21日 00時00分 公開
[横田健彦(株)東芝]

 Jakarta ProjectCommonsサブプロジェクトはさまざまな場面で活用できるライブラリを集めたプロジェクトです。今回から3回にわたってCommonsの代表的なコンポーネントについて使い方を説明していきたいと思います。

Commonsサブプロジェクトとは

 Commonsは多くのコンポーネントから構成されています。これらのうち正式に提供されていて安定して利用可能なものはThe Commons Properに属しており、実験的なコンポーネントについてはThe Sandboxに属しています。本原稿の執筆時点ではThe Commons Properには、次の17のコンポーネントが登録されています。

BeanUtils JavaのリフレクションAPIとイントロスペクションAPI関連の利用しやすいラッパーを提供するコンポーネント
Betwixt JavaBeansとXML文書のマッピングを行うためのコンポーネント
Cactus サーバサイドのJavaコードのためのテスティングフレームワーク。現在ではCactusはJakartaのトップレベルのプロジェクトに移動されている
CLI コマンドライン引数やオプションに関するシンプルなAPIを提供するコンポーネント
Collections Javaコレクションフレームワークを拡張するクラス群を提供するコンポーネント
Discovery さまざまなスキーマを用いてサービス名や参照名とリソース名を対応づけることによってリソース(クラスを含む)の場所を特定するためのツールを提供するコンポーネント
Digester XMLとJavaオブジェクトのマッピングユーティリティ。主にXMLで記述された設定ファイルをパースするために用いる
DBCP データベースのコネクションプーリングサービスを提供するコンポーネント
FileUpload ServletやWebアプリケーションにファイルアップロード機能を追加するためのコンポーネント
HttpClient クライアントサイドで動作するHTTPプロトコルのフレームワークを提供するコンポーネント
JXPath JavaBeansの命名規則に従うようなJavaのクラスをXPathの構文を用いて操作するためのユーティリティを提供するコンポーネント
Lang java.langパッケージに属するクラスにさらなる機能性を提供するようなユーティリティクラス群を提供するコンポーネント
Latka HTTP機能の自動テスティングのためのテストスイート
Logging さまざまなロギングAPIの実装をラップして利用するためのコンポーネント
Modeler JMX(Java Management Extensions)仕様と互換性のあるモデルMBeanを生成するためのメカニズムを提供するコンポーネント
Pool オブジェクトプーリング機能を提供するコンポーネント
Validator 受信データの妥当性を検証するメソッドと検証規則をXMLファイルで定義するためのシンプルで拡張可能なフレームワークを提供するコンポーネント

 今回から3回にわたって、これらのコンポーネントのうち比較的利用場面が多いと思われるDigester、DBCP、Pool、Collections、Langについてサンプルプログラムを使って説明していきたいと思います(なおLoggingについては別の回にご説明します)。初回である今回はDigesterの説明をします。

Digester - XML形式の設定ファイルを手軽に読み込む

 プログラムの設定ファイルといえばjava.util.Propertiesクラスで読み込むことのできる形式であるプロパティリスト(リスト1)がまず思い浮かぶと思います。この「キーと要素のペア」式のファイルは扱いが簡単であるため小規模のプログラムでは手軽に用いることができますが、その半面構造化されていないため複雑な設定を記述する必要のある大規模プログラムで利用するには不向きであるといえます。

リスト1 プロパティリストの例
data-source.driverClass=com.mysql.jdbc.Driver
data-source.user=testuser
data-source.password=testpassword
data-source.url=
jdbc\:mysql\://localhost/test?useUnicode=true&characterEncoding=euc-jp

 そこで、ある程度以上の規模のプログラムでは構造を持つデータ形式であるXML(Extensible Markup Language)を用いて設定ファイルを記述するのが一般的になっており、例えばTomcatやStrutsなどでも設定ファイルはXMLで記述されています。そこで今回取り上げるサンプルプログラムでもXMLで設定ファイルを記述することにします。リスト2は今回のサンプルプログラムのための設定ファイルであり、データベース接続のための設定情報が記述されています。

リスト2 XMLで記述された設定ファイル
<?xml version="1.0" encoding="ISO-8859-1" ?>
<sample>
  <data-source>
    <set-property property="driverClass" value="com.mysql.jdbc.Driver" />
    <set-property property="user" value="testuser" />
    <set-property property="password" value="testpassword" />
    <set-property property="url"
value="jdbc:mysql://localhost/test?useUnicode=
true&amp;characterEncoding=euc-jp" />
  </data-source>
</sample>

 通常XMLで記述されたファイルを解釈するにはDOM(Document Object Model)やSAX(Simple API for XML Parsing)を用いますが、これらは機能が豊富であり汎用的なAPIで構成されているため、単純なことをしたい場合でもある程度以上の量のコードを記述する必要があります。これに対して今回取り上げるDigesterはいわば設定ファイルを読み込むことに特化しているため、SAXやDOMを生で使用する場合に比べて格段に少ないコード量で設定ファイルの読み込み機能を実装することができます。

 DigesterはもともとはStrutsにおいてXMLで書かれた設定ファイルを読み込むために開発されたユーティリティクラスでしたが、現在ではJakarta Commonsのコンポーネントとして提供されています。

Digesterの仕組み

 Digesterを利用すると、XMLファイルをJavaのオブジェクトツリーの形で読み込むことができます。XMLファイルからオブジェクトツリーを構築するための仕組みを知るには、Digesterが持つ次の2つの機能要素について知る必要があります。

  • オブジェクトスタック
  • XML要素(タグ)とマッチしたときに実行されるプロセッシングルール群

 Digesterを利用するには、対象となるXMLファイルのそれぞれの要素(タグ)とマッチするようなパターン文字列と、マッチしたときに何を行うかを表すプロセッシングルールの組をあらかじめ登録しておく必要があります。Digester#parse()メソッドが呼ばれると、Digesterは登録されているパターン文字列とXML要素を逐次照合します。そしてXML要素がパターン文字列にマッチした場合、パターン文字列に対応するプロセッシングルールを実行します。プロセッシングルールとしてはオブジェクトを生成したりオブジェクトのメソッドを呼び出したりするほか、オブジェクトスタックを操作するようなルールを指定できます。こうしてオブジェクトスタックをテンポラリバッファとして利用しつつオブジェクトツリーを構築していけるようになっています。

 なお、よく利用するようなプロセッシングルールはあらかじめ用意されていますが、ユーザーが自由に作成することもできます。プロセッシングルールの作成方法やあらかじめ用意されているプロセッシングルールについてはJakartaのページで公開されているドキュメントを参照してください。

Digesterを用いたプログラミング例

 原稿執筆時のDigesterの最新バージョンは1.3(2003年3月現在)です。DigesterはJakarta Commonsのページからダウンロードしてください。また、このバージョンのDigesterを利用するにはほかに以下のCommonsコンポーネントが必要です。

 またDigesterは内部でSAXパーサを利用していますので、JavaプラットフォームとしてJ2SEのバージョン1.3以前をお使いの場合は別途CrimsonやXercesなどのSAXパーサを用意する必要があります。

 それではサンプルプログラム(ここから一連のソースファイルをダウンロードできます)を用いて具体的にDigesterの動作を見ていきましょう(リスト3)。

 サンプルプログラムではDigesterを用いてリスト2の設定ファイルを読み込みます。読み込んだ結果、<sample>要素に対応するSampleConfigクラス(リスト4)のオブジェクトが<data-source>要素に対応するDataSourceConfigクラス(リスト5)のオブジェクトを子に持つオブジェクトツリーが生成されます。

リスト3 Sample.java
package net.skirnir.sample;

import java.io.*;
import java.util.*;

/**
 * Digesterを利用したサンプルプログラムです。
 */
public class Sample
{
  /**
   * メインプログラムです。
   *
   * @param args コマンドライン引数。
   */
  public static void main(String[] args)
    throws Exception
  {
    // 設定ファイルを読み込むためのクラスインスタンスを生成します。
    SampleConfig sc = new SampleConfig();

    // 引数で設定された設定ファイルを読み込んで
    // オブジェクトツリーを構築します。
    sc.load(new File(args[0]));

    // 読み込んだ結果を標準出力に出力します。
    Properties prop = sc.getDataSourceConfig().getProperties();
    Enumeration enum = prop.propertyNames();
    while (enum.hasMoreElements()) {
      String name = (String)enum.nextElement();
      String value = prop.getProperty(name);
      System.out.println(name + " = " + value);
    }
  }
}

リスト4 SampleConfig.java
package net.skirnir.sample;

import java.io.*;
import org.apache.commons.digester.*;
import org.xml.sax.*;

/**
 * &lt;sample&gt;タグの内容に対応するクラスです。
 * 子要素として&lt;data-source&gt;タグに対応する
 * DataSourceConfigオブジェクトを持ちます。
 */
public class SampleConfig
{
  /** 子要素であるDataSourceConfigオブジェクトです。 */
  private DataSourceConfig    dataSourceConfig_;

  /**
   * 指定された設定ファイルを読み込んで
   * オブジェクトツリーを構築します。
   *
   * @param configFile 設定ファイル。
   */
  public void load(File configFile)
  {
    try {
      Digester digester = new Digester();       
      digester.setValidating(false);         
      digester.push(this);              
  
      digester.addObjectCreate("sample/data-source", 
        "net.skirnir.sample.DataSourceConfig");
      digester.addSetNext("sample/data-source",    
        "setDataSourceConfig",
        "net.skirnir.sample.DataSourceConfig");
  
      digester.addCallMethod("sample/data-source/set-property",
        "setProperty", 2);
      digester.addCallParam("sample/data-source/set-property",  
        0, "property");
      digester.addCallParam("sample/data-source/set-property",  
        1, "value");
  
      digester.parse(configFile);         
    } catch (IOException ex) {
      throw new RuntimeException(
        configFile.getPath() + ": " + ex.toString());
    } catch (SAXException ex) {
      throw new RuntimeException(
        configFile.getPath() + ": " + ex.toString());
    }
  }

  /**
   * DataSourceConfigオブジェクトを返します。
   *
   * @return DataSourceConfigオブジェクト。
   */
  public DataSourceConfig getDataSourceConfig()
  {
    return dataSourceConfig_;
  }

  /**
   * DataSourceConfigオブジェクトを設定します。
   * このメソッドはDigesterのAddSetNextルールによって呼び出されます。
   *
   * @param dataSourceConfig DataSourceConfigオブジェクト。
   */
  public void setDataSourceConfig(DataSourceConfig dataSourceConfig)
  {
    dataSourceConfig_ = dataSourceConfig;
  }
}

リスト5 DataSourceConfig.java
package net.skirnir.sample;

import java.util.*;

/**
 * &lt;data-source&gt;タグの内容に対応するクラスです。
 */
public class DataSourceConfig
{
  /** 設定値を保持するプロパティオブジェクトです。 */
  private Properties   prop_ = new Properties();

  /**
   * プロパティオブジェクトを返します。
   *
   * @return プロパティオブジェクト。
   */
  public Properties getProperties()
  {
    return prop_;
  }
  
  /**
   * 指定されたプロパティ名のプロパティ値を返します。
   *
   * @param name プロパティ名。
   * @return プロパティ値。
   */
  public String getProperty(String name)
  {
    return prop_.getProperty(name);
  }

  /**
   * 指定されたプロパティ名のプロパティ値を設定します。
   * このメソッドはDigesterのCallMethodルールによって呼び出されます。
   *
   * @param name プロパティ名。
   * @param value プロパティ値。
   */
  public void setProperty(String name, String value)
  {
    prop_.setProperty(name, value);
  }
}

 サンプルプログラムをコンパイルしてクラスパスを適切に設定したうえでコマンドラインにて、

java net.skirnir.sample.Sample XMLファイル名

というコマンドを実行することで、生成したオブジェクトツリーが持つ設定値を表示させることができます。

 ところで、Digesterを用いてXMLファイルを読み込むためのメソッドがSampleConfigの中のload()メソッドです(リスト6)。

リスト6 load()メソッド
public void load(File configFile)
  {
    try {
      
Digester digester = new Digester(); (1)
digester.setValidating(false); (2)
digester.push(this);             (3)
                    
digester.addObjectCreate("sample/data-source", (4)
"net.skirnir.sample.DataSourceConfig");  
digester.addSetNext("sample/data-source", (5)
        "setDataSourceConfig",
        "net.skirnir.sample.DataSourceConfig");
  
      
digester.addCallMethod("sample/data-source/set-property", (6)
        "setProperty", 2);
      
digester.addCallParam("sample/data-source/set-property", (7)
        0, "property");
      
digester.addCallParam("sample/data-source/set-property", (8)
        1, "value");
  
      
digester.parse(configFile); (9)
    } catch (IOException ex) {
      throw new RuntimeException(
        configFile.getPath() + ": " + ex.toString());
    } catch (SAXException ex) {
      throw new RuntimeException(
        configFile.getPath() + ": " + ex.toString());
    }
  }

 このメソッドの内容について、順を追って説明していきましょう。

 (1)でorg.apache.commons.Digesterクラスのインスタンスを生成します。(2)では読み込むXMLファイルの検証を行わないことをDigesterに指示しています。検証を行う場合はDigester#setValidating(true)とします。(3)ではこのオブジェクト(SampleConfig)自身をオブジェクトスタックにプッシュしています。

 これでDigesterの初期設定は完了です。次に(4)(8)の部分でプロセッシングルールを登録していきます。(4)ではsample/data-sourceというパターン文字列とオブジェクトを生成するためのプロセッシングルール(ObjectCreateRule)の組を登録しています。sample/data-sourceというパターン文字列はXMLファイル中の<sample>要素の中の<data-source>要素とマッチします。

 またObjectCreateRuleは第2引数に指定したクラス名のクラスのインスタンスを生成してオブジェクトスタックにプッシュするようなルールです。従って、(4)の意味は「<sample>要素の中に<data-source>要素が見つかったらnet.skirnir.sample.DatasSourceConfigクラスのインスタンスを生成してオブジェクトスタックにプッシュせよ」ということになります。

 引き続き(5)ではsample/data-sourceというパターン文字列とオブジェクトスタック上の2つのオブジェクトを関連付けるためのプロセッシングルール(SetNextRule)の組を登録しています。SetNextRuleはオブジェクトスタックの上から2番目(next)のオブジェクトについて第2引数に指定したメソッドを呼び出すようなルールです。呼び出されるメソッドは第3引数に指定したクラス名のクラスのインスタンスを引数として持つと仮定され、実際の引数としてはオブジェクトスタックの一番上にあるオブジェクトが渡されます。従って(5)の意味は「<sample>要素の中に<data-source>要素が見つかったらオブジェクトスタックの上から2番目のオブジェクトについてsetDataSourceConfig()メソッドを呼び出せ。このとき引数としてはオブジェクトスタックの一番上のオブジェクトを渡せ」ということになります。

 最後のプロセッシングルールが(6)(8)のCallMethodRuleとCallParamRuleです。これら2つは組で働きます。意味はオブジェクトスタックの一番上のオブジェクトについてCallMethodRuleの第2引数で指定したメソッドを呼び出します。呼び出すメソッドの引数の個数は第3引数で指定した個数で、実際の引数としてはCallParamRuleの第3引数で指定した属性(パターンにマッチした要素の属性です)の値が渡されます。

 何番目の引数として渡されるかは第2引数で指定されます。従って(6)(8)の意味は「<sample>要素の中の<data-source>要素の中に<set-property>要素が見つかったらオブジェクトスタックの一番上のオブジェクトについてsetProperty(<set-property>要素のproperty属性の値、<set-property>要素のvalue属性の値)メソッドを呼び出せ」ということになります。

 以上でプロセッシングルールの登録は完了です。後は(9)のようにDigester#parse()メソッドを呼び出せばXMLファイルの読み込みが行われます。XMLファイルを読み込み、ファイルの先頭から順に要素が登録されているパターン文字列と一致するかチェックされ、一致している場合は対応するプロセッシングルールを呼び出します。

おわりに

 パターン照合とプロセッシングルールの実行機構によってオブジェクトスタックを操作することでXMLファイルからJavaのオブジェクトツリーを生成するDigesterの仕組みは、最初は複雑に思えるかもしれませんが、慣れるとどういうXMLファイルを読み込むにはどういうパターン文字列とプロセッシングルールを登録すべきかが思い浮かぶようになります。皆さんもぜひ実際にDigesterを用いてXMLファイルを読み込むプログラムを作成することでDigesterに慣れていってください。

 次回はデータベースコネクションのプーリングを行うためのDBCPと汎用のオブジェクトプーリング機構を構築するためのPoolについて説明します。

筆者プロフィール

横田健彦(よこた たけひこ)

東京工業大学卒業後、(株)東芝に入社。現在、知識メディアラボラトリーにてコミュニティベース情報共有システムの研究に従事。小学校のころからコンピュータに触れ、主にゲームプログラミングを通してBASIC、アセンブラをはじめとする多数の言語を学ぶ。JavaではJakartaプロジェクトの成果物を利用していく中で主にWebアプリケーションプログラミングの面白さに引かれ、Ja-Jakartaプロジェクトの活動に貢献する一方でオープンソースのJavaベースのWebコンテンツ管理システムであるKvasir/Soraの開発を行っている。



Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。