Le serveur d’application JBoss EAP est certifié JAVA EE 6, il implémente donc via le framework IronJacamar le standard JAVA EE Connector Architecture (JCA). Ce standard permet de connecter les applications Java EE à des ressources externes comme par exemple un broker JMS, un système de fichier ou un mainframe. Il est possible de faire l’analogie du standard JCA avec le standard JDBC, JDBC se limitant uniquement aux bases de données de type relationnel, JCA se veut beaucoup plus large, il permet d’intégrer tout type de systèmes d’informations d’entreprise (EIS)

Le standard JCA va prendre en charge l’ensemble des fonctionnalités suivantes (liste non-exhaustive) :

  • Connexion au système (socket, IPC, …)
  • Gestion des transactions (locales, XA)
  • Sécurisation de l’accès à la ressource distante
  • Management du cycle de vie et du threading

Le fournisseur de service va fournir aux développeurs et à l’intégrateur un driver appelé dans le contexte JCA : Resource Adapter

Nous allons voir dans notre exemple :

Best Pratice d’integration JMS entre Websphere MQ (WMQ) et JBoss EAP 6

Matériels

Pour ce lab, nous allons avoir besoin de deux logiciels :

Warning

Attention, il s’agit de version d’essai ou d’évaluation uniquement, le déploiement en production nécessite l’achat de souscription pour JBoss et de license pour IBM

Développement

Afin de pouvoir tester notre intégration nous allons avoir besoin de développer un client pour l’envoi et un MDB pour la réception d’un message JMS.

Pour l’envoi

Dans notre cas nous allons créer un EJB Stateless pour envoyer le message JMS. Mais il est possible d’utiliser un autre mécanisme pour l’envoi, une servlet par exemple.

  // ..OMIT .. //

  @Stateless
  public class MoMSenderBean {

    // ..OMIT .. //

    @Resource(name = "jms/connectionFactory")
    ConnectionFactory factory;

    @Resource(name = "jms/queue/Queue")
    Queue queue;

    public void sendMessage(String message) throws JMSException {

              Connection connection = factory.createConnection();
              LOGGER.trace("Recuperation d'une connexion {}", connection);

              Session session = connection.createSession(false, AUTO_ACKNOWLEDGE);
              MessageProducer producer = session.createProducer(queue);
              LOGGER.trace("Creation du sender {} sur la queue {}", producer, queue);

              TextMessage textMessage = session.createTextMessage(message);
              textMessage.setJMSCorrelationID(UUID.randomUUID().toString());
              producer.send(textMessage);
              producer.close();
              session.close();
              connection.close();
    }
  }

Warning

On notera dans ce code que les ressources JMS (Factory et Queue) sont récupérées du scope courant du module.

Plus d’information sur JNDI avec JBoss

Pour la réception

Dans le monde Java EE, la réception de message JMS se fait par l’intermediaire d’un bean particulier. Ce bean est un Message Driven Bean (MDB), c’est à dire que l’objet est appelé quand un message est reçu dans la file JMS. La classe doit héritée de l’interface javax.jms.MessageListener

// ..OMIT .. //

public class MoMMDB implements MessageListener {

  // ..OMIT .. //

	public void onMessage(Message message) {
		LOGGER.info("Received Message from queue: {}" + (TextMessage) message);

	}
}

Warning

Il est bien sûr possible décorer le MDB avec les annotations ActivationSpec, mais ces annotations noie dans le code des problematiques contextuelles à l’application (environnement extérieur à l’application). Avec d’autre resource adapter cela peut s’avérer plus simple.

Configuration Application

Nous allons maintenant configurer l’application afin de nous connecter à la bonne file JMS. Dans un premier temps nous allons utiliser le fichier ejb-jar.xml afin de definir les principaux objets.

Définition Java EE (ejb-jar.xml)

