XPなアプリケーションサーバ

Seasarは、eXtreme Programingの開発手法を使って開発されてアプリケーションサーバで、
シンプルなこと、徹底的にテストされていることが特徴です。
Webの機能は、Jettyを使っています。
データベースとして、HSQLDBも組み込まれているので、ダウンロードしてすぐに
様々な機能を試してみることができます。
シーサープラグインによって、Eclipseで快適に開発することもできます。



稼働環境

JDK1.4とEclipse2.1.1以上が必要です。

インストール

ダウンロードしたseasarsetupXXX.jarを適当なディレクトリにコピーして、 jar xf seasarsetupXXX.jarで解凍します。
seasarというディレクトリができるので、今後このディレクトリを$SEASAR_HOMEと 呼ぶことにします。
バージョン1.3以上のantがまだインストールされていないなら
コマンドラインから実行できるようにするために、
$SEASAR_HOME/binをPATH環境変数に加えてください。
コマンドラインから、ant -versionを実行して、バージョンが表示されればOKです。
これで、Seasarのインストールはこれで終了です。

開始と終了

Windowsの場合、$SEASAR_HOME/bin/startSeasar.batをダブルクリックします。
Unixの場合、$SEASAR_HOME/binにcdしてから、./startSeasar.shを実行します。

INFO  2003-04-01 21:03:59,183 org.seasar.system.Seasar
[ISSR0001]seasar started
のように表示されればOKです。 ブラウザから、http://localhost:8080/にアクセスし、
SeasarのWelcomeページが表示されることも確認してください。

終了は、Windowsの場合、$SEASAR_HOME/bin/shutdownSeasar.batをダブルクリックします。
Unixの場合、$SEASAR_HOME/binにcdしてから、./shutdownSeasar.shを実行します。
$SEASAR_HOME/logs/seasar.logの最後に

INFO  2003-04-01 22:30:37,678 org.seasar.system.Seasar 
[ISSR0003]seasar shutdown
のように表示されればOKです。
Seasarのログは、$SEASAR_HOME/logsに書き出されます。
まだ、Seasarを終了させていない場合は、ここで終了させてください。

Jettyの設定

Jettyの設定は、$SEASAR_HOME/classes/jetty.xmlで行います。
通常は、何もいじる必要はありません。詳しい設定内容は、 Jettyのホームページで確認してください。

Webアプリケーションのデプロイ

Webのアプリケーションをデプロイするには、
warファイル、あるいは、warファイルを解凍したディレクトリを
$SEASAR_HOME/webappsにコピーします。
Seasarが起動中なら、$SEASAR_HOME/bin/restartJetty.bat(Unixの場合restartJetty.sh)を実行します。
Seasarが起動していないなら、単にSeasarを再起動するだけでOKです。
現状では、Webアプリケーションのホットデプロイには対応していませんが、
Jettyの起動・終了は一瞬なので、どきどきしながらホットデプロイされるのをまつより、
手動で行ったほうが、すっきりすると思っています。
ルールエンジンのNazunaは、ホットデプロイに対応しています。

シーサープラグイン

実は、Seasarの起動・停止・再起動・Jettyの再起動といった処理は、
Eclipseから簡単に行うことができます。
Seasarプラグインの設定は次のように行います。
Eclipseをインストールしたディレクトリを$ECLIPSE_HOMEと呼ぶことにします。
Seasarプラグインを既にインストール済なら、$ECLIPSE_HOME/pluginsから削除します。
$SEASAR_HOME/plugins/org.seasar.eclipse_x.x.xを$ECLIPSE_HOME/pluginsにコピーして
Eclipseを再起動します。
Eclipseのウィンドウ->設定のシーサーのシートでシーサーホームを設定します。
次にEclipseのウィンドウ->パースペクティブのカスタマイズを選び
その他のツリーを開いて、シーサーをチェックしてOKをクリックします。
シーサーがメニューに現れるので、起動・停止・再起動・Jettyの再起動と
いった処理を簡単に行うことができます。

シーサープロジェクト

Eclipseでシーサープロジェクトを作って開発すると、
そのまま、デプロイされたWebApplicationとして、
Jettyに認識されるので、Jettyを再起動するだけで、
ブラウザを使ったテストを行うことができます。
メニューから、ファイル->新規->プロジェクト->Java->シーサープロジェクトを選んで、
次へボタンをクリックします。
プロジェクト名をexamplesとし、終了ボタンをクリックします。
パッケージ・エクスプローラーでexamplesプロジェクトを右クリックし、
最新表示を実行した後に、プロジェクトのツリーを展開すると、
WEB-INF/src,WEB-INF/classes,WEB-INF/libが作成され、
必要なファイルが既にコピーされている事がわかります。
Javaのビルドパスも設定済みなので、面倒な設定を行うことなく
すぐに開発に取り掛かることができます。

