minimize

事業拡大のため、新しい仲間を募集しています。
→詳しくはこちら

ここでは、僕がJBossを使っていて気が付いた点などを書こうと思います。
使用したバージョンは JBoss 3.2.3 です。
新しく書いたものが上になるように並べてあります。

JBossで認証を使ってみる

JBossの認証は基本的にTomcatと同じですが、login-config.xml というファイルを使用すれば
より簡単に認証方法をカスタマイズすることができます。
今回は、データベースを用いた認証を使ってみます。

web.xmlへの記述

まず、認証を必要するリソースを決定します。
とりあえず今回は、簡略化のために全てのリソースを認証の対象とします。
web.xml に以下の記述を追加します。

<security-constraint>
  <web-resource-collection>
    <web-resource-name>Protected Area</web-resource-name>
    <url-pattern>/</url-pattern>
  </web-resource-collection>
  <auth-constraint>
    <role-name>test_role</role-name>
  </auth-constraint>
</security-constraint>
<login-config>
  <auth-method>BASIC</auth-method>
</login-config>

以上により、このアプリケーションが保持する全てのリソースにアクセスする際には認証が必要となります。
認証方法はBASIC認証を使用します。
その際、test_role というロールを持つユーザのみアクセスできるようにします。

jboss-web.xmlへの記述

以下の記述を追加します。

<security-domain>java:/jaas/MyAppRealm</security-domain>

これにより、認証に使用するセキュリティ・マネージャのJNDI名を定義します。
このセキュリティ・マネージャは次項で定義することになります。

login-config.xmlへの記述

$JBOSS_DIST/server/default/conf/login-config.xml に以下の記述を追加します。

<application-policy name = "MyAppRealm">
  <authentication>
    <login-module code = "org.jboss.security.auth.spi.DatabaseServerLoginModule"
      flag = "required">
      <module-option name = "dsJndiName">java:/DefaultDS</module-option>
      <module-option name="principalsQuery">
        select passwd from Users where username=?
      </module-option>
      <module-option name="rolesQuery">
        select userRoles from UserRoles where username=?
      </module-option>
    </login-module>
  </authentication>
</application-policy>

認証情報の取得に、JNDI名「java:/DefaultDS」により定義されたデータベースを使用します。
このファイルを修正したらJBossを再起動する必要があります。
sarファイルというものを利用すれば、アプリケーション毎に login-config.xml を用意できるようですが
現段階では未調査なので説明は省きます。

さらに、データベースに以下のテーブルを追加します。

CREATE TABLE Users(username VARCHAR(64) PRIMARY KEY, passwd VARCHAR(64))
CREATE TABLE UserRoles(username VARCHAR(64), userRoles VARCHAR(32))

Usersテーブルにはユーザ名とパスワードの対応を、
UserRolesテーブルにはユーザ名とロール名の対応を定義します。
例えば、以下のようにレコードを追加します。

INSERT INTO Users VALUES ('test_user', 'password')
INSERT INTO UserRoles VALUES ('test_user', 'test_role')

もちろん、これらを一つのテーブルにまとめる事も可能です。
その際は login-config.xml に記述するSQL文を修正して下さい。

画面からの確認

あとはブラウザからこのアプリケーションにアクセスするだけです。
認証ダイアログが開いたら、test_user / password と入力して認証が成功する事を確認しましょう。
一度認証が成功したら、ブラウザを閉じるまで認証ダイアログは表示されなくなります。

jspのデバッグ

jspについて」で説明したように、JBossはjspをコンパイルしたクラスを
特定の場所に格納します。
この処理を行っているのはどのモジュールでしょうか。
deployディレクトリにあるjbossweb-tomcat.sarがそれです。
このサブディレクトリの中に jasper-compiler.jar というjarファイルがあります。

これはTomcatで使用しているものと全く同じものです。
ですから、このjarファイルを修正すればjspのデバッグが可能になるはずです。
Eclipse/WSAD にあるパッチを改良して作ってみました。
jasper-compiler-custom.jar がそれです。
前述した jasper-compiler.jar を削除してこのファイルに置き換えてから
JBossを再起動して下さい。

