Chapter 2. JEUS MQ Client Programming

Table of Contents

2.1. Overview
2.2. JMS Administered Objects
2.2.1. Defining JNDI Services
2.2.2. Connection Factories
2.2.3. Destination
2.3. Connections and Sessions
2.3.1. Creating Connections
2.3.2. Sharing Physical Connections
2.3.3. Creating Sessions
2.3.4. Client Facility Pooling
2.3.5. NONE_ACKNOWLEDGE Mode
2.3.6. JMSContext
2.4. Messages
2.4.1. Message Header Field
2.4.2. Message Properties
2.4.3. Message Body
2.4.4. FileMessage
2.5. Transactions
2.5.1. Local Transactions
2.5.2. Distributed Transactions

This chapter describes JEUS MQ client types, procedures for creating a client, and administered objects.

2.1. Overview

This section discusses JEUS MQ client types and procedures for creating a client.

Client Types

There are two types of JMS clients, message producers and message consumers, and a single client can play both roles.

JEUS MQ clients can be divided into the following categories according to the methods used for Java application deployment and execution.

  • Independent applications

    Independently executed in the Java Standard Edition (SE) environment.

  • Java EE applications

    Deployed to Java EE servers, like EJBs and servlets. For information about deploying Java EE applications to JEUS and executing them, refer to "JEUS Applications & Deployment Guide".

  • Message-driven beans

    A message-driven bean is a type of EJB. This allows a single-threaded Java SE application to run by using multiple threads concurrently. For more information about message-driven beans (hereafter, MDB), refer to EJB related books or Java EE Tutorials.

    For information about how to use MDB in JEUS, refer to JEUS EJB Guide. "Chapter 9. Message Driven Beans (MDBs)".

Note

Currently, JEUS MQ does not support clients that are written in a language other than Java.

Steps for Creating a Client

The following are the steps for creating a JEUS MQ client using an independent application, a JEUS MQ client.

When the client program that runs in the Java EE environment is specified in the XML Deployment Descriptor, the lookup process of the initial Java Naming and Directory Interface (hereafter JNDI) is not required.

  1. Create a JNDI InitialContext.

    Context context = new InitialContext();

    The JEUS JNDI service configuration will be discussed in "2.2.1. Defining JNDI Services".

  2. Obtain a connection factory through JNDI.

    ConnectionFactory connectionFactory =
        (ConnectionFactory) context.lookup("jms/ConnectionFactory");

    A connection factory can be obtained by using the JEUS MQ API, without using JNDI lookup. For more information, refer to "2.2.2. Connection Factories".

  3. Create a connection through the obtained connection factory.

    Connection connection = connectionFactory.createConnection();
  4. Create a session from the connection.

    Session session = connection.createSession(false, AUTO_ACKNOWLEDGE);
  5. Look up a destination through JNDI

    Destination destination = (Destination) context.lookup("ExamplesQueue");

    Like the connection factory, a destination can be obtained using JEUS MQ API, without using JNDI lookup. For more information, refer to "2.2.3. Destination".

  6. To send a message, create a message producer using the session object.

    MessageProducer producer = session.createProducer(destination);

    To receive a message, create a message consumer using the session object.

    MessageConsumer consumer = session.createConsumer(destination);

    To asynchronously receive the message, register the object that implements the MessageListener interface in the message consumer.

    MessageListener listener = new MyMessageListener();
    consumer.setMessageListener(listener);
  7. Initiate the connection.

    connection.start();
  8. Send or receive messages to execute the business logic.

    Send a message through the message producer.

    TextMessage message = session.createTextMessage("Hello, World");
    producer.send(msg);

    To synchronously receive messages, invoke the receive() method of the message consumer.

    Message message = consumer.receive();

    To asynchronously receive messages, the received messages are processed by the onMessage() method of the MessageListener object registered in Step 6.

  9. After all messages have been sent and received, close the connection.

    connection.close();

2.2. JMS Administered Objects

There are two types of JMS administered objects, the connection factory and destination.

In general, they are created on the server through JMS server settings, and managed by the administrator. The JMS client obtains the objects or object references from the server in order to use the JMS Service. JNDI lookup is a typical way for the client to obtain the JMS administered objects from the server.

