SeamFramework.orgCommunity Documentation

第5章 Seamコンポーネントの構成

5.1. プロパティ設定によるコンポーネントの構成
5.2. components.xmlによるコンポーネントの構成
5.3. 細分化した構成ファイル
5.4. 構成可能なプロパティの型
5.5. XML名前空間の使用

XMLベースの構成を最小にするという哲学はSeamでは徹底されています。それにもかかわらず、XMLを使ってSeamを構成したいというさまざまな理由が存在します。 Javaコードからデプロイメント固有の情報を切り離したい、 再利用可能なフレームワークを作成可能にしたい、 Seam組み込み機能を構成したい等の理由です。 Seamはコンポーネントを構成する二つのアプローチを提供します。 プロパティファイルまたは web.xml でのプロパティ設定による構成と、 components.xml による構成です。

Seamコンポーネントには、サーブレットコンテキストパラメータ、システムプロパティ、あるいはクラスパスのルートに存在するseam.propertiesと名付けられたプロパティファイルのいずれかによって、構成プロパティを提供することが可能です。

構成可能なSeamコンポーネントは、構成属性に対してJavaBeansスタイルのプロパティ用セッターメソッドを公開しなければなりません。 com.jboss.myapp.settings という名前のSeamコンポーネントが、setLocale() という名前のセッターメソッドを持つとすると、seam.properties ファイル内の com.jboss.myapp.settings.locale という名前のプロパティや起動時の -D でorg.jboss.seam.properties.com.jboss.myapp.settings.locale という名前のシステムプロパティをサーブレットコンテキストパラメータとして提供することが可能となり、Seamはそのコンポーネントを生成するときにはlocale属性の値を設定します。

同じメカニズムはSeam自身の構成にも使われます。たとえば、対話のタイムアウトを設定するには、 web.xmlseam.properties または org.jboss.seam.properties をプレフィックスとするシステムプロパティにおいて、 org.jboss.seam.core.manager.conversationTimeout の値を提供します。 (org.jboss.seam.core.manager という名前の組み込みSeamコンポーネントが存在し、 それには setConversationTimeout() というセッターメソッドがあります。)

components.xmlファイルはプロパティ設定に比べパワフルです。 次を行うことができます。

components.xmlファイルは次の三つの異なる場所に置くことができます。

通常、Seamコンポーネントはデプロイメントスキャナーがseam.properties ファイルやMETA-INF/components.xmlを持つアーカイブ内で @Nameアノテーションの付いたクラスを発見したときにインストールされます (ただし、@Install アノテーションがデフォルトでインストールしないと 指定していない限り) 。 components.xmlファイルを使えば、 アノテーションを上書きする必要があるような特別な場合に対処することができます。

例えば、次の components.xml ファイルはjBPMをインストールします。


<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:bpm="http://jboss.com/products/seam/bpm">
    <bpm:jbpm/>
</components
>

これは以下と同じことをします。


<components>
    <component class="org.jboss.seam.bpm.Jbpm"/>
</components
>

これは2種類の異なるSeam管理対象永続コンテキストとインストールと構成を行います。


<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:persistence="http://jboss.com/products/seam/persistence"

    <persistence:managed-persistence-context name="customerDatabase"
                       persistence-unit-jndi-name="java:/customerEntityManagerFactory"/>
        
    <persistence:managed-persistence-context name="accountingDatabase"
                       persistence-unit-jndi-name="java:/accountingEntityManagerFactory"/>            

</components
>

これは以下と同じです。


<components>
    <component name="customerDatabase" 
              class="org.jboss.seam.persistence.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName"
>java:/customerEntityManagerFactory</property>
    </component>
    
    <component name="accountingDatabase"
              class="org.jboss.seam.persistence.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName"
>java:/accountingEntityManagerFactory</property>
    </component>
</components
>

この例はセッションスコープのSeam管理対象永続コンテキストを生成します (実際には推奨されるものではありません)。


<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:persistence="http://jboss.com/products/seam/persistence"

  <persistence:managed-persistence-context name="productDatabase" 
                                          scope="session"
                     persistence-unit-jndi-name="java:/productEntityManagerFactory"/>        

