IBM WebSphere MQ with Sun Generic Resource Adapter for JMS on GlassFish
Sun Generic Resource Adapter for JMSと IBM WebSphere MQ の組み合わせをGlassFish上で動かしてみます。
Sun Generic Resource Adapter for JMSのデプロイ
管理コンソールのアプリケーション > コネクタモジュールから配備します。ファイルを選択して「了解」ボタンをクリックするとリソースアダプタのプロパティー編集画面へ遷移します。ここでorg.seasar.jms.core.deploy.impl.WMQResourceAdapterDeployer#setupProperties()にてセットされている通りにプロパティをセットします。
コネクタ接続プールの作成
管理コンソールのリソース > コネクタ > コネクタ接続プールから作成します。リソースアダプタには先ほどデプロイしたものを指定します。次の画面で追加プロパティを設定しますが、ここにはorg.seasar.jms.core.deploy.impl.JMSManagedConnectionFactoryDeployer#setProperty()と同じものを設定します(名前:ConnectionFactoryProperties,値:QueueManager=Xxx,…)。
コネクタリソースの作成
管理コンソールのリソース > コネクタ > コネクタリソースから作成します。今回JNDI名は/jms/connectionとしました。
以上でGlassFish側の設定は終了です。次にS2JMSの設定です。
S2JMS側ではリソースアダプタの設定が不要になります。コネクションファクトリをlookupするだけになります。
jms.dicon
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" "http://www.seasar.org/dtd/components24.dtd"> <components> <!-- コネクションファクトリ --> <component class="javax.jms.QueueConnectionFactory"> @org.seasar.extension.j2ee.JndiResourceLocator@lookup("jms/connection") </component> <!-- セッションファクトリ --> <component class="org.seasar.jms.core.session.impl.SessionFactoryImpl" /> <!-- メッセージ送信コンポーネント --> <component instance="prototype" class="org.seasar.jms.core.impl.MessageSenderImpl"> <property name="destinationFactory"> <!-- デスティネーション (キューまたはトピック) ファクトリ --> <component class="org.seasar.jms.core.destination.impl.QueueFactory"> <!-- キュー名を指定します --> <property name="name">"TEST_QUEUE"</property> </component> </property> </component> </components>
s2container.dicon
こちらの設定も忘れずに。。。これでJDBC,JMSともにGlassFishのJTAで動かすことができます。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" "http://www.seasar.org/dtd/components24.dtd"> <components> <include condition="#ENV == 'ut'" path="warmdeploy.dicon" /> <include condition="#ENV == 'ct'" path="hotdeploy.dicon" /> <include condition="#ENV != 'ut' and #ENV != 'ct'" path="cooldeploy.dicon" /> <component class="org.seasar.framework.container.factory.SimplePathResolver"> <initMethod name="addRealPath"> <arg>"jta.dicon"</arg> <arg>"jta-sun9.dicon"</arg> </initMethod> </component> </components>
S2 2.4.18以降でS2JMS
S2JMSのアウトバウンド通信で受信を繰り返していたら、なぜか10回受信したところで止まってしまう現象に遭遇。
この10回という数字はなんだろう?と思っていろいろやってみたところ、コネクションプールのデフォルト値らしいことが判明。さらによ〜く調べてみるとプールからコネクションを取れずにずっと空きまちになっている感じでした。さらにさらによ〜〜く調べてみるとプールにコネクションを戻せていないようでした。
前は普通に使えてたのにな・・・って思ったところで閃きました。S2のバージョンをS2JMS(というかS2JCA)が依存している2.4.17にしてみようと。
結果、やはり2.4.17にしたら普通に使えました。で、移行ガイドを見たら
S2JTA において,Synchronization#afterCompletion(int) メソッドは完全にトランザクションコンテキスト外で呼び出されるようになりました (JTA の仕様です).afterCompletion() 内で TransactionManager#getTransaction() を呼び出すと,従来は完了した Transaction を取得できましたが,rc4 からは null が返されます.
これかー。でも2.4.18以降が使いたいなー。という訳でS2JCAをいじってみました。
org.seasar.jca.outbound.policy.AbstractTransactionBoundedPoolingPolicy
Synchronizationを実装するのをやめて以下のような内部クラスを作ります。
public class SynchronizationImpl implements Synchronization { /** トランザクション */ protected final Transaction tx; /** * インスタンスを構築します。 * * @param tx * トランザクション */ public SynchronizationImpl(final Transaction tx) { this.tx = tx; } public final void beforeCompletion() { } public void afterCompletion(final int status) { releaseContext(tx); } }
releaseContextはトランザクションをもらうように変更します。
protected void releaseContext(Transaction tx) { final ManagedConnectionPool<Object> pool = pools.get(tx); if (pool != null) { pool.close(); } }
org.seasar.jca.outbound.policy.XATransactionBoundedPoolingPolicy
associateTxの
tx.registerSynchronization(this);
の部分を
TransactionUtil.registerSynchronization(tx, new SynchronizationImpl(tx));
あるいは
tx.registerSynchronization(new SynchronizationImpl(tx));
に変更する。
まぁほとんどorg.seasar.extension.dbcp.impl.ConnectionPoolImplのコピペですが、とりあえずこれで動くようになりました。
S2JMS on GlassFish その2
前回はうっかりS2JTAのままで動かしてしまったので、今回はアプリケーションサーバ側のJTAを使って動かしたいと思います。
s2container.dicon
Sun Java System Application Server用の設定ファイルが用意されているようなので、それを使うように変更します。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" "http://www.seasar.org/dtd/components24.dtd"> <components> <include path="cooldeploy.dicon"/> <component class="org.seasar.framework.container.factory.SimplePathResolver"> <initMethod name="addRealPath"> <arg>"jta.dicon"</arg> <arg>"jta-sun9.dicon"</arg> </initMethod> </component> </components>
JmsPage.java
画面はロールバック感を出すために、基本的に1〜10を送信するがXが入力された場合だけ10を送信せずに例外を投げるように変更。また、受信側もタイムアウトの設定を追加。
package hatena.beerman.web.jms; import org.seasar.jms.core.MessageReceiver; import org.seasar.jms.core.MessageSender; public class JmsPage { public String sendMessage; public String recieveMessage; public MessageSender messageSender; public MessageReceiver messageReceiver; public javax.jms.ConnectionFactory connectionFactory; public void doSend() { final int LOOP = 10; final String ROLL_BACK = "X"; for (int i = 1; i <= LOOP; i++) { if (i == LOOP && ROLL_BACK.equals(sendMessage)) { throw new RuntimeException(); } else { messageSender.send(String.valueOf(i)); } } } public void doRecieve() { final long TIMEOUT = 1000L; recieveMessage = messageReceiver.setTimeout(TIMEOUT).receiveText(); } }
今回も無事動きました!と言いたいところでしたが、TransactionManagerのlookupに失敗してしまいました。TransactionManagerの部分は
@org.seasar.extension.j2ee.JndiResourceLocator@lookup("java:appserver/TransactionManager")
とやるのが正解みたいです。という訳でそこだけ書き換えたjta-sun9.diconを用意してみたところ無事に動きました!
on GlassFish
Sun Java System Application Server9.1でS2JMSを動かしてみました。
接続プールの作成
管理コンソールから簡単に作れました。
リソース-->JMSリソース-->接続ファクトリ
JNDI名「jms/hoge1」で登録。
Queueは
リソース-->JMSリソース-->送信先リソース
で登録すれば作成されるみたいです。とりあえず「hoge」というQueueを登録しました。JNDI名は「jms/hoge2」。
普通は外部のQueueを使うと思いますが、ここでは自分のとこのQueueに送受信してみます。
S2JMS側の設定
Dolteng 0.30.0で作ったdiconファイルを基に作りました。jms-outbound.diconを以下のように変更します。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" "http://www.seasar.org/dtd/components24.dtd"> <components> <description> 1.マネージドコネクションファクトリをJNDIで取得するように変更。 2.リソースアダプタはアプリケーションサーバのものを使うので削除。 3.デスティネーションファクトリはSimpleDestinationFactoryに変更。 </description> <!-- マネージドコネクションファクトリ --> <component class="javax.jms.ConnectionFactory"> @org.seasar.extension.j2ee.JndiResourceLocator@lookup("jms/hoge1") </component> <!-- セッションファクトリ --> <component class="org.seasar.jms.core.session.impl.SessionFactoryImpl"/> <!-- メッセージ送信コンポーネント --> <component instance="prototype" class="org.seasar.jms.core.impl.MessageSenderImpl"> <property name="destinationFactory"> <!-- デスティネーション (キューまたはトピック) ファクトリ --> <component class="org.seasar.jms.core.destination.impl.SimpleDestinationFactory"> <property name="destination"> @org.seasar.extension.j2ee.JndiResourceLocator@lookup("jms/hoge2") </property> </component> </property> </component> <!-- メッセージ受信コンポーネント --> <component instance="prototype" class="org.seasar.jms.core.impl.MessageReceiverImpl"> <property name="destinationFactory"> <!-- デスティネーション (キューまたはトピック) ファクトリ --> <component class="org.seasar.jms.core.destination.impl.SimpleDestinationFactory"> <property name="destination"> @org.seasar.extension.j2ee.JndiResourceLocator@lookup("jms/hoge2") </property> </component> </property> </component> </components>
動作確認
Teedaで簡単な確認画面を作成しました(htmlは省略)。
package hatena.beerman.web.jms; import org.seasar.jms.core.MessageReceiver; import org.seasar.jms.core.MessageSender; public class JmsPage { public String sendMessage; public String recieveMessage; public MessageSender messageSender; public MessageReceiver messageReceiver; public void doSend() { messageSender.send(sendMessage); } public void doRecieve() { recieveMessage = messageReceiver.receiveText(); } }
ちゃんと動いてくれました〜。
ジェネリックスのワイルドカードとキャスト
S2JUnit4の拡張アサートの「データセットとマップのリストが等しいことをアサート」するメソッド
public static void assertMapEquals(String message, DataSet expected, List<Map<?, ?>> list) { adapter.assertMapListEquals(message, expected, list); }
を使いたかったのですが、なぜか「データセットとマップが等しいことをアサート」するメソッド
public static void assertMapEquals(String message, DataSet expected, Map<?, ?> map) { adapter.assertMapEquals(message, expected, map); }
の方を呼びにいこうとしてしまい、パラメータの型が一致しないとコンパイラに怒られてしまって困ってしまいました。
原因はパラメータとしてList
List<?> list1 = new ArrayList<String>(); List<?> list2 = new ArrayList<?>(); List<Map<?, ?>> list3 = new ArrayList<Map<String, Object>>(); List<Map<?, ?>> list4 = new ArrayList<Map<?, ?>>();
list1とlist3が○でlist2とlist4が×だと勘違いしていたのですが、実際にはlist1とlist4が○でlist2とlist3が×でした。よくよく考えれば確かに納得なのですが。。。