This section explains how to obtain a connection factory or destination reference from the JEUS MQ server, and how to implement JEUS JNDI service in the client programs to use JNDI lookup. In addition, it discusses how to dynamically create a destination in the JEUS MQ server by using the API, and describes the dead message destination of JEUS MQ server.

2.2.1. Defining JNDI Services

JNDI service should be defined first to obtain JMS administered objects through JNDI lookup.

JNDI service can be defined using the following three properties.

  • java.naming.factory.initial

  • java.naming.factory.url.pkgs

  • java.naming.provider.url

The three defined properties can be applied in the following ways.

  • Creating a JNDI property file

    The easiest way to define JNDI service is to create the jndi.properties file where the property values are configured.

    The following is an example of the jndi.properties file that defines the environment for using JEUS JNDI service.

    [Example 2.1] <<jndi.properties>>

    java.naming.factory.initial=jeus.jndi.JEUSContextFactory
    java.naming.factory.url.pkgs=jeus.jndi.jns.url
    java.naming.provider.url=127.0.0.1:9736

    To enable InitialContext objects to read the jndi.properties file settings, put the jndi.properties file in the class path, or include it in a JAR file with other files for deployment.

  • Passing the parameters to system property

    It is difficult to modify the jndi.properties file included in the JAR file at runtime. The configuration values should be passed to the system properties when executing the client as in the following.

    java -Djava.naming.factory.initial=jeus.jndi.JEUSContextFactory \
         -Djava.naming.factory.url.pkgs=jeus.jndi.jns.url \
         -Djava.naming.provider.url=127.0.0.1:9736 \
         . . .
  • Passing the parameters as applet properties

    If the JEUS MQ client is an applet, it is better to pass the parameters by using the <param> element inside the <applet> element in the HTML file rather than creating a jndi.properties file as in the following.

    <applet code="JeusMqApplet" width="640" height="480">
        <param name="java.naming.factory.initial"
               value="jeus.jndi.JEUSContextFactory"/>
        <param name="java.naming.factory.url.pkgs" value="jeus.jndi.jns.url"/>
        <param name="java.naming.provider.url" value="127.0.0.1:9736"/>
    </applet>

    In the applet, configure an environment that is needed to create the InitialContext using the Applet.getParameter() method.

    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY, getParameter("java.naming.factory.initial"));
    env.put(Context.URL_PKG_PREFIXES, getParameter("java.naming.factory.url.pkgs"));
    env.put(Context.PROVIDER_URL, getParameter("java.naming.provider.url"));
    
    Context context = new InitialContext(env);
  • Inserting the configuration value in the code

    The environment that is needed to create the InitialContext can be directly implemented in the client code. Include the properties in the Hashtable object, and use them as the parameters of the InitialContext constructor.

    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY, "jeus.jndi.JEUSContextFactory");
    env.put(Context.URL_PKG_PREFIXES, "jeus.jndi.jns.url");
    env.put(Context.PROVIDER_URL, "127.0.0.1:9736");
    
    Context context = new InitialContext(env);

    Note

    However, this method may incur maintenance inconvenience because the source code needs to be modified to use other JNDI services or change the service definition.

2.2.2. Connection Factories

A connection factory has useful information on creating a connection to the JEUS MQ server.

In general, the JMS client obtains a connection factory through JNDI lookup, and uses it to create a connection with the JMS server.

ConnectionFactory connectionFactory =
    (ConnectionFactory) context.lookup("jms/ConnectionFactory");
QueueConnectionFactory queueConnectionFactory =
    (QueueConnectionFactory) context.lookup("jms/QueueConnectionFactory");
TopicConnectionFactory topicConnectionFactory =
    (TopicConnectionFactory) context.lookup("jms/TopicConnectionFactory");

To use distributed transactions in JEUS MQ, look up XAConnectionFactory, XAQueueConnectionFactory, and XATopicConnectionFactory classes as in the following.

The Java EE client can obtain a connection factory by using the Resource annotation, instead of using the InitialContext.lookup() method.

@Resource(mappedName="jms/ConnectionFactory")
private static ConnectionFactory connectionFactory;

If the JNDI Service is not available, a connection factory can be obtained by using the API provided by JEUS MQ.

jeus.jms.client.util.JeusConnectionFactoryCreator connectionFactoryCreator =
    new jeus.jms.client.util.JeusConnectionFactoryCreator();