</components
>

<components>
            
    <component name="productDatabase"
              scope="session"
              class="org.jboss.seam.persistence.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName"
>java:/productEntityManagerFactory</property>
    </component>

</components
>

永続コンテキストのような基盤となるオブジェクトに対してauto-create オプションを使用するのは一般的なことです。そうすることで、@In アノテーションを使うときに明示的にcreate=trueを指定することを防ぐ ことができます。


<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:persistence="http://jboss.com/products/seam/persistence"

  <persistence:managed-persistence-context name="productDatabase" 
                                    auto-create="true"
                     persistence-unit-jndi-name="java:/productEntityManagerFactory"/>        

</components
>

<components>
            
    <component name="productDatabase"
        auto-create="true"
              class="org.jboss.seam.persistence.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName"
>java:/productEntityManagerFactory</property>
    </component>

</components
>

<factory>宣言は、値もしくはメソッドバインディング式を指定して、 それが最初に参照されたときにコンテキスト変数値を初期化するようにできます。


<components>

    <factory name="contact" method="#{contactManager.loadContact}" scope="CONVERSATION"/>

</components
>

Seamコンポーネントの「エイリアス」 (別名) が生成可能です。


<components>

    <factory name="user" value="#{actor}" scope="STATELESS"/>

</components
>

よく使用される式に対しても「エイリアス」を生成することすら可能です。


<components>

    <factory name="contact" value="#{contactManager.contact}" scope="STATELESS"/>

</components
>

<factory>宣言でauto-create="true"を使うことは 日常的によく目にすることです。


<components>

    <factory name="session" value="#{entityManager.delegate}" scope="STATELESS" auto-create="true"/>

</components
>

デプロイとテストの両方において components.xml ファイルをほんの少し修正するだけで同じファイルを再利用したいということがあります。 Seamは components.xml ファイル内に@wildcard@ 形式のワイルドカードを配置することが可能で、Antビルドスクリプト (デプロイ時) やクラスパスに components.properties というファイルを与えること (開発時) によって値を置き換えることができます。このアプローチはSeamサンプルプログラムで見ることができます。

もしもXMLで構成が必要な大量のコンポーネントがあるなら、components.xml に含まれる情報を多くの細かなファイルに分割することは意味があるでしょう。 Seamはあるクラス com.helloworld.Helloの設定を com/helloworld/Hello.component.xmlという名前のリソース内に置くことができます。 (もしかしたらこのパターンに見覚えがあるかもしれません。なぜなら、 Hibernateでも同様のやり方をしているからです。) そのファイルのルート要素は<components> または <component>要素のいずれかが可能です。

最初のオプションはファイル内に複数コンポーネントの定義が可能です。


<components>
    <component class="com.helloworld.Hello" name="hello">
        <property name="name"
>#{user.name}</property>
    </component>
    <factory name="message" value="#{hello.message}"/>
</components
>

二番目のオプションは単一コンポーネントしか定義または構成できませんが、 煩雑さはありません。


<component name="hello">
    <property name="name"
>#{user.name}</property>
</component
>

二番目のオプションでは、クラス名はコンポーネント定義が登場するファイル名によって暗に指定されます。

あるいは、com/helloworld/components.xmlcom.helloworldパッケージ内のすべてのクラスの構成をすることも可能です。

文字列、プリミティブ、プリミティブラッパー型は、あなたが予想する通りに構成できます。

org.jboss.seam.core.manager.conversationTimeout 60000

<core:manager conversation-timeout="60000"/>

<component name="org.jboss.seam.core.manager">
    <property name="conversationTimeout"
>60000</property>
</component
>

文字列またはプリミティブから構成される配列、セット、リストもサポートされます。

org.jboss.seam.bpm.jbpm.processDefinitions order.jpdl.xml, return.jpdl.xml, inventory.jpdl.xml

<bpm:jbpm>
    <bpm:process-definitions>
        <value
>order.jpdl.xml</value>
        <value
>return.jpdl.xml</value>
        <value
