japan.internet.com The Internet & IT Network


RSSニュース検索
カテゴリ
> トップページ
> Webビジネス
> Eコマース
> Webファイナンス
> Webマーケティング
> パブリック
> Webテクノロジー
> 携帯・ワイヤレス
> Linux Today
> Linux Tutorial
> J.I.C.ブログ
キャリア
> 転職ならen
> 派遣ならen
> アルバイトならen
> IT求人情報
ヘッドライン
> 今日のヘッドライン
> 週間ヘッドライン
Special Link
> フォトコミュニティ
> ストックフォト
> クリップアート
> イラスト
> フェリカ
> Web2.0
> 写真
イベント&セミナー
> イベントカレンダー
> 書評「IT の耳」
> 出張・接待検索
> ニュースガジェット 注目
無料ニュースメール
> 新規登録
> 変更・解除
> オプトインメールの登録・変更・解除
インフォメーション
> パートナーサイト
転職ならエン
就職ならen
求人ならen
履歴書ならen
アルバイトならエン
CRM/SFAならオラクル
> グループ会社
株式会社アエリア
(株)サンゼロミニッツ
株式会社エアネット
> お問い合わせ
> 広告掲載について
> リンクについて
> 著作権について
> その他お問い合わせ
> 利用規約
> 個人情報保護方針
> 会社概要地図
デベロッパー 2006年3月14日 10:10
デベロッパー・バックナンバー
Jakarta Commonsを使ってJDKクラスを拡張する:パート1

著者: Narayanan A.R.  オリジナル版を読む プリンター用 記事を転送
2006年3月14日 10:10 付の記事
海外internet.com発の記事
このエントリーを含むはてなブックマーク この記事をクリップ! Buzzurlにブックマーク Yahoo!ブックマークに登録 newsing it!

はじめに

 今ではJavaコミュニティのほとんどだれもがJakarta Commonsのことを知っています。Jakarta CommonsはJakartaのさまざまなプロジェクトで使われている再利用可能なクラスの集まりです。しかし、これらのクラスを独立したコンポーネントとして自分のJavaプロジェクトの中でも利用できることは案外知られていないのではないでしょうか。本稿はJakarta Commonsのさまざまなコンポーネントを紹介する3回シリーズの第1弾です。このシリーズでは実際のサンプルアプリケーションを通じてJakarta Commonsコンポーネントの使い方を説明していきます。これらのサンプルはJakarta Commonsコンポーネントを例示するだけのものではなく、典型的なJavaプロジェクトで再利用できる有用な機能を盛り込んだ完全なアプリケーションです。

 本稿では次のコンポーネントを取り上げます。

  • Validator
  • Collections
  • Chain
  • Functors
  • Lang

 本稿には完全なソースコードが付属しており、各サンプルのテストケースをJUnitで起動することで実行できます。

著者注
 Commonsコンポーネントのアーキテクチャと用法を理解するためには、オブジェクト指向プログラミング(OOP)とGang of Fourのデザインパターン(Command、Decorator、Singleton、Factory、Chain of Responsibility、Composite)についての基本的知識が非常に役立ちます。

Validator

 Commons Validatorは、StrutsでWebアプリケーションのフォームの妥当性検査に使用するコンポーネントです。このコンポーネントは任意のJava Beanの妥当性を検査するように作られていますが、ドメインレイヤオブジェクトや、Swingアプリケーションへのユーザー入力、あるいはWebサービスに送られるXML形式のデータなどの妥当性検査にも使用できます。

 私が見てきた大部分のプロジェクトでは、妥当性検査が次の3つの方法のいずれかで書かれています。

  1. ドメインオブジェクト内で
  2. ユーティリティクラス内で
  3. カスタムフレームワークとして

 最初の2つのアプローチを採用した場合は、結果的にデザインが好ましくないものになります。なぜなら、開発者がそれぞれ独自に妥当性検査メソッドを書く傾向にあるからです。たとえ単にエラーを記録したり、例外をスローしたり、それをユーザーに表示したりするだけの妥当性検査コードであっても、それがアプリケーションのあちこちに点在するのは望ましくありません。もう1つのデメリットは、せっかくエラーメッセージをリソースバンドルから読み取って表示する仕組みを実現しても、そのためのコードがあちこちに重複して存在することになる、という点です。たとえ妥当性検査のためのコードであっても、コードの重複は望ましくありません。

 今回は、試しにCommons Validatorを使ってJava Swingアプリケーションへのユーザー入力の妥当性検査を行います。図1は、ユーザーが登録情報を入力するGUIのスナップショットです。

図1 Swingアプリケーションのスナップショット
図1 Swingアプリケーションのスナップショット

 このアプリケーションでCommons Validatorをどのように利用しているかを詳しく説明する前に、このデザインで重要な役割を果たすクラスを紹介しておきます(ソースコードを見ると理解しやすいでしょう)。以下のクラスはパッケージ「in.co.narayanan.commons.validator」の一部です。

RegistrationScreen他のGUIコンポーネントを含むメインフレーム。Singletonパターンのクラスです。
Registrationユーザー登録情報を表現するドメインオブジェクト。
ListenersGUIイベントを処理して、それらを適切なコマンドに委譲するためのアダプタクラス。
SubmitCommandCommandパターンを使って送信操作を表現します(妥当性検査はこのクラスから開始されます)。
RegistrationContextMediatorパターンを使ってイベントリスナおよびコマンドクラスに必要なコンテキスト情報を渡します。
ValidatorEngineCommons Validatorを使ってRegistration Beanの妥当性を検査し、結果を処理します。

 このサンプルの目的は、登録時にユーザーが入力したログイン名(proposedLoginName)とメールアドレス(mailAddress)の妥当性を検査することです。SubmitCommandの中で、ユーザーが入力した値がRegistrationドメインオブジェクトに格納されます。

Commons Validatorの使用手順

 Commons Validatorコンポーネントを作成するには、通常は次の手順を行います。

