第13章 Smooks の拡張

既存の Smooks の全機能 (Java バインディング、EDI 処理など) は、明確に定義された複数の API の拡張より構築されます。これらの API を今後の項で取り上げます。
Smooks の主な拡張ポイント/API はリーダー API および ビジター API になります。
リーダー API
すべてのメッセージ断片やサブ断片に対する正確に定義された階層的イベント (SAX イベントモデルを基にした) として他の Smooks コンポーネントが消費できるよう、ソース/入力データ (リーダー) を処理するための API です。
ビジター API
ソース/入力リーダーによって作成されるメッセージ断片の SAX イベントを消費するための API です。
これらのコンポーネントがどのように設定されるかは、Smooks の拡張を書く時に大変重要になります。これは、すべての Smooks コンポーネントに共通するため、最初に取り上げます。

13.1. Smooks コンポーネントの設定

Smooks のコンポーネントはすべて全く同じように設定されます。Smooks Core では、Smooks コンポーネントはすべて「リソース」で、以前の項で説明した SmooksResourceConfiguration インスタンスを使用して設定されます。
Smooks はコンポーネントに対して名前空間 (XSD) 固有の XML 設定を構築するメカニズムを提供しますが、最も基本的な設定 (および SmooksResourceConfiguration クラスへ直接マッピングする設定) はベース設定の名前空間 (http://www.milyn.org/xsd/smooks-1.1.xsd) からの基本的な <resource-config> XML 設定です。
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd">

    <resource-config selector="">
        <resource></resource>
        <param name=""></param>
    </resource-config>

</smooks-resource-list>
  • selector 属性はリソースが「選択される」メカニズムです (ビジター実装の XPath など)。
  • resource 要素は実際のリソースです。Java クラス名や、テンプレートなどの他のリソース形式になります。本項では、リソースは Java クラス名であることを前提とします。
  • param 要素は、resource 要素に定義されたリソースの設定パラメータです。
Smooks は、リソースのランタイム表現の作成 (resource 要素で命名されたクラスの構築など) や設定パラメーターのインジェクションをすべて処理します。また、リソースタイプを判断し、判断したリソースタイプよりセレクタなどを解釈する方法を判断します。たとえば、リソースがビジターインスタンスの場合、セレクターが XPath で、ソースメッセージ断片を選択することを認識します。

13.1.1. 設定アノテーション

コンポーネントが作成された後、<param> 要素の詳細を用いて設定する必要があります。この設定には @ConfigParam および @Config アノテーションを使用します。

13.1.1.1. @ConfigParam

@ConfigParam アノテーションは、リフレクションを用いて、アノテーションが付いたプロパティと同じ名前を持つ <param> 要素から名前付きパラメータをインジェクトします。異なる名前でも問題ありませんが、デフォルトの挙動はコンポーネントプロパティの名前に対して照合します。
このアノテーションは以下の理由でコンポーネントの無駄なコードを大幅に削除できます。
  • アノテーションの付いたコンポーネントプロパティに設定する前に <param> 値のデコードを処理します。Smooks は主なタイプすべて (int、Double、File、Enums など) に DataDecoder を提供しますが、同梱されるデコーダーが特定のデコード要件に対応できない場合、カスタムの DataDecoder を実装し、使用することが可能です (例: @ConfigParam(decoder = MyQuirkyDataDecoder.class))。カスタムデコーダーが登録されている場合、Smooks は自動的にそのカスタムデコーダーを使用します (アノテーション上にデコーダープロパティを定義する必要はありません)。DetaDecoder 実装の登録に関する詳細は、DataDecoder の Javadoc を参照してください。
  • config プロパティの choice 制約をサポートするため、設定された値が定義された choice の値の 1 つでない場合に設定例外が生成されます。たとえば、ONOFF を制約される値として持つプロパティがある場合があります。このアノテーションに choice プロパティを使用して、設定を制約し、例外を発生させることが可能です (例: @ConfigParam(choice = {"ON", "OFF"}))。
  • デフォルトの設定値を指定できます (例: @ConfigParam(defaultVal = "true"))。
  • プロパティの設定値が必須または任意であるかどうかを指定できます (例: @ConfigParam(use = Use.OPTIONAL) )。デフォルトでは、すべてのプロパティは REQUIRED ですが、defaultVal を暗黙的に設定すると OPTIONAL としてプロパティをマーク付けできます。

例13.1 @ConfigParam の使用

次の例は、アノテーション付けされたコンポーネント DataSeeder とそれに対応する Smooks 設定を表しています。
public class DataSeeder 
{
   @ConfigParam
   private File seedDataFile;

   public File getSeedDataFile() 
   {
      return seedDataFile;
   }

   // etc...
}
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd">
   <resource-config selector="dataSeeder">
      <resource>com.acme.DataSeeder</resource>
         <param name="seedDataFile">./seedData.xml</param>
      </resource-config>
</smooks-resource-list>

13.1.1.2. @Config

@Config アノテーションは、コンポーネントに関連する完全な SmooksResourceConfiguration インスタンスをリフレクションを用いてアノテーション付けされたコンポーネントプロパティにインジェクトします。タイプが SmooksResourceConfiguration でないコンポーネントプロパティにアノテーションが追加されると、結果的にエラーが発生します。

例13.2 @Config の使用

public class MySmooksComponent 
{
    @Config
    private SmooksResourceConfiguration config;

    // etc...
}

13.1.1.3. @Initialize および @Uninitialize

@ConfigParam アノテーションは、単純な値でコンポーネントを設定する場合に便利ですが、初期化コードを書く必要があるさらに複雑な設定がコンポーネントに必要になることもあります。このような場合、Smooks は @Initialize アノテーションを提供します。
また、初期化中に取得されたリソースの一部を開放する場合など、関連する Smooks インスタンスが破棄された時に (ガベッジコレクション) 初期化中に実行される作業を取り消す必要がある時があります。このような場合、Smooks は @Uninitialize アノテーションを提供します。
基本的な初期化/非初期化シーケンスは次のように記述できます。
smooks = new Smooks(..);

// Initialize all annotated components
@Initialize

// Use the smooks instance through a series of filterSource invocations...
smooks.filterSource(...);
smooks.filterSource(...);
smooks.filterSource(...);
... etc ...

smooks.close();

// Uninitialize all annotated components
@Uninitialize

例13.3 @Initialize と @Uninitialize の使用

この例では、初期化時にデータベースへ複数の接続を開くコンポーネントがあり、Smooks インスタンスを閉じる時にこれらのデータベースリソースをすべて開放する必要があることを仮定します。
public class MultiDataSourceAccessor 
{
    @ConfigParam
    private File dataSourceConfig;

    Map<String, Datasource> datasources = new HashMap<String, Datasource>();

    @Initialize
    public void createDataSources() 
    {
        // Add DS creation code here....
        // Read the dataSourceConfig property to read the DS configs...
    }

    @Uninitialize
    public void releaseDataSources() 
    {
        // Add DS release code here....
    }

    // etc...
}
@Initialize および @Uninitialize アノテーションを使用する時は次の点に注意してください。
  • @Initialize および @Uninitialize メソッドは、引数がゼロのパブリックメソッドでなければなりません。
  • @ConfigParam プロパティはすべて最初の @Initialize メソッドが呼び出される前に初期化されます。そのため、@ConfigParam コンポーネントプロパティを初期化処理への入力として使用できます。
  • @Uninitialize メソッドはすべて Smooks.close メソッドへの呼び出しへ応答するために呼び出されます。

13.1.1.4. カスタム設定名前空間の定義

Smooks は、コンポーネントのカスタム設定名前空間を定義するメカニズムをサポートします。これにより、<resource-config> ベースの設定を使用して、汎用の Smooks リソースとして扱う代わりに、検証可能なコンポーネントの XSD ベースのカスタム設定をサポートすることが可能になります。
基本処理には 2 つの手順が関係します。
  1. ベースの http://www.milyn.org/xsd/smooks-1.1.xsd 設定名前空間を拡張するコンポーネントの設定 XSD を書きます。この XSD はコンポーネントのクラスパス上に置く必要があります。/META-INF/ フォルダにあり、名前空間 URI と同じパスを持つ必要があります。たとえば、拡張された名前空間 URI が http://www.acme.com/schemas/smooks/acme-core-1.0.xsd である場合、物理的な XSD ファイルは /META-INF/schemas/smooks/acme-core-1.0.xsd のクラスパスに置く必要があります。
  2. カスタムの名前空間設定を SmooksResourceConfiguration インスタンスへマッピングする Smooks 設定の名前空間マッピング設定ファイルを書きます。このファイルはマッピングする名前空間の名前を基に命名する必要があり (慣例により)、XSD と同じフォルダのクラスパス上に物理的に置く必要があります。上記の例の場合、Smooks マッピングファイルは /META-INF/schemas/smooks/acme-core-1.0.xsd-smooks.xml になります。-smooks.xml が末尾にあることに注目してください。
このメカニズムを理解するには、Smooks のコード内にある既存の拡張された名前空間設定を確認するのが最も簡単です。Smooks コンポーネントはすべて (Java バインディング機能を含む) このメカニズムを使用して設定を定義します。 Smooks Core は拡張された設定名前空間を複数定義します。

13.2. ソースリーダーの実装

新しいソースリーダーを Smooks に実装し、設定するのは簡単です。プロセスの Smooks 固有の部分は簡単で、問題にはなりません。リーダーを実装するためのソースデータ形式の複雑な関数に対して努力を必要とします。
カスタムデータ形式のリーダーを実装すると、即座にすべての Smooks 機能がそのデータ形式 (Java バインディング、テンプレート、永続化、検証、分割とルーティングなど) へ開放されます。そのため、比較的小さな投資でも大きな利益を生むことができます。リーダーが標準の org.xml.sax.XMLReader インターフェースを Java JDK より実装することのみが Smooks の要件になります。しかし、リーダー実装を設定可能にしたい場合は、org.milyn.xml.SmooksXMLReader インターフェースを実装する必要があります。org.milyn.xml.SmooksXMLReaderorg.xml.sax.XMLReader の拡張です。既存の org.xml.sax.XMLReader 実装を使用したり、新しいリーダーを実装するのは簡単です。
Smooks を使用するリーダーを実装する簡単な例を見てみましょう。この例では、コンマ区切り値 (CSV) レコードのストリームを読み取れるリーダーを実装し、CSV ストリームを Smooks によって処理可能な SAX イベントのストリームへ変換します。Smooks が許可する作業をすべて行うことができます。
最初に基本のリーダークラスを実装します。
public class MyCSVReader implements SmooksXMLReader 
{
   // Implement all of the XMLReader methods...
}
org.xml.sax.XMLReader インターフェースからの 2 つのメソッドに注目してください。
  1. setContentHandler(ContentHandler) は Smooks Core によって呼び出されます。これは、リーダーの org.xml.sax.ContentHandler インスタンスを設定します。org.xml.sax.ContentHandler インスタンスのメソッドは parse(InputSource) メソッド内より呼び出されます。
  2. parse(InputSource) : ソースデータ入力ストリームを受け取り、解析を行い (この例では CSV ストリーム)、 setContentHandler(ContentHandler) メソッドに提供される org.xml.sax.ContentHandler インスタンスへの呼び出しより SAX イベントストリームを生成するメソッドです。
CSV レコードに関連するフィールドの名前を用いて CSV リーダーを設定する必要があります。カスタムリーダー実装の設定は他の Smooks コンポーネントと同じです。
上記のメソッドとフィールドの設定をもう少し詳しく見てみましょう。
public class MyCSVReader implements SmooksXMLReader 
{

    private ContentHandler contentHandler;

    @ConfigParam
    private String[] fields; // Auto decoded and injected from the "fields" <param> on the reader config.

    public void setContentHandler(ContentHandler contentHandler) {
        this.contentHandler = contentHandler;
    }

    public void parse(InputSource csvInputSource) throws IOException, SAXException {
        // TODO: Implement parsing of CSV Stream...
    }

    // Other XMLReader methods...
}
基本的なリーダー実装スタブがあります。新しいリーダー実装をテストするため、単体テストを書くことができます。
最初に、CSV 入力の例が必要となります。names.csv という名前のファイルにある簡単な名前のリストを使用しましょう。
Tom,Jones
Mike,Jones
Mark,Jones
次に、MyCSVReader を用いて Smooks を設定するテスト Smooks 設定が必要となります。前述の通り、Smooks にあるものすべてはリソースで、基本的な <resource-config> 設定を用いて設定することができます。このように設定することもできますが、若干無駄が多いため、Smooks はリーダーの設定に特化した基本的な <reader> 設定要素を提供します。テストの設定は mycsvread-config.xml にあり、次のようになります。
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd">
   <reader class="com.acme.MyCSVReader">
      <params>
         <param name="fields">firstname,lastname</param>
      </params>
   </reader>
</smooks-resource-list>
そして、JUnit テストクラスが必要になります。
public class MyCSVReaderTest extends TestCase 
{
    
    public void test() {
        Smooks smooks = new Smooks(getClass().getResourceAsStream("mycsvread-config.xml"));
        StringResult serializedCSVEvents = new StringResult();

        smooks.filterSource(new StreamSource(getClass().getResourceAsStream("names.csv")), serializedCSVEvents);

        System.out.println(serializedCSVEvents);

        // TODO: add assertions etc
    }
}
これで、カスタムリーダー実装を持つ基本設定が設定され、開発を促進する単体テストが作成されました。リーダーの parse メソッドはまだ何も実行せず、テストクラスはアサーションなどを作成していません。次に、parse メソッドを実装しましょう。
public class MyCSVReader implements SmooksXMLReader 
{
    private ContentHandler contentHandler;

    @ConfigParam
    private String[] fields; // Auto decoded and injected from the "fields" <param> on the reader config.

    public void setContentHandler(ContentHandler contentHandler) 
    {
        this.contentHandler = contentHandler;
    }

    public void parse(InputSource csvInputSource) throws IOException, SAXException 
    {
        BufferedReader csvRecordReader = new BufferedReader(csvInputSource.getCharacterStream());
        String csvRecord;

        // Send the start of message events to the handler...
        contentHandler.startDocument();
        contentHandler.startElement(XMLConstants.NULL_NS_URI, "message-root", "", new AttributesImpl());

        csvRecord = csvRecordReader.readLine();
        while(csvRecord != null) 
        {
            String[] fieldValues = csvRecord.split(",");
        
            // perform checks...

            // Send the events for this record...
            contentHandler.startElement(XMLConstants.NULL_NS_URI, "record", "", new AttributesImpl());
            for(int i = 0; i < fields.length; i++) 
            {
                contentHandler.startElement(XMLConstants.NULL_NS_URI, fields[i], "", new AttributesImpl());
                contentHandler.characters(fieldValues[i].toCharArray(), 0, fieldValues[i].length());
                contentHandler.endElement(XMLConstants.NULL_NS_URI, fields[i], "");            
            }
            contentHandler.endElement(XMLConstants.NULL_NS_URI, "record", "");            

            csvRecord = csvRecordReader.readLine();    
        }

        // Send the end of message events to the handler...
        contentHandler.endElement(XMLConstants.NULL_NS_URI, "message-root", "");
        contentHandler.endDocument();
    }

    // Other XMLReader methods...
}
この時点で単体テストを実行すると、次の出力がコンソールに表示されます (フォーマット済み)。
<message-root>
    <record>
        <firstname>Tom</firstname>
        <lastname>Jones</lastname>
    </record>
    <record>
        <firstname>Mike</firstname>
        <lastname>Jones</lastname>
    </record>
    <record>
        <firstname>Mark</firstname>
        <lastname>Jones</lastname>
    </record>
</message-root>
この後に、テストを拡大したり、リーダー実装コードを強化したりします。
これで、リーダーを使用して Smooks によってサポートされる操作をすべて実行できるようになりました。たとえば、名前を PersonName オブジェクトの List へバインドするために次の設定 (java-binding-config.xml) を使用することができます。
<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">
    <reader class="com.acme.MyCSVReader">
        <params>
         <param name="fields">firstname,lastname</param>
        </params>
    </reader>
    <jb:bean BeanId="peopleNames" class="java.util.ArrayList" createOnElement="message-root">
        <jb:wiring BeanIdRef="personName" />
    </jb:bean>
    <jb:bean BeanId="personName" class="com.acme.PersonName" createOnElement="message-root/record">
        <jb:value property="first" data="record/firstname" />
        <jb:value property="last" data="record/lastname" />
    </jb:bean>
</smooks-resource-list>
この設定のテストは次のようになるでしょう。
public class MyCSVReaderTest extends TestCase 
{
    public void test_java_binding() 
    {
        Smooks smooks = new Smooks(getClass().getResourceAsStream("java-binding-config.xml"));
        JavaResult javaResult = new JavaResult();

        smooks.filterSource(new StreamSource(getClass().getResourceAsStream("names.csv")), javaResult);

        List<PersonName> peopleNames = (List<PersonName>) javaResult.getBean("peopleNames");

        // TODO: add assertions etc
    }
}
  • リーダーインスタンスが平行使用されることはありません。Smooks Core は各メッセージに対して新しいインスタンスを作成するか、readerPoolSize FilterSettings プロパティに従ってインスタンスをプールし、再使用します。
  • 現在のフィルタリングコンテキストに対する Smooks ExecutionContext へリーダーがアクセスする必要がある場合、リーダーは org.milyn.xml.SmooksXMLReader インターフェースを実装する必要があります。
  • ソースデータがバイナリデータストリームである場合、リーダーは org.milyn.delivery.StreamReader インターフェースを実装する必要があります。
  • Smookscode> インスタンス上で設定する GenericReaderConfigurator インスタンスを使用して、リーダーをソースコード内 (単体テストなど) で設定することができます。
  • 基本の <reader> 設定を使用できますが、カスタムの CSV リーダー実装にカスタムの設定名前空間 (XSD) を定義することが可能です。この定義についてはここでは取り上げません。EDIReaderCSVReaderJSONReader など、Smooks に提供されるリーダー実装に対する拡張された設定名前空間を見るため、ソースコードを確認します。これより、カスタムリーダーに対してカスタムの設定名前空間を定義する方法が分かるはずです。

13.2.1. バイナリソースリーダーの実装

バイナリデータソースのソースリーダーを実装することも可能です。この場合、リーダーは org.milyn.delivery.StreamReader インターフェースを実装する必要があります。これは、InputStream が確実に提供されるよう Smooks ランタイムに伝えるマーカーインターフェースです。
バイナリリーダー実装は非バイナリリーダー実装 (上記を参照) と本質的に同じですが、parse メソッドの実装は InputSource から InputStream を使用し (例: InputSource.getCharacterStream() ではなく InputSource..getByteStream() を呼び出す)、デコードされたバイナリデータから XML イベントを生成することが異なります。

例13.4 バイナリソースリーダーの実装

簡単な parse メソッド実装は次のようになります。
public static class BinaryFormatXXReader implements SmooksXMLReader, StreamReader 
{
    @ConfigParam
    private String xProtocolVersion;

    @ConfigParam
    private int someOtherXProtocolConfig;

    // etc...

    public void parse(InputSource inputSource) throws IOException, SAXException {
        // Use the InputStream (binary) on the InputSource...
        InputStream binStream = inputSource.getByteStream();

        // Create and configure the data decoder... 
        BinaryFormatXDecoder xDecoder = new BinaryFormatXDecoder();
        xDecoder.setProtocolVersion(xProtocolVersion);
        xDecoder.setSomeOtherXProtocolConfig(someOtherXProtocolConfig);
        xDecoder.setXSource(binStream);

        // Generate the XML Events on the contentHandler...
        contentHandler.startDocument();

        // Use xDecoder to fire startElement, endElement etc events on the contentHandler (see previous section)...

        contentHandler.endDocument();
    }

    // etc....
}
BinaryFormatXXReader リーダーを Smooks に設定する方法は、他のリーダーの場合と同じです (前項の説明通り)。
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd">

    <reader class="com.acme.BinaryFormatXXReader">
        <params>
         <param name="xProtocolVersion">2.5.7</param>
         <param name="someOtherXProtocolConfig">1</param>
            ... etc... 
        </params>
    </reader>

    ... Other Smooks configurations e.g. <jb:bean> configs for binding the binary data into Java objects... 

</smooks-resource-list>
Smooks 実行コードは次のようになります (StreamSource に提供される InputStream に注意してください)。この場合、XML と Java オブジェクトの 2 つの結果を生成します。
StreamResult xmlResult = new StreamResult(xmlOutWriter);
JavaResult javaResult = new JavaResult();

InputStream xBinaryInputStream = getXByteStream();

smooks.filterSource(new StreamSource(xBinaryInputStream), xmlResult, javaResult);

// etc... Use the beans in the javaResult...

13.3. 断片ビジターの実装

ビジター実装は Smooks の主戦力となる実装です。Smooks のそのまま使用可能な機能 (Java バインディング、テンプレート、永続化など) のほとんどは、ビジター実装を 1 つ以上作成すると作成されます。ビジター実装は ExecutionContext および ApplicationContext コンテキストオブジェクトを通じて連携することが多く、連携して共通の目的を達成します。
Smooks は 2 つのタイプのビジター実装をサポートします。
  1. org.milyn.delivery.sax.SAXVisitor サブインターフェースを基にした SAX ベースの実装。
  2. org.milyn.delivery.sax.SAXVisitor サブインターフェースを基にした DOM ベースの実装。
実装は SAX と DOM の両方をサポートできますが、SAX 専用ビジターを実装することが推奨されます。SAX ベースの実装は通常、作成が容易で、より高速です。そのため、ここでは SAX のみを集中的に説明します。

重要

ビジター実装はすべてステートレスオブジェクトとして扱われます。単一のビジターインスタンスは、複数のメッセージ全体で同時に使用可能でなければなりません (例: Smooks.filterSource メソッドへの複数の同時呼び出し全体)。現在の Smooks.filterSource の実行に関連するステートはすべて ExecutionContext に格納される必要があります。

13.3.1. SAX ビジター API

SAX ビジター API は複数のインターフェースで構成されます。これらのインターフェースは、SAXVisitor 実装がキャプチャし処理できる org.xml.sax.ContentHandler SAX イベントが基になっています。SAXVisitor によって解決されたユースケースによっては、これらのインターフェースの 1 つまたはすべてを実装する必要がある場合があります。
org.milyn.delivery.sax.SAXVisitBefore
ターゲットの fragment 要素の startElement SAX イベントをキャプチャします。
public interface SAXVisitBefore extends SAXVisitor 
{
    void visitBefore(SAXElement element, ExecutionContext executionContext) 
                                          throws SmooksException, IOException;
}
org.milyn.delivery.sax.SAXVisitChildren
ターゲットの fragment 要素に対する文字ベースの SAX イベントをキャプチャし、子 fragment 要素の startElement イベントに対応する Smooks が生成した (擬似) イベントもキャプチャします。
public interface SAXVisitChildren extends SAXVisitor 
{
    void onChildText(SAXElement element, SAXText childText, ExecutionContext 
                        executionContext) throws SmooksException, IOException;

    void onChildElement(SAXElement element, SAXElement childElement, 
         ExecutionContext executionContext) throws SmooksException, IOException;
}
org.milyn.delivery.sax.SAXVisitAfter
ターゲット fragment 要素の endElement SAX イベントをキャプチャします。
public interface SAXVisitAfter extends SAXVisitor 
{
    void visitAfter(SAXElement element, ExecutionContext executionContext) 
                                          throws SmooksException, IOException;
}
SAX イベントをすべてキャプチャする必要があるこれらの実装が便利なように、上記の 3 つのインターフェースは org.milyn.delivery.sax.SAXElementVisitor インターフェースの単一のインターフェースへ一緒にプルされます。
XML の一部を用いてこれらのイベントを表します。
<message>
    <target-fragment>      <!-- SAXVisitBefore.visitBefore -->
        Text!!             <!-- SAXVisitChildren.onChildText -->  
        <child>            <!-- SAXVisitChildren.onChildElement -->
        </child>
    </target-fragment>     <!-- SAXVisitAfter.visitAfter -->
</message>
上記は XML のソースメッセージイベントストリームを表しています。形式は EDI、CSV、JSON やその他の形式でも可能です。理解しやすくするため、XML としてシリアライズされたソースメッセージイベントストリームとして考慮してください。
上記の SAX インターフェースの通り、org.milyn.delivery.sax.SAXElement タイプはすべてのメソッド呼び出しへ渡されます。このオブジェクトには、属性やそれらの値が含まれるターゲット断片要素に関する詳細が含まれています。また、テキストの蓄積を管理するためのメソッドや、Smooks.filterSource(Source, Result) メソッド呼び出しへ渡された StreamResult インスタンスに関連する Writer にアクセスするためのメソッドも含まれます。テキストの蓄積や StreamResult 書き込みについては、今後の項で詳細に説明します。

13.3.2. テキストの蓄積

SAX はストリームベースの処理モデルです。ドキュメントオブジェクトモデル (DOM) を作成したり、イベントデータを「蓄積」することはありません。SAX が巨大なメッセージストリームの処理に適切な処理モデルであるのはそのためです。
org.milyn.delivery.sax.SAXElement には常にターゲット要素に関連する属性データが含まれますが、SAXVisitBefore.visitBefore イベントと SAXVisitAfter.visitAfter イベントの間発生する SAX イベント (SAXVisitChildren.onChildText) を持つ断片の子テキストデータは含まれません (上図を参照)。 結果的にパフォーマンスが大幅に劣化することがあるため、テキストイベントは SAXElement 上では蓄積されません。SAXVisitor 実装がある断片のテキストの内容にアクセスする必要がある場合、ターゲットの断片に対してテキストを蓄積するよう、Smooks に明示的に伝える必要があります。これを実行するには、SAXVisitorSAXVisitBefore.visitBefore メソッド実装内部より SAXElement.accumulateText メソッドを呼び出します。
public class MyVisitor implements SAXVisitBefore, SAXVisitAfter 
{
   public void visitBefore(SAXElement element, ExecutionContext executionContext)
                                             throws SmooksException, IOException 
   {
      element.accumulateText();
   }

   public void visitAfter(SAXElement element, ExecutionContext executionContext) 
                                             throws SmooksException, IOException 
   {
      String fragmentText = element.getTextContent();

      // ... etc ...
   }
}
SAXVisitBefore.visitBefore を実装する代わりに、@TextConsumer アノテーションを SAXVisitor 実装に付けて対応することが可能です。
@TextConsumer
public class MyVisitor implements SAXVisitAfter 
{
   public void visitAfter(SAXElement element, ExecutionContext executionContext) 
                                             throws SmooksException, IOException 
   {
      String fragmentText = element.getTextContent();

        // ... etc ...
    }
}
SAXVisitAfter.visitAfter イベントが発生するまで、断片テキストはすべて使用できないことに注意してください。

13.3.3. StreamResult 書き込み/シリアライゼーション

Smooks.filterSource(Source, Result) メソッドに 1 つまたは複数の Result タイプ実装を用いることができますが、その 1 つが javax.xml.transform.stream.StreamResult クラスです。Smooks は StreamResult インスタンスを通じてソースをストリーミングします。
デフォルトでは、常に Smooks.filterSource(Source, Result) メソッドへ提供される StreamResult インスタンスに対して、完全なソースイベントストリームが XMLとしてシリアライズされます。Smooks.filterSource(Source, Result) メソッドに提供されるソースが XML ストリームで、StreamResult インスタンスが Result インスタンスの 1 つとして提供される場合、1 つ以上の断片を変更する SAXVisitor 実装を 1 つ以上用いて Smooks インスタンスが設定されている場合を除き、ソース XML は変更されずに StreamResult へ書き出されます。
デフォルトのシリアライゼーションの挙動は、フィルター設定にて有効または無効にすることができます。
あるメッセージ断片のシリアライズされた形式を変更したい場合、SAXVisitor を実装してトランスフォーメーションを実行し、XPath のような式を使用してメッセージ断片へターゲットする必要があります。

注記

提供されるテンプレートコンポーネントの 1 つを使用してメッセージ断片のシリアライズされた形式を変更することも可能です。これらのコンポーネントは SAXVisitor 実装でもあります。
断片のシリアライズされた形式を変換するよう SAXVisitor を実装するには、その SAXVisitor 実装が StreamResult へ書き込むよう Smooks に伝えることが重要になります。これは、Smooks では単一の断片へ複数の SAXVisitor 実装をターゲットできますが、断片ごとに 1 つの SAXVisitor のみが StreamResult へ書き込みできるためです。2 つ目の SAXVisitorStreamResult へ書き込みしようとすると、SAXWriterAccessException が発生しするため、Smooks の設定を変更する必要があります。
SAXVisitor の 1 つが StreamResult へ書き込みできるようにするには、StreamResult への Writer の「所有権」をその SAXVisitor が「取得する」必要があります。これには、SAXVisitBefore.visitBefore メソッド内部より SAXElement.getWriter(SAXVisitor) メソッドヘ呼び出しを行い、thisSAXVisitor パラメータとして渡します。
public class MyVisitor implements SAXElementVisitor 
{
   public void visitBefore(SAXElement element, ExecutionContext executionContext) 
                                             throws SmooksException, IOException 
   {
      Writer writer = element.getWriter(this);

      // ... write the start of the fragment...
   }

   public void onChildText(SAXElement element, SAXText childText, 
                                    ExecutionContext executionContext) 
                                       throws SmooksException, IOException 
   {
      Writer writer = element.getWriter(this);

      // ... write the child text...
   }

   public void onChildElement(SAXElement element, SAXElement childElement, 
                                    ExecutionContext executionContext) 
                                       throws SmooksException, IOException 
   {
   
   }

   public void visitAfter(SAXElement element, ExecutionContext executionContext) 
                                             throws SmooksException, IOException 
   {
      Writer writer = element.getWriter(this);
      // ... close the fragment...
   }
}
サブ断片のシリアライゼーションを制御する必要がある場合、Writer インスタンスをリセットし、サブ断片のシリアライゼーションを迂回しなければなりません。これには、SAXElement.setWriter を呼び出します。
シリアライズ/変換するターゲット断片にサブ断片が発生しないことが分かっている場合があります。このような場合、SAXElement.getWriter メソッドを呼び出して Writer の所有権を獲得するため、SAXVisitBefore.visitBefore を実装するのでは不十分です。@StreamResultWriter アノテーションがあるのはこのためです。このアノテーションを @TextConsumer アノテーションと組み合わせて使用すると、SAXVisitAfter.visitAfter メソッドの実装のみが必要となります。
@TextConsumer
@StreamResultWriter
public class MyVisitor implements SAXVisitAfter 
{
   public void visitAfter(SAXElement element, ExecutionContext executionContext) 
                                                throws SmooksException, IOException 
   {
      Writer writer = element.getWriter(this);

      // ... serialize to the writer ...
   }
}

13.3.3.1. SAXToXMLWriter

SAXElement データを XML としてシリアライズすることを少しでも容易にするため、Smooks は SAXToXMLWriter クラスを提供します。このクラスにより、SAXVisitor 実装を書くことが可能になります。
@StreamResultWriter
public class MyVisitor implements SAXElementVisitor 
{
   private SAXToXMLWriter xmlWriter = new SAXToXMLWriter(this, true);

   public void visitBefore(SAXElement element, ExecutionContext executionContext) 
                                             throws SmooksException, IOException 
   {
      xmlWriter.writeStartElement(element);
   }

   public void onChildText(SAXElement element, SAXText childText, ExecutionContext 
                              executionContext) throws SmooksException, IOException 
   {
      xmlWriter.writeText(childText, element);
   }

   public void onChildElement(SAXElement element, SAXElement childElement, 
      ExecutionContext executionContext) throws SmooksException, IOException 
   {
   
   }

   public void visitAfter(SAXElement element, ExecutionContext executionContext) 
                                             throws SmooksException, IOException 
   {
      xmlWriter.writeEndElement(element);
   }
}
SAXToXMLWriter コンストラクタの 2 つ目の引数がブール値であることに気付かれたかもしれません。これは encodeSpecialChars 引数であり、rewriteEntities フィルター設定を基に設定する必要があります。@StreamResultWriter アノテーションをクラスから SAXToXMLWriter インスタンス宣言へ移動する場合、Smooks は SAXToXMLWriter インスタンスを作成し、関連する Smooks インスタンスの rewriteEntities フィルター設定を用いて初期化します。
@TextConsumer
public class MyVisitor implements SAXVisitAfter 
{
   @StreamResultWriter
   private SAXToXMLWriter xmlWriter;

   public void visitAfter(SAXElement element, ExecutionContext executionContext) 
                                             throws SmooksException, IOException 
   {
      xmlWriter.writeStartElement(element);
      xmlWriter.writeText(element);
      xmlWriter.writeEndElement(element);
   }
}

13.3.4. ビジターの設定

SAXVisitor 設定は、他の Smooks コンポーネントと同じように挙動します。
Smooks ビジターインスタンスの設定に関し、設定 selector が XPath 式と同様に解釈されることが最も重要な点になります。
また、ビジターインスタンスは Smooks インスタンス上のプログラムコード内で設定することが可能です。これは単体テストに使用すると大変便利です。

13.3.4.1. ビジター設定の例

この例では、次のような大変単純な SAXVisitor 実装を使用します。
@TextConsumer
public class ChangeItemState implements SAXVisitAfter 
{
   @StreamResultWriter
   private SAXToXMLWriter xmlWriter;

   @ConfigParam
   private String newState;

   public void visitAfter(SAXElement element, ExecutionContext executionContext) 
                                             throws SmooksException, IOException 
   {
      element.setAttribute("state", newState);

      xmlWriter.writeStartElement(element);
      xmlWriter.writeText(element);
      xmlWriter.writeEndElement(element);
   }
}
状態が OK の <order-item> 断片上でファイアするよう ChangeItemState を宣言的に設定するのは、次の通り簡単です。
<smooks-resource-list 
   xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd">
 
   <resource-config selector="order-items/order-item[@status = 'OK']">
      <resource>com.acme.ChangeItemState </resource>
      <param name="newState">COMPLETED</param>
   </resource-config>
 
</smooks-resource-list>
カスタム設定名前空間を使用して、ChangeItemState コンポーネントの明確でより強く型付けされた設定を定義することが可能です。カスタム設定名前空間を用いると、この例のように簡単にコンポーネントを設定できます。
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" 
   xmlns:order="http://www.acme.com/schemas/smooks/order.xsd">
 
   <order:changeItemState itemElement="order-items/order-item[@status = 'OK']" 
      newState="COMPLETED" />
 
</smooks-resource-list>
このビジターは次のようにソースコードで設定することも可能です。
Smooks smooks = new Smooks();

smooks.addVisitor(new ChangeItemState().setNewState("COMPLETED"), 
                                 "order-items/order-item[@status = 'OK']");

smooks.filterSource(new StreamSource(inReader), new StreamResult(outWriter));

13.3.5. ビジターインスタンスのライフサイクル

Smooks は ExecutionLifecycleCleanable および VisitLifecycleCleanable インターフェースを使用して、ビジターコンポーネントに固有する 2 つのコンポーネントライフサイクルイベントをサポートします。

13.3.5.1. ExecutionLifecycleCleanable

このライフサイクルインターフェースを実装するビジターコンポーネントは、Smooks.filterSource 後のライフサイクル操作を実行できます。
public interface ExecutionLifecycleCleanable extends Visitor 
{
   public abstract void executeExecutionLifecycleCleanup(
                                          ExecutionContext executionContext);
}
基本の呼び出しシーケンスは次のように記述できます (executeExecutionLifecycleCleanup 呼び出しに注目してください)。
smooks = new Smooks(..);

smooks.filterSource(...);
// ** VisitorXX.executeExecutionLifecycleCleanup **
smooks.filterSource(...);
// ** VisitorXX.executeExecutionLifecycleCleanup **
smooks.filterSource(...);
// ** VisitorXX.executeExecutionLifecycleCleanup **
 ... etc ...
このライフサイクルメソッドは、Smooks.filterSource 実行ライフサイクルの周囲にスコープ指定されたリソースが、関連する ExecutionContext に対して確実にクリーンアップされるようにします。

13.3.5.2. VisitLifecycleCleanable

このライフサイクルインターフェースを実装するビジターコンポーネントは、SAXVisitAfter.visitAfter 後のライフサイクル操作を実行できます。
public interface VisitLifecycleCleanable extends Visitor 
{
   public abstract void executeVisitLifecycleCleanup(ExecutionContext executionContext);
}
基本の呼び出しシーケンスは次のように記述できます (executeVisitLifecycleCleanup 呼び出しに注目してください)。
smooks.filterSource(...);
    
    <message>
        <target-fragment>      < --- VisitorXX.visitBefore
            Text!!                       < --- VisitorXX.onChildText  
            <child>                      < --- VisitorXX.onChildElement
            </child>                 
        </target-fragment>     < --- VisitorXX.visitAfter
        ** VisitorXX.executeVisitLifecycleCleanup **
        <target-fragment>      < --- VisitorXX.visitBefore
            Text!!                       < --- VisitorXX.onChildText  
            <child>                      < --- VisitorXX.onChildElement
            </child>                 
        </target-fragment>     < --- VisitorXX.visitAfter
        ** VisitorXX.executeVisitLifecycleCleanup **
    </message>
    VisitorXX.executeExecutionLifecycleCleanup

smooks.filterSource(...);
    
    <message>
        <target-fragment>      < --- VisitorXX.visitBefore
            Text!!                       < --- VisitorXX.onChildText  
            <child>                      < --- VisitorXX.onChildElement
            </child>                 
        </target-fragment>     < --- VisitorXX.visitAfter
        ** VisitorXX.executeVisitLifecycleCleanup **
        <target-fragment>      < --- VisitorXX.visitBefore
            Text!!                       < --- VisitorXX.onChildText  
            <child>                      < --- VisitorXX.onChildElement
            </child>                 
        </target-fragment>     < --- VisitorXX.visitAfter
        ** VisitorXX.executeVisitLifecycleCleanup **
    </message>
    VisitorXX.executeExecutionLifecycleCleanup
このライフサイクルメソッドは、SAXVisitor 実装の単一での断片実行の周囲にスコープ指定されたリソースが、関連する ExecutionContext に対して確実にクリーンアップされるようにします。

13.3.6. ExecutionContext と ApplicationContext

Smooks はステート情報を保存 2 つのコンテキストオブジェクトを提供します。
ExecutionContext は、Smooks.filterSource メソッドの単一実行の周囲にスコープ指定されます。Smooks ビジター実装はすべて単一の Smooks.filterSource 実行のコンテキスト内でステートレスである必要があるため、ビジター実装は Smooks.filterSource メソッドの複数の同時実行にまたがって使用することが可能です。Smooks.filterSource の実行が完了すると、ExecutionContext インスタンスに保存されたすべてのデータを損失します。 ExecutionContext はビジター API のメッセージイベント呼び出しすべてに提供されます。
ApplicationContext は、関連する Smooks インスタンスの周囲にスコープ指定されます。たとえば、1 つの ApplicationContext インスタンスのみが Smooks インスタンスごとに存在します。このコンテキストオブジェクトを使用して、複数の Smooks.filterSource 実行にまたがって維持およびアクセスする必要があるデータを保存することが可能です。ApplicationContext クラスプロパティを宣言し、@AppContext アノテーションを付けると、コンポーネント (SAXVisitor コンポーネントを含む) は関連する ApplicationContext インスタンスへアクセスできます。
public class MySmooksComponent 
{
    @AppContext
    private ApplicationContext appContext;

    // etc...
}