第5章 出力データの生成

Smooks の Javabean カートリッジを使用して、メッセージデータより Java オブジェクトを作成および投入することができます。
Smooks のこの機能を XML、EDI、CSV などの 単なる Java バインディングフレームワークとして使用することが可能ですが、Smooks の Java バインディング機能は他の機能の基盤ともなっています。これは、Smooks が作成する Java オブジェクト (データをバインドする) を BeanContext クラスより使用可能にするためです。このクラスは、Smooks の ExecutionContext を介して Smooks ビジター実装が使用できるようにする Java Bean コンテキスト です。
Javabean カートリッジに提供される機能によって構築される既存機能の一部には次のようなものがあります。
  • テンプレート: 通常、テンプレートを BeanContext のオブジェクトに適用します。
  • バリデーション: ビジネスルールのバリデーションでは通常、ルールを BeanContext のオブジェクトに適用します。
  • メッセージの分割とルーティング: オブジェクト自体をルーティングするか、テンプレートを適用して操作の結果を新しいファイルへルーティングして、BeanContext のオブジェクトから分割メッセージを生成します。
  • 永続化 (データベースの読み書き): これらの機能は、データベースにコミットされる Java オブジェクト (エンティティなど) を作成および投入するための Java バインディング機能に依存します。データベースから読み取られるデータは通常 BeanContext へバインドされます。
  • メッセージのリッチ化: 上記の説明通り、 リッチ化されたデータ (データベースなどから読み取られたデータ) は通常 BeanContext へバインドされます。その BeanContext より、Java バインディング機能自体を含む Smooks の他の機能すべてが使用可能になります (表現ベースバインディング に対して使用可能)。これにより、Smooks によって生成されたメッセージをリッチ化することができます。
次の例はすべてこの XML メッセージから派生します。
<order>
    <header>
        <date>Wed Nov 15 13:45:28 EST 2006</date>
        <customer number="123123">Joe</customer>
    </header>
    <order-items>
        <order-item>
            <product>111</product>
            <quantity>2</quantity>
            <price>8.90</price>
        </order-item>
        <order-item>
            <product>222</product>
            <quantity>7</quantity>
            <price>5.20</price>
        </order-item>
    </order-items>
</order>
http://www.milyn.org/xsd/smooks/javabean-1.4.xsd 設定名前空間を介して JavaBean カートリッジを使用します (スキーマを IDE にインストールして、自動補完機能をが使用できるようにします)。
設定例は次の通りです。
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd">
 
    <jb:bean BeanId="order" class="example.model.Order" createOnElement="#document" />
 
</smooks-resource-list>
この設定は example.model.Order クラスのインスタンスを作成し、「order」という BeanId 下の Bean コンテキストへバインドします。インスタンスは #document 要素のメッセージの最初に作成されます (root order 要素の最初)。
  • BeanId: Bean 識別子です。
  • class: Bean の完全修飾クラス名です。
  • createOnElement: この属性を使用して Bean インスタンスが作成されるタイミングを制御します。バインディング設定 (jb:bean 要素の子要素) より Bean プロパティの投入を制御します。
  • createOnElementNS: この属性より createOnElement の名前空間を指定できます。
Javabean カートリッジは Java Bean の次の状態を設定します。
  • 公開された引数のないコンストラクタがあります。
  • 公開プロパティセッターメソッドがあります。特定の名前形式に従う必要はありませんが、 標準のプロパティセッターメソッド名の形式に従う方がよいでしょう。
  • 直接 Java Bean プロパティを設定できません。
前項の設定では、 example.model.Order Bean インスタンスを作成し、 Bean コンテキストにバインドしました。 次項ではデータを Bean インスタンスにバインドする方法を説明します。
Javabean カートリッジでは 3 種類のデータバインディングを使用できます (使用するには、データバインディングを jb:bean 要素の子要素として追加します)。
  • jb:value: ソースメッセージのイベントストリームからのデータ値をターゲット Bean へバインドするために使用されます。
  • jb:wiring: Bean コンテキストからの他の Bean インスタンスをターゲット Bean の Bean プロパティへ「プラグ」するために使用されます。
    この設定を使用して オブジェクトグラフ (Java オブジェクトインスタンスの疎コレクションとは逆) を構築できます。
    BeanId、Java クラスタイプ、またはアノテーションを基に Bean をプラグできます。
  • jb:expression: 名前の通り、この設定を使用して式から算出された値をバインドできます。 注文商品の合計値を OrderItem Bean へバインドする場合が簡単な例になります (値段と数量を掛けた値を算出する式の結果を基にします)。
    execOnElement 属性式を使用して、式が評価され結果がバインドされる要素を定義します (定義しない場合、親である jb:bean createOnElement の値を基に式が実行されます)。
    targeted 要素の値は、「_VALUE」(アンダーラインに注意) 下の文字列変数として式で使用可能です。
ここで、Order XML メッセージを使用して完全な XML から Java バインディングへの設定について学びましょう。XML メッセージから投入する Java オブジェクトは次の通りです (「getter」と「setter」は省略されています)。
public class Order {
    private Header header;
    private List<OrderItem> orderItems;
}
 
public class Header {
    private Date date;
    private Long customerNumber;
    private String customerName;
    private double total;
}
 
public class OrderItem {
    private long productId;
    private Integer quantity;
    private double price;
}
データを order XML からオブジェクトモデルにバインドする時に使用しなければならない Smooks 設定は次の通りです。
<?xml version="1.0"?>
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd">
 