connectionFactoryCreator.setFactoryName("ConnectionFactory");
connectionFactoryCreator.addBrokerAddress("127.0.0.1", 9741, "internal");
ConnectionFactory connectionFactory =
    (ConnectionFactory) connectionFactoryCreator.createConnectionFactory();

Note

When using JNDI lookup or the Resource annotation, the JNDI name of the connection factory object (e.g., "jms/ConnectionFactory" in the example) is used to obtain a connection factory. When using the JeusConnectionFactoryCreator object, the connection factory name (e.g., "Connection- Factory" in the previous example) which is internally used by JEUS MQ server is used. For the difference between the connection factory name and JNDI name, refer to the example in "3.3.3. Configuring Connection Factories".

2.2.3. Destination

There are two types of destinations, queue and topic, used as a storage for messages managed by the server. The JMS client obtains a reference to the destination object managed by the server in order to receive or send a message from/to a destination

In general, the JEUS MQ client obtains a destination by looking up the JNDI name on the JNDI server where the destination references are registered.

Queue queue = (Queue) context.lookup("jms/ExamplesQueue");
Topic topic = (Topic) context.lookup("jms/ExamplesTopic");

The Java EE client can obtain a destination reference by using the @Resource annotation, instead of using the InitialContext.lookup() method as in the following.

@Resource(mappedName="jms/ExamplesQueue")
private static Queue queue;

Like connection factories, JEUS MQ API can be used to obtain a destination reference without JNDI Lookup. To refer to the destination, use the destination name that is internally used by the JEUS MQ server.

jeus.jms.client.util.JeusDestinationCreator destinationCreator =
    new jeus.jms.client.util.JeusDestinationCreator();
destinationCreator.setDestinationName("ExamplesQueue");
destinationCreator.setDestinationClass(Destination.class);
Destination destination = (Destination) destinationCreator.createDestination();

It is also possible to use the Session.createQueue (string) and Session.createTopic (string) methods. They are implemented differently for each JMS implementation. As shown in the previous example, the actual destination name must be passed as a parameter.

Queue queue = session.createQueue("ExamplesQueue");
Topic topic = session.createTopic("ExamplesTopic");

Dynamic Creation of Destinations

JEUS MQ enables a client to dynamically create a destination on the server by using the Session.createQueue (string) and Session.createTopic (string) methods.

When specifying a destination string, append a "?" after the destination name. To use options, append each option using the form "param=value" after the "?". To set two or more options, use "&" as a delimiter. The destination parameters and values can be specified like those set in the domain.xml configuration file.

Note

Currently, there are two options used to dynamically create a destination, the export-name and multiple-receiver for a queue and the export-name for a topic.

The following example shows how to dynamically create a queue by using the destination name "Dynamic- Queue" and the JNDI name "jms/DynamicQueue". A queue can simultaneously receive two or more message consumers.

QueueConnectionFactory queueConnectionFactory =
    (QueueConnectionFactory) context.lookup("jms/QueueConnectionFactory");
QueueConnection queueConnection = queueConnectionFactory.createQueueConnection();
QueueSession queueSession =
    queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = queueSession.createQueue(
    "DynamicQueue?export-name=jms/DynamicQueue&multiple-receiver=true");

The API has the following features.

  • If the specified destination name already exists on the server, or another object is already registered as the specified JNDI name, a JMSException is thrown. If a JNDI name is not provided, the destination name is used as the JNDI name.

  • A connection needs to be created in order to enable the JEUS MQ server security function. If the subject used for creating a connection does not have "createDestination" permission on the "jeus.jms.destination.creation" resource, a JMSException is thrown.

  • Other settings follow the basic settings of the destination.

  • The dynamically created destinations are deleted when the JEUS MQ server shuts down.

Destinations created in the above example operate in the same way as destinations depending on settings until JEUS MQ server shuts down.

Note

A destination cannot be dynamically created when JEUS MQ clustering is enabled.

Dead Message Destination

Dead message destination is a system destination where failed messages are saved. A message may fail if the destination of the message could not be found, or the message has been recovered more than the allowed number of times.

The following are the cases when a message that is received by the consumer is recovered to the server-side destination.

  • The message consumer calls Session.rollback() or Session.recover().

  • Due to external reasons such as XA transaction rollback.

  • An exception occurs while executing the onMessage() method of the message listener.