<ejb-jar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd" version="3.0">
  <description>MoM Application EJB / MDB </description>
  <display-name>MoM Application EJB / MDB</display-name>
  <enterprise-beans>
    <message-driven>
      <description>MoM MDB</description>
      <ejb-name>MessageDrivenBean</ejb-name>
      <ejb-class>net.a.g.jee.mom.mdb.MoMMDB</ejb-class>
      <transaction-type>Container</transaction-type>
      <message-destination-type>javax.jms.Queue</message-destination-type>
    </message-driven>
    <session>
      <ejb-name>MoMSenderBean</ejb-name>
      <ejb-class>net.a.g.jee.mom.ejb.MoMSenderBean</ejb-class>
    </session>
  </enterprise-beans>
</ejb-jar>

Configuration des implementations spécifiques (jboss-ejb3.xml)

Comme nous souhaitons pouvoir nous connecter à un serveur WebsphereMQ nous allons devoir configurer le MDB afin qu’il réceptionne les messages. On effectura aussi le mapping des ressources d’environnement du serveur d’application avec les resources de l’application.

<jboss:ejb-jar xmlns:jboss="http://www.jboss.com/xml/ns/javaee"
  xmlns="http://java.sun.com/xml/ns/javaee" xmlns:jee="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mdb="urn:resource-adapter-binding"
  xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-ejb3-2_0.xsd http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd"
  version="3.1" impl-version="2.0">
  <jee:enterprise-beans>
    <jee:message-driven>
      <jee:ejb-name>MessageDrivenBean</jee:ejb-name>
      <jee:ejb-class>net.a.g.jee.mom.mdb.MoMMDB</jee:ejb-class>
      <jee:transaction-type>Container</jee:transaction-type>
      <jee:message-destination-type>javax.jms.Queue</jee:message-destination-type>
      <jee:activation-config>
        <jee:activation-config-property>
          <jee:activation-config-property-name>channel</jee:activation-config-property-name>
          <jee:activation-config-property-value>${websphere.channel}</jee:activation-config-property-value>
        </jee:activation-config-property>
        <jee:activation-config-property>
          <jee:activation-config-property-name>queueManager</jee:activation-config-property-name>
          <jee:activation-config-property-value>${websphere.queueManager}</jee:activation-config-property-value>
        </jee:activation-config-property>
        <jee:activation-config-property>
          <jee:activation-config-property-name>transportType</jee:activation-config-property-name>
          <jee:activation-config-property-value>${websphere.transportType}</jee:activation-config-property-value>
        </jee:activation-config-property>
        <jee:activation-config-property>
          <jee:activation-config-property-name>hostName</jee:activation-config-property-name>
          <jee:activation-config-property-value>${websphere.hostName}</jee:activation-config-property-value>
        </jee:activation-config-property>
        <jee:activation-config-property>
          <jee:activation-config-property-name>port</jee:activation-config-property-name>
          <jee:activation-config-property-value>${websphere.port}</jee:activation-config-property-value>
        </jee:activation-config-property>
        <jee:activation-config-property>
          <jee:activation-config-property-name>username</jee:activation-config-property-name>
          <jee:activation-config-property-value>${websphere.username}</jee:activation-config-property-value>
        </jee:activation-config-property>
        <jee:activation-config-property>
          <jee:activation-config-property-name>password</jee:activation-config-property-name>
          <jee:activation-config-property-value>${websphere.password}</jee:activation-config-property-value>
        </jee:activation-config-property>
        <jee:activation-config-property>
          <jee:activation-config-property-name>destination</jee:activation-config-property-name>
          <jee:activation-config-property-value>java:/jboss/jms/wmq/queue/Queue</jee:activation-config-property-value>
        </jee:activation-config-property>
        <jee:activation-config-property>
          <jee:activation-config-property-name>useJNDI</jee:activation-config-property-name>
          <jee:activation-config-property-value>true</jee:activation-config-property-value>
        </jee:activation-config-property>
        <jee:activation-config-property>
          <jee:activation-config-property-name>destinationType</jee:activation-config-property-name>
          <jee:activation-config-property-value>javax.jms.Queue</jee:activation-config-property-value>
        </jee:activation-config-property>
      </jee:activation-config>
    </jee:message-driven>
    <jee:session>
      <jee:ejb-name>MoMSenderBean</jee:ejb-name>
      <jee:ejb-class>net.a.g.jee.mom.ejb.MoMSenderBean</jee:ejb-class>
      <jee:resource-ref>
        <jee:res-ref-name>jms/connectionFactory</jee:res-ref-name>
        <jee:res-type>javax.jms.ConnectionFactory</jee:res-type>
        <jee:lookup-name>java:/jboss/jms/wmq/connectionFactory</jee:lookup-name>
      </jee:resource-ref>
      <jee:resource-env-ref>
        <jee:resource-env-ref-name>jms/queue/Queue</jee:resource-env-ref-name>
        <jee:lookup-name>java:/jboss/jms/wmq/queue/Queue</jee:lookup-name>
      </jee:resource-env-ref>
    </jee:session>
  </jee:enterprise-beans>
  <jee:assembly-descriptor>
    <mdb:resource-adapter-binding>
      <jee:ejb-name>MessageDrivenBean</jee:ejb-name>
      <mdb:resource-adapter-name>${websphere.resource.adapter}</mdb:resource-adapter-name>
    </mdb:resource-adapter-binding>
  </jee:assembly-descriptor>
