JMS (Jakarta Messaging, formerly Java Message Service) is a message queue service used to send messages to a queue or topic.

The JMS API allows messages to be sent to a server and retrieved from the server synchronously or asynchronously. The documentation for the JMS API can be found at this link.

In order for a message to be sent, a target configuration must be created in the JMS service (for more information on sending, see JMS Messages). The queue manager defined in the configuration must be accessible at the specified address.

If the connection to the message queue system is interrupted, the application will attempt to reestablish it when an attempt is made to send something. Once the message queue system is accessible again, the connection can also be reestablished by restarting the JMS service. Services can be managed in the Service Editor under Administration > Development > Service Editor.

Various message queue systems (MQ systems) can be used as queue managers and servers. Modules are already available for supporting the two most common MQ systems, IBM MQ and ActiveMQ, and are supplied as standard.

There is also (untested) support for JNDI (Java Naming and Directory Interface). Please note: There are no predefined configuration options here, so care must be taken to set up the JNDI lookup for the corresponding MQ system correctly.

Supporting additional MQ systems

A separate module must be created for each additional MQ system that is to be supported. The structure of such a module is shown below using the existing module for ActiveMQ as an example.

First, a new Maven project must be created to serve as the basis for the new module. (In our example, tl-service-jms-activemq)

In the second step, the correct Java package must now be created in the src/main/java folder.

To ensure that the module can access the required classes of the JMS service without any problems, the path should consist of the base path of the service com.top_logic.services.jms and the appended module name.

The code for the ActiveMQ module is located in the following package: com.top_logic.services.jms.activemq. This package now contains two Java classes: ActiveMQClient.java and package-info.java.

The configuration for the MQ system is created in the first class and the connection to it is prepared. In package-info.java, on the other hand, only the package path needs to be specified (here package com.top_logic.services.jms.activemq;).

The classes for the module (e.g., ActiveMQClient.java) are named according to the scheme MQSystemName + Client (e.g., for IBM MQ, IBMMQClient.java).

Implementation

The module class (in our example ActiveMQClient.java) must extend the class JMSClient from com.top_logic.services.jms.

public class ActiveMQClient extends JMSClient {

This is necessary so that the JMSService can use the appropriate configuration. Furthermore, standard configurations that are always required are adopted in this way.

First, the class now needs a configuration interface. The display order of the configuration options can be specified using the annotation @DisplayOrder({}), but this is optional.

In a configuration interface, a get method must be created for each configuration, to which a name can also be assigned.

The following example from ActiveMQClient.java contains references in the annotation to PRODUCER_CONFIGS and CONSUMER_CONFIGS, which are set in JMSClient.Config.

These two options can be used in the annotation @DisplayOrder({}), but should not be changed or used elsewhere.

/**
 * 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();
}

As can be seen in the example above, the configuration options can be of different types and are given their configuration name as a string by the annotation @Name(). This is used to give the configuration a translatable name and, if desired, a tooltip.

The type URLScheme is an enum and ensures that a drop-down menu appears in the configuration from which you can select an option from the enum. In this enum, one of four options can be selected.

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

After configuration, the class still needs a constructor and must implement the abstract method setupConnectionFactory.

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;
}

All settings required by the selected MQ system to create a ConnectionFactory must now be made here. This must be returned so that a connection to the MQ system can be established.

Names, tooltips & translations

In addition to the Java package com.top_logic.services.jms.activemq that has been created, a folder META-INF must also be created, if it does not already exist. This folder contains one file for each language in which the configuration is to be available.

The files must adhere to the following naming convention messages_de.properties, where _de represents the language abbreviation. (This could also be _en, for example.)

In this file, each line specifies a name, tooltip, or translation. A line begins with the path of the value to be specified, followed by the text for that value. For example:

com.top_logic.services.jms.activemq.ActiveMQClient.Config.user = User
com.top_logic.services.jms.activemq.ActiveMQClient.Config.user.tooltip = The user name for logging in to the message queue server.
com.top_logic.services.jms.activemq.ActiveMQClient.Config.password = Password
com.top_logic.services.jms.activemq.ActiveMQClient.Config.password.tooltip = The password for the specified user name.


The module is now complete and should be available for selection when configuring a target in JMSService in the user interface.