The recovered message gets re-sent to the message consumer. However, if message processing fails repeatedly due to a business logic or client program error, messages will accumulate at the destination and the client cannot receive them. To resolve this problem, JEUS MQ limits the number of times that a message can be recovered. Set a value in the "JMS_JEUS_RedeliveryLimit" message property variable, and send the message.

message.setIntProperty("JMS_JEUS_RedeliveryLimit", <integer value>)

The default message property value can be modified by setting the "jeus.jms.client.default-redelivery-limit" system property when executing the message producer client.

-Djeus.jms.client.default-redelivery-limit=<integer value>

If not set, 3 is used as the default value. When booting, the JEUS MQ server creates a dead message destination with the name "JEUSMQ_DLQ". In general, the messages saved in the dead message destination are processed using the system administration tool. General clients can also access and process the dead message destination by using this name.

Note

Too many messages in a dead message destination may cause an OutOfMemoryError. Specify a client that will receive messages, or remove messages in the dead message destination periodically by using a system management tool.

2.3. Connections and Sessions

When a JMS client exchanges messages with a server, it must connect to the server and create a session. The connections and the sessions are very important client resources in JMS that may affect the system performance and determine error response methods. JEUS MQ provides a number of options to enhance connection performance.

This section describes how to set the connection options and how the JEUS MQ client runs based on the specified options.

2.3.1. Creating Connections

Connection is the first object that a client creates to work with a JMS server, and is physically or logically linked with the JMS server. Connections are created by calling the createConnection() method of the connection factory.

public Connection createConnection()
                            throws JMSException;
public Connection createConnection(String userName, String password)
                            throws JMSException;

If the JEUS MQ server uses the security function and the subject that has the userName/password does not have "createConnection" permission on the "jeus.jms.client.connectionFactory" resource, a JMSException is thrown.

The following option can be set as a system property when executing the JEUS MQ client.

-Djeus.jms.client.connect.timeout=<long value>

This option specifies the amount of time that the ConnectionFactory.createConnection() method waits for a successful connection. The default value is 5 seconds, and the unit is in milliseconds. If not connected within this time, a JMSException occurs in the ConnectionFactory.createConnection() method. If set to "0", it waits for a successful connection indefinitely.

This setting can be modified at runtime by adding either of the following statements to the client code:

System.setProperty("jeus.jms.client.connect.timeout", <long value>);

or

System.setProperty(jeus.jms.common.JeusJMSProperties.KEY_CLIENT_CONNECT_TIMEOUT,
                   <long value>);

2.3.2. Sharing Physical Connections

An application sends a message using the following steps.

  1. Create a connection

  2. Create a session

  3. Create a message producer

  4. Send a message

  5. Close a connection

If a JMS connection can be connected to only one physical connection (socket), performance is reduced because a new physical connection has to be created whenever a message is sent. Creating a physical connection takes longer than sending a message. The more physical connections are created, the more file descriptors are used which may cause IOExceptions to occur and disrupt system stability.

In a JEUS 6 Fix#6 and later, physical connections can be shared to resolve performance and stability problems mentioned previously. However, in some environments, since it would be better to create a new physical connection each time instead of sharing connections, an option is provided to set this function.

In general, one physical connection is connected to each ConnectionFactory, but if JEUS MQ failover is configured or "jeus.jms.client.use-single-server-entry" is set to "false", one physical connection is used for each connection. For more information about JEUS MQ Failover, refer to "제5장 JEUS MQ Failover".

The following are JVM options related to physical connection sharing.

  • -Djeus.jms.client.use-single-server-entry=<boolean value>

    This option determines whether to create one physical connection for each ConnectionFactory and share it with other connections. The default value is true. If set to false, the physical connection is used for only one connection.

    To modify the setting at runtime, add either of the following statements to the client code:

    System.setProperty("jeus.jms.client.use-single-server-entry", <boolean value>);

    or

    System.setProperty(jeus.jms.common.JeusJMSProperties.USE_SINGLE_SERVER_ENTRY,
                             <boolean value>);

  • -Djeus.jms.client.single-server-entry.shutdown-delay=<long value>

    This option determines when to disconnect the physical connection if it is not in use. Idle physical connections are not maintained but returned to the system. (Unit: milliseconds, default value: 600,000 (10 minutes))

    To modify the setting at runtime, add either of the following statements to the client code:

    System.setProperty("jeus.jms.client.single-server-entry.shutdown-delay",
                        <long value>);

    or

    System.setProperty(jeus.jms.common.JeusJMSProperties.SINGLE_SERVER_ENTRY_SHUTDOWN_DELAY,
                       <long value>);