1. POJO妥当性検査クラスを書くか再利用する

 Commons Validatorを使用するための最初のステップは、一群のデータに対して妥当性検査を行うクラスを書くことです。このサンプルでは、proposedLoginNamemailAddressの妥当性検査を行うためにGenericBeanValidatorというクラスを書きました。GenericBeanValidatorクラスの構造は次のようになっています。

public class GenericBeanValidator {

   public static boolean validateRequired(Object bean, Field field)
   {
      String value =
         ValidatorUtils.getValueAsString(bean, field.getProperty());
      return !GenericValidator.isBlankOrNull(value);
   }

   public static boolean validateEmail(Object bean, Field field)
   {
      String value =
         ValidatorUtils.getValueAsString(bean, field.getProperty());

      return GenericValidator.isEmail(value);
   }

}

 この妥当性検査クラスはどんなAPIにも依存しない、まったくのPOJOクラスなので、単体テストが簡単にできます。この目標を達成するために、今回はValidatorUtilsクラスとGenericValidatorクラスを使用しました。これらのクラスはCommons Validatorの一部です。getValueAsStringメソッドはRegistration Beanからプロパティの値を取り出し、文字列を返します。GenericBeanValidatorクラスはGenericValidatorのラッパーのように機能してBeanの妥当性を検査します。GenericValidatorクラスには他にも種々の妥当性検査を行うためのユーティリティメソッドが数多く含まれています(Commons Validatorを最大限に利用するには、javadocを参照してください)。

2. マッピングXMLファイルを定義する

 次のステップでは、妥当性検査クラス(前のステップで書いたものと同じようなクラス)を宣言して、それらを妥当性検査すべきBeanに結び付けるためのXMLファイルを定義します。ValidatorエンジンはこのXMLファイルを使用します(後ほど詳しく説明します)。このサンプルの「registration.xml」ファイルは次のようになります。

<form-validation>
   <global>
      <validator name="required"
         classname=
            "in.co.narayanan.commons.validator.GenericBeanValidator"
         method="validateRequired"
         methodParams=
            "java.lang.Object, org.apache.commons.validator.Field"
         msg="required.field"/>

      <validator name="email"
         classname=
            "in.co.narayanan.commons.validator.GenericBeanValidator"
         method="validateEmail"
         methodParams=
            "java.lang.Object, org.apache.commons.validator.Field"
         msg="invalid.email"/>
   </global>
   <formset>
      <form name="userRegistration">
         <field property="proposedLoginName" depends="required">
            <arg0 key=
               "userRegistration.proposedLoginName.displayname"/>
         </field>
         <field property="mailAddress" depends="email">
            <arg0 key="userRegistration.mailAddress.displayname"/>
         </field>
      </form>
   </formset>
</form-validation> 

 妥当性検査クラスは<validation>タグ内で宣言する必要があります。ここで重要なプロパティは、論理名(name)、クラス名(classname)、メソッド名(method)、メソッド引数(methodParams)、およびメッセージキー(msg)で、これは妥当性検査が失敗した場合にエラーメッセージを取得するのに使われます。このサンプルでは、proposedLoginNamemailAddressの妥当性検査を行うために2つのエントリが定義されています。

 <form>タグは<validator>定義をJava Beanプロパティにマップするために使われています。このサンプルの<arg0>タグはJava Beanプロパティの外部化されたフィールド名を取得するために使われています。これはロケール固有の表示用フィールド名をユーザーに表示するときに便利です。表示用フィールド名とエラーメッセージは外部化され、ファイル「applicationResource.properties」の中で利用できるようになっています。<field>定義内では任意の数の<argX>タグを使用できます。

3. Validatorを初期化して実行する

 Commons Validatorエンジンを実行すると、プロパティに関する妥当性検査がXMLファイルマッピングで定義されているかどうかがチェックされます。定義されている場合は、該当する妥当性検査クラスメソッドが呼び出され、必要な引数が渡されます。すべてのプロパティについてこのプロセスが繰り返され、結果がエラーメッセージとともに蓄積されます(結果の処理方法については後で説明します)。

 次のコードは、SubmitCommandが使用するValidatorEngineクラスから抜粋したものです。

InputStream definitionStream =
   reg.getClass().getResourceAsStream(definitionXml);
....
resources = new ValidatorResources(definitionStream);
....
Validator validator = new Validator(resources, Registration.NAME);
validator.setParameter(Validator.BEAN_PARAM, reg);
....
results = validator.validate();
....
return parseResults(formName, reg, results, resources);

 ValidationResourcesクラスはXMLファイル内の定義を表しています。このクラスを初期化するにはdefinitionXmlストリームを渡します。その後、このインスタンスを妥当性検査の対象となるBeanの名前と一緒にValidatorに渡します。妥当性検査クラスで必要な追加パラメータがある場合は、setParameterメソッドを使ってValidatorに渡すことができます。GenericBeanValidatorクラスのvalidateXXXXメソッドには、java.lang.Objectorg.apache.commons.validator.Fieldという2つの引数があったことを思い出してください。Validatorは、実行時にこの最初のパラメータにBeanのインスタンスを渡し、2番目のパラメータに適切なフィールド参照を渡します。

 ところで、Validatorは他のオブジェクト(たとえばHttpServletRequestオブジェクト)の参照をどうやって妥当性検査メソッドに渡すのでしょうか。実は完全修飾クラス名とHttpServletRequestクラスのインスタンスへの参照を指定して、ValidatorのsetParameterメソッドを呼び出すのです。Validator内の妥当性検査メソッドを呼び出せば妥当性検査を開始することができます。

4. 結果を処理する

 Validatorは実行時にValidatorResultsのインスタンスを返します。このインスタンスには、実行される妥当性検査についての情報がすべて格納されます。結果の視覚的な構造は次のようになります。

[
   [property1]
      [action1]
      [action2]
   [property2]
      [action1]
]

 すべてのBeanプロパティの結果はValidatorActionインスタンスの形で結び付けられます。これらのプロパティを反復処理し、ValidatorResultクラスのisValidメソッドを呼び出すことにより(パラメータにはアクション名を指定)、妥当性検査が成功したか失敗したかを判断します。具体的な解析ロジックについては、ValidatorEngineクラスのparseResultsメソッドのコードを見てください。