</jboss:ejb-jar>

Plusieurs points importants dans cette configuration sont à noter :

  • L’utilisation de placeholder dans l’ensemble des configurations techniques afin de centraliser l’information dans le standalone.xml
    • hostName -> ${websphere.hostName}
    • port -> ${websphere.port}
    • queueManager -> ${websphere.queueManager}
  • La propriété useJNDI est activée afin de récuperer la queue par son nom JNDI
  • Le mapping des noms JNDI du serveur JBoss avec les noms JNDI du module applicatif
    • Nom de la queue configurée dans le serveur : java:/jboss/jms/wmq/queue/Queue
    • Nom de la queue utilisée par le module applicatif : jms/queue/Queue
  • Le binding du ressource adapteur (driver JCA) de websphere MQ au bean MDB de réception.
    • Balise <mdb:resource-adapter-binding>

Warning

Il est preferable d’utiliser les fichiers XML dans un contexte d’entreprise, en effet le changement d’environement et les fréquentes modifications nécessite souvent des mises à jour d’informations. Le code Java doit dépendre le moins possible de l’environnement et de l’implémentation des systèmes externes.

Info

Si l’application doit se connecter à un autre système JMS, je preconise de recréer un autre fichier jboss-ejb3.xml, le mapping sera de nouveau à effectuer dans ce fichier.

Configuration JBoss

Dans ce chapitre nous allons voir comment configurer le fichier standalone.xml de l’instance JBoss EAP.

Les systèmes properties

Nous reprennons ici l’ensembles des variables d’environnement de connexion à WebsphereMQ que nous avons vu dans le fichier jboss-ejb3.xml.

<system-properties>
  <property name="websphere.hostName"         value="localhost"/>
  <property name="websphere.port"             value="1414"/>
  <property name="websphere.username"         value="mqm"/>
  <property name="websphere.password"         value="mqm"/>
  <property name="websphere.channel"          value="SYSTEM.AUTO.SVRCONN"/>
  <property name="websphere.transportType"    value="CLIENT"/>
  <property name="websphere.queueManager"     value="QUEUE.MANAGER"/>
  <property name="websphere.queueName"        value="Q_QUEUE"/>
  <property name="websphere.resource.adapter" value="wmq.jmsra.rar"/>
</system-properties>

Info