Note

The JMS specification has a restriction on closing connections in onMessage of MessageListener as this may cause a deadlock.

2.3.3. Creating Sessions

JMS session is the basic unit for all messaging tasks such as sending a created message to the destination or receiving a message from the destination. A session is also a unit that is used for a JMS client that participates in a local or XA transaction.

A session and all tasks processed in the session need to be processed by a single thread context, and this means that the session objects are not thread-safe.

Note

The JEUS MQ client does not guarantee safe operation when multiple threads use a single session. It is recommended to create as many sessions as the number of threads. However, since creating multiple sessions means that multiple threads are used, the JMS specification has a restriction on more than one session created for a client that operates on Java EE web or EJB container.

The following API is used to create a session from the connection object.

public Session createSession(boolean transacted, int acknowledgeMode)
                             throws JMSException;

The JMS specification defines the following four acknowledge modes.

Session.AUTO_ACKNOWLEDGE = 1
Session.CLIENT_ACKNOWLEDGE = 2
Session.DUPS_OK_ACKNOWLEDGE = 3
Session.SESSION_TRANSACTED = 0

JEUS MQ supports an additional acknowledge mode to maximize the messaging performance.

jeus.jms.JeusSession.NONE_ACKNOWLEDGE = -1

Note

The JMS specification does not allow a client that operates on Java EE web or EJB container to use local transactions. Thus, such a client cannot create transacted sessions, and CLEINT_ACKNOWLEDGE is disabled for ACKNOWLEDGE mode.

2.3.4. Client Facility Pooling

As mentioned in the previous section, the client facilities including connections, sessions, and message producers are repeatedly created for use. However, these objects exchange messages with the server each time they are created and multiple messages have to be sent and received in order to send a single user message, which can result in performance degradation.

To resolve this issue, JEUS MQ provides client facility pooling. This function can only be used when the message producer is used in a non-transaction environment or an environment where the JEUS MQ failover is not configured. If a message consumer or a transaction is used, the client facilities that are not pooled are removed immediately when they are closed.

This function is optional. To enable the function, configure the following JVM options.

  • -Djeus.jms.client.use-pooled-connection-factory=<boolean value>

    Option to enable Client Facility Pooling function. (Default value: true)

  • -Djeus.jms.client.pooled-connection.check-period=<long value>

    Interval for deleting unused pooled objects. (Unit: milliseconds, default value: 60,000 (1 minute))

  • -Djeus.jms.client.pooled-connection.unused-timeout=<long value>

    Option to remove the pooled objects that have been idle longer than the specified time period. (Unit: milliseconds, default value: 120,000 (2 minutes))

2.3.5. NONE_ACKNOWLEDGE Mode

Except for transacted sessions, JMS message acknowledge modes are meaningful only when receiving messages, but the NONE_ACKNOWLEDGE mode also affects the sending of messages.

The acknowledge mode improves performance, but makes reliable messaging difficult. Therefore, when deciding whether to use the NONE_ACKNOWLEDGE mode, consider the message's characteristics, performance and reliability requirements, etc.

Note

When exchanging FileMessages or in transacted sessions, the NONE_ACKNOWLEDGE mode works like the AUTO_ACKNOWLEDGE mode.

Sending Messages

In general, after a client sends a message to the JMS server, the client thread calls the MessageProducer.send() method and suspends its operation until it receives a reply from the server.

In the NONE_ACKNOWLEDGE mode, MessageProducer.send() returns immediately after the client sends a JMS message to the server. This can improve message transmission performance reducing the client waiting time.

The following figure shows the difference in how a message is sent in general and in the NONE_ACKNOWLEDGE mode. The grey boxes indicate that the messages are logged when they arrive at the server.

[Figure 2.1] Sending Messages in General and in NONE_ACKNOWLEDGE mode

Sending Messages in General and in NONE_ACKNOWLEDGE mode

A message may be lost in the following cases.

  • When a network failure occurs while a message is being transmitted from the client to the server.

  • When an error occurs on the JEUS MQ server before a message arrives at the JEUS MQ server and is added to the destination.