>inventory.jpdl.xml</value>
    </bpm:process-definitions>
</bpm:jbpm
>

<component name="org.jboss.seam.bpm.jbpm">
    <property name="processDefinitions">
        <value
>order.jpdl.xml</value>
        <value
>return.jpdl.xml</value>
        <value
>inventory.jpdl.xml</value>
    </property>
</component
>

文字列値のキーと、文字列またはプリミティブの値から成るマップでさえもサポートされます。


<component name="issueEditor">
    <property name="issueStatuses">
        <key
>open</key
> <value
>open issue</value>
        <key
>resolved</key
> <value
>issue resolved by developer</value>
        <key
>closed</key
> <value
>resolution accepted by user</value>
    </property>
</component
>

多値プロパティを構成するときは、Seamはデフォルトで components.xml に属性を配置した順番を保存します(SortedSet/SortedMapを使用しないときはSeamはTreeMap/TreeSetを使います)。もしそのプロパティが(例えば、LinkedListのように)具体的な型を持つなら、Seamはその型を使います。

次のように完全修飾名を指定することでその型を上書きすることも可能です。


<component name="issueEditor">
   <property name="issueStatusOptions" type="java.util.LinkedHashMap">
      <key
>open</key
> <value
>open issue</value>
      <key
>resolved</key
> <value
>issue resolved by developer</value>
      <key
>closed</key
> <value
>resolution accepted by user</value>
   </property>
</component
>

最後に、値バインディング式 (value-binding expression) を使ってコンポーネントを連携させることができます。 これは@Inを使った注入とはまったく異なるので注意してください。 なぜなら、それは呼び出し時ではなく、コンポーネント生成時に起こるからです。 したがって、JSFやSpringのような既存のIoCコンテナによって提供される依存性注入により近いです。


<drools:managed-working-memory name="policyPricingWorkingMemory"
    rule-base="#{policyPricingRules}"/>

<component name="policyPricingWorkingMemory"
    class="org.jboss.seam.drools.ManagedWorkingMemory">
    <property name="ruleBase"
>#{policyPricingRules}</property>
</component
>

SeamはコンポーネントのBeanプロパティへ初期値を代入する前にEL式の文字列も解決します。そこでコンテキスト依存データをコンポーネントにインジェクトすることも可能になります。


<component name="greeter" class="com.example.action.Greeter">
    <property name="message"
>Nice to see you, #{identity.username}!</property>
</component
>

しかし、一つ重要な例外があります。もしも初期値が代入されようとするプロパティの型がSeamのValueExpression または MethodExpressionであるなら、そのEL式の評価は遅延されます。その代わり、適切な式のラッパーが生成されてそのプロパティに代入されます。SeamアプリケーションフレームワークでのHomeコンポーネントのメッセージテンプレートがその一例になります。


<framework:entity-home name="myEntityHome"
    class="com.example.action.MyEntityHome" entity-class="com.example.model.MyEntity"
    created-message="'#{myEntityHome.instance.name}' has been successfully added."/>

コンポーネントの内部では、 ValueExpression または MethodExpression 上で getExpressionString() を呼び出すことによって式の文字列にアクセス可能です。もしもそのプロパティが ValueExpressionであるなら、その値を getValue() によって解決可能ですし、もしもそのプロパティがMethodExpressionであるなら、 invoke(Object args...)を使ってそのメソッドを呼び出すことができます。MethodExpressionプロパティ へ値を代入するためには、その初期値全体は単一のEL式でなければなりません。

例に示す通り、コンポーネントを宣言するには、XML名前空間を使用する、使用しないという二つの相異なる方法があります。以下は名前空間を使用しない典型的なcomponents.xmlファイルを示します。


<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
            xsi:schemaLocation="http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.2.xsd">

    <component class="org.jboss.seam.core.init">
        <property name="debug"
>true</property>
        <property name="jndiPattern"
>@jndiPattern@</property>
    </component>
    
</components
>

ご覧の通り、これは幾分煩雑です。 さらに悪いことには、コンポーネントと属性の名前は、デプロイ時の妥当性検証の対象となりません。

