リモート(別プロセス)のJBossサーバー上のキューにXAトランザクション内で正しくメッセージを送受信するには(続編)

以前にJBoss Messagingでリモートのキューに対してXAトランザクション内でメッセージを送受信するにはResourceAdapterの登録が必要であるということを紹介いたしました。
リモート(別プロセス)のJBossサーバー上のキューにXAトランザクション内で正しくメッセージを送受信するには - 達人プログラマーを目指して
JBoss MessagingのクライアントAPIは直接はXAのリソースとしてトランザクションに参加してくれないため、一旦リソースアダプターを使ってJCAのリソースプールとしてサーバーに登録しないといけないということです。ということで、理屈は頭でなんとなく理解できていたつもりだったのですが、今日実際に会社の環境で試したら、ほんのちょっとの設定の違いで正しくリモートのキューがJNDIルックアップできず結構な時間はまってしまいました。上記の記事で説明した通り、deploy\messaging\jms-ds.xmlに対してJMSProviderLoaderと接続ファクトリーに対するタグを追加すればよいのですが、以下の書き方ではリモートのキューのJNDIルックアップがうまくいきません。

   <!-- The JMS provider loader -->
  <mbean code="org.jboss.jms.jndi.JMSProviderLoader"
     name="jboss.messaging:service=JMSProviderLoader,name=RemoteJMSProvider">
    <attribute name="ProviderName">RemoteJMSProvider</attribute>
    <attribute name="ProviderAdapterClass">
      org.jboss.jms.jndi.JNDIProviderAdapter
    </attribute>
    <!-- The combined connection factory -->
    <attribute name="FactoryRef">java:/XAConnectionFactory</attribute>
    <!-- The queue connection factory -->
    <attribute name="QueueFactoryRef">java:/XAConnectionFactory</attribute>
    <!-- The topic factory -->
    <attribute name="TopicFactoryRef">java:/XAConnectionFactory</attribute>
    <!-- Access JMS via JNDI -->
    <attribute name="Properties">
       java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
       java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
       java.naming.provider.url=リモートのアドレス:1099
    </attribute>
  </mbean>

  <!-- JMS XA Resource adapter, use this to get transacted JMS in beans -->
  <tx-connection-factory>
    <jndi-name>RemoteJmsXA</jndi-name>
    <xa-transaction/>
    <rar-name>jms-ra.rar</rar-name>
    <connection-definition>org.jboss.resource.adapter.jms.JmsConnectionFactory</connection-definition>
    <config-property name="SessionDefaultType" type="java.lang.String">javax.jms.Topic</config-property>
    <config-property name="JmsProviderAdapterJNDI" type="java.lang.String">java:/RemoteJMSProvider</config-property>
    <max-pool-size>20</max-pool-size>
  </tx-connection-factory>

そして、以下が正しく動作する設定です。

   <!-- The JMS provider loader -->
  <mbean code="org.jboss.jms.jndi.JMSProviderLoader"
     name="jboss.messaging:service=JMSProviderLoader,name=RemoteJMSProvider">
    <attribute name="ProviderName">RemoteJMSProvider</attribute>
    <attribute name="ProviderAdapterClass">
      org.jboss.jms.jndi.JNDIProviderAdapter
    </attribute>
    <!-- The combined connection factory -->
    <attribute name="FactoryRef">/XAConnectionFactory</attribute>
    <!-- The queue connection factory -->
    <attribute name="QueueFactoryRef">/XAConnectionFactory</attribute>
    <!-- The topic factory -->
    <attribute name="TopicFactoryRef">/XAConnectionFactory</attribute>
    <!-- Access JMS via JNDI -->
    <attribute name="Properties">
       java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
       java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
       java.naming.provider.url=リモートのアドレス:1099
    </attribute>
  </mbean>

  <!-- JMS XA Resource adapter, use this to get transacted JMS in beans -->
  <tx-connection-factory>
    <jndi-name>RemoteJmsXA</jndi-name>
    <xa-transaction/>
    <rar-name>jms-ra.rar</rar-name>
    <connection-definition>org.jboss.resource.adapter.jms.JmsConnectionFactory</connection-definition>
    <config-property name="SessionDefaultType" type="java.lang.String">javax.jms.Topic</config-property>
    <config-property name="JmsProviderAdapterJNDI" type="java.lang.String">java:/RemoteJMSProvider</config-property>
    <max-pool-size>20</max-pool-size>
  </tx-connection-factory>

間違い探しのようですが違いがわかりますか。つまりFactoryRefのアドレスがjava:/XAConnectionFactoryではなくて/XAConnectionFactoryでなくてはならないということです。java:の部分が余分ということですね。確かに、もともと参考にしたサンプルでもjava:の部分は付いていなかったのですが、もともとjms-ds.xml上に定義されていたローカルのキュー用の設定ではjava:/XAConnectionFactoryと指定されていたため、どうせなら統一しておいたほうが美しいだろうということで無意識のうちにすべてjava:をつけてしまい、それでアクセスできなくなっていたのですね。
ちなみに、上記のような設定をしておいた場合

  • ローカルのJNDIツリーからjava:/RemoteJmsXAというJNDI名で接続ファクトリーをルックアップ
  • キューをリモートのJNDIツリーからリモートサーバーのキュー名(/queue/TestQueueなど)でルックアップ

という流れになります。ルックアップするJNDIツリーが接続ファクトリーとキューとで異なるので注意が必要です。Springを使う場合は以下のような設定となりますね。とにかく非常にややこしいです。(コメント欄のnekopさんのアドバイスを参照してExternalContextという機能を使うとアプリ内でリモートのJNDIツリーを意識せずに済むようです。リモートサーバーのアドレス設定が一元管理できるのでそちらの方が一般的にベターでしょう。)

	<!-- ConnectionFactory -->
	<jee:jndi-lookup id="connectionFactory" jndi-name="java:/RemoteJmsXA" /><!-- XA対応接続ファクトリー -->

	<!-- JMS Template -->
	<bean id="remoteJmsTemplate" class="org.springframework.jms.core.JmsTemplate">
		<property name="connectionFactory" ref="connectionFactory" />
		<property name="receiveTimeout" value="10000" />
		<property name="destinationResolver" ref="destinationResolver" />
	</bean>

	<bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate">
		<property name="environment">
			<props>
				<prop key="java.naming.factory.initial">org.jnp.interfaces.NamingContextFactory</prop>
				<prop key="java.naming.factory.url.pkgs">org.jboss.naming:org.jnp.interfaces</prop>
				<prop key="java.naming.provider.url">jnp://リモートアドレス:1099</prop>
			</props>
		</property>
	</bean>

	<bean id="destinationResolver" class="org.springframework.jms.support.destination.JndiDestinationResolver" >
		<property name="jndiTemplate" ref="jndiTemplate"  />
	</bean>

なおHow to send a tx transactional jms message from a local jboss to a remote jboss (version 4.2.3.GA) | My notesでは1100番ポートのHA-JNDIを使うサンプルとなっていますが、クラスタ環境は必須ではなく普通のレプリケートされないJNDIツリーで大丈夫です。
最終的にjava - Sending JMS message to remote queue in JBoss AS via connection pool - Stack Overflowを参考にさせていただきました。