The lost message is not recovered even if the message transmission method is set to DeliveryMode.PERSISTENT.

Receiving Messages

A JMS server does not delete the message information until it receives an acknowledgement from the client that received the message. This can help improve the reliability of messaging, but is not good for performance because each acknowledgment message incurs network overhead.

In the NONE_ACKNOWLEDGE mode, a user can expect faster message transmission speed. This is because the JEUS MQ server sends a JMS message to the client and then immediately deletes the message information from the server without sending an acknowledgement message to the client.

The following figure shows how a message is received in the AUTO_ACKNOWLEDGE and NONE_ACKNOWLEDGE modes. The gray box indicates that a message is deleted from the server.

[Figure 2.2] Receiving Messages in AUTO_ACKNOWLEDGE and NONE_ACKNOWLEDGE modes

Receiving Messages in AUTO_ACKNOWLEDGE and NONE_ACKNOWLEDGE modes

In the following cases, a message may be lost.

  • When a network error occurs while a message is being transmitted from the client to the server.

  • When an error occurs while the message is being processed by the JEUS MQ client library.

  • When an exception occurs in the onMessage() method of the MessageListener object registered by the client.

The lost message cannot be recovered by calling the Session.recover() method.

2.3.6. JMSContext

The JMS specification provides the JMSContext interface that combines connections and sessions, and has roles in both of them. Just like in connections, JMSContext can be created by using the following APIs defined in ConnectionFactory.

public Connection createContext()
                            throws JMSException;
public Connection createContext(int sessionMode)
                            throws JMSException;
public Connection createContext(String userName, String password)
                            throws JMSException;
public Connection createConnection(String userName, String password, int sessionMode)
                            throws JMSException;

The ACKNOWLEDGE mode is set just like when creating a session. However, transactions are used according to the mode, not a parameter.

In JEUS MQ, JMSContext uses client facility pooling just like connections and sessions. As a result, connections and session pools are shared. Therefore, detailed configuration depends on the settings for connections and session pools.

2.4. Messages

This section discusses the extended features of JMS messaging supported by JEUS MQ.

2.4.1. Message Header Field

JMS defines the following message header fields.

  • JMSDestination

  • JMSDeliveryMode

  • JMSMessageID

  • JMSTimestamp

  • JMSCorrelationID

  • JMSReplyTo

  • JMSRedelivered

  • JMSType

  • JMSExpiration

  • JMSPriority

Because JEUS MQ assigns a unique ID to each message, it does not support the following functions:

  • MessageProducer.setDisableMessageID( boolean) method disables message ID assignment to each JMS message.

  • MessageProducer.setDisableTimestamp(boolean) method disables timestamp assignment to each JMS message.

  • Overriding of the JMSDeliveryMode, JMSExpiration and JMSPriority field values that are set in the client.

If a client calls the Message.getJMSMessageID() method after sending a message using the session whose acknowledge mode is set to NONE_ACKNOWLEDGE, the message ID is displayed as NULL. This is because MessageProducer.send() returns before receiving a response from the server. For more information, refer to "2.3.5. NONE_ACKNOWLEDGE Mode".

2.4.2. Message Properties

JMS defines the following property names that start with "JMSX?".

  • JMSXUserID

  • JMSXAppID

  • JMSXDeliveryCount

  • JMSXGroupID

  • JMSXGroupSeq

  • JMSXProducerTXID

  • JMSXConsumerTXID

  • JMSXRcvTimestamp

  • JMSXState

Note

The "JMSX?" message properties are not required, except for JMSXDeliveryCount, and they are not supported by JEUS MQ.

The following message properties are supported by JEUS MQ.

  • JMS_JEUS_Schedule

    The amount of time that the JEUS MQ server waits before sending a message to the message consumer. The property is identical to Message Delivery Delay defined by JMS. The JEUS MQ server sends the message to the message consumer after the specified amount of time (JMSTimestamp value) has passed since the arrival of the message. The timestamp is a "long" value in milliseconds.

    Message.setLongProperty("JMS_JEUS_Schedule", <long value>);

    Note

    Message Delivery Delay overrides this property.

  • JMS_JEUS_Compaction

    The option to compact the message body. If set to true, the message body is compacted by using the ZLIB library when transmitting the message over the network.

    Message.setBooleanProperty("JMS_JEUS_Compaction", <boolean value>);

  • JMS_JEUS_RedeliveryLimit

    The maximum number of times to re-send a message to a message consumer. When the number of re-send attempts exceeds the specified limit, the message is saved in the dead message destination (Section 2.2.3, “Dead Message Destination”).

    Message.setIntProperty("JMS_JEUS_RedeliveryLimit", <integer value>);