ソースから作成したい場合は、src.tbz を展開して
Tomcat4のソースをダウンロードしたフォルダに上書きして再コンパイルして下さい。

そして、何からのjspを表示させてみます。
ワークディレクトリ/コンテキスト名/org/apache/jsp/ 以下にclassファイルが展開されていれば
パッチ適用は成功です。
以下、デバッグの手順です。

ワークディレクトリを基準ディレクトリにしてEclipseのJavaプロジェクトを新規作成します。
Javaソースディレクトリを プロジェクト名/コンテキスト名 にして、
クラスファイル出力フォルダも同じ場所にして下さい。
そして、以下のjarファイルをライブラリに追加します。

javax.servlet.jar

$JBOSS_DIST/server/default/lib 以下にあります。

jasper-runtime.jar

$JBOSS_DIST/server/default/deploy/jbossweb-tomcat.sar 以下にあります。
に、このjspをコンパイルするのに必要なjar等があればそれも追加します。
ば struts.jar や、Tomcatプロジェクト等。
でダイアログを完了させ、エラーが出ていない事を確認して下さい。
は山ほど出ていると思いますが、とりあえず気にせず先に進みます。

「プログラマたるもの警告が出たままでは先に進むわけにはいかん」という方(笑)は
プロジェクトのプロパティからJavaコンパイラを選択して
Unused imports, Usage of deprecated API を「Ignore」にして下さい。
これで警告は全て消えるはずです。

ここまで来れば、あとは「デバッグについて」で説明した手順で
jspのデバッグが可能になります。
ホント、これ出来ると出来ないのじゃ開発効率が全然違いますよ。
是非覚えておきましょう。わからない事があれば 掲示板 までお願いします。

MySQLでトランザクション機能をサポートする

MySQLにはトランザクションをサポートしたInnoDBというタイプのテーブルがあります。
これを使うには、テーブル作成時にそれを明記しておく必要があります。
何の指定も無しにテーブルを作成した場合、そのテーブルは自動的にMyISAMテーブルになります。
このテーブルはトランザクションをサポートしていません。

デフォルトでCMPが自動生成するテーブルをInnoDBテーブルにしたい場合は、
standardjbosscmp-jdbc.xml に以下の要素を記述します。

<post-table-create>
  <sql-statement>
    ALTER TABLE %%t TYPE=INNODB
  </sql-statement>
</post-table-create>

追加する場所は、/jbosscmp-jdbc/default/remove-table の直後にする必要があります。
xmlでは記述する順番を間違えるだけで構文エラーになってしまいます。

なお、MySQLはデフォルトでInnoDBテーブルそのものが使用不可能になっています。
この場合、my.cnf(Windowsの場合は my.ini)ファイルに以下の一文を追加します。
詳しい設定内容についてはMySQLのマニュアルを読んで下さい。

innodb_data_file_path = ibdata1:10M:autoextend

追加したらMySQLを再起動する必要があります。

jbosscmp-jdbc.xmlについて

各EJBコンポーネントには、jbosscmp-jdbc.xml という設定ファイルがあります。
一方、JBoss全体の設定を記述するには、confディレクトリにある standardjbosscmp-jdbc.xml に記述します。
両者は同じdtd(jbosscmp-jdbc_3_2.dtd)を使用しているように見えますが、実は違います(JBoss3.2.3で確認)。
確かに standardjbosscmp-jdbc.xmljbosscmp-jdbc_3_2.dtd の構文を使えますが
jbosscmp-jdbc.xml で使用できるのは jbosscmp-jdbc_3_0.dtd の構文だけです。

つまり、jbosscmp-jdbc_3_2.dtd で新たに定義された要素は
EJBコンポーネント毎に設定を変更することは出来ず
JBoss全体の設定しか記述できないことになります。