(1)   <jb:bean BeanId="order" class="com.acme.Order" createOnElement="order">
(1.a)     <jb:wiring property="header" BeanIdRef="header" />
(1.b)     <jb:wiring property="orderItems" BeanIdRef="orderItems" />
      </jb:bean>
 
(2)   <jb:bean BeanId="header" class="com.acme.Header" createOnElement="order">
(2.a)     <jb:value property="date" decoder="Date" data="header/date">
              <jb:decodeParam name="format">EEE MMM dd HH:mm:ss z yyyy</jb:decodeParam>
          </jb:value>
(2.b)     <jb:value property="customerNumber" data="header/customer/@number" />
(2.c)     <jb:value property="customerName" data="header/customer" />
(2.d)     <jb:expression property="total" execOnElement="order-item" >
              += (orderItem.price * orderItem.quantity);
          </jb:expression>
      </jb:bean>
 
(3)   <jb:bean BeanId="orderItems" class="java.util.ArrayList" createOnElement="order">
(3.a)     <jb:wiring beanType="com.acme.OrderItem" /> <!-- Could also wire using BeanIdRef="orderItem" -->
      </jb:bean>
 
(4)   <jb:bean BeanId="orderItem" class="com.acme.OrderItem" createOnElement="order-item">
(4.a)     <jb:value property="productId" data="order-item/product" />
(4.b)     <jb:value property="quantity" data="order-item/quantity" />
(4.c)     <jb:value property="price" data="order-item/price" />
      </jb:bean>
 
</smooks-resource-list>
  • 設定 (1) は com.acme.Order Bean インスタンス (トップレベル Bean) の作成ルールを定義します。この Bean インスタンスをメッセージの一番始め (order 要素) に作成します。
    各 Bean インスタンスを作成します。(1)、 (2)、 (3) すべてはメッセージの一番始めの (4) を許可します (order 要素上)。 これを行うのは、 投入されたモデルではこれら Bean の単一インスタンスのみが存在するためです。
  • 設定 (1.a) と (1.b) は、Header (2) および ListOrderItem Bean インスタンス (3) を order Bean インスタンスにワイヤリングするワイヤリング設定を定義します (beanIdRef 属性の値と、 (2) と (3) に定義された beanId 値の参照方法を確認してください)。 (1.a) と (1.b) のプロパティ属性はワイヤリングが行われる Order Bean プロパティを定義します。
    Java クラスタイプ (beanType) を基に Bean をオブジェクトへワイヤリングしたり、特定のアノテーションを付けて (beanAnnotation) Bean をオブジェクトへワイヤリングすることも可能です。
    設定 (2) は com.acme.Header Bean インスタンスを作成します。
  • 設定 (2.a) は、Header.date プロパティにバインドする値を定義します。どこでバインディング値がソースメッセージから選択されるかはデータ属性が定義することに注意してください。この例では header/date 要素からになります。また decodeParam サブ要素を定義する方法にも注目してください。 これは DateDecoder を設定します。
  • 設定 (2.b) は、Header.customerNumber プロパティへの値バインディング設定を定義します。 ソースメッセージにある要素属性よりバインディング値を選択するため、 データ属性を設定する方法に注目してください。
    設定 (2.b) は、注文合計が算出され Header.total プロパティに設定される式バインディングを定義します。execOnElement 属性は、 order-item 要素上で式を評価 (およびバインド/再バインド) する必要があることを Smooks に伝えます。 そのため、 複数の order-item 要素がソースメッセージにある場合、 この式は各 order-item に実行され、 新しい合計値が Header.total プロパティに再バインドされます。 式が現在の orderItem の合計を現在の注文合計 (header.total) に追加する方法に注目してください。
  • 設定 (2.d) は、各注文商品の合計 (数量に価格を掛けた値) を現在の合計に足してこれまでの合計が算出される式バインディングを定義します。設定 (3) は、 OrderItem インスタンスを保持するための ListOrderItem Bean インスタンスを作成します。
  • 設定 (3.a) は、com.acme.OrderItem タイプのすべての Bean ((4) など) をリストへワイヤリングします。このワイヤリングはプロパティ属性を定義しないことに注意してください。 これは、 コレクションへワイヤリングされるためです (アレイへワイヤリングされる場合も同様です)。また、 beanType 属性の代わりに BeanIdRef 属性を使用してもこのワイヤリングを実行できたことに注目してください。
  • 設定 (4) は OrderItem Bean インスタンスを作成します。 createOnElement プロパティがどのように order-item 要素に設定されるか注目してください。 これは、各 order-item 要素に対してこの Bean の新しいインスタンスが作成されるようにするためです (そして、ListOrderItem (3.a) へワイヤリングするためです)。