2.4.3. Message Body

JMS specification defines the following five types of messages, according to the message body type.

  • StreamMessage

  • MapMessage

  • TextMessage

  • ObjectMessage

  • BytesMessage

Each message type can be created by the session object using the following APIs.

public StreamMessage createStreamMessage()
                            throws JMSException;
public MapMessage createMapMessage()
                            throws JMSException;
public TextMessage createTextMessage()
                            throws JMSException;
public TextMessage createTextMessage(String text)
                            throws JMSException;
public ObjectMessage createObjectMessage()
                            throws JMSException;
public ObjectMessage createObjectMessage(Serializable object)
                            throws JMSException;
public BytesMessage createBytesMessage()
                            throws JMSException;

2.4.4. FileMessage

JEUS MQ supports the FileMessage type in addition to the basic JMS message types.

Since JMS runs based on the message content, the memory has to retain the message content when sending and receiving messages. This may cause memory overflow on the client or server if the message size is too big.

To avoid this problem, JEUS MQ supports the FileMessage type that sends message contents in block units.

Creating Messages

Create a FileMessage using the following method defined in the jeus.jms.JeusSession class.

public jeus.jms.FileMessage createFileMessage()
                                       throws javax.jms.JMSException;
public jeus.jms.FileMessage createFileMessage(java.net.URL url)
                                       throws javax.jms.JMSException;

The Session, QueueSession, and TopicSession objects created by the JEUS MQ client library can be casted to a jeus.jms.JeusSession, jeus.jms.JeusQueueSession, and jeus.jms.JeusTopicSesison objects respectively.

FileMessage Interface

The following is the definition of the jeus.jms.FileMessage interface.

public interface FileMessage extends javax.jms.Message {
    public java.net.URL getURL();
    public void setURL(java.net.URL url)
                throws javax.jms.MessageNotWriteableException;
    public boolean isURLOnly();
    public void setURLOnly(boolean urlOnly);    
}

To send a message, the URL of the message file can be set using the setURL() method. The file can also be passed to the JeusSession.createFileMessage() method as a parameter when the message is created.

The urlOnly property determines whether to only send the URL of the file on the server to the message consumer. This property determines the return value of getURL() that is called on the FileMessage object.

The following describes getURL() value according to the urlOnly property.

urlOnlygetURL()
trueURL of a file on the JEUS MQ server. This URL is used to receive the file using protocols such as HTTP and FTP.
falseURL of a temporary file on the local server where JEUS MQ client library saves the entire content of the file that it receives. For more information, refer to "Temporary File Path".

When sending a FileMessage, the MessageProducer.send() method always returns a value after all file contents are sent to the server. This is also applicable in the NONE_ACKNOWLEDGE mode.

Note

A file included in a FileMessage is divided into 4 KB blocks. The block size can be modified by specifying the system property like "-Djeus.jms.file.blocksize=<integer value>" when executing the JEUS MQ client.

Temporary File Path

If a message consumer receives a file in a FileMessage, the file is saved in the temporary file path. The path is specified according to the following conditions.

  • If the application is deployed to JEUS:

    SERVER_HOME/.workspace/client/
  • If the jeus.jms.client.workdir system property is set:

    A path specified as the system property
  • Otherwise:

    USER_HOME/.jeusmq_client_work/

FileMessage Transmission Example

This section shows how to use the FileMessage API to send a FileMessage.

[Figure 2.3] FileMessage Transmission Example

FileMessage Transmission Example

The following Java code shows how to send the "/home/jeus/send_test/send.file" file using a FileMessage object.

[Example 2.2] Sending a FileMessage

. . .

jeus.jms.JeusSession session = (jeus.jms.JeusSession)
    connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(destination);

jeus.jms.FileMessage message = session.createFileMessage();
File file = new File("/home/jeus/send_test/send.file");
message.setURL(file.toURI.toURL());

producer.send(message);

. . .