次にexamplesプロジェクトをインポートします。
examplesプロジェクトを右クリックして、インポート->ファイルシステムを選びます。
ソースディレクトリを$SEASAR_HOME/examplesにし、左側のツリーのexamplesディレクトリを
チェックし、終了ボタンをクリックします。
これで、examplesプロジェクトが完成しました。
Seasarをまだ起動していない場合は、起動してください。
既に起動済みの場合は、Jettyを再起動します。
起動しているかどうかあやふやな場合(笑)は、Seasarを再起動します。

ブラウザから、 http://localhost:8080/examples/jsp/now.jspにアクセスします。

Tue Oct 07 19:54:26 JST 2003 
のようになれば、設定はうまくいってます。

次は、FlashからSeasarにアクセスしてみましょう。
http://localhost:8080/examples/flash/AddRuletClient.htmlにアクセスします。
1と2を入力して、計算ボタンをクリックすると3と表示されたはずです。

ブラウザを使ったデバッグも簡単です。
EclipseでWEB-INF/srcのツリーを展開し、examples.org.seasar.nazuna.AddRuletを
ダブルクリックします。
このルーレットは、先ほどのFlashから呼び出されたルーレットです。
return a + b;と書かれた行の左端をダブルクリックして、ブレークポイントを設定します。
もう一度、 http://localhost:8080/examples/flash/AddRuletClient.htmlにアクセスします。
1と2を入力して、計算ボタンをクリックすると、タスクトレーのEclipseが点滅し、
ブレークポイントに制御が移ったことが確認できます。
引数の確認もステップ実行も思いのままです。

Seasar自身のデバッグも簡単です。
メニューからナビゲート->型を開くを選び、Nazunaと入力します。
マッチングする型からNazunaを選びます。
public static final Object executeRulet(String className, Object[] args)
メソッドのreturnしている行に対して、ブレークポイントを設定します。
もう一度、 http://localhost:8080/examples/flash/AddRuletClient.htmlにアクセスします。
1と2を入力して、計算ボタンをクリックすると、タスクトレーのEclipseが点滅し、
ブレークポイントに制御が移ったことが確認できます。
Seasar自身のデバッグも思いのままです。

WebApplicationのコンテキストパスは、後から変更することもできます。
変更は、プロジェクトを右クリックして、プロパティ->シーサーのページで行います。
シーサープロジェクトであるのチェックをはずすと、jetty.xmlからWebApplicationの
設定が削除されます。
シーサープロジェクトであるをチェックしなおすと、jetty.xmlにWebApplicationの
設定を再追加することもできます。

プロパティエディタ

シーサープラグインを使えば、日本語を含んだ*.propertiesも簡単に扱えます。
これまでのように、native2asciiを実行する必要もありません。
メニューのウィンドウ->設定->ワークベンチ->ファイルの関連付けで
*.propertiesを選び、シーサープロパティエディタをデフォルトに指定します。
Eclipseから*.propertiesを作成して、日本語で入力し保存すると、
自動的にユニコードでエスケープされます。
ユニコードでエスケープされた*.propertiesを読み込んだときは、日本語で表示されます。

HSQLDBの設定

HSQLDBの設定は、$SEASAR_HOME/classes/seasar-config.xmlのHsqldbServiceの
プロパティを設定することで行います。
最初は特に変える必要はないでしょう。
各プロパティの意味は次のとおりです。

名前 説明
database
データベース名を設定します。
$SEASAR_HOME/dataにデータベース名.拡張子のファイルが作成されます。
データベースが存在していない場合、自動的に作成されます。
各ファイルの意味は、データベースの構成ファイル を参照してください。
port
JDBC Driverの接続を受け付けるポートです。デフォルトは、9001になります。
silent
falseの場合、HSQLDB自身の詳細なメッセージをコンソールに書き出します。
trace
trueの場合、JDBCの詳細なメッセージをコンソールに書き出します。
compact
trueの場合、SHUTDOWN時に、CACHED TABLEのデータを再作成して、
データベースのサイズをコンパクトにします。
CASHED TABLEの詳細は、テーブルの種別を参照してください。
checkpointInterval
HSQLDBに対して、CHECKPOINTを発行する間隔を指定します。単位は秒です。
0だとCHECKPOINTは発行しません。
retryCount
Seasarは、HSQLDBを非同期に開始させているため、
本当に開始したことを確かめるために
非同期開始後、1秒おきにHSQLDBにSQL文を発行しています。
エラーが起きなければ、HSQLDBが起動済みとみなして、
後続のサービスを起動し、エラーが起きたら、retryCountの間、
SQL文を再発行して起動チェックを続けます。
データ量が増えて、HSQLDBの開始が遅くなった場合には、
retryCountを増やしてください。

データベース構成ファイル

HSQLDBのデータベースは、$SEASAR_HOME/dataディレクトリに作成され、
以下のファイルで構成されます。

名前 説明
データベース名.properties
データベースごとの設定を行います。
各項目の意味は、データベース名.propertiesの詳細 を参照してください。
データベース名.script
テーブルの定義とMEMORY TABLEのデータが書き込まれます。
通常にCREATE TABLEをするとMEMORY TABLEが作成されます。
MEMORY TABLEの詳細は、テーブルの種別を参照してください。
データベース名.data
CACHED TABLEのデータが書き込まれます。
CACHED TABLEの詳細は、テーブルの種別を参照してください。
データベース名.backup
1番最後にSHUTDOWN, CHECKPOINTしたときのCACHED TABLEのデータを
ZIP形式で圧縮したファイルです。