この中で特に重要と思われるものが、
post-table-createunknown-pk です。
前者はデータベース特有のテーブル作成SQL文の定義、
後者はプライマリキーの自動生成に関する項目です。

使用するデータベースを一つに限定すれば、
前者を standardjbosscmp-jdbc.xml に記述することは特に問題にはなりません。
しかし、unknown-pkstandardjbosscmp-jdbc.xml に記述することは
全てのテーブルでプライマリキーの自動生成を行う事が強制されてしまいます(←認識不足かもしれません)。
これは実装上かなりの問題を抱える可能性があります。

不十分なチェックに注意

JBossがデプロイ時に出すエラーメッセージは全般的に判りにくいものが多いです。
が、それ以上に注意したいのが「デプロイ時に発生しないエラー」です。
ejb-jar.xml 等には dtd が定義されているのですが、このdtdに違反していても
エラーが発生しなかったりします。
僕はこの前、

<relationship-role-source>
  <ejb-name>OrganizationEJB</ejb-name>
</relationship-role-source>
<cmr-field>
  <cmr-field-name>memberGangsters</cmr-field-name>
  <cmr-field-type>java.util.Set</cmr-field-type>
</cmr-field>

と書くべきところを

<relationship-role-source>
  <ejb-name>OrganizationEJB</ejb-name>
  <cmr-field>
    <cmr-field-name>memberGangsters</cmr-field-name>
    <cmr-field-type>java.util.Set</cmr-field-type>
  </cmr-field>
</relationship-role-source>

と書いてしまった事に気付かずにデプロイしてしまい、
実行時の原因不明エラーにかなり悩みました。
本来、relationship-role-source の子に cmr-field は格納できないはずなのですが
チェックがされていないようで無視されてそのままデプロイできてしまったのです。

JNDI一覧を表示する

EJBの参照に使われるJNDI。
おそらくJBoss開発を始めたばかりの頃は NameNotFoundException が多発してる方も多いと思います。
そんなとき役に立つのがJNDI一覧表です。

ブラウザから http://localhost:8080/jmx-console/ にアクセスして、
service=JNDIView というリンクを探しましょう。
このページにある list という文字の右側にある Invoke ボタンを押すと
JNDIの一覧が表示されます。

JNDIには Namespace という概念があります。
これは、Javaのパッケージングと同じ仕組みでJNDIをグループ分けするものです。
通常よく使うものを挙げます。

Global

一覧表の Global JNDI Namespace 配下に表示されています。
jboss.xml に記述する jndi-name 等はこのNamespaceに属します。

プログラム上からの呼び出し例を挙げます。
以下のjboss.xmlがあるとき、

<jboss>
  <enterprise-beans>
    <session>
      <ejb-name>DiaryManagerBean</ejb-name>
      <jndi-name>limy/diaryManager</jndi-name>
    </session>
  </enterprise-beans>
</jboss>

一覧表には

Global JNDI Namespace
  +- limy (class: org.jnp.interfaces.NamingContext)
  |   +- diaryManager (proxy: $Proxy264 ...)

のように表示されているはずです。
このとき、プログラム上から

context.lookup("limy/diaryManager")

とすれば、目的のDiaryManagerBeanを取得することができます。
これはリモートマシンからアクセスする場合も同じです。

java

一覧表の java: Namespace 配下に表示されています。
データソース等はこのNamespaceに属します。

java: Namespace
  +- DefaultDS (class: org.jboss.resource.adapter.jdbc.WrapperDataSource)

このとき、プログラム上から

context.lookup("java:/DefaultDS")

とすれば、目的のデータソースを取得することができます。
つまり「Namespace名 + / + JNDI名」によりアクセスが可能になります。

java:comp

ejb-jar.xml に記述したものはこのNamespaceに属します。
以下のejb-jar.xmlがあるとき、

<session>
  <ejb-name>DiaryBean</ejb-name>
  <env-entry>
    <env-entry-name>temp.dir</env-entry-name>
    <env-entry-type>java.lang.String</env-entry-type>
    <env-entry-value>/tmp</env-entry-value>
  </env-entry>
