JMS (Jakarta Messaging, früher Java Message Service) ist ein Message Queue Service, der dazu dient Nachrichten an eine Warteschlange (Queue) oder ein Thema (Topic) zu senden.

Die JMS API ermöglicht hierbei Nachrichten an einen Server zu senden und synchron oder asynchron vom Server abzufragen. Die Dokumentation zur JMS API ist zu finden unter diesem Link.

Damit eine Nachricht gesendet werden kann, muss eine Ziel-Konfiguration im JMS-Service angelegt sein (weitere Information zum Senden unter JMS Nachrichten). Der in der Konfiguration definierte Warteschlangenmanager muss unter der angegebenen Adresse erreichbar sein.

Sollte die Verbindung zum Message Queue System abbrechen, versucht die Anwendung diese wieder herzustellen, wenn versucht wird etwas zu senden. Ist das Message Queue System wieder erreichbar, kann die Verbindung auch durch einen Neustart des JMS Service wieder aufgebaut werden. Dienste können im Service Editor unter Administration > Development > Service Editor verwaltet werden.

Als Wartschlangenmanager und Server können verschiedene Message Queue Systeme (MQ Systeme) eingesetzt werden. Für die Unterstützung der zwei gängigen MQ Systeme IBM MQ und ActiveMQ existieren bereits Module, die von Werk aus mitgeliefert werden.

Des weiteren gibt es eine (nicht getestete) Unterstützung für JNDI (Java Naming and Directory Interface). Achtung: Hier gibt es keine vorgegebenen Konfigurationsoptionen, es muss also darauf geachtet werden, den JNDI Lookup für das entsprechende MQ System korrekt aufzubauen.

Weitere MQ Systeme unterstützen

Für jedes weitere MQ System, das unterstützt werden soll, muss ein eigenes Modul angelegt werden. Der Aufbau eines solchen Moduls wird im Folgenden beispielhaft am existierenden Modul für ActiveMQ gezeigt.

Zuerst muss ein neues Maven-Projekt angelegt werden, das die Basis für das neue Modul darstellt. (In unserem Beispiel tl-service-jms-activemq)

Im zweiten Schritt muss nun das korrekte Java-Package im src/main/java Ordner angelegt werden.

Damit das Modul ohne Probleme auf die benötigten Klassen des JMS Service zugreifen kann, sollte der Pfad aus dem Basis-Pfad des Service com.top_logic.services.jms und dem angehängten Modulnamen bestehen.

Der Code für das Modul ActiveMQ liegt in folgendem Paket: com.top_logic.services.jms.activemq. Hierin liegen nun 2 Java Klassen: ActiveMQClient.java und package-info.java.

In der ersten Klasse wird die Konfiguration für das MQ System angelegt und der Verbindungsaufbau zu diesem vorbereitet. In package-info.java dagegen muss lediglich der package-Pfad angegeben werden (hier package com.top_logic.services.jms.activemq;)

Die Klassen für das Modul (z.B. ActiveMQClient.java) sind nach dem Schema MQSystemName + Client benannt (z.B. für IBM MQ IBMMQClient.java).

Implementierung

Die Modulklasse (in unserem Beispiel ActiveMQClient.java) muss die Klasse JMSClient aus com.top_logic.services.jms erweitern.

public class ActiveMQClient extends JMSClient {

Dies ist notwendig, damit der JMSService die entsprechende Konfiguration verwenden kann. Des weiteren werden so standardmäßige Konfigurationen, die immer benötigt werden, übernommen.

Als erstes benötigt die Klasse nun ein Konfigurations-Interface. Über die Annotation @DisplayOrder({}) kann die Anzeigereihenfolge der Konfigurationsoptionen festgelegt werden, dies ist allerdings optional.

In einem Konfigurations-Interface muss für jede Konfiguration eine get Methode angelegt werden, der zusätzlich ein Name zugewiesen werden kann.

Folgendes Beispiel aus ActiveMQClient.java enthält in der Annotation Referenzen auf PRODUCER_CONFIGS und CONSUMER_CONFIGS, die in der JMSClient.Config gesetzt werden.

Diese beiden Optionen können in der Annotation @DisplayOrder({}) verwendet werden, sollten aber anderweitig nicht geändert oder benutzt werden.

/**
 * Configuration options for {@link ActiveMQClient}.
 */
@DisplayOrder({
    Config.URL_SCHEME,
    Config.HOST,
    Config.PORT,
    Config.USER,
    Config.PASSWORD,
    Config.PRODUCER_CONFIGS,
    Config.CONSUMER_CONFIGS })
public interface Config extends JMSClient.Config {