その他の用途

 このサンプルでは、Commons ValidatorをJava Beanアプリケーションに対するユーザー入力の妥当性検査に利用してみましたが、Commons Validatorは他にもさまざまなアプリケーションで使用でき、モジュール型設計、高い生産性、容易なメンテナンスといった利点が得られます。たとえば次のような使い方が考えられます。

  • XML形式でWebサービスレイヤに送られる注文情報に妥当性検査を適用する
  • ある時点におけるアプリケーション内のクラスの状態に妥当性検査を適用する(クラスの状態が要求を満たすものでなければ適切な例外をスローするなど)
  • JMXと併用してアプリケーションを監視する
  • 完全にJavaで実装されたルールエンジンの中で使用する(妥当性検査クラスを複雑なビジネスアプリケーション内のドメインオブジェクトに作用する個別ルールとして使用する)

 このフレームワークには、Webアプリケーションにおける電子メールやURLなどの情報をクライアント側で妥当性検査するためのJavaScript関数もいくつか含まれています。

Collections

 Commons Collectionsは、これだけを単独の記事として取り上げてもよいほど大きなトピックです。本稿ではAPIについて概説するにとどめ、最も一般的な実例を示します。

 JDKにJava Collections Frameworkがあるのに、なぜCommons Collectionsが必要なのかと疑問を持つ人もいるかもしれません。Collectionsは、Java Collections Frameworkのクラスとインターフェイスを拡張し、機能強化するものです。実のところ、私はこれをJDKの一部にすべきだとさえ思っています。

 私が特に好んで使用している機能をいくつか挙げておきます。

  • バッグインターフェイス
  • 固定サイズ、デュアル、およびLRUのマップ
  • オブジェクト配列およびマップのイテレータ
  • マップのMultiKey
  • APIを使いやすくする多数のユーティリティクラス
  • 大部分のクラスの動作をカスタマイズできるデコレータ

 Collectionsのクラスは次のようなパッケージに編成されています。

  • org.apache.commons.collections
  • org.apache.commons.collections.bag
  • org.apache.commons.collections.bidimap
  • org.apache.commons.collections.buffer
  • org.apache.commons.collections.collection
  • org.apache.commons.collections.comparators
  • org.apache.commons.collections.functors
  • org.apache.commons.collections.iterators
  • org.apache.commons.collections.keyvalue
  • org.apache.commons.collections.list
  • org.apache.commons.collections.map
  • org.apache.commons.collections.set

org.apache.commons.collections

 「org.apache.commons.collections」パッケージには、他のパッケージで実装されているインターフェイスと、コレクションクラスまたはコレクションクラスのデコレートバージョンをインスタンス化するためのファクトリとして機能するユーティリティクラスが含まれています。ArrayStackBeanMapExtendedPropertiesFastArrayListFastHashMapFastTreeMapといった注目すべきクラスが含まれています。これらのクラスの詳細情報はjavadocに網羅されていますが、表1で各クラスの実際の用法を簡単に紹介しておきます。

表1 「org.apache.commons.collections」パッケージ内の注目すべきクラス
コレクションクラス実際の用法
ArrayStackArrayStackはシングルスレッド環境で使用するためにArrayListをスタック実装にしたものです。たとえば、特定のメソッドの中でだけ何かの処理にスタックを使用したいという場合は、この実装を使用した方がJDK-1.4.2のVectorを使ったスタック実装よりも有利です。
BeanMapSwing GUIウィジェットのJButtonと同様、このクラスを使用すると、マップさえもJava Beanとして扱うことができます。アプリケーションのデータソースや一連の設定プロパティを表現するGUIをデザインする際に、マップを画面上でドラッグアンドドロップすることができます。
ExtendedPropertiesこのパッケージで私が最も気に入っているクラスです。設定プロパティをロードするためのloadメソッドを持つjava.util.Propertiesに似ていますが、こちらの実装には次のような利点があります。
・同じプロパティキーに複数の値を持たせることができます。
・値を異なる行に分割することができます。
・非文字列値を取得するための便利なヘルパーメソッドがあります。
たとえば、getFloatメソッドはfloat型のプロパティを取得します。そのため、設定プロパティ値を解析するためにラッパーメソッドを書く必要はありません。
FastArrayList
FastHashMap
FastTreeMap
大部分の操作が読み取り専用ならば、これらのクラスをマルチスレッド環境で使用できます。これらのクラスは、それぞれArrayListHashMapTreeMapをベースにしています。

org.apache.commons.collections.bag

 org.apache.commons.collections.bagクラスは、オブジェクトの複数のコピーをListに追加するという要件を備えたアプリケーションでとても役に立ちます。こうした状況では、通常は、オブジェクトをArrayListに追加し、特定の型の追加オブジェクトの数を判定する際には毎回反復処理を行うという方法が使われています。この要件に該当する好例はオンラインショッピングカートです。このアプローチの欠点は、メモリと速度の面でパフォーマンスが劣ることです。

 この要件に対処するには、オブジェクトのコピーを1つだけ保持し、同じ型のエントリが新たに追加されるたびにカウントを増やしていくというやり方が好ましいでしょう。それにはCommons CollectionsのHashBagクラスとTreeBagクラスが適しています(それぞれHashMapTreeMapをベースにしています)。

 ソースコードの「in.co.narayanan.commons.collections.bag」パッケージを見ると、バッグの実際の用法がわかります。このサンプルでは、さまざまなオペレーティングシステムのソフトウェアライセンスの注文を処理し、バッグ内の注文された製品を表現しています。

 これらのクラスでは、もっと具体的なバージョンやデコレータが利用できます。詳細についてはjavadocを参照してください。