The following example shows how to obtain an InputStream from the file's URL and write the file contents in the "/home/jeus/recv_test/recv.file" file.

[Example 2.3] Receiving a FileMessage

. . .
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageConsumer consumer = session.createConsumer(destination);
Message message = consumer.receive();

if (message instanceof jeus.jms.FileMessage) {
    URL url = ((jeus.jms.FileMessage) message).getURL();
    if (url != null) {
        InputStream inputStream = url.openStream();
        BufferedInputStream bufInputStream = new BufferedInputStream(inputStream);

        File outFile = new File("/home/jeus/recv_test/recv.file");
        FileOutputStream fileOutputStream = new FileOutputStream(outFile);
        BufferedOutputStream bufOutputStream =
            new BufferedOutputStream(fileOutputStream);

        int buf;
        while ((buf = bufInputStream.read()) != -1) {
            bufOutputStream.write(buf);
        }
        bufOutputStream.close();
        bufInputStream.close();
    }
}
. . .

2.5. Transactions

This section discusses the features of local transactions in JEUS MQ and distributed(XA) transactions and the scope of the transactions. It also explains what a client developer needs to know to process a transaction.

JMS transaction involves the task of sending and receiving messages inside a session. JMS specification defines the local transaction that starts and ends inside a session. It also defines the distributed transaction that involves the processing of other resources such as one or more JMS sessions, EJBs, and JDBCs.

Since a message sent through a session that is involved in a transaction is not seen as having arrived at the server, the message does not get delivered to the message consumer that is created in the same session. They also do not appear in a queue browser that is created in the same session.

2.5.1. Local Transactions

A local transaction is executed by a session that is created by setting the transacted parameter value to true in the Connection.createSession(boolean transacted, int acknowledgeMode) method. It includes all messaging tasks since the last commit or rollback or the tasks that have been executed since the creation of the session. This means that all tasks belong to a particular transaction.

Multiple sessions cannot be processed in a single local transaction, but one or more message producers can be created and sent to multiple destinations. Also, a message can be received from multiple destinations.

The following figure shows the tasks participating in a local transaction and the scope.

[Figure 2.4] JMS Transaction Scope

JMS Transaction Scope


A local transaction is completed by the session's commit() or rollback() API. Since a transacted session always participates in a transaction, there is no API that can start a transaction by its name.

Unlike in a distributed transaction, the client can commit or roll back a transaction in a local transaction. Therefore, it is possible to process messages asynchronously in the transaction.

Note

The JMS specification does not allow a client that operates on Java EE web or EJB container to use local transactions. Thus, such a client cannot create transacted sessions.

2.5.2. Distributed Transactions

In the JMS specification, XAResource provided by XASession can be registered to participate in an XA transaction. The actual implementation may vary for each vendor.

The following points must be taken into consideration to use XASession in the JEUS MQ client.

  • When invoking Session.getAcknowledgeMode() on an XASession, if the XASession is participating in a global transaction it returns Session.SESSION_TRANSACTED, and otherwise it returns Session.AUTO_ACKNOWLEDGE.

  • If Session.commit() or Session.rollback() is called on an XASession participating in a global transaction, the TransactionInProgressException or IllegalStateException is triggered.

Propagation of Distributed Transactions

The JEUS MQ client library registers an XAResource in a global transaction of the thread that uses a JMS API, regardless of when the XASession was created. To propagate a transaction associated with the client thread, a specific API invocation is required. JEUS MQ's participation in a distributed transaction is made only through a synchronous API invocation, which includes the transmission or synchronous reception of messages. The messages asynchronously received through a MessageListener must not be included in a distributed transaction.

Note

To process an asynchronously received message in a distributed transaction, use message-driven beans (MDB). For more information, refer to JEUS EJB Guide. "Chapter 9. Message Driven Beans (MDBs)".

Recovery of Distributed Transactions

The JEUS MQ server stores the ongoing session tasks of a transaction in a storage in order to recover them when the server has to restart due to an unexpected error. The transaction manager can obtain the IDs of ongoing transactions in JEUS MQ through the XAResource obtained from the XASession, and use them to commit or roll back the transactions.

To quickly recover from a failure, it is recommend to use the JEUS MQ failover function. For more information, refer to "제5장 JEUS MQ Failover".

When restarting, JEUS MQ server automatically rolls back the tasks of transactions that are not in an in-doubt state.