    /**
     * Configuration name for {@link #getURLScheme()}.
     */
    String URL_SCHEME = "url-scheme";

    /**
     * Configuration name for {@link #getHost()}.
     */
    String HOST = "host";

    /**
     * Configuration name for {@link #getPort()}.
     */
    String PORT = "port";

    /**
     * Configuration name for {@link #getUser()}.
     */
    String USER = "user";

    /**
     * Configuration name for {@link #getPassword()}.
     */
    String PASSWORD = "password";

    /**
     * The URL-Scheme of the connection.
     */
    @Mandatory
    @Name(URL_SCHEME)
    URLScheme getURLScheme();

    /**
     * The host of the target queue.
     */
    @Mandatory
    @Name(HOST)
    String getHost();

    /**
     * The port of the target queue.
     */
    @Name(PORT)
    @IntDefault(-1)
    int getPort();

    /**
     * The user name to log in to the message queue server.
     */
    @Name(USER)
    String getUser();

    /**
     * The password to the given user name.
     */
    @Encrypted
    @Name(PASSWORD)
    String getPassword();
}

Wie in obigem Beispiel zu sehen, können die Optionen der Konfiguration verschiedene Typen haben und erhalten durch die Annotation @Name() ihren Konfigurations-Namen als String. Dieser wird dazu verwendet der Konfiguration einen übersetzbaren Namen und auf Wunsch auch einen Tooltip zu geben.

Der Typ URLScheme ist eine Enum und sorgt dafür, dass in der Konfiguration ein DropDown erscheint, aus dem man eine Option der Enum auswählen kann. In dieser Enum kann eine von 4 Optionen gewählt werden.

public enum URLScheme {
    TCP,
    UDP,
    VM,
    JGROUPS;
}

Nach der Konfiguration benötigt die Klasse noch einen Konstruktor und muss die Abstrakte Methode setupConnectionFactory implementieren.

public ActiveMQClient(InstantiationContext context, Config config) {
    super(context, config);
}

@Override
public ConnectionFactory setupConnectionFactory() {
    Config config = (Config) getConfig();
    String url = config.getURLScheme().toString().toLowerCase() + "://" + config.getHost() + ":" + config.getPort();
    ActiveMQConnectionFactory amqcf = new ActiveMQConnectionFactory(url);
    amqcf.setUser(config.getUser());
    amqcf.setPassword(config.getPassword());
    return amqcf;
}

Hier müssen nun alle Einstellungen vorgenommen werden, die das gewählte MQ System benötigt, um eine ConnectionFactory zu erstellen. Diese muss zurückgegeben werden, damit eine Verbindung zu dem MQ System aufgebaut werden kann.

Namen, Tooltips & Übersetzungen

Neben dem erstellten Java-Package com.top_logic.services.jms.activemq muss noch ein Ordner META-INF angelegt werden, sofern dieser noch nicht existiert. Hierein kommt je eine Datei pro Sprache, in der die Konfiguration verfügbar sein soll.

Die Dateien müssen sich an folgende Bennenung halten messages_de.properties, wobei das _de das Kürzel der Sprache repräsentiert. (hier könnte bspw. auch _en stehen)

In dieser Datei bestimmt je eine Zeile einen Namen, Tooltip beziehungsweise die Übersetzung dieser. Eine Zeile beginnt mit dem Pfad des Wertes, der die Angabe erhalten soll gefolgt von dem Text für diesen. Beispielhaft:

com.top_logic.services.jms.activemq.ActiveMQClient.Config.user = Benutzer
com.top_logic.services.jms.activemq.ActiveMQClient.Config.user.tooltip = Der Benutzername für die Anmeldung beim Nachrichtenwarteschlangen-Server.
com.top_logic.services.jms.activemq.ActiveMQClient.Config.password = Passwort
com.top_logic.services.jms.activemq.ActiveMQClient.Config.password.tooltip = Das Passwort für den angegebenen Benutzernamen.


Nun ist das Modul fertig und sollte bei der Konfiguration eines Ziels im JMSService in der Oberfläche ausgewählt werden können.