<session>

一覧表には

java:comp namespace of the DiaryBean bean:
  +- env (class: org.jnp.interfaces.NamingContext)
  |   +- temp.dir (class: java.lang.String)

のように表示されているはずです。
このとき、プログラム上から

context.lookup("java:comp/env/temp.dir")

とすれば、目的の文字列を取得することができます。
先程の場合と同様に、「Namespace名 + / + JNDI名」によりアクセスが可能になります。


JNDIを理解することは、JBoss開発の第一歩です。
NameNotFoundException にめげずに頑張りましょう。

jspについて

jsp内でバグが発生した場合、jspから生成されたJavaソースを参照したい場面が出てくるはずです。
これらはworkディレクトリを呼ばれる場所に保管されています。

Tomcat等ではworkDirを記述することでコンテキスト毎にworkディレクトリを変更できますが、
JBossでもそのような事が可能なのかは不明です。
とりあえずデフォルトではJBossディレクトリ配下にworkディレクトリが設定されていますので
そこを見ましょう。
場所はJBossのバージョンによって異なります。

JBoss3.2.1

$JBOSS_DIST/server/default/tmp/deploy/server/default/deploy/work/MainEngine/localhost です。
メチャメチャ深いですな(笑)

JBoss3.2.3

$JBOSS_DIST/server/default/work/MainEngine/localhost です。
まだマシになりました(笑)
ossのログには、エラーの起こったソースファイルと行番号が書き出されるので
スを見ればある程度エラーの発生箇所を掴むことができます。
mplateを使っていたりすると嫌になる位大量のログが出て少々ヘコみますが(笑)。

jspのデバッグ(解決前)

しかし、jspのデバッグは恐らくうまくいきません。
これはJBossにバンドルされているTomcat4固有の問題で、よく話題にもなりますね。
jspのデバッグ ← これを解決するパッチを作ったので参考にして下さい。

再デプロイ時

war(ear)ファイルを再デプロイすると、そのコンテンツのworkディレクトリ内が削除されてしまいます。
正確にはwarファイルをアンデプロイした段階でこの処理が実行されます。

この動作を変更するには、
$JBOSS_DIST/server/default/deploy/jbossweb-tomcat.sar/META-INF/jboss-service.xml
に以下の要素を追加します。

<attribute name="DeleteWorkDirs">false</attribute>

これで、workディレクトリの中身は削除されません。

しかし、JBossを再起動するとこの設定に関係無くworkディレクトリは削除されてしまいます。
そこで、JBossをシャットダウンする前にworkディレクトリからclassファイルを別の場所に保管しておいて
再起動後にworkディレクトリにコピーするという手法を使ってみます。
こうすれば、JBoss再起動後にもworkディレクトリが削除されないので
その分jspコンパイル時間を短縮できます。

ただし、コピーするのはJBoss再起動後も内容が変更されないjspだけにして下さい。
そうしないと、再起動後に修正したjspがコンパイルされない危険性があります。

war(ear)ファイルをdeployディレクトリに展開して配置すれば
こんな面倒な事をする必要は無いのですが、
この方法だと展開したディレクトリ内のJava(class)ファイルを修正したときに
そのクラスがリロードされません。
Tomcatでいうところの reloadable が有効でないのです。
設定により変えられるのかも知れませんが、わからないので知っている方がいたら教えて下さい。

デバッグについて

JBossのデバッグは比較的簡単です。
Javaで動くアプリケーションのほとんどが採用しているJDWPというプロトコルを使います。
ここ とか ここ とかを参考にしてみて下さい。
Tomcat等でも同様の方法でデバッグを行うので、覚えておいて損は無いと思います。

Eclipseを使ったデバッグ方法の手順を簡単に説明すると…

JBossをJDWPオプション付きで起動させる