org.apache.commons.collections.bidimap

 たいていのJava開発者は、キー/値のキーを取り出したい場合には、2つのHashMapを用意し、1つ目のHashMapの値を2つ目のHashMapにキーとして渡す、という方法を使用してきました。通常、名前と値を平等に扱いたければ、このテクニックを使用する必要があります。このケースでは値さえもキーになり得ます。

 org.apache.commons.collections.bidimapは、PeopleSoftとSiebelのコマンド処理エンジンを統合するプロトタイプアダプタです(ただし、双方に同等のコマンドが存在することが前提となります)。このアダプタの関連クラスは「in.co.narayanan.commons.collections.bidimap」パッケージに含まれています。まず、サンプル内のクラスSiebelPeopleSoftConnectorのコードをひととおり見てみてください。このクラスはアダプタとして働き、コマンドマッピングをBidiMap内に保持します。Siebelコマンドを処理する要求が到着すると、BidiMapから対応するPeopleSoftコマンドが取得され、PeopleSoftコマンドエンジンに渡されます。PeopleSoftコマンドでは逆の処理が行われます。このサンプルに含まれているのはアプリケーションのスケルトンだけです。

 請負で設計をする場合は、このクラスの修正不能バージョン(開発者が内容を変更できないもの)を使用すると便利です。これにより、開発者が内容を処理しているときに誤りを犯す危険性を大幅に減らすことができます。

org.apache.commons.collections.buffer

 コレクションから特定の順序でオブジェクトを取り除きたい場合は、バッファインターフェイスの実装を利用することができます。注目すべきクラスとして、CircularFifoBufferPriorityBufferBoundedFifoBufferBlockingBufferなどを挙げることができます。いずれにもデコレートバージョンがあります。

 パッケージ「in.co.narayanan.commons.collections.buffer」にバッファサンプルがあります。これはCircularFifoBufferクラスの例を示しています。このクラスは移動ウィンドウと考えることができます。コレクションのサイズは固定しており、満杯になるとオブジェクトが先入れ先出し(FIFO)方式で削除されます。

 このアプリケーションの目的は、リモートサーバーにパフォーマンスデータを報告することです。パフォーマンスデータは監視に使用されるだけなので、データが失われてもそれほど問題にはなりません。パフォーマンスデータを報告するクライアントアプリケーションは、ReportPerformanceDataクラスをインスタンス化し、reportPerformanceメソッドを呼び出す必要があります。パフォーマンスデータは循環バッファに追加されます。

 ReportTaskクラスはパフォーマンスデータオブジェクトを削除し、それをパフォーマンスサーバーに送ります。報告タスクを非同期に開始するためにJava 1.5の並行処理ユーティリティクラスThreadPoolExecutorが使用されるので、バッファ内に報告すべきデータがあればデータが報告されます。

org.apache.commons.collections.collection

 「org.apache.commons.collections.collection」パッケージにはデコレータクラスが含まれています。これらのクラスはjava.util.Collectionインターフェイスを直接実装しています。そのため、Collectionインターフェイスを実装しているクラスならどのクラスでも、これらのデコレータを利用することができます。PredicatedCollectionCompositeCollectionSynchronizedCollectionTransformedCollectionTypedCollectionUnmodifiableCollectionなどがよく使われるクラスです。表2に、これらのデコレータをどのような場合に使用するかを示します。

表2 「org.apache.commons.collections.collection」パッケージ内のよく使われるクラス
デコレータクラス使用する状況
PredicatedCollectionコレクションへのオブジェクトの追加を"amount >= $15000"などの条件で制限したいとき。このクラスをインスタンス化するには、条件を別オブジェクトとして定義します。これはプレディケイトとして呼び出され、decorateファクトリメソッドに引数として渡されます。
CompositeCollectionコレクションのコレクションを作成し、オブジェクトの追加または削除を統一的な方法で行いたいとき。
SynchronizedCollection既存のコレクションをスレッドセーフにしたいとき。
TransformedCollectionコレクションへの追加時に常にオブジェクトの形式を変換したいとき(たとえばStringからIntegerに変換するなど)。
TypedCollectionコレクションに追加するオブジェクトの型を制限したいとき。これはJava 1.5のgenericsに似ています。
UnmodifiableCollection既存のコレクション参照への修正を制限したいとき。

org.apache.commons.collections.comparators

 このパッケージにはさまざまな再利用可能クラスが含まれており、コレクション内でオブジェクトをソートするときに役立ちます。特に注目したいのがNullComparatorクラスとFixedOrderComparatorクラスです(表3を参照)。

表3 NullComparatorクラスとFixedOrderComparatorクラス
再利用可能なクラス機能
NullComparator配列またはリストの中でエントリをソートする際にnullエントリを最後部に移動します。
FixedOrderComparatorコレクション内での順序をあらかじめ定義されたリストに戻します。

org.apache.commons.collections.functors

 「org.apache.commons.collections.functors」パッケージについては、後のセクションで詳しく説明します。

org.apache.commons.collections.iterators

 「org.apache.commons.collections.iterators」パッケージ内の多くのクラスはjava.util.Iteratorインターフェイスを実装しています。MapIteratorArrayIteratorCollatingIteratorLoopingIteratorIteratorUtilsなどが最も役に立つクラスです。このパッケージ内のクラスを利用するには、IteratorUtilsクラス内のメソッドをよく調べる必要があるでしょう。

org.apache.commons.collections.keyvalue

 「org.apache.commons.collections.keyvalue」パッケージ内のMultiKeyクラスは非常に有用です。アプリケーション内でドメインオブジェクトを作成し、それを複合主キーと対にしてマップに格納したい場合は、対象レコードの主キーを構成する値を渡してMultiKeyのインスタンスを作成します。その後、このインスタンスをドメインオブジェクトを格納するためのマップに渡します。

 このパッケージのその他の一般的な用法として、ロケール固有のエントリをマップに格納するという例が挙げられます。この場合は、実際のキーとロケール名からキーが構成されます。

org.apache.commons.collections.list

 「org.apache.commons.collections.list」パッケージ内の注目すべきクラスは、TreeListFixedSizeListNodeCachingLinkedListCursorableLinkedListTransformedListPredicatedListです。これらのクラスのことは、javadocを見ればすぐに分かります。