データベース名.propertiesの詳細

データベースごとの設定を行います。
各プロパティの意味は以下のとおりになります。

プロパティ名 説明
readonly デフォルトはfalse。
trueの場合、データベースを更新することができなくなります。
sql.month デフォルトはtrue。
trueの場合、month(Date)が1-12を返します。
falseの場合、0-11を返します。
sql.enforce_size デフォルトはfalse。
trueの場合、VARCHARやCHARACTERのデータ型の値が、
テーブルの定義にあわせて、カットされたりパディングされたりします。
falseの場合、なにもしません。
sql.compare_in_locale CHARACTER、VARCHARデータ型で、ソートするときに
JREのロケールに従うかどうかを表します。
デフォルトはfalse。
trueの場合、現在のJREのロケールに従ってソートされます。
falseの場合、POSIX標準にしたがってソートされます。
CACHED TABLEを含むデータベースの場合、この値を変更すると
インデックスがおかしくなります。
正しく変更するためには、Seasarを停止させた後、値を変更し
HsqldbServiceのcompactプロパティをtrueにして
Seasarを起動し、すぐに停止させてください。
停止させるときに、HSQLDBに対して、SHUTDOWN COMPACTを実行しているので
インデックスが再作成されます。
その後、compactプロパティを元に戻して、Seasarを起動しなおします。
sql.strict_fk バージョン1.7.1以降は、設定しても無視されます。
sql.strong_fk バージョン1.7.1以降は、設定しても無視されます。
hsqldb.cache_scale CACHED TABLEのデータがメモリ内で、2^hsqldb.cache_scaleのサイズ分
キャッシュされます。
デフォルトは14で、8-16でなければいけません。
hsqldb.log_size 自動CHECKPOINTに達していなくても、メモリ上のlogのサイズが
指定した値を超えると、CHECKPOINTが実行されます。
単位は、Mバイトで、デフォルトは200です。
hsqldb.gc_interval GCを強制的に実行するインターバルを指定します。
デフォルトは、0なのでGCは強制されません。
通常は0以外を設定すべきではありません。

テーブルの種別

HSQLDBは、一時的なテーブルと3つの永続的なテーブルをサポートしています。

一時的なテーブルは、CREATE TEMP TABLE文で作成します。
データはメモリ上に保存され、Connectionがクローズされるとデータも消えます。

永続的なテーブルは、MEMORY、CACHED、TEXTの3つがあります。

MEMORY TABLEは、CREATE TABLE文で作成します。
すべてのデータはメモリと.scriptファイルに保存されます。
起動時に、.scriptファイルが読み込まれて、メモリにテーブルの内容が
再作成されます。

CACHED TABLEは、CREATE CACHED TABLE文で作成します。
hsqldb.cache_scaleによって計算されるサイズまでは、
メモリ上にデータが保存され、それを超えると.dataファイルにデータが書き出されます。
大量のデータを扱えますが、速度は、MEMORY TABLEより遅くなります。

TEXT TABLEは、CREATE TEXT TABLE文で作成します。
詳細は、TEXT TABLEのマニュアル を参照してください。

DatabaseManager

HSQLDBに接続するためのユーティリティとして、$SEASAR_HOME/binに
runHsqldbManagerが用意されています。
起動すると、接続のTypeを聞かれます。
HSQL Database Engine Serverを選んで、OKをクリックすると
HSQLDBに接続できます。SHUTDOWNは、実行しないでください。

コネクションプールの設定

データベースに接続するためには、コネクションプールの設定をする必要があります。
コネクションプールの設定は、$SEASAR_HOME/classes/connectionpool-config.xmlに対して 行います。

connectionPoolタグのdataSourceName属性で、このコネクションプールは 識別されます。
connectionPoolタグは、複数記述することができますが、
その中で、dataSourceName属性の値はユニークでなければなりません。

jndiName属性にJNDIからルックアップするときの名前を指定します。
慣例として、コネクションプールのJNDI名は、jdbc/dataSourceNameとします。

xaDataSourceClassName属性にjavax.sql.XADataSourceインターフェースを
実装したクラス名を指定します。JDBC DriverがXAに対応していない場合、
org.seasar.sql.XADataSourceImplを指定します。
XA機能をまともに実装できているJDBC Driverは少ないので、
通常は、org.seasar.sql.XADataSourceImplを指定します。

timeout属性で、コネクションをクローズしてから、
プールでキープされる時間(秒)を指定します。
指定した時間を過ぎると、コネクションは破棄されます。

poolSize属性で、同時にアクティブになれる
コネクションの数を指定します。
この数を超える要求があると、コネクションがプールに返されるまで
その要求はブロックされます。

propertyタグで、xaDataSourceClassNameで指定したクラスのインスタンスに対する
プロパティを指定します。org.seasar.sql.XADataSourceImplの場合、
driverClassName,URL,user,passwordを下記のように設定します。
下記の例は、HSQLDB、Oracle、PostgreSQLの場合です。