run.batの中から JPDA を探して、その文からremキーワードを削除します。
jdwp.bat等の名前を付け別ファイルに保存しておくのが無難でしょう。
この段階でJBossコンソールは止まったように見えますが心配せず次に行って下さい。

Eclipseからリモートデバッグを起動させる

これでデバッガがアタッチされ、JBossコンソールが動き始めます。

通常のデバッグと同じようにブレークポイントを設定し、テストプログラム等を起動する

テストプログラムの起動はデバッグ実行ではなく通常実行でOKです
ークポイント到達時にデバッガに制御が移れば無事デバッグは成功です。
、ちょっとした注意点を二つほど。

Cygwinは使わない

まぁ普通の人はそんな事しないでしょうが、WindowsのCygwin上からrun.sh等を使ってJbossを起動すると
デバッグがうまくいきません。素直にコマンドプロンプトからrun.bat等を使いましょう。

Eclipse3.0M6

まだ正式版では無いので仕方無い事かもしれませんが、Eclipse3.0M6を使ったデバッグがうまくいきません。
具体的には、デバッグポイントに到達した段階でプログラムが停止してしまいます。

この状況を打破するには、デバッグポイントを含むJavaクラスのclassファイルを再生成させる必要があります。
例えば、ソースファイルに適当な文字を入力してからそれを消してSaveする等。
理由は判りませんが、こうすると何故か停止していたプログラムが再開され
ブレークポイントに制御が移ります。

こんな面倒くさい事をしたくない人は、正式版が出るまでEclipse2を使いましょう。

プライマリキー自動生成機能

EJB2.0には、CMPのテーブルにプライマリキーを自動生成する機能があります。
以下、デフォルトで用意されている「Hypersonic SQL」データベースを使って説明します。

まず、CMPエンティティBeanを用意します。
そして、ejb-jar.xmlprim-key-classjava.lang.Object と記述します。
そして同階層にある primkey-field 要素を削除します。
これで、このエンティティBeanが生成するテーブルにプライマリキーが自動的に生成されます。

MySQLでAUTO INCREMENT属性を使う

EJB2.0では、CMPのテーブルにAUTO_INCREMENT属性を付けることが出来ます。
今のところ、MySQLにおいてプライマリキー自動生成機能での利用が確認できています。

standardjbosscmp-jdbc.xmlを修正する

EJBコンポーネント毎の設定ファイルに jbosscmp-jdbc.xml があります。
これと同じdtdを利用するものに、JBoss共通設定ファイル standardjbosscmp-jdbc.xml があります。
どちらに記述しても良さそうですが、何故か jbosscmp-jdbc.xml に記述してもエラーになってしまいます。

AUTO_INCREMENT属性が使えるのは jbosscmp-jdbc_3_2.dtd からですが、
どうやらJBoss3.2.3で利用される jbosscmp-jdbc.xml は
jbosscmp-jdbc_3_0.dtd の構文しか使えないようです。
jbosscmp-jdbc.xmlについて」を参照して下さい。

記述できるのは、standardjbosscmp-jdbc.xml の defaults 属性以下になります。
つまり、今のところデータベース毎に設定を変えることは出来ません。

なお、ここから先の説明は既にJBossでMySQLを利用しているものと仮定します。
JBossをインストールしただけでは、MySQLを利用できません。
とりあえず簡単に説明すると、
$JBOSS_JIST/server/default/lib/ ディレクトリに mysql-connector-XXX.jar をコピーして
$JBOSS_JIST/server/default/deploy/ ディレクトリに mysql-ds.xml ファイルを新規作成して下さい。
以下がその内容例です。

<datasources>
  <local-tx-datasource>
    <jndi-name>DefaultDS</jndi-name>
    <connection-url>jdbc:mysql://localhost/jboss</connection-url>
    <driver-class>org.gjt.mm.mysql.Driver</driver-class>
    <user-name>xxx</user-name>
    <password>yyy</password>
  </local-tx-datasource>
</datasources>