名前空間を使ったバージョンはこのようになります。


<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
            xmlns:core="http://jboss.com/products/seam/core"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation=
                "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.2.xsd 
                 http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.2.xsd">

    <core:init debug="true" jndi-pattern="@jndiPattern@"/>

</components
>

スキーマ宣言は冗長ではありますが、実際のXMLの内容は簡潔かつ理解しやすいものです。 このスキーマは利用可能な各コンポーネントと属性に関する詳細情報を提供するもので、 XMLエディタでインテリジェントな自動補完入力を可能にします。 名前空間付きの要素の使用は、正しいcomponents.xmlファイルの生成と保守をより簡単にしてくれます。

さて、これは組み込みSeamコンポーネントに対しては良く機能しますが、果たしてユーザーコンポーネントに対してはどうでしょうか。 最初に、Seamは二つの混在したモデルをサポートします。 一つはユーザーコンポーネントに対する一般的な<component> 宣言、 もう一つは組み込みコンポーネントに対する名前空間付きの宣言です。 Seamはユーザーコンポーネントに対しても簡単に名前空間を宣言できるようにしてくれています。

任意のJavaパッケージには、@Namespaceアノテーションをパッケージに付加することによって、XML名前空間を関連付けることができます。 (パッケージレベルのアノテーションは、パッケージディレクトリ内のpackage-info.javaという名前のファイルで宣言されます。) これはseapayデモからの例です。

@Namespace(value="http://jboss.com/products/seam/examples/seampay")

package org.jboss.seam.example.seampay;
import org.jboss.seam.annotations.Namespace;

やらなければならないことは、components.xmlで名前空間スタイルを使うことだけです! こうして次のように書くことが可能になります。


<components xmlns="http://jboss.com/products/seam/components"
            xmlns:pay="http://jboss.com/products/seam/examples/seampay"
            ... >

    <pay:payment-home new-instance="#{newPayment}"
                      created-message="Created a new payment to #{newPayment.payee}" />

    <pay:payment name="newPayment"
                 payee="Somebody"
                 account="#{selectedAccount}"
                 payment-date="#{currentDatetime}"
                 created-date="#{currentDatetime}" />
     ...
</components
>

または、


<components xmlns="http://jboss.com/products/seam/components"
            xmlns:pay="http://jboss.com/products/seam/examples/seampay"
            ... >

    <pay:payment-home>
        <pay:new-instance
>"#{newPayment}"</pay:new-instance>
        <pay:created-message
>Created a new payment to #{newPayment.payee}</pay:created-message>
    </pay:payment-home>
    
    <pay:payment name="newPayment">
        <pay:payee
>Somebody"</pay:payee>
        <pay:account
>#{selectedAccount}</pay:account>
        <pay:payment-date
>#{currentDatetime}</pay:payment-date>
        <pay:created-date
>#{currentDatetime}</pay:created-date>
     </pay:payment>
     ...
</components
>

これらのサンプルは名前空間付き要素の二つの利用モデルを説明します。 最初の宣言では<pay:payment-home>paymentHomeコンポーネントを参照しています。

package org.jboss.seam.example.seampay;

...
@Name("paymentHome")
public class PaymentController
    extends EntityHome<Payment>
{
    ... 
}

その要素名はコンポーネント名をハイフンで連結した形式になっています。 その要素の属性名はプロパティ名をハイフンで連結した形式になっています。

二番目の宣言では、<pay:payment>要素はorg.jboss.seam.example.seampayパッケージでのPaymentクラスを参照します。 Payment のケースでは、あるエンティティがSeamコンポーネントとして宣言されようとしています。

package org.jboss.seam.example.seampay;

...
@Entity
public class Payment
    implements Serializable
{
    ...
}

ユーザー定義コンポーネントに対して妥当性検証と自動補完入力が機能するようにしたいなら、 スキーマが必要になります。Seamはコンポーネントの集まりからスキーマを自動生成するような機能はまだ提供していませんので、手動で生成する必要があります。標準的なSeamパッケージのスキーマ定義はガイドとして利用できます。

次はSeamによって使用済みの名前空間です。