connectionpool-config.xmlをセーブしてください。
これで、サーバの設定は終了です。

<connectionPools>

    <connectionPool
        dataSourceName="hsqldb"
        jndiName="jdbc/hsqldb"
        xaDataSourceClassName="org.seasar.sql.XADataSourceImpl"
        timeout="600"
        poolSize="20">
        <properties>
            <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
            <property name="URL" value="jdbc:hsqldb:hsql://localhost:9001"/>
            <property name="user" value="sa"/>
            <property name="password" value=""/>
        </properties>
    </connectionPool>

    <connectionPool
        dataSourceName="oracle"
        jndiName="jdbc/oracle"
        xaDataSourceClassName="org.seasar.sql.XADataSourceImpl"
        timeout="600"
        poolSize="20">
        <properties>
            <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
            <property name="URL" value="jdbc:oracle:thin:@d1606:1521:ark"/>
            <property name="user" value="scott"/>
            <property name="password" value="tiger"/>
        </properties>
    </connectionPool>
    
    <connectionPool
        dataSourceName="postgres"
        jndiName="jdbc/postgres"
        xaDataSourceClassName="org.seasar.sql.XADataSourceImpl"
        timeout="600"
        poolSize="20">
        <properties>
            <property name="driverClassName" value="org.postgresql.Driver"/>
            <property name="URL" value="jdbc:postgresql://localhost/testdb2"/>
            <property name="user" value="scott"/>
            <property name="password" value="tiger"/>
        </properties>
    </connectionPool>

</connectionPools>

デモデータの準備

データベースに接続するプログラムを動かすために$SEASAR_HOME/sqlの スクリプトを実行します。
オラクルの場合はdemo-oracle.sql、HSQLDBの場合はdemo-hsqldb.sqlを実行します。
HSQLDBの場合は、既にデモデータが用意されているので、
実際に実行する必要はありません。
他のデータベースをお使いの方は、データベースにあわせてスクリプトを修正してください。

コネクションプールへの接続

コネクションプールには、JNDI経由(javax.naming.InitialContext)で接続します。
どこに接続するのかは、InitialContextのコンストラクタに渡すPropertiesで 設定します。

Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, "org.seasar.naming.NamingContextFactory");
env.put(Context.PROVIDER_URL, "localhost:1108");
InitialContext ctx = new InitialContext(env);

Context.INITIAL_CONTEXT_FACTORYに設定する値は、
アプリケーションサーバごとに異なりますが、
Seasarの場合は、org.seasar.naming.NamingContextFactoryを指定します。

Context.PROVIDER_URLでSeasarの場所を指定します。
ホスト名:ポート番号の形式です。
ポート番号は、$SEASAR_HOME/classes/seasar-config.xmlの
portプロパティの値とあわせる必要があります。デフォルトは、1108です。

<service className="org.seasar.system.RMIAdaptorService">
    <properties>
        <property name="port" value="1108"/>
    </properties>
</service>

InitialContextからjavax.sql.DataSourceを取得します。
lookup()の引数に指定するJNDI名は、$SEASAR_HOME/classes/connectionpool-config.xmlで connectionPoolにつけたjndiNameになります。

後は、javax.sql.DataSource#getConnection()で、コネクションを取り出し、
JDBCの処理を行うことができます。

DataSource ds = (DataSource) ctx.lookup("jdbc/hsqldb");
Connection con = ds.getConnection();

examples.org.seasar.ConnectionPoolExample

package examples.org.seasar;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

public class ConnectionPoolExample {

    private static final String FACTORY =
        "org.seasar.naming.NamingContextFactory";
    private static final String URL = "localhost:1108";
    private static final String JNDI_NAME = "jdbc/hsqldb";
    private static final String SQL = "SELECT ename FROM emp";