Le mode BINDING est le second type de transport possible pour la connexion à WebsphereMQ, mais il nécessite l’installation de composant binaires spécifiques aux systèmes d’exploitation.

Le mécanisme de remplacement

JBoss va utiliser les variables d’environnement et va les remplacer dans certain type objet de l’application.

Il y a trois types de fichier dans lesquels JBoss peut remplacer les placeholder par les variables d’environnement :

  • Les fichiers descripteurs du standard Java EE
    • spec-descriptor-property-replacement
    • web.xml, ejb-jar.xml, etc…
  • Les fichiers descripteurs spécifiques à JBoss
    • jboss-descriptor-property-replacement
    • jboss-web.xml, jboss-ejb3.xml, etc…
  • Les annotations
    • annotation-property-replacement
<subsystem xmlns="urn:jboss:domain:ee:1.2">
  <spec-descriptor-property-replacement>false</spec-descriptor-property-replacement>
  <jboss-descriptor-property-replacement>true</jboss-descriptor-property-replacement>
  <annotation-property-replacement>false</annotation-property-replacement>
</subsystem>

Note

Je préconise d’effectuer le remplacement uniquement pour les fichiers descripteurs JBoss, car cela est spécifique à JBoss.

Le ressource adapteur pour la connexion à WMQ

Enfin pour terminer, nous allons configurer le ressource adapteur dans l’instance JBoss.

<subsystem xmlns="urn:jboss:domain:resource-adapters:1.1">
  <resource-adapters>
    <resource-adapter id="wmq.jmsra.rar">
      <archive>wmq.jmsra.rar</archive>
      <transaction-support>LocalTransaction</transaction-support>
      <connection-definitions>
        <connection-definition class-name="com.ibm.mq.connector.outbound.ManagedConnectionFactoryImpl" jndi-name="java:/jboss/jms/wmq/connectionFactory" use-java-context="true" pool-name="MQConnectionFactory">
          <config-property name="port">${websphere.port:1414}</config-property>
          <config-property name="hostName">${websphere.hostName:localhost}</config-property>
          <config-property name="username">${websphere.username:mqm}</config-property>
          <config-property name="password">${websphere.password:mqm}</config-property>
          <config-property name="channel">${websphere.channel:SYSTEM.AUTO.SVRCONN}</config-property>
          <config-property name="transportType">${websphere.transportType:CLIENT}</config-property>
          <config-property name="queueManager">${websphere.queueManager:QUEUE.MANAGER}</config-property>
          <security>
            <application/>
          </security>
        </connection-definition>
      </connection-definitions>
      <admin-objects>
        <admin-object class-name="com.ibm.mq.connector.outbound.MQQueueProxy" jndi-name="java:/jboss/jms/wmq/queue/Queue" use-java-context="true" pool-name="QueuePool">
          <config-property name="baseQueueManagerName">${websphere.queueManager:QUEUE.MANAGER}</config-property>
          <config-property name="baseQueueName">${websphere.queueName:Q_QUEUE}</config-property>
        </admin-object>
      </admin-objects>
    </resource-adapter>
  </resource-adapters>
</subsystem>

Info

On remarquera l’utilisation des placeholders dans cette partie de configuration du fichier standalone.xml

Il nous reste plus qu’à déposer le resource adapter de Websphere MQ wmq.jmsra.rar et notre application dans le répertoire de déploiement de l’instance de JBoss ${JBOSS_HOME}/standalone/deployments, et le tour est joué.

Conclusion

L’intégration du connecteur WebsphereMQ est relativement aisé dans le serveur d’application JBoss. En respectant les principes de ce post, il est possible d’obtenir une application modulaire et facile à maintenir. Le déploiement sur différents environnements (DEV, INT, PROD) en est simplifié, seules les variables d’environnements dans le fichier standalone.xml sont à modifier.


URL

Remerciements

  • Akram B. A. @RedHat pour certaines ressources de ce post
  • Guillaume C. @RedHat pour ces conseils