AUTO_INCREMENT属性を使う場合、
libディレクトリにコピーするJDBCドライバは MySQL Connector/J 3 以上を使用する必要があります。
では、先に進みます。

まず、defaults/datasource-mapping を mySQL に変更します。
そして、既存の unknown-pk , entity-command 要素を以下で置き換えます。

<unknown-pk>
  <unknown-pk-class>java.lang.Integer</unknown-pk-class>
  <field-name>id</field-name>
  <column-name>id</column-name>
  <jdbc-type>INTEGER</jdbc-type>
  <sql-type>INT(11)</sql-type>
  <auto-increment/>
</unknown-pk>

<entity-command name="mysql-get-generated-keys"/>

こうすれば、プライマリキー自動生成機能 で説明した手順で
テーブルにプライマリキーを自動生成し、そのカラムにAUTO_INCREMENT属性を付けることが出来ます。
生成されるプライマリキーはInteger型になります。

よく、プライマリキーを一意制約の代わりに使おうとする人がいますが
そういう人はこれを機に考えを改めましょう。
EJBにおいてプライマリキーは行を識別するだけに使われるべきです。

クラスローダについて

JBossではdeployディレクトリにjarファイルを置くことでデプロイが行えます。
大きなコンテンツを作成する場合など、複数のjarファイルに分割したい事はあると思います。
このとき、あるjarファイルから他のjarファイル内のクラスを利用することはできるのでしょうか。

試してみた結果、何の記述をしなくても利用することが可能でした。
これは、deployディレクトリにある全てのjarファイルに含まれるクラス群が
一つのクラスローダ内で扱われていることを意味します。

Tomcat等では、クラスローダはアプリケーション毎に異なるので
共通のクラスファイルはlibディレクトリに格納する必要があるのですが
JBossではその必要はありません。

JBoss起動時の弊害

ただし、例えば common.jar に共通クラスを格納しておいて
sub.jar からそれを参照する場合、sub.jar より先に common.jar をデプロイしている必要があります。

そして、JBoss起動時にはこれらのデプロイ順序が予測できません。
つまりJBoss起動時に common.jar と sub.jar という二つのファイルが存在していた場合、
これらのデプロイは成功するときとしないときがあります。
先に common.jar をデプロイしてくれれば上手くいくのですが、
これは必ずしも保証されず先に sub.jar がデプロイされるときもあります。

この場合、sub.jar をデプロイしようとした段階でエラーが発生してしまいます。
そして common.jar のデプロイは成功します。
結果、sub.jar を再デプロイ(一旦deployフォルダから削除してコピーし直す)する必要が出てくるのです。
jarファイルのデプロイ順序を制御する方法はあるのかもしれませんが、今のところわかりません。
よって、確実にデプロイを成功させる為にはJBoss起動前に全てのjarファイルを削除しておいて
JBoss起動後に順序を決めてデプロイするのがいいと思います。

Struts利用時の注意

JBossでStrutsを使うとき、jsp等のWeb系ファイルと
Action等のJavaファイルを別々のファイルに分けることは可能でしょうか。
結果からいうと可能です。

まず、web.xml を格納するear(またはwar)ファイルには
struts-config.xml を始めとしたstrutsの設定ファイルを一緒に格納する必要があります。
そしてjspやhtml、イメージファイルも同一jarファイルに格納しなければなりません。
つまり、Java(class)ファイルを除く全てのファイル群は
一つのearファイルにまとめて格納する必要があります。
複数のearファイルから同一のコンテキストを作成しようとしてもエラーになるはずです。

Javaクラス群を別のjarファイルに格納することは可能です。
しかし、この場合に注意が必要です。
Actionクラスを修正した場合、それを含むjarファイルを再デプロイするだけでは
その修正が反映されません。
これはおそらくstrutsが内部的にActionクラスをキャッシュしているからだと思われます。
修正を反映するには、web.xmlが格納されたjarファイルを再デプロイする必要があります。
deployフォルダから削除してコピーし直せばOKです。