org.apache.commons.collections.map

 「org.apache.commons.collections.map」パッケージ内の注目すべきクラスは、CaseInsensitiveMapCompositeMapFixedSizeMapFlat3MapLazyMapLinkedMapLRUMapMultiKeyMapPredicatedMapSingletonMapStaticBucketMapです。これらのクラスのことは、javadocを見ればすぐにわかります。

org.apache.commons.collections.set

 「org.apache.commons.collections.set」パッケージ内の注目すべきクラスは、CompositeSetListOrderedSetMapBackedSetPredicatedSetTransformedSetTypedSetUnmodifiableSetです。これらのクラスのことは、javadocを見ればすぐにわかります。

Chain

 Gang of FourのCOR(Chain of Responsibility)デザインパターンは、一群の操作をモデル化するのによく使われます。各操作をコマンドオブジェクトとしてモデル化し、他のオブジェクトとリンクしてチェーンにする必要があります。これらのコマンドオブジェクトはデータに作用し、チェーンの次のコマンドに制御を渡すべきかどうかを示します。コードに変更を加えなくても操作を簡単に追加/削除できるのが利点です。実行が成功するためには、コマンドの実行時にコマンドラインから渡された引数とサブコマンドを処理コードで利用できなければなりません。

 Commons Chainコンポーネントは、アプリケーション内でこのパターンを使用するのに必要なインターフェイスと実装を提供します。APIは明快で、理解するのも難しくはないでしょう。以降では、このコンポーネントを現実の問題に適用することで用法を説明していきます。CLI(Command Line Interface)アプリケーションを開発したり使用したりした経験のある開発者は少なくないと思います。本稿のCommons Chainのサンプルは、HTTP、FTP、PINGという基本的なネットワークプロトコルを使用するためのCLIアプリケーションです(ソースコードについては「in.co.narayanan.commons.chain」パッケージを見てください)。次のリストはいくつかのコマンドを抜粋したものです。

java CommandProcessor -user admin -password manager -ping {host}
java CommandProcessor -user admin -password manager
 -ftp {host} -get {path_to_file}
java CommandProcessor -user admin -password manager
 -http -get {path_to_file}

 今回のサンプルでは、-user引数と-password引数を使って、ネットワークコマンドではなくローカルデータベースに対してユーザーの妥当性検査を行います。-get引数は、-ftp-httpという両マスタコマンドについて、指定のURLのファイルを取得するためのサブコマンドです。ここではCommons Chainの使用例を示すことが狙いなので、この実装にはスケルトンコードしか入っていません。そのため、-pingコマンドの実行時にはコンソールにログメッセージが表示されるだけで、実際にターゲットに接続することはありません。あとは各自で完全なアプリケーションに仕上げてください。その際はCommons Net APIを使用するようお勧めします。

 この問題に対してCORパターンを使用すると、デザインが大幅に簡素化されてモジュール性が向上します。

CLIコンテキスト

 CLIコマンドの実行時に操作が正常に行われるためには、処理コードで引数とサブコマンドを利用できる必要があります。CommandlineContextクラスとCLICommandクラスはドメインオブジェクトで、これらはコマンドとサブコマンドと引数を表現します。

 CLICommandは自分自身への参照を持っています。これはサブコマンドを表現するためのCompositeデザインパターン実装であり、これにより任意の数のコマンドをネストすることが可能になります。CLICommandのインスタンスはuserpasswordと一緒にCommandlineContextに入れられるので、実行されるCLI操作の全情報を表現します。コンテキストは実行時にチェーン内のコマンドに渡されます。コンテキスト情報を利用したり制御を次のコマンドに渡したりするのは、チェーン内の各コマンドが行います。

 このサンプルではCLIの引数をプライベート変数として表現し、ゲッターメソッドを使ってアクセスします。代替策としてContextBase実装を使用する方法もあります。この方法ではJava Beanイントロスペクションを使って属性値を取得し、Mapビューを提供します。したがって、getUsergetPasswordのような専用ゲッターメソッドをget("user")get("password")で置き換えることができます。

チェーン内のコマンド

 コマンドに必要なコンテキスト情報を表現したら、次にコマンドとチェーンとサブチェーンをモデル化します(後ほどCommons Chain APIでコマンドとチェーンをどのように実行するかを示します)。Pingクラスは-pingのために実行されるコマンドです。このクラスはexecuteメソッドに渡されたコンテキストを使って、コマンドタイプが-pingかどうかをチェックします。コマンドタイプが他のものなら、このメソッドはfalseを返して、チェーン内の次のコマンドに制御を渡す必要があることをChainフレームワークに知らせます。

 Ftpクラスはマスタコマンド-ftpを表現します。したがって、ftpのサブコマンドである-get-lsを処理するために、これはChainBaseのサブクラスとしてモデル化されます。実行メソッドはコマンドタイプをチェックします。それが-ftpなら、このチェーン内のFtpGetコマンドとFtpLsコマンドを実行するために基本クラスのexecuteが呼び出されます。このアプリケーションでは、コマンドに-http -getなどのサブコマンドが含まれていれば、それはChainとしてモデル化され、該当するコマンド制御が委ねられます。

チェーン

 このアプリケーションで使われるチェーンとコマンドを視覚的に表現すると、次のようになります。

Main Chain
Authenticator (Verifies user and password) --> Ping (Command) --> 
   Ftp (Sub-chain A) --> Default (Prints invalid command message)

Sub-chain A
FtpGet (Command) --> FtpLs (Command)

 チェーンはChainBaseクラスまたはCatalogBaseクラスを使って実行時にプログラム内から作成できます。代替策として、XMLファイル内でコマンドとチェーンを指定し、ConfigParserを使ってチェーンを構築するという方法もあります。今回のサンプルで使用する「catalog.xml」ファイルは以下のとおりです。

<?xml version="1.0" ?>
<catalog>
   <chain name="CommandProcessor">
       <command name="authenticate"
          className="in.co.narayanan.commons.chain.Authenticator"/>
       <command name="ping"
          className="in.co.narayanan.commons.chain.Ping"/>
       <chain name="ftp"
          className="in.co.narayanan.commons.chain.Ftp">
           <command name="get"
              className="in.co.narayanan.commons.chain.FtpGet"/>
           <command name="ls"
              className="in.co.narayanan.commons.chain.FtpLs"/>
       </chain>
       <command name="default"
          className="in.co.narayanan.commons.chain.DefaultCLICommand"/>
   </chain>
</catalog>

フレームワークのセットアップと実行

 次のリストはCommons Chainフレームワークをセットアップして実行するためのコードです。

ConfigParser parser = new ConfigParser();
parser.parse(getClass().getResource("catalog.xml"));

Catalog catalog = CatalogFactoryBase.getInstance().getCatalog();
....

Command command = catalog.getCommand("CommandProcessor");
command.execute(getPingCommand());
command.execute(getFtpGetCommand());
command.execute(getFtpLsCommand());
command.execute(getInvalidCommand());
command.execute(getInvalidCredentials());

 「catalog.xml」ファイルが解析され、結果としてCatalogの参照がCatalogFactoryBaseから取得されます。これで各CLIコマンドの呼び出しの「モックアップ」ができたことになります。完全なコードはテストケースクラスTestChainに収められています。なお、別個のクラスでコマンドライン入力を解析してコンテキストオブジェクトを作成し、それらをチェーンに渡すのが理想的です。その際はCommons CLIを使用することができます。

Functors

 Functors APIの目的は、手続き型プログラム要素をオブジェクトとしてモデル化することです。たとえば、何かの条件に関してVector内またはArrayList内のオブジェクトを評価し、その結果に基づいてアクションをとるようなコードを書いたことはないですか? あるいは、if-elseif-elseif-else文やswitch-case文を書いた経験は? Functorsを使用すると、if-elseswitch-caseのような条件とこれらの条件の中の式を、独立したオブジェクトとしてモデル化することができます。では、なぜそうする必要があるのでしょうか。この手法には次のような利点があります。

  • モジュール型設計になるため、機能の拡張や修正が容易になります。StrategyパターンとCommandパターンを一緒に使って、操作の中のアルゴリズムを条件によって切り替えることができます。
  • 単体テストケースを書くことが容易になります。
  • リフレクションを使って、条件に応じた操作を動的にロードすることができます。

 技術的に見ると、ifwhileforswitchといったプログラム要素の多くはFunctorsを使ってモデル化することができます。この考え方は、連続的なif-else文、switch-case文、およびコレクションに対する操作でよく使われます。

 このAPIに含まれる重要なインターフェイスはPredicateClosureです。Predicateは条件を表現するのに使います。結果がtrueなら、Closureexecuteメソッドが呼び出されます。

 本稿で紹介するFunctorsサンプルは、データベースコマンドを処理する単純なスクリプト処理エンジンです。このサンプルでは、スクリプトコマンドに対してどのコマンドクラスを呼び出すかを定義したswitch-caseディシジョンツリーをFunctorsでモデル化しています。完全なコードは「in.co.narayanan.commons.collections.functors」パッケージに収められています。使用するデータベースコマンドの構文は次のとおりです。

add <tablename>, <column values>
delete <tablename>, <key_column>, <value>
modify <tablename>, <key_column>, <value>, <column name and new values>

 このスクリプトエンジンは、addスクリプトコマンドが呼び出されたときに、データベースに対してSQLのINSERT文を実行する必要があります。他のコマンドも同様に処理する必要があります。ScriptCommandクラスはドメインオブジェクトであり、スクリプトコマンドとその引数を表現します。AddTaskDeleteTaskModifyTaskの各クラスには、スクリプトコマンドのために実行するロジックが含まれています。このエンジンをモデル化するための従来の方法を理解するため、ProcessCommand1クラスから抜粋した次のコードをよく見てください。

public void process(ScriptCommand command) {
   switch(command.getType()) {
      case ADD: {
         executeAdd(command);
      } break;

      case MODIFY: {
         executeModify(command);
      } break;

      case DELETE: {
         executeDelete(command);
      } break;
   }
}

 ここでは、switch-caseブロック内でスクリプトコマンドの種類をチェックし、該当するコマンドを実行しています。

 次のリストはProcessCommand2クラスからのもので、Functorsを使って同じ動作を実現できることを示しています。

public void start() {
   Map predicatesAndClosures = new Flat3Map();
   predicatesAndClosures.put(new AddPredicate(), new AddClosure());
   predicatesAndClosures.put(
      new ModifyPredicate(), new ModifyClosure());
   predicatesAndClosures.put(
      new DeletePredicate(), new DeleteClosure());

   Closure switchClosure =
      ClosureUtils.switchClosure(predicatesAndClosures);
   CollectionUtils.forAllDo(commands, switchClosure);
}

private static class AddPredicate implements Predicate {
   public boolean evaluate(Object object) {
      ScriptCommand command = (ScriptCommand)object;
      return command.getType() == CommandType.ADD ? true : false;
   }
}

private static class AddClosure implements Closure {
   public void execute(Object input) {
      ScriptCommand command = (ScriptCommand)input;
      AddTask add = new AddTask();
      add.add(command.getTableName(), command.getArgs());
   }
}

 AddPredicateはコマンドタイプを評価します。AddClosureにはaddスクリプトコマンドを実行するロジックが含まれています。プレディケイトとクロージャはマップされて、ClosureUtilsユーティリティクラス内のswitchClosureメソッドに渡されます。結果がtrueの場合、返されたSwitchClosure参照は、クロージャにマップされたプレディケイトと呼び出しを評価します。trueが返されるまで、マップ内の各プレディケイトについて、これが繰り返されます。

 このサンプルは、クロージャを併用してプログラムフローを実現する方法を示しています。ここでは、上記のAddClosureとCommons Collections API内のSwitchClosureを一緒に使用しています。たとえば、SwitchClosure参照をIfClosureと使用することができます。

 ビジネスアプリケーションでのFunctorsの一般的な用途は、一群のビジネスルールをドメインオブジェクトインスタンスのコレクションに適用し、ルール結果に関連してアクションをとることです。ルールはプレディケイトとしてモデル化し、アクションはクロージャとしてモデル化する必要があります。Jythonに統合したPythonスクリプティング言語を使用すれば、完全なルールエンジンを構築することができます。その場合、プレディケイトとクロージャを書くためにPythonスクリプトを使用する必要があります。

Lang

 このLang APIは「java.lang」パッケージに対する拡張です。私はCommons Langが将来JDKの一部になることを望んでいます。このパッケージにはJavaプロジェクトで使用できるユーティリティメソッドが数多く含まれています(JakartaのWebサイトに包括的なユーザーガイドがあり、javadocには大部分のクラスについて簡単な用法が記載されています)。本稿では特に興味深いクラスだけを紹介します。サンプルのソースファイルは「in.co.narayanan.commons.lang」パッケージに収められています。

 StringUtilsWordUtilsStringEscapeUtilsRandomStringUtilsの各クラスには有用な文字列操作メソッドが備わっています。以下のリストでは、これらのメソッドの中でよく使うものを取り上げ、その用法を示します。

 次のメソッドは、たとえばJTableセルなどでGUI内の表示領域が制限されている場合に、メッセージの一部だけを表示します。

String message = "Please check the logs for an error.";
message = StringUtils.abbreviate(message, 25);

 次のメソッドは、Javaで開発されるデスクトップアプリケーションで非常に役立ちます(たとえば、電子メールを作成して送信する機能が必要なJava Swingアプリケーションなど)。

String message = "jakarta commons";
message = StringUtils.capitalize(message);

 次に示すのはWordUtilsクラスからのもので、文字列を大文字にする2種類のメソッドです。

String message = "jakarta commons";
message = WordUtils.capitalize(message);

String message = "jakarta COMMONS";
message = WordUtils.capitalizeFully(message);

 詳しい用法についてはjavadocを参照してください。

 次のメソッドは前後にスペースを入れてメッセージを位置合わせするのに便利です。

String message = "java";
message = StringUtils.center(message, 10);

 これまでは文字列処理のためにトークン化のコードを書くことがよくありましたが、Langを使えばその必要はありません。splitメソッドはトークン化の処理を行い、String配列を返してくれます。joinメソッドを使用すると、String配列から新しい文字列を作成できます。

String message = "hello how are you";
String messageTokens[] = StringUtils.split(message, ’ ’);

String newMessage = StringUtils.join(messageTokens, ’$’);

 文字列内でのパスワード表示も、overlayメソッドで簡単に実現できます。replaceメソッドは非常に優秀で、単語を検索して置換する機能も備えています。そのため、文字位置を気にする必要はありません。

String message = "This is a demo for replace and overlay feature";
message = StringUtils.replace(message, "demo", "illustration");

message = "Your password is: somepassword";
message = StringUtils.overlay(message, "*****", 18, message.length());

 StringEscapeUtilsクラスには、HTML、Java文字列、JavaScript文字列、SQL、およびXMLをエスケープ/エスケープ解除するためのメソッドがあります。

String html = "<h1>This will be displayed in the <i>HTML</i> page. 
Hence the <b>tags</b> will be escaped.<h1>";
String escapedHtml = StringEscapeUtils.escapeHtml(html);

 次のメソッドではランダムなパスワードを生成できます。

String randomPassword = RandomStringUtils.randomAlphanumeric(8);
System.out.println("Random Password:" + randomPassword);

 以降では、各パッケージについて簡単に触れておきます。

org.apache.commons.lang

 ArrayUtilsBooleanUtilsCharRangeCharSetCharUtilsSystemUtilsValidateが注目すべきクラスです。詳細はjavadocを参照してください。

org.apache.commons.lang.builder

 クラスCompareToBuilderEqualsBuilderHashCodeBuilderToStringBuilderは、それぞれメソッドcompareToequalshashCodetoStringを実装するのに役立ちます。これらのクラスは、ほぼすべてのJavaプロジェクトで必要になります。

org.apache.commons.lang.enums

 Java 1.5では列挙型がサポートされています。したがって、このパッケージが役に立つのはJDK 1.5より前のプロジェクトです。このパッケージのjavadocには使用例が掲載されています。

org.apache.commons.lang.exception

 NestableExceptionクラスは例外の原因をリンクするためのサポートを提供します。すべての例外についてスタックトレースができると、エラーの根本原因をすばやく突き止めるのに役立ちます。これは特にJDK 1.4より前のプロジェクトで有用です。JDK 1.4以降には、ネストした例外のサポートが備わっています。

org.apache.commons.lang.math

 IntRangeFractionNumberUtilsRandomUtilsが注目すべきクラスです。詳細はjavadocを参照してください。

org.apache.commons.lang.mutable

 java.langパッケージ内の基本型のラッパーは不変ですが、このパッケージに含まれているラッパークラスは可変です。詳細はjavadocを参照してください。

org.apache.commons.lang.time

 DateUtilsDataFormatUtilsFastDateFormatStopWatchなどのクラスが有用です。詳細はjavadocを参照してください。

まとめ

 Jakarta Commonsについて紹介するシリーズ第1回では、次のことを取り上げました。

  • Commonsのさまざまなコンポーネントが提供する豊富な機能
  • ユーティリティクラスの特定のコンポーネントやメソッドの具体的な用途
  • さまざまなAPIで使われているデザインパターン
  • さまざまなAPIのパッケージとクラスの概要

 これからJavaアプリケーションをデザインしたり開発したりするときには、有用なクラスを選んで適切に使用できるのではないでしょうか。このシリーズの第2回と第3回では、さらに興味深いコンポーネントについて解説します。最後までお付き合いいただけば、あなたもJakarta Commonsコンポーネントの大部分のクラスをJDKの一部にすべきだと確信するでしょう。

著者紹介

Narayanan A.R.(Narayanan A.R.)
テスト駆動開発、アジャイル方法論、Javaテクノロジ、およびデザインパターンの熱烈な擁護者。Javaテクノロジを使ったソフトウェアのデザインおよび開発に携わって数年になる。


関連記事
  • J2SE 5.0のコレクションの使い方を覚える
  • サン、アイデンティティ管理基盤ソフトの日本語版新版を発表
  • ソフィア・クレイドル、携帯 Java アプリ圧縮ツール最新版
  • 「Sun Java Studio Creator 2」日本語対応版の無償配布を開始
  • 次世代エンタープライズ Java 登場は間近、Sun がプレビュー版


  • 関連テーマ
  • ウィジェット
  • XML
  • Java
  • Apache
  • ドメイン


  • ★最新トップニュース
    海外 【台湾】フォックスコン・インターナショナルの上期利益、56%の大幅下落(Webファイナンス 8月29日 13:50)
    EMS 大手であるフォックスコン(Foxconn=鴻海精密)グループ子会社で香港上場会社の Foxconn International Holdings(FIH=富士康国際)」は27日、2008年上半期の決算発表を行った。
    コラム IT を変えつつあるのはどの技術?(Webビジネス 8月29日 13:30)
    IT マネジャーと企業幹部の間で、IT を最も大胆に変えつつある技術トレンドを巡って意見の相違があるのは当然だ。これら両陣営は敵対関係にある場合が多いため、両者が今日の技術トレンドを違った角度から見ているのは予想できる。
    国内 CTC、日本 HP、マイクロソフト、Hyper-V ベースの仮想化ソリューションを今秋提供へ(Webテクノロジー 8月29日 13:30)
    伊藤忠テクノソリューションズ(CTC)、日本ヒューレット・パッカード(日本 HP)、マイクロソフトの3社は、2008年8月27日、日本 HP とマイクロソフトのサーバー製品・仮想化技術などを組み合わせ、ソリューション検証を共同で実施する、と発表した。
    海外 Facebook 誕生のいきさつが映画に(Webマーケティング 8月29日 13:20)
    ハリウッドの著名脚本家 Aaron Sorkin 氏が、Facebook に自分のページを開設した。Sony Pictures から依頼を受け、Facebook 誕生物語の映画脚本を書くためという。
    海外 価格競争の影響が出た第2四半期のサーバー売上(Webビジネス 8月29日 13:10)
    IDC が、2008年第2四半期のサーバー市場調査を発表した。売上を得るためにベンダー各社が大幅に値下げしている状況がうかがえる内容だ。
    トピックス
    > オススメのIT系求人情報【毎週月曜日更新】
    footer_301.gif


    リサーチ
    > デイリーリサーチDLサイト
    > OnlineResearchPortal (リサーチデータバンク)
    > モバイルリサーチ with goo
    footer_301.gif
    キーワード
    > iPhone > Youtube
    > Google > モバイルノート
    > 半導体 > ウィルコム
    > テーマ一覧はこちら
    footer_301.gif
    セミナー情報
    > 第2回インターネットコムマーケティングセミナー
    「モバイルマーケティングの世界」〜これだけはやっておきたいモバイルマーケティング施策とは〜
    9月24日(水)13:00〜17:00 ITS 山王健保会館
    ※詳しくはこちら
    footer_301.gif
    デベロッパー
    > DevX
    > CodeGuru
    > developer.com
    footer_301.gif
    j.i.c.ブログ
    ブログ一覧
    ベンチャー専門家の目利きブログ「なぜこの企業は伸びるのか?」 【ベンチャー専門家の目利きブログ「なぜこの企業は伸びるのか?」】
    「選択と集中」選択をして集中しない限りは勝てない/メディカル・コミュニケーションズ株式会社(8月29日)
    データメーション 【データメーション】
    Apple は顧客の忠誠心にあぐらをかいているのか? (8月27日)
    Graphic Design Forum 【Graphic Design Forum】
    次へとつながる輪 (8月27日)
    最新テクノロジーの意外な処方箋 【最新テクノロジーの意外な処方箋】
    あなたが舌なめずりしたくなるようなもの(8月26日)
    エンジニアの独り言 【エンジニアの独り言】
    データをローカルに保存するWebアプリケーション(8月22日)
    デスマーチからの脱却 【デスマーチからの脱却】
    30min. iPhoneアプリリリース(8月18日)
    footer_301.gif
    最新コラム一覧
    IT マネジメント IT マネジメント

    IT を変えつつあるのはどの技術?(8月29日)
    最新ハイテク講座 最新ハイテク講座

    繁栄か滅亡か!巨大なエネルギー「原子力」の未来(8月29日)
    developer.com developer.com

    レガシーWebアプリケーションをWebLogic Portal内のフルページIFrameとして統合する(8月29日)
    百式のネットビジネス研究 百式のネットビジネス研究

    友達にあなた特製のクスリを贈ることができる「Get Your Drug On」(8月29日)
    週刊-サイト別アクセス状況データ 週刊-サイト別アクセス状況データ

    ビデオリサーチインタラクティブ調査(月間インターネットオーディエンスデータ)(8月28日)
    「IT の耳」 「IT の耳」

    【書評】『1回の会議・打ち合わせで必ず結論を出す技術』――無意味な会議を撲滅する(8月28日)
    ハードウェアから見たデータベース ハードウェアから見たデータベース

    巨大テーブル活用術1(8月28日)
    ウチのサイトを SEO ウチのサイトを SEO

    検索エンジンが見ている世界(8月27日)
    エンジニア転職ノウハウ開発室 エンジニア転職ノウハウ開発室

    目指せecoエンジニア!グリーンITで地球を救え(8月26日)
    アイレップの SEM フロンティア アイレップの SEM フロンティア

    円滑に SEO を導入・実施するための組織体制を構築しよう(3)(8月26日)
    footer_301.gif
    専門チャンネル
    > セキュリティチャネル > テレコムチャネル
    > サーチエンジンウォッチ
    footer_301.gif
    海外のインターネットコム アメリカ韓国ドイツトルコ
    関連企業のサイト:ストックフォト イラスト ネットストリート ホテル予約サイト タウン情報 出張 事業継承 シミュレーション トランクルーム 優待映画チケット 田舎暮らしガイド オリジナルデザインTシャツ ニタコエ
    Copyright 2008 Jupitermedia Corporation All Rights Reserved. http://www.internet.com/
    space.gif space.gif