    public static void main(String[] args) {
        try {
            Properties env = new Properties();
            env.put(Context.INITIAL_CONTEXT_FACTORY, FACTORY);
            env.put(Context.PROVIDER_URL, URL);
            InitialContext ctx = new InitialContext(env);
            DataSource ds = (DataSource) ctx.lookup(JNDI_NAME);
            Connection con = null;
            Statement stmt = null;
            ResultSet rs = null;
            try {
                con = ds.getConnection();
                stmt = con.createStatement();
                rs = stmt.executeQuery(SQL);
                while (rs.next()) {
                    System.out.println(rs.getString("ename"));
                }
            } finally {
                if (rs != null) {
                    rs.close();
                }
                if (stmt != null) {
                    stmt.close();
                }
                if (con != null) {
                    con.close();
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

このプログラムを実行して下記のようなエラーになった場合は、
まだSeasarが開始されていません。

javax.naming.NamingException: java.net.ConnectException: Connection refused: connect
    at org.seasar.naming.NamingUtil.convertNamingException(NamingUtil.java:14)
    at org.seasar.naming.NamingServerWrapper.lookup(NamingServerWrapper.java:44)
    at org.seasar.naming.NamingContext.lookup(NamingContext.java:105)
    at org.seasar.naming.NamingContext.lookup(NamingContext.java:98)
    at javax.naming.InitialContext.lookup(InitialContext.java:347)
    at examples.org.seasar.ConnectionPoolExample.main(ConnectionPoolExample.java:26)
Caused by: java.net.ConnectException: Connection refused: connect
    at java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:295)
    at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:161)
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:148)
    at java.net.Socket.connect(Socket.java:425)
    at java.net.Socket.connect(Socket.java:375)
    at java.net.Socket.(Socket.java:290)
    at java.net.Socket.(Socket.java:118)
    at org.seasar.system.RMIConnector.getRMIAdaptorBySocket(RMIConnector.java:74)
    at org.seasar.system.RMIConnector.getRMIAdaptor(RMIConnector.java:41)
    at org.seasar.system.MBeanProxy.invoke(MBeanProxy.java:100)
    at $Proxy0.lookup(Unknown Source)
    at org.seasar.naming.NamingServerWrapper.lookup(NamingServerWrapper.java:42)
	... 4 more

Seasarを開始させた後にプログラムを実行しましょう。
下記のような結果になるはずです。

SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER

トランザクション処理

トランザクションも、JNDI経由(InitialContext)で、
javax.transaction.UserTransactionを取得して処理します。
Connectionのcommit,rollbackを使うよりも、JDBCの処理とトランザクション処理を
明確に分離できます。
トランザクションのために、複数のメソッドで、Connectionを持ちまわるような必要もなくなります。
InitialContextを取得する部分は、DataSourceの場合と同様です。
lookup()で指定するJNDI名は、java:comp/UserTransactionになります。

トランザクションは、UserTransaction#begin()で開始します。
トランザクションをコミットしたい場合は、UserTransaction#commit()を呼び出します。
トランザクションをロールバックしたい場合は、UserTransaction#rollback()を呼び出します。

examples.org.seasar.TransactionExample

package examples.org.seasar;

import java.sql.Connection;
import java.sql.Statement;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import javax.transaction.UserTransaction;

public class TransactionExample {

    private static final String FACTORY =
        "org.seasar.naming.NamingContextFactory";
    private static final String URL = "localhost:1108";
    private static final String TX_JNDI_NAME = "java:comp/UserTransaction";
    private static final String DS_JNDI_NAME = "jdbc/hsqldb";
    private static final String SQL =
        "UPDATE emp SET ename='SCOTT' WHERE empno = 7788";

    public static void main(String[] args) {
        try {
            Properties env = new Properties();
            env.put(Context.INITIAL_CONTEXT_FACTORY, FACTORY);
            env.put(Context.PROVIDER_URL, URL);
            InitialContext ctx = new InitialContext(env);
            UserTransaction tx = (UserTransaction) ctx.lookup(TX_JNDI_NAME);
            DataSource ds = (DataSource) ctx.lookup(DS_JNDI_NAME);
            Connection con = null;
            Statement stmt = null;
            tx.begin();
            try {
                con = ds.getConnection();
                stmt = con.createStatement();
                int rows = stmt.executeUpdate(SQL);
                System.out.println("updated " + rows + " row(s)");
                tx.commit();
            } catch (Exception ex) {
                tx.rollback();
                ex.printStackTrace();
            } finally {
                if (stmt != null) {
                    stmt.close();
                }
                if (con != null) {
                    con.close();
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

実行結果は下記のようになります。

DEBUG 2003-09-15 21:01:32,299 [main] Transaction.begin()
updated 1 row(s)
DEBUG 2003-09-15 21:01:32,489 [main] Transaction.commit()

SeasarContext

ConnectionPoolExampleやTransactionExampleのように、JNDIの設定を
プログラムにハードコーディングしてしまうと、
変更に弱いプログラムになります。
また、JNDIのルックアップは、比較的重たい処理なので、
キャッシュの仕組みも必要になります。
JNDIの設定をXMLに記述し、ルックアップのキャッシュ機能を持ったクラスが、SeasarContextです。
SeasarContextの設定は、seasar-context.xmlで行います。
$SEASAR_HOME/classesにあるものをCLASSPATHに通されているディレクトリにコピーして使います。

seasarContextタグがルートになり、子タグとしてcontextタグを複数記述することができます。

contextタグのname属性で、どのコンテキストなのかを識別します。

jndiPropertiesタグとpropertyタグで、InitialContextにコンストラクタに渡す
Propertiesを設定します。

dataSourceタグのjndiName属性は、connectionpool-config.xmlのconnectionPool/jndiNameに 記述したものを指定します。

userTransactionタグのjndiName属性は、java:comp/UserTransaction固定です。

<seasarContext>
    <context name="seasar">
        <jndiProperties>
            <property name="java.naming.factory.initial" value="org.seasar.naming.NamingContextFactory"/>
            <property name="java.naming.factory.url.pkgs" value="org.seasar.naming"/>
            <property name="java.naming.provider.url" value="localhost:1108"/>
        </jndiProperties>
        <dataSource jndiName="jdbc/hsqldb"/>
        <userTransaction jndiName="java:comp/UserTransaction"/>
    </context>
</seasarContext>

SeasarContextの取得

SeasarContextは、SeasarContext#getInstance(String contextName)で取得します。
contextNameは、seasar-context.xmlのcontextタグのname属性に指定した値です。
引数なしでgetInstance()を呼び出すと、seasar-context.xmlの最初のcontextタグの内容が返されます。
最もよく使うcontextを最初に記述し、通常は引数なしのgetInstance()を使い、
他のcontextが必要になったら、contextNameを引数にしてgetInstance()を呼び出すといいでしょう。

Connectionの取得

Connectionは、SeasarContext#getConnection()で取得できます。

トランザクション

トランザクションは、SeasarContext#begin()で開始し、
SeasarContext#commit()でコミット、
SeasarContext#rollback()でロールバックできます。

examples.org.seasar.SeasarContextExample

package examples.org.seasar;

import java.sql.Connection;
import java.sql.Statement;

import org.seasar.util.SeasarContext;

public class SeasarContextExample {

    private static final String SQL =
        "UPDATE emp SET ename='SCOTT' WHERE empno = 7788";
	
    public static void main(String[] args) {
        try {
            SeasarContext ctx = SeasarContext.getInstance();
            Connection con = null;
            Statement stmt = null;
            ctx.begin();
            try {
                con = ctx.getConnection();
                stmt = con.createStatement();
                int rows = stmt.executeUpdate(SQL);
                System.out.println("updated " + rows + " row(s)");
                ctx.commit();
            } catch (Exception ex) {
                ctx.rollback();
                ex.printStackTrace();
            } finally {
                if (stmt != null) {
                    stmt.close();
                }
                if (con != null) {
                    con.close();
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

他のアプリケーションサーバの利用

実は、seasar-context.xmlを書き換えれば、他のアプリケーションサーバにも接続できます。
最初はSeasar上で開発して、後からWebLogicやWebSphereに移行したとしても、
SeasarContext経由でアプリケーションサーバを利用していれば、
コードの書き換えは不要です。
WebLogicやWebSphereに接続するためのseasar-context.xmlは以下のようになります。

<seasarContext>
    <context name="WebLogic">
        <jndiProperties>
            <property name="java.naming.factory.initial" value="weblogic.jndi.WLInitialContextFactory"/>
            <property name="java.naming.provider.url" value="t3://localhost:7001"/>
        </jndiProperties>
        <dataSource jndiName="jdbc/oracle"/>
        <userTransaction jndiName="javax.transaction.UserTransaction"/>
    </context>
    
    <context name="WebSphere">
        <jndiProperties>
            <property name="java.naming.factory.initial" value="com.ibm.websphere.naming.WsnInitialContextFactory"/>
            <property name="java.naming.provider.url" value="iiop://localhost:900"/>
        </jndiProperties>
        <dataSource jndiName="jdbc/oracle"/>
        <userTransaction jndiName="jta/usertransaction"/>
    </context>
</seasarContext>

メッセージ

メッセージ体系

Seasarのメッセージは、8桁のメッセージコードでPropertiesファイルに格納されています。
最初の1桁がメッセージの種別で、E:エラー、W:ワーニング、I:インフォメーションで構成されます。
次の3桁がシステム名でSeasarの場合は、SSRになります。
最後の4桁は、連番です。

message-config.properties

システム名によって、Propertiesファイルの場所が決まっていて、
$SEASAR_HOME/classes/message-config.propertiesで管理されています。
フォーマットは、システム名=プロパティファイルのパスとなっていて、
Seasarの場合、SSR=org.seasar.message.SSRMessagesと記述されているので、
$SEASAR_HOME/classes/org/seasar/message/SSRMessages.propertiesに
実際のメッセージが格納されていることになります。

メッセージファイルのフォーマット

メッセージファイルのフォーマットは、

メッセージコード=メッセージ
メッセージコード=メッセージ
...
のようになっています。
メッセージの部分は、java.text.MessageFormatの形式でフォーマットされますが、
実際のメッセージの組み立ては、org.seasar.message.MessageFormatter#getMessage(String messageCode, Object[] args)
で行ってます。
例えば、MessageFormatter.getMessage("ESSR0007", new Object[]{"aaa"})を呼び出した場合、
SSRMessages.propertiesには、ESSR0007={0} should not be nullと記述されているので、
"aaa should not be null"というメッセージが組み立てられることになります。

多言語対応

ロケールに応じたPropertiesファイルを用意することで、多言語に対応することもできます。
例えば、SSRMessages_ja.propertiesを作成し、
ESSR0007={0}には、値が設定されていなければいけません
のようにメッセージを登録すると、ロケールがjaの場合には、
"aaaには、値が設定されていなければいけません"というメッセージになります。
メッセージファイルの日本語の部分は、実際は、native2asciiコマンドで、UNICODEにする必要があります。

native2ascii SSRMessages.properties > SSRMessages_ja.properties
のようなオペレーションになります。

他システムでの利用

このメッセージの仕組みは、Seasar以外のシステムでも使えます。
jp.co.aaaというパッケージがあった場合、AAAをシステム名とし、
message-config.propertiesにAAA=jp.co.aaa.message.AAAMessagesのエントリを追加すると、
jp.co.aaa.message.AAAMessagesのメッセージファイルでXAAA9999のメッセージを
管理できるようになります。

メッセージ管理のメリット

メッセージを一元管理することで、メッセージの文言が変わったときも
プロパティを変えるだけで済むようになり、多言語対応も簡単になります。

SeasarException

Seasarの例外は、SeasarExceptionで統一的に扱われています。SeasarExceptionのコンストラクタは、

SeasarException(String messageCode, Object[] args)
SeasarException(String messageCode, Object[] args, Throwable cause)
となっており、メッセージの仕組みを利用しています。
例外のクラスではなく、メッセージコードで例外の種別を切り分けることで、
例外クラスの増殖を抑え、メッセージ管理のメリットも受けることができます。
同様の機能を持ったクラスとしてSeasarRuntimeExceptionがあります。

Seasarサービス

Seasarは、プラグイン型のアーキテクチャを採用していて、JNDI、JTA、コネクションプールなどの
サービスも実は、Seasarサービスとして組み込まれています。
$SEASAR_HOME/classes/seasar-config.xmlをみると現在組み込まれているサービスを
確認できます。

seasar-config.xml

<seasar>
  <services>
    <service className="org.seasar.system.JMXService"/>
    <service className="org.seasar.system.RMIAdaptorService">
      <properties>
        <property name="port" value="1108"/>
      </properties>
    </service>
    <service className="org.seasar.naming.NamingService"/>
    <service className="org.seasar.transaction.TransactionService"/>
    <service className="org.seasar.sql.ConnectionPoolService"/>
    <service className="org.seasar.mbean.MBeanService"/>
  </services>
</seasar>

JMXServiceは、Seasarの中核となるサービスで、JMXの機能を提供しています。

RMIAdaptorServiceは、JMXServiceをRMI経由で利用するためのアダプタ機能を提供しています。
この例のように、プロパティに値を設定することもできます。
RMIAdaptorServiceには、getPort(),setPort()のメソッドが定義されているため、
Seasarは、portをプロパティとして認識できます。プロパティがプリミティブ型の場合、
文字列からプリミティブ型への変換は自動的に行われます。
SeasarContextの説明で java.naming.provider.urlにlocalhost:1108を
指定していたかと思いますが、この1108はRMIAdaptorServiceのポート番号をあわせる必要があります。

NamingServiceは、JNDIの機能を提供しています。

TransactionServiceは、JTA(トランザクション)の機能を提供しています。

ConnectionPoolServiceは、コネクションプールの機能を提供しています。

MBeanServiceは、MBeanをサービスとしてSeasarに組み込む機能を提供しています。
MBeanについては、MBeanサービスで説明します。

Seasarサービスを提供するためには、org.seasar.system.Lifecycleインターフェースを
実装したクラスをseasar-config.xmlのclassNameに記述するだけなので簡単です。
上記の例のように、サービスのインスタンスのプロパティを設定することもできます。

org.seasar.system.Lifecycle

import org.seasar.util.SeasarException;

public interface Lifecycle {

    public void start() throws SeasarException;

    public void stop() throws SeasarException;
}

Seasarの起動時に、seasar-config.xmlに登録されている順番で、各サービスの
start()が呼び出されます。
Seasarの終了時には、seasar-config.xmlに登録されている逆の順番で、各サービスの
stop()が呼び出されます。

MBeanサービス

MBeanサービスとは、JMXの規約を満たしたクラスをサービスとして登録し、
JNDI経由でそのサービスを取得して、メソッドを呼び出せるようにする機能です。
Seasarがクライアントと同じVMにいる場合は、ローカル呼び出しになり
Seasarがクライアントと異なるVMにいる場合は、リモート呼び出しになります。
呼び出しの制御は、自動的に行われるため、サービスを利用する側は、
リモート呼び出しなのか、ローカル呼び出しなのか意識する必要はありません。

MBeanには幾つか種類があるのですが、今のところ、Seasarがサポートしているのは、
Standard MBeanと呼ばれているものです。
Standard MBeanの作成は簡単です。
HelloクラスをMBeanにしたい場合は、HelloMBeanというインターフェースを作成し、
そのインターフェースにMBeanとして実行したいメソッドを記述し、
Helloクラスで、HelloMBeanをimplementsするだけです。

package examples.org.seasar.mbean;

public interface HelloMBean {

    public String greeting();
}
package examples.org.seasar.mbean;

public class Hello implements HelloMBean {

    public String greeting() {
        return "Hello World";
    }
}

これだけで、MBeanは完成。今度は、$SEASAR_HOME/classes/mbean-config.xmlに登録します。
mbeanタグのclassName属性に、examples.org.seasar.mbean.Helloを指定してください。
Seasarは、停止させておきます。

<mbeanService>
  <mbeans>
    <mbean className="examples.org.seasar.mbean.Hello"/>
  </mbeans>
</mbeanService>

Hello.class,HelloMBean.classを$SEASAR_HOME/classes/examples/org/seasar/mbeanにコピーします。
jarファイルにして、$SEASAR_HOME/libに置くこともできます。
Seasarを立ち上げれば、MBeanとして認識されます。

mbeanタグで、name属性を省略すると、クラス名から、パッケージ名をのぞいた部分が
MBeanの名前になります。先ほどの例だと、名前はHelloです。
<mbean className="foo.Hello" name="Hello2"/>
と定義すると、名前は、Hello2になります。

MBeanServiceによって、JNDI空間にmbean/名前でデプロイされているので、
Helloという名のMBeanにアクセスするには、次のようにします。

SeasarContext ctx = SeasarContext.getInstance(Seasarコンテキスト名);
HelloMBean hello = (HelloMBean) ctx.lookup("mbean/Hello");
hello.greeting();

また、デプロイ時に、MBeanのプロパティを設定することもできます。

public class Hello implements HelloMBean {

    private String _message;
    
    public String greeting() {
        return "Hello " + _message;
    }

    public String getMessage() {
        return _message;
    }
    
    public void setMessage(String message) {
        _message = message;
    }
}
のように、messageプロパティが定義されている場合、
<mbean className="examples.org.seasar.mbean.Hello">
  <properties>
    <property name="message" value="Hoge"/>
  </properties>
</mbean>
のようにしてプロパティを設定できます。

初期処理や終了処理が必要な場合は、org.seasar.system.Lifecycleを実装してください。
Seasarの起動時にstart()メソッドが呼び出され、Seasarの終了時にstop()メソッドが呼び出されます。

JMX Notification

JMX Notificationでは、登場人物が3人います。
メッセージを通知する人(A)、メッセージを通知される人(B)、メッセージを送る人(C)です。
BはAに対してメッセージを受け取りたいという登録をあらかじめしておきます。
CがAに対してメッセージを送ると、Aは自分に登録されている人(B)に対し、
メッセージを送るわけです。
メッセージは、シリアライズ可能なものなら何でもOKです。
メッセージを通知する人は、Seasarに汎用的なクラスが用意されているので
それをそのまま使うことができます。
個別に開発する必要はありません。

org.seasar.mbean.Notify

package org.seasar.mbean;

import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;


public class Notify extends NotificationBroadcasterSupport
    implements NotifyMBean {

    public static final String TYPE = "Notify";
    private long _seq = 0;

    public void send(Object data) {
        Notification notification = new Notification(TYPE, this, ++_seq);
        notification.setUserData(data);
        sendNotification(notification);
    }
}

メッセージを受け取りたい人は、org.seasar.mbean.NotifyListenerSupportを継承して、
receive(Notification)を実装します。
送ったデータは、Notification.getUserData()で取り出すことができます。

examples.org.seasar.mbean.HelloNotifyListener

package examples.org.seasar.mbean;

import javax.management.Notification;

import org.seasar.mbean.NotifyListenerSupport;


public class HelloNotifyListener extends NotifyListenerSupport {

    public void receive(Notification notification) {
        System.out.println(notification.getUserData());
    }
}

メッセージを送りたい人は、JNDIからメッセージを通知する人を取り出し、
send(Object)を呼び出します。
メッセージを通知する人のJNDI名は、mbean-config.xmlに記述した名前とあわせます。
mbean/mbean-config.xmlに記述した名前になります。

examples.org.seasar.mbean.HelloNotifySender

package examples.org.seasar.mbean;

import org.seasar.mbean.NotifyMBean;
import org.seasar.util.SeasarContext;

public class HelloNotifySender {
	
    public static void main(String[] args) {
        try {
            SeasarContext ctx = SeasarContext.getInstance();
            NotifyMBean notify = (NotifyMBean) ctx.lookup("mbean/HelloNotify");
            notify.send("Hello World");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

上記のクラスを使うためのmbean-config.xmlの内容は次のようになります。
$SEASAR_HOME/classes/mbean-config.xmlのコメントアウトされている部分を
解除してください。
remoteNotificationListenersのコメントアウトはそのままにしておきます。

通知する人

<mbeans>
    <mbean className="org.seasar.mbean.Notify" name="HelloNotify"/>
</mbeans>

通知される人

<notificationListeners>
    <notificationListener className="examples.org.seasar.mbean.HelloNotifyListener" targetName="HelloNotify"/>
</notificationListeners>

mbean-config.xmlを書き換えて、Seasarを再起動すると準備完了です
HelloNotifySenderを実行するとSeasarのコンソールにHello Worldと表示されるはずです。
EclipseからSeasarを起動している場合は、デバッグパースペクティブのSeasarをクリックすると
Seasarのコンソールを見ることができます。

JMX RMI Notification

別のサーバで稼動しているSeasarからメッセージを受け取ることもできます。
リモートのサーバに接続するための設定を
seasar-context.xmlに記述し
mbean-config.xmlのremoteNotificationListenerタグのseasarContextName属性に
リモートSeasar用のコンテキスト名を指定します。

<remoteNotificationListener className="examples.org.seasar.mbean.HelloNotifyListener"
    targetName="HelloNotify" seasarContextName="remote"/>