Beispiel: Message Driven Bean (JBoss 4.0)

Inhalt:

Anlegen der Application
Anlegen der Message Driven Bean "Message"
MDB goes Server
Anlegen des Applicationclients

Beispiel für eine Message Driven Bean, die ihre Messages per Application Client erhält.
Hier gibt es das Projekt zum Download (dies ist ein EAR-Export, die Importanleitung findet man im Stateless-Beispiel): Message.ear
ACHTUNG: Nach dem Import XDoclet-Builder im EJB-Projekt aktivieren !

Aufbau des Beispieles

a) Message Driven Bean, die an einer Queue hängt.
b) Application Client der die Nachrichten abschickt.


Anlegen der Application

Ein "EAR Application Project" mit dem Namen "Message" erstellen.
Zu erzeugende Module definieren. Dieses Beispiel benötigt ein EJB-Projekt und ein Anwendungsclientprojekt.
Erzeugen einer Application (Module)


Anlegen der Message Driven Bean "Message"

Wir fügen eine Message Driven Bean zu.
Message Driven Bean (1)
Sie soll "MessageBean" heißen und im Package "de.fhw.swtvertiefung.knauf.message" liegen.
Message Driven Bean (2)
Im nächsten Schritt ist wichtig dass als "Destination" hier "Queue" steht. Seit Version 1.0 müssen wir den "Destination JNDI Name" anpassen, hier steht im Default nur "My", ich habe es hier geändert auf den JNDI-Namen der Queue: "queue/MessageBeanQueue".
Message Driven Bean (3)
Auch im letzten Schritt belassen wir die Defaults.
Message Driven Bean (4)
Das Ergebnis sieht im Projektexplorer so aus:
Message Driven Bean (5)
Es wurden insgesamt drei Klassen erzeugt:
-MessageBean ist die eigentliche Bean-Klasse.
-MessageMdb ist eine Subklasse von MessageBean, sie wurde von XDoclet erzeugt und ist völlig nutzlos :-(.
-MessageUtil enthält Hilfsmethoden für z.B. das Holen einer Queue oder einer ConnectionFactory aus dem JNDI.


Den Rest der Arbeit müssen wir in der Bean-Klasse und mittels XDoclet erledigen.
Wir ändern die XDoclet-Deklaration so ab:
 * @ejb.bean name="Message" 
 *     acknowledge-mode="Auto-acknowledge"
 *     destination-type="javax.jms.Queue"     
 *     transaction-type="Container"
 *     destination-jndi-name="queue/MessageBeanQueue"
 *     connection-factory-jndi-name="ConnectionFactory" 
 *     
 * @jboss.destination-jndi-name name="queue/MessageBeanQueue" 
Die Hauptarbeit geschieht im Tag @ejb.bean: Der destination-type gibt an ob es sich bei dieser Bean um eine Queue (Punkt-zu-Punkt-Verbindung, der Client schickt die Nachricht an einen bestimmten Empfänger, wie eine Telefonverbindung) oder ein Topic (es gibt eine unbekannte Anzahl von Empfängern, vergleichbar mit dem Radio-Senden) handelt.
Der transaction-type ist "Container".
Die nächsten beiden Angaben, destination-jndi-name und connection-factory-jndi-name, werden nur in der Klasse MessageUtil verwendet, und zwar in den static Hilfsmethoden für das Holen der Queue bzw. der Connection-Factory. Die Connection-Factory ist die Default-Connectionfactory von JBoss.
Mit dem Tag @jboss.destination-jndi-name wird der JNDI-Name der Queue angegeben, an der diese Bean lauscht. Geben wir hier nichts an bleibt es dem Server überlassen. JBoss würde sie als queue/Message ablegen.

In ejb-jar.xml sieht das so aus:
      <message-driven >
         <description><![CDATA[<!-- begin-xdoclet-definition -->]]></description>

         <ejb-name>Message</ejb-name>

         <ejb-class>de.fhw.swtvertiefung.knauf.message.MessageMdb</ejb-class>

         <messaging-type>javax.jms.MessageListener</messaging-type>
         <transaction-type>Container</transaction-type>
         <message-destination-type>javax.jms.Queue</message-destination-type>
         <activation-config>
           <activation-config-property>
             <activation-config-property-name>destinationType</activation-config-property-name>
             <activation-config-property-value>javax.jms.Queue</activation-config-property-value>
           </activation-config-property>
           <activation-config-property>
             <activation-config-property-name>acknowledgeMode</activation-config-property-name>
             <activation-config-property-value>Auto-acknowledge</activation-config-property-value>
           </activation-config-property>
         </activation-config>

      </message-driven> 
Das zugehörige Stück aus "jboss.xml" deklariert den JNDI-Namen der Queue der Message Driven Bean:
      <message-driven>
         <ejb-name>Message</ejb-name>                       
         <destination-jndi-name>queue/MessageBeanQueue</destination-jndi-name>
      </message-driven> 


Jetzt nur noch die Methode onMessage implementieren:
  /**Methode aus Interface "MessageListener": Ausführen der übergebenen Message.
   * Hier erfolgt nur eine Logausgabe.
   * Alle JMS-Fehler werden als EJB-Exception behandelt.
   * @param message Die zu verarbeitende Message.
   */
  public void onMessage(javax.jms.Message message)
  {
    try
    {
      MessageBean.logger.info("onMessage: Message vom Typ " + message.getClass().toString() + " erhalten");
      if (message instanceof TextMessage)
      {
        TextMessage textMessage = (TextMessage) message;
        MessageBean.logger.info("TextMessage enthält diesen Text: " + textMessage.getText() );
      }
      else
        MessageBean.logger.info("Sonstige Message. toString() = " + message.toString() );
    }
    catch (JMSException jex)
    {
      MessageBean.logger.log( Level.SEVERE, "Fehler beim Verarbeiten der Message: " + jex.getMessage(), jex );
      throw new EJBException ("Fehler beim Verarbeiten der Message: " + jex.getMessage(), jex );
    }
  } 
Schon sind wir kurz davor die MDB auf den Server zu packen.


MDB goes Server

Bevor wir die MDB auf den Server schieben deklarieren wir die Queue. Hier gibt es drei Möglichkeiten:



Anlegen des Applicationclients

Der Applicationclient muss nicht die EJB-JARs referenzieren, da MessageDrivenBeans über keine Remote/Local-Interfaces verfügen.
Es sind folgende Schritte nötig:
-Klasse "MessageApplication" zufügen, die nur den MessageFrame angezeigt. Sie muss in "Manifest.mf" als Main-Class eingetragen werden.
-Eine neue Klasse vom Typ "Java" -> "Swing" -> "JFrame Visual Class" zufügen. Auf ihr einen Button und ein Textfeld plazieren. Beim Button-Klick soll die Eingabe aus dem Textfeld an die MessageBean geschickt werden.
Der Code dazu sieht so aus:
private void sendMessage()
  {
    try
    {
      Properties props = new Properties();
      props.setProperty(Context.INITIAL_CONTEXT_FACTORY,"org.jnp.interfaces.NamingContextFactory");
      props.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.naming.client");
      props.setProperty(Context.PROVIDER_URL, "jnp://localhost:1099");
      props.setProperty("j2ee.clientName", "MessageClient");
      
      //JMS initialisieren. Die ConnectionFactory aus dem JNDI holen:
      InitialContext initialContext = new InitialContext(props);
      QueueConnectionFactory queueConnectionFactory = 
          (QueueConnectionFactory) initialContext.lookup("java:comp/env/jms/MBConnectionFactory");

      //Die konfigurierte Queue holen:
      Queue queue = (Queue) initialContext.lookup("java:comp/env/jms/MBQueueRef");

      //Verbindung erzeugen:
      QueueConnection queueConnection = queueConnectionFactory.createQueueConnection();
      QueueSession queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
      QueueSender queueSender = queueSession.createSender(queue);

      //Senden der Nachricht:
      TextMessage textMessage = queueSession.createTextMessage();
      textMessage.setText(this.jTextFieldMessage.getText());

      queueSender.send(textMessage);

      //Fertig.
      JOptionPane.showMessageDialog(this, "Nachricht wurde gesendet und sollte im Serverlog stehen !");
    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
  }

-Jetzt kommt die Hauptarbeit: die Resourcen-Referenzen müssen deklariert werden:
In "application-client.xml" wird folgendes eingefügt:
   <resource-ref>
      <res-ref-name>jms/MBConnectionFactory</res-ref-name>
      <res-type>javax.jms.QueueConnectionFactory</res-type>
      <res-auth>Container</res-auth>
   </resource-ref>
   <resource-ref>
      <res-ref-name>jms/MBQueueRef</res-ref-name>
      <res-type>javax.jms.Queue</res-type>
      <res-auth>Container</res-auth>
   </resource-ref>

Die eigentliche Verknüpfung mit den globalen JNDI-Namen der JBoss-Resourcen erfolgt über die Datei "jboss-client.xml". In den bisherigen Beispielen war es nie nötig diese zu verwenden, doch jetzt ist es soweit. Wir könnten natürlich den folgenden Code zufügen, aber um den WebTools-Plugin ein bißchen besser kennen zu lernen machen wir das hier über den umständlichen Weg:
Zuerst einmal (dies muss nur einmal geschehen !) fügen wir die DTD "jboss-client_4_0.dtd" in den XML-Katalog ein. Dazu unter "Window" -> "Preferences" -> "Web and XML" -> "XML Catalog" wählen. Hier einen neuen Eintrag in der Kategorie "User Specified Entries" zufügen. Nebenbei: die DTD liegt auch im JBoss-VErzeichnis unter "\docs\dtd\jboss-client_4_0.dtd" (aus dieser habe ich auch die Werte geklaut). Aber wir referenzieren trotzdem die aus dem Internet.
Als URI wird die Adresse auf der JBoss-Homepage angegeben: http://www.jboss.org/j2ee/dtd/jboss-client_4_0.dtd
"Key Type" ist "Public ID".
"Key" ist -//JBoss//DTD Application Client 4.0//EN.
XML-Katalog (1)
Das Ergebnis sieht so aus:
XML-Katalog (2)
Jetzt können wir "jboss-client.xml" anlegen. Dazu Rechtsklick auf "META-INF" im Client-Projekt und "New" -> "Other..." wählen. Wir wählen "XML" -> "XML" aus.
jboss-client.xml (1)
Im ersten Schritt wird die Option "Create XML file from a DTD file" gewählt.
jboss-client.xml (2)
Im nächsten Schritt wird der Dateiname "jboss-client.xml" angegeben und des META-INF-Verzeichnis als Ziel gewählt.
jboss-client.xml (3)
Die DTD-Datei steckt im XML-Katalog, wir wählen sie aus:
jboss-client.xml (4)
Im letzten Schritt können wir die Defaults belassen:
jboss-client.xml (5)
Das erzeugt eine Rumpf-XML-Datei mit sauberer DTD-Deklaration.

Jetzt fügen wir die Resourcen-Referenzen zu. Wichtig ist dass die Elemente res-ref-name in "jboss-client.xml" den gleichen Wert haben wie die zugehörigen Element in "application-client.xml" !
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-client PUBLIC "-//JBoss//DTD Application Client 4.0//EN" "http://www.jboss.org/j2ee/dtd/jboss-client_4_0.dtd" >
<jboss-client>
  <jndi-name>MessageClient</jndi-name>
  <resource-ref>
      <res-ref-name>jms/MBConnectionFactory</res-ref-name>
      <jndi-name>ConnectionFactory</jndi-name>
   </resource-ref>
   <resource-ref>
      <res-ref-name>jms/MBQueueRef</res-ref-name>
      <jndi-name>queue/MessageBeanQueue</jndi-name>
   </resource-ref>
</jboss-client> 
Jetzt die Anwendung per Rechtsklick -> "Run as" -> "Run as Java Application" ausführen. Nach dem Klick auf den Button sollte die Message im Serverlog bzw. auf der Console erscheinen.



Version 1.1.0.1, Stand 16.01.2006
Historie:
1.0.0.0 (23.11.2005): Erstellt
1.0.1.0 (27.11.2005): Application Client verwendet saubere Resource-Referenzen und "jboss-client.xml"
1.1.0.0 (11.01.2006): Angepaßt an WTP 1.0, Varianten "per JMXConsole" und "im EJB-Projekt" für die Queue-Konfiguration zugefügt.
1.1.0.1 (16.01.2006): Verweis auf WTP-Bug 123509 und den Fix in Bild 1.0.1.