この設定の createOnElement 属性が order-item 要素に設定されないと (たとえば order、 header、order-item 要素の 1 つに設定された場合)、 単一の OrderItem Bean インスタンスのみが作成され、バインディング設定 (4.a など) がソースメッセージの各 order-item 要素に対する Bean インスタンスのプロパティバインディングを上書きします。 たとえば、ソースメッセージにある最後の order-item からの order-item データが含まれる単一の OrderItem インスタンスのみがある ListOrderitem となります。
バインディングの秘訣の一部は次の通りです。
  • モデルに単一の Bean インスタンスのみが存在する場合は、jb:bean createOnElement を root 要素 (または #document) に設定します。
    これを コレクション Bean インスタンス の recurring 要素に設定します。

    警告

    この場合、正しい要素を指定しないとデータが失われる可能性があります。
  • jb:value デコーダ:ほとんどの場合、 Smooks は jb:value バインディングで使用されるデータタイプデコーダを自動的に検出しますが、設定が必要になるデコーダもあります (DateDecoder [decoder="Date"] がその 1 つです)。このような場合、バインディング上でデコーダー属性を定義する必要があります (そのデコーダのデコードパラメータを指定するための jb:decodeParam 子要素も定義する必要があります)。
  • コレクションへバインドする時、jb:wiring プロパティは必要ありません。
  • 必要なコレクションタイプを設定するには、jb:bean クラスを定義し、コレクションエントリでワイヤリングします。アレイの場合、 jb:bean クラス属性値に角括弧を用いて postfix に対応します (例: class="com.acme.OrderItem[]")。
DataDecoder 実装は DataEncoder インターフェースを実装することもできます。名前の通り、DataEncoder はオブジェクト値を文字列へエンコードしフォーマットするメソッドを実装します。
使用可能な DataDecoder/DataEncoder 実装は次の通りです。
  • Date: 文字列を java.util.Date インスタンスへデコードまたはエンコードします。
  • Calendar: 文字列を java.util.Calendar インスタンスへデコードまたはエンコードします。
  • SqlDate: 文字列を java.sql.Date インスタンスへデコードまたはエンコードします。
  • SqlTime: 文字列を java.sql.Time インスタンスへデコードまたはエンコードします。
  • SqlTimestamp: 文字列を java.sql.Timestamp インスタンスへデコードまたはエンコードします。
これらの実装はすべて同じように設定します。
Date の例は次の通りです。
<jb:value property="date" decoder="Date" data="order/@date">
    <jb:decodeParam name="format">EEE MMM dd HH:mm:ss z yyyy</jb:decodeParam>
    <jb:decodeParam name="locale">sv_SE</jb:decodeParam>
</jb:value>
SqlTimestamp の例は次の通りです。
<jb:value property="date" decoder="SqlTimestamp" data="order/@date">
    <jb:decodeParam name="format">EEE MMM dd HH:mm:ss z yyyy</jb:decodeParam>
    <jb:decodeParam name="locale">sv</jb:decodeParam>
</jb:value>

注記

decodeParam iformat は、日付形式の ISO 8601 を基にしています。
ロケールの decodeParam 値はアンダースコアで区切られた文字列で、最初のトークンがロケールの ISO 言語コード、2 つ目のトークンが ISO 国コードになります。
decodeParam は言語と国の 2 つのパラメータとして指定することも可能です。
<jb:value property="date" decoder="Date" data="order/@date">
    <jb:decodeParam name="format">EEE MMM dd HH:mm:ss z yyyy</jb:decodeParam>
    <jb:decodeParam name="locale-language">sv</jb:decodeParam>
    <jb:decodeParam name="locale-country">SE</jb:decodeParam>
</jb:value>
使用可能な数字ベースの DataDecoder/DataEncoder 実装は次の通りです。
  • BigDecimalDecoder: 文字列を java.math. BigDecimal インスタンスへデコードまたはエンコードするのに使用します。
  • BigIntegerDecoder: 文字列を java.math. BigInteger インスタンスへデコードまたはエンコードするのに使用します。
  • DoubleDecoder: 文字列を java.lang.Double インスタンス (プリミティブを含む) へデコードまたはエンコードするのに使用します。
  • FloatDecoder: 文字列を java.lang.Float インスタンス (プリミティブを含む) へデコードまたはエンコードするのに使用します。
  • IntegerDecoder: 文字列を java.lang.Integer インスタンス (プリミティブを含む) へデコードまたはエンコードするのに使用します。
  • LongDecoder: 文字列を java.lang.Long インスタンス (プリミティブを含む) へデコードまたはエンコードするのに使用します。
  • ShortDecoder: 文字列を java.lang.Short インスタンス (プリミティブを含む) へデコードまたはエンコードするのに使用します。
これらの実装はすべて同じように設定します。
以下は BigDecimal の例になります。
<jb:value property="price" decoder="BigDecimal" data="orderItem/price">
    <jb:decodeParam name="format">#,###.##</jb:decodeParam>
    <jb:decodeParam name="locale">en_IE</jb:decodeParam>
</jb:value>
以下は Integer の例になります。
<jb:value property="percentage" decoder="Integer" data="vote/percentage">
    <jb:decodeParam name="format">#%</jb:decodeParam>
</jb:value>

注記

decodeParam の形式は NumberFormat パターンの構文を基にしています。
ロケールの decodeParam 値はアンダースコアで区切られた文字列で、最初のトークンがロケールの ISO 言語コード、2 つ目のトークンが ISO 国コードになります。
decodeParam を言語と国の 2 つのパラメータとして指定することも可能です。
<jb:value property="price" decoder="Double" data="orderItem/price">
    <jb:decodeParam name="format">#,###.##</jb:decodeParam>
    <jb:decodeParam name="locale-language">sv</jb:decodeParam>
    <jb:decodeParam name="locale-country">SE</jb:decodeParam>
</jb:value>
入力メッセージのデータを基に、異なる値をオブジェクトモデルにバインドしたい時があります。この場合、式ベースのバインディングを使用することができますが、次のようにマッピングデコーダを使用することも可能です。
<jb:value property="name" decoder="Mapping" data="history/@warehouse">
    <jb:decodeParam name="1">Dublin</jb:decodeParam>
    <jb:decodeParam name="2">Belfast</jb:decodeParam>
    <jb:decodeParam name="3">Cork</jb:decodeParam>
</jb:value>
上記の例では、入力データ値である「1」が 「Dublin」という値として name プロパティへマッピングされます。値「2」と「3」も同様です。
列挙デコーダは特殊なマッピングデコーダです。データ入力値が列挙値/名へ正確にマッピングする場合、列挙は通常自動的にデコードされます (特別な設定は必要ありません)。これ以外の場合、入力データ値から列挙値/名へのマッピングを定義する必要があります。
次の例では、入力メッセージの header/priority フィールドには "LOW"、 "MEDIUM"、"HIGH" という値が含まれています。これらの値を LineOrderPriority の列挙値である "NOT_IMPORTANT"、 "IMPORTANT"、 "VERY_IMPORTANT" へそれぞれマッピングする必要があります。
<jb:value property="priority" data="header/priority" decoder="Enum">
    <jb:decodeParam name="enumType">example.trgmodel.LineOrderPriority</jb:decodeParam>
    <jb:decodeParam name="LOW">NOT_IMPORTANT</jb:decodeParam>
    <jb:decodeParam name="MEDIUM">IMPORTANT</jb:decodeParam>
    <jb:decodeParam name="HIGH">VERY_IMPORTANT</jb:decodeParam>
</jb:value>

注記

マッピングが必要な場合、 enumType decodeParam を使用して列挙型を指定する必要もあります。
デフォルトでは、Bean を作成した断片 (createOnElement) が処理された後、Smooks 設定の最初にある Bean 以外の Bean はすべて BeanContext から削除されます (createOnElement 断片の start/visitBefore で BeanContext に Bean が追加され、 end/visitAfter で BeanContext より削除されます)。
デフォルトでは、Smooks 設定の最初に設定された Bean を除くすべての Bean にこのルールが適用されます (デフォルトでは、最初の Bean のみが BeanContext に保持されるため、メッセージが処理された後もこの Bean にアクセスできます)。
この挙動を変更するには、jb:bean 要素の retain configuration 属性を使用します。この属性を使用すると、Smooks BeanContext 内で Bean の保持を手動制御できます。
Java Bean カートリッジ:
  • ソース/入力メッセージストリームより文字列値を抽出します。
  • decoder および decodeParam の設定を基に文字列値をデコードします (これらの設定が定義されていないと、デコーダをリフレクションで解決しようとします)。
  • ターゲット Bean にデコードされた値を設定します。
デコードの手順を実行する前に、文字列データ値を 事前処理 する必要がある場合があります。
事前処理が必要となる場合の例には、ソースデータに数値デコードのロケール設定によってサポートされない文字がある場合などがあります (たとえば、数値 876592.00 がなんらかの理由で "876_592!00"jb:bean 要素として表される場合など)。この値を二重の値としてデコードするには、アンダースコアと感嘆符を削除し、感嘆符をピリオドに置き換えます。
カスタム DataDecoder を書いて対処するのも 1 つの手段ですが (この操作を繰り返し行う必要がある場合に推奨されます)、迅速な対応が必要な場合は valuePreprocess decodeParam を指定できます。これは、デコードを行う前に文字列値に適用できる簡単な式です。
以下は、前述の数値デコードの問題を解決する一例となります。
<!-- A bean property binding example: -->
<jb:bean BeanId="orderItem" class="org.milyn.javabean.OrderItem" createOnElement="price">
    <jb:value property="price" data="price" decoder="Double">
        <jb:decodeParam name="valuePreprocess">value.replace("_", "").replace("!", ".")</jb:decodeParam>
    </jb:value>
</jb:bean>
<!-- A direct value binding example: -->
<jb:value BeanId="price" data="price" decoder="BigDecimal">
    <jb:decodeParam name="valuePreprocess">value.replace("_", "").replace("!", ".")</jb:decodeParam>
</jb:value>

注記

上記の例では、文字列データ値は値変数名を介して式で参照されます (式は、値文字列上で操作し文字列を返す有効な MVEL 式になります)。
Java Bean カートリッジでは ファクトリ を使用して Bean を作成できます。このような場合、パブリックパラメータを持たないコンストラクタ を使用する必要はありません。クラス属性で実際のクラス名を定義する必要もありません。オブジェクトのインターフェースを使用すれば十分ですが、そのインターフェースのメソッドへバインドすることのみ可能です (ファクトリを定義しても、常に Bean 定義にクラス属性を設定する必要があります)。
ファクトリ定義は Bean 要素のファクトリ属性で設定されます。デフォルトのファクトリ定義言語は次のようになります。
some.package.FactoryClass#staticMethod{.instanceMethod}
この基本的な定義言語を使用して、Bean を作成するために Smooks が呼び出す静的なパブリックパラメーターを持たないメソッドを定義します。instanceMethod の部分は任意です。これを設定すると、静的メソッドより返されるオブジェクトを呼び出すメソッドを定義し、Bean が作成されるはずです。 { } 文字は任意の部分を示す目的でのみ使用されているため、実際の定義では使用しないようにしてください。
次の例は、静的ファクトリメソッドを使用して ArrayList オブジェクトをインスタンス化する方法を表しています。
<jb:bean
   BeanId="orders"
   class="java.util.List"
   factory="some.package.ListFactory#newList"
   createOnElement="orders"
>
     <!-- ... bindings -->
</jb:bean>
"some.package.ListFactory#newList" ファクトリ定義は、Bean を作成するには some.package.ListFactory クラス上で newList メソッドを呼び出す必要があることを定義します。クラス属性は Bean を List オブジェクトとして定義します。特定タイプの List オブジェクト (ArrayList や LinkedList など) は ListFactory 自体によって決定されます。
別の例を見てみましょう。
<jb:bean
   BeanId="orders"
   class="java.util.List"
   factory="some.package.ListFactory#getInstance.newList"
   createOnElement="orders"
>
     <!-- ... bindings -->
</jb:bean>
これは、静的メソッド getInstance を使用して ListFactory のインスタンスを読み出す必要があり、ListFactory オブジェクト上で newList メソッドを呼び出して List オブジェクトを作成する必要があることを定義します。このコンストラクトでは シングルトンファクトリ を使用できます。
異なる定義言語を使用した後にデフォルトの基本言語を使用することができます。たとえば、MVEL をファクトリ定義言語として使用できます。
使用したい定義言語を宣言する方法は 3 つあります。
定義言語はエイリアスを持つことが可能です。たとえば、MVEL のエイリアスは「mvel」です。特定のファクトリ定義に MVEL を使用したい場合、「mvel:some.package.ListFactory.getInstance().newList()」 のように定義の前に「mvel:」を付けます。デフォルトの基本言語のエイリアスは「basic」です。
言語をグローバルなデフォルトして設定するには、「factory.definition.parser.class」グローバルパラメータを、使用したい言語 の FactoryDefinitionParser インターフェースを実装するクラスの完全クラスパスに設定する必要があります。
注記:「:」が含まれるデフォルト言語を持つ定義がある場合、その定義の前に「default:」を付けないと例外が発生します。
エイリアスを使用する代わりに、「org.milyn.javabean.factory.MVELFactoryDefinitionParser:some.package.ListFactory.getInstance().newList()」のように、使用したい言語の FactoryDefinitionParser インターフェースを実装するクラスの完全クラスパスに設定することも可能ですが、これはテストの目的でのみ使用することが推奨されます。言語にエイリアスを定義した方がよいでしょう。
独自の言語を定義したい場合は、「org.milyn.javabean.factory.FactoryDefinitionParser」インターフェースを実装する必要があります。例として、「org.milyn.javabean.factory.MVELFactoryDefinitionParser」 や 「org.milyn.javabean.factory.BasicFactoryDefinitionParser」を参照してみてください。
定義言語のエイリアスを定義するには、「org.milyn.javabean.factory.Alias」アノテーションとエイリアス名を FactoryDefinitionParser クラスに追加します。
Smooks がエイリアスを検索できるようにするには、クラスパスのルート上に「META-INF/smooks-javabean-factory-definition-parsers.inf」というファイルを作成する必要があります。このファイルには、Alias アノテーションを持つ FactoryDefinitionParser インターフェースを実装するすべてのファイルの完全クラスパスが含まれている必要があります (新しい行で区切ります)。
MVEL には基本のデフォルト定義言語より優れた利点がいくつかあります。たとえば、ファクトリオブジェクトとして Bean コンテキストよりオブジェクトを使用したり、パラメータを用いてファクトリメソッドを呼び出すことが可能です。これらのパラメータは定義内に定義することが可能で、Bean コンテキストからのオブジェクトであることも可能です。MVEL を使用できるようにするには、「mvel」をエイリアスとして使用するか、「factory.definition.parser.class」グローバルパラメータを「org.milyn.javabean.factory.MVELFactoryDefinitionParser」に設定します。
前述と同じユースケースで MVEL を用いた例は次の通りです。
<smooks-resource-list
   xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
   xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd">
 
    <jb:bean
        BeanId="orders"
        class="java.util.List"
        factory="mvel:some.package.ListFactory.getInstance().newList()"
        createOnElement="orders"
    >
        <!-- ... bindings -->
    </jb:bean>
 
</smooks-resource-list>
次の例では、MVEL を使用して Bean コンテキストの既存 Bean より List オブジェクトを抽出します。この例の Order オブジェクトには、order 行の追加に使用しなければならないリストを返すメソッドが含まれています。
<smooks-resource-list
   xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
   xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd">
 
    <jb:bean
        BeanId="order"
        class="some.package.Order"
        createOnElement="order"
    >
        <!-- ... bindings -->
    </jb:bean>
 
    <!-- 
           The factory attribute uses MVEL to access the order 
           object in the bean context and calls its getOrderLines() 
           method to get the List. This list is then added to the bean 
           context under the BeanId 'orderLines' 
    -->
    <jb:bean
        BeanId="orderLines"
        class="java.util.List"
        factory="mvel:order.getOrderLines()" 
        createOnElement="order"
    >
        <jb:wiring BeanIdRef="orderLine" />
    </jb:bean>
 
    <jb:bean
        BeanId="orderLine"
        class="java.util.List"
        createOnElement="order-line"
    >
        <!-- ... bindings -->
    </jb:bean>
 
</smooks-resource-list>
MVEL をデフォルトのファクトリ定義言語として使用しないのはなぜか不思議に思っている方がいるかもしれません。現在、基本の定義言語と MVEL のパフォーマンスは同等です。基本の定義言語の方が高速でない理由は、現在リフレクションを使用してファクトリメソッドを呼び出すためです。しかし、リフレクションの代わりにバイトコード生成を使用する計画があります。これにより、パフォーマンスが大幅に改善されるはずです。MVEL がデフォルトの言語となっていると、基本の定義言語が提供する基本機能のみが必要な場合にパフォーマンスを改善することができなくなります。
アレイオブジェクトはサポートされません。ファクトリがアレイを返す場合、Smooks はある時点で例外をスローします。
キーと値ペアをマップにバインドする
バインディングの jb:value プロパティ属性が定義されていない場合 (または空の場合)、選択されたノード名がマップエントリのキーとして使用されます (beanClass がマップである場合)。
マップキーを定義する方法はもう 1 つあります。 jb:value プロパティ属性の値を @ 文字で始めることです。 @ 以降の値は、 マップキーが選択される選択したノードの属性名を定義します。 例は次の通りです。
<root>
    <property name="key1">value1</property>
    <property name="key2">value2</property>
    <property name="key3">value3</property>
</root>
設定は次のようになります。
<jb:bean BeanId="keyValuePairs" class="java.util.HashMap" createOnElement="root">
    <jb:value property="@name" data="root/property" />
</jb:bean>
これにより、 設定されたキー [key1, key2, key3] の 3 つのエントリを持つハッシュマップが作成されます。
@ 文字表記は Bean ワイヤリングでは機能しません。 カートリッジは @文字を含むプロパティ属性の値をマップエントリキーとして使用します。
独自の Bean クラスを記述せずに完全なオブジェクトモデルを作成することは可能です。 この仮想モデルは、 マップとリストのみを使用して作成されます。たとえば、xml->java->xml や xml->java->edi などのモデル駆動型のトランスフォメーションの一部としてなど、2 つのプロセス手順の間で Javabean カートリッジを使用する場合にとても便利です。
この原理について次の例を参照してください。
<?xml version="1.0"?>
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" 
                      xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd"
                      xmlns:ftl="http://www.milyn.org/xsd/smooks/freemarker-1.1.xsd">
 
    <!--
    Bind data from the message into a Virtual Object model in the bean context....
    -->    
    <jb:bean BeanId="order" class="java.util.HashMap" createOnElement="order">
        <jb:wiring property="header" BeanIdRef="header" />
        <jb:wiring property="orderItems" BeanIdRef="orderItems" />
    </jb:bean>    
    <jb:bean BeanId="header" class="java.util.HashMap" createOnElement="order">
        <jb:value property="date" decoder="Date" data="header/date">
            <jb:decodeParam name="format">EEE MMM dd HH:mm:ss z yyyy</jb:decodeParam>
        </jb:value>
        <jb:value property="customerNumber" decoder="Long" data="header/customer/@number" />
        <jb:value property="customerName" data="header/customer" />
        <jb:expression property="total" execOnElement="order-item" >
            header.total + (orderItem.price * orderItem.quantity);
        </jb:expression>
    </jb:bean>    
    <jb:bean BeanId="orderItems" class="java.util.ArrayList" createOnElement="order">
        <jb:wiring BeanIdRef="orderItem" />
    </jb:bean>    
    <jb:bean BeanId="orderItem" class="java.util.HashMap" createOnElement="order-item">
        <jb:value property="productId" decoder="Long" data="order-item/product" />
        <jb:value property="quantity" decoder="Integer" data="order-item/quantity" />
        <jb:value property="price" decoder="Double" data="order-item/price" />
    </jb:bean>
 
    <!--
    Use a FreeMarker template to perform the model driven transformation on the Virtual Object Model...
    -->
    <ftl:freemarker applyOnElement="order">
        <ftl:template>/templates/orderA-to-orderB.ftl</ftl:template>
    </ftl:freemarker>
 
</smooks-resource-list>
上記の例では、どのように仮想モデル (マップ) のデコーダ属性を常に定義するかに注目してください。Smooks は、データをマップへバインドするためのデコードタイプを自動検出できないため、型付けされた値を仮想モデルにバインドする必要がある場合は適切なデコーダを指定する必要があります。このような場合にデコーダが指定されないと、Smooks はデータを文字列として仮想モデルにバインドします。
model-driven-basic と model-driven-basic-virtual の例を見てみましょう。
複数のデータエントリを単一のバインディングに統合する
複数のデータエントリを単一のバインディングに統合するには、 表現ベースバインディング (jb:expression) を使用します。
Smooks 1.3 より、Javabean カートリッジに直接値バインディング (Direct value binding) と呼ばれる新しい機能が導入されました。直接値バインディングは Smooks DataDecoder を使用して選択された data 要素/属性よりオブジェクトを作成し、直接 Bean コンテキストへ追加します。
ValueBinder クラスは値バインディングを行うビジターです。
値バインディングの XML 設定は、Smooks 1.3 の JavaBean スキーマの一部です (http://www.milyn.org/xsd/smooks/javabean-1.4.xsd)。値バインディングの要素は value です。
値には次のような属性があります。
  • BeanId: 作成されたオブジェクトが Bean コンテキストでバインドされる ID。
  • data: バインドされるデータ値のデータセレクタ。例: "order/orderid" 、 "order/header/@date" など。
  • dataNS: "data" セレクタの名前空間。
  • decoder: 値を文字列から異なるタイプへ変換するための DataDecoder 名。DataDecoder は decodeParam 要素を用いて設定できます。
  • default : 選択されたデータが null または空の文字列であった場合のデフォルト値です。
「典型的な」注文メッセージを例とし、注文番号、名前、および日付を整数 (Integer) と文字列 (String) の形式で値オブジェクトとして取得します。
メッセージ
<order xmlns="http://x">
     <header>
         <y:date xmlns:y="http://y">Wed Nov 15 13:45:28 EST 2006</y:date>
         <customer number="123123">Joe</customer>
         <privatePerson></privatePerson>
     </header>
     <order-items>
         <!-- .... -->
     </order-items>
 </order>
設定
<?xml version="1.0"?>
 <smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd">
 
    <jb:value
       BeanId="customerName"
       data="customer"
       default="unknown"
    />
 
    <jb:value
       BeanId="customerNumber"
       data="customer/@number"
       decoder="Integer"
    />
 
   <jb:value
       BeanId="orderDate"
       data="date"
       dateNS="http://y"
       decoder="Date"
    >
         <jb:decodeParam name="format">EEE MMM dd HH:mm:ss z yyyy</jb:decodeParam>
         <jb:decodeParam name="locale-language">en</jb:decodeParam>
         <jb:decodeParam name="locale-country">IE</jb:decodeParam>
   </jb:value>
 
 </smooks-resource-list>
値バインダーは org.milyn.javabean. 値オブジェクトを使用してプログラムを用いて設定することができます。
xml 設定の例と同じメッセージの例を使用します。
//Create Smooks. normally done globally!
Smooks smooks = new Smooks();
 
//Create the Value visitors
Value customerNumberValue = new Value( "customerNumber", "customer/@number").setDecoder("Integer");
Value customerNameValue = new Value( "customerName", "customer").setDefault("Unknown");
 
//Add the Value visitors
smooks.addVisitors(customerNumberValue);
smooks.addVisitors(customerNameValue);
 
//And the execution code: 
JavaResult result = new JavaResult();
 
smooks.filterSource(new StreamSource(orderMessageStream), result);
Integer customerNumber = (Integer) result.getBean("customerNumber");
String customerName = (String) result.getBean("customerName");
Java バインディングの設定は、Bean 設定クラスを使用するとプログラムを用いて Smooks に追加できます。
このクラスを使用すると、特定クラス上で Java バインディングを実行するための Smooks インスタンスをプログラムを用いて設定することができます。グラフに投入するには、 Bean 同士をバインディングして Bean インスタンスのグラフを作成します。 Bean クラスは Fluent API を使用します (すべてのメソッドが Bean インスタンスを返します)。そのため、設定同士を「文字列化」して Bean 設定グラフを構築することが容易になります。
「典型的な注文メッセージを例とし、 対応する Java オブジェクトモデルへバインドします。
メッセージ
<order xmlns="http://x">
    <header>
        <y:date xmlns:y="http://y">Wed Nov 15 13:45:28 EST 2006</y:date>
        <customer number="123123">Joe</customer>
        <privatePerson></privatePerson>
    </header>
    <order-items>
        <order-item>
            <product>111</product>
            <quantity>2</quantity>
            <price>8.90</price>
        </order-item>
        <order-item>
            <product>222</product>
            <quantity>7</quantity>
            <price>5.20</price>
        </order-item>
    </order-items>
</order>
Java モデル (getter/setter は省略)
public class Order {
    private Header header;
    private List<OrderItem> orderItems;
}
 
public class Header {
    private Long customerNumber;
    private String customerName;
}
 
public class OrderItem {
    private long productId;
    private Integer quantity;
    private double price;
}
設定コード
Smooks smooks = new Smooks();
 
Bean orderBean = new Bean(Order.class, "order", "/order");
 
orderBean.bindTo("header",
    orderBean.newBean(Header.class, "/order")
        .bindTo("customerNumber", "header/customer/@number")
        .bindTo("customerName", "header/customer")
    ).bindTo("orderItems",
    orderBean.newBean(ArrayList.class, "/order")
        .bindTo(orderBean.newBean(OrderItem.class, "order-item")
            .bindTo("productId", "order-item/product")
            .bindTo("quantity", "order-item/quantity")
            .bindTo("price", "order-item/price"))
    );
 
smooks.addVisitors(orderBean);
実行コード
JavaResult result = new JavaResult();
 
smooks.filterSource(new StreamSource(orderMessageStream), result);
Order order = (Order) result.getBean("order");

The API supports factories. You can provide a factory object of the type org.milyn.javabean.factory.Factory, that will be called when a new bean instance needs to be created.

Here is an example where an anonymous Factory class is defined and used:

Bean orderBean = new Bean(Order.class, "order", "/order", new Factory<Order>() {
 
 public Order create(ExecutionContext executionContext) {
  return new Order();
 }
 
});
Java Bean カートリッジには、バインディング設定テンプレートを生成するため使用できる org.milyn.javabean.gen.ConfigGenerator ユーティリティクラスが含まれています。このテンプレートをバインディング定義の基準として使用することができます。
コマンドラインからの実行
$JAVA_HOME/bin/java -classpath <classpath> org.milyn.javabean.gen.ConfigGenerator -c <rootBeanClass> -o <outputFilePath> [-p <propertiesFilePath>]
  • -c コマンドライン引数は、バインディング設定が生成されるモデルのルートクラスを指定します。
  • -o コマンドライン引数は生成された設定出力のパスとファイル名を指定します。
  • -p コマンドライン引数は追加のバインディングパラメータを指定する、パスとファイル名の任意バインディング設定ファイルを指定します。
  • 任意の -p プロパティファイルパラメータを使用すると追加の設定パラメータを指定できます。
  • packages.included: セミコロン区切りのパッケージリストです。 生成されたバインディング設定にこれらのパッケージと一致するクラスのフィールドが含まれます。
  • packages.excluded: セミコロン区切りのパッケージリストです。 生成されたバインディング設定にはこれらのパッケージと一致するクラスのフィールドは含まれません。
バインディング設定がソースデータモデルで機能するようにするため、 ターゲットクラスに対してこのユーティリティを実行した後、 通常次のフォローアップタスクを実行する必要があります。
各 jb:bean 要素に対し、Bean インスタンスの作成に使用する必要がある event 要素へ createOnElement 属性を設定します。
jb:value データ属性を更新し、 Bean プロパティのバインディングデータを提供する event 要素/属性を選択します。
jb:value デコーダ属性をチェックします。 実際のプロパティタイプに応じて設定されるため属性はすべて設定されません。これらの属性は手作業で設定する必要があります。 たとえば、データフィールドなど、バインディングの一部にデコーダの jb:decodeParam サブ要素を設定する必要がある場合があります。
バインディング設定要素 (jb:value および jb:wiring) を再度チェックします。必ず、生成された設定にすべての Java プロパティが含まれるようにします。
セレクタ値の判定は特に Java のような非 XML ソース (Java など) では難しいことがあります。 このような場合、 HTML Reporting ツールを使用すると便利です。このツールは Smooks で表示されるように入力メッセージモデル (セレクタが適用されます) を想定することができます。最初に、空のトランスフォーメーション設定を用いてソースデータを使用してレポートを生成します。このレポートには、設定を追加する必要があるモデルが記載されます。設定は 1 つずつ追加し、適用されたことを確認するためレポートを再実行します。
生成された設定の例は次の通りです。 "$TODO$" トークンに注目してください。
<?xml version="1.0"?>
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd">
 
    <jb:bean BeanId="order" class="org.milyn.javabean.Order" createOnElement="$TODO$">
        <jb:wiring property="header" BeanIdRef="header" />
        <jb:wiring property="orderItems" BeanIdRef="orderItems" />
        <jb:wiring property="orderItemsArray" BeanIdRef="orderItemsArray" />
    </jb:bean>
 
    <jb:bean BeanId="header" class="org.milyn.javabean.Header" createOnElement="$TODO$">
        <jb:value property="date" decoder="$TODO$" data="$TODO$" />
        <jb:value property="customerNumber" decoder="Long" data="$TODO$" />
        <jb:value property="customerName" decoder="String" data="$TODO$" />
        <jb:value property="privatePerson" decoder="Boolean" data="$TODO$" />
        <jb:wiring property="order" BeanIdRef="order" />
    </jb:bean>
 
    <jb:bean BeanId="orderItems" class="java.util.ArrayList" createOnElement="$TODO$">
        <jb:wiring BeanIdRef="orderItems_entry" />
    </jb:bean>
 
    <jb:bean BeanId="orderItems_entry" class="org.milyn.javabean.OrderItem" createOnElement="$TODO$">
        <jb:value property="productId" decoder="Long" data="$TODO$" />
        <jb:value property="quantity" decoder="Integer" data="$TODO$" />
        <jb:value property="price" decoder="Double" data="$TODO$" />
        <jb:wiring property="order" BeanIdRef="order" />
    </jb:bean>
 
    <jb:bean BeanId="orderItemsArray" class="org.milyn.javabean.OrderItem[]" createOnElement="$TODO$">
        <jb:wiring BeanIdRef="orderItemsArray_entry" />
    </jb:bean>
 
    <jb:bean BeanId="orderItemsArray_entry" class="org.milyn.javabean.OrderItem" createOnElement="$TODO$">
        <jb:value property="productId" decoder="Long" data="$TODO$" />
        <jb:value property="quantity" decoder="Integer" data="$TODO$" />
        <jb:value property="price" decoder="Double" data="$TODO$" />
        <jb:wiring property="order" BeanIdRef="order" />
    </jb:bean>
 
</smooks-resource-list>
Smooks.filterSource メソッドが呼び出された後、JavaResult インスタンスの正確な内容は保証されません。 このメソッドの呼び出し後、JavaResult インスタンスにはビジター実装によって追加できる Bean コンテキストの最終内容が含まれます。
jb:result 設定を Smooks の設定で使用すると JavaResult に返される Bean セットを制限できます。次の設定例では、ResultSet にorder Bean のみを保持するよう Smooks に伝えます。
<?xml version="1.0"?>
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
                      xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd">
 
    <!-- Capture some data from the message into the bean context... -->
    <jb:bean BeanId="order" class="com.acme.Order" createOnElement="order">
        <jb:value property="orderId" data="order/@id"/>
        <jb:value property="customerNumber" data="header/customer/@number"/>
        <jb:value property="customerName" data="header/customer"/>
        <jb:wiring property="orderItems" BeanIdRef="orderItems"/>
    </jb:bean>
    <jb:bean BeanId="orderItems" class="java.util.ArrayList" createOnElement="order">
        <jb:wiring BeanIdRef="orderItem"/>
    </jb:bean>
    <jb:bean BeanId="orderItem" class="com.acme.OrderItem" createOnElement="order-item">
        <jb:value property="itemId" data="order-item/@id"/>
        <jb:value property="productId" data="order-item/product"/>
        <jb:value property="quantity" data="order-item/quantity"/>
        <jb:value property="price" data="order-item/price"/>
    </jb:bean>
 
    <!-- Only retain the "order" bean in the root of any final JavaResult. -->
    <jb:result retainBeans="order"/>
 
</smooks-resource-list>
この設定が適用された後、 order Bean 以外の JavaResult.getBean(String) メソッドへの呼び出しは null を返すようになります。 他の Bean インスタンスは「order」グラフへワイヤリングされるため、 上記のような例では適切に挙動します。
Smooks v1.2 より、JavaSource インスタンスが Smooks.filterSource メソッドへ提供されると (フィルターソースインスタンスとして)、Smooks は JavaSource を使用して、Smooks.filterSource 呼び出しに対し ExecutionContect に関連する Bean コンテキストを構築するようになりました。そのため、JavaSource Bean インスタンスの一部は JavaResult で可視できるようになりました。