Using the external client

This topic describes the external client for the IBM® Reliable Transport Extension for WebSphere® sMash.

Overview

The external client allows Java™ applications to interact with WebSphere sMash using the IBM Reliable Transport Extension. The external application can send and receive messages using the Java Message Service (JMS) API.

Installation

The external client is packaged as a set of jar files. To obtain these, create an external client application from the zero command line:

zero create externalClientLib from zero:zero.messaging.externalclient

where 'externalClientLib' is an arbitrary application name. The external client jar files will be in the externalClientLib/jars directory.

Adding the External Client to your classpath

The External Client supports two different kinds of classloading environments: plain Java Standard Edition (JSE), and implementations of the Open Service Gateway Initiative (OSGi) platform such as Equinox, from the Eclipse project. OSGi classloading allows jars to be packaged inside primary jars, known as 'bundles' or 'plugins', whereas JSE does not. The external client includes several jar files which are only separately required in JSE, since they are also packaged within other jar files, and so available directly to OSGi applications. All external client applications should include the following jars:

com.ibm.micro.utils_2.1.0.2-datestamp.jar
com.ibm.mqttclient.jms_2.1.0.2-datestamp.jar
geronimo-jms_1.1_spec-1.0.1.jar
The interface classes defining the JMS API
geronimo-jta_1.0.1B_spec-1.0.1.jar
The interface classes defining the Java Transaction API (JTA)
jtm.jar
A transaction manager that implements JTA
zero.messaging.externalclient.version.jar
Queue and QueueConnection factory classes necessary to build client applications

Plain JSE applications (but not OSGi ones) must add the three following jars to the classpath:

com.ibm.msg.client.commonservices.jar
com.ibm.msg.client.jms.internal.jar
com.ibm.msg.client.provider.jar

Applications that will run in non-English language environments must also include,

com.ibm.micro.utils.nl_2.1.0.2-datestamp.jar
com.ibm.mqttclient.jms.nl_2.1.0.2-datestamp.jar

Finally, applications that use the Spring Framework will be able to make use of,

jtmSpring.jar

The external client public API

The Messaging External Client provides two classes necessary to connect a JMS client application to a Messaging broker instance.

package zero.messaging.externalclient.wrapper;
import javax.jms.Queue;
import javax.jms.JMSException; 
public class QueueFactory { 
	public static Queue createInstance (String queueName) throws JMSException;
}

The queueName parameter can be either:

package zero.messaging.externalclient.wrapper;
import javax.jms.JMSException;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
public class WrappedQueueConnectionFactory implements QueueConnectionFactory {
	public WrappedQueueConnectionFactory(String brokerName, String brokerHostName, int portNumber) throws JMSException;
	public WrappedQueueConnectionFactory(String brokerName, String brokerHostName, int portNumber, String userid, String password) throws JMSException;
	public QueueConnection createQueueConnection() throws JMSException;
	public QueueConnection createQueueConnection(String username, String password) throws JMSException;
	public String getJNDIName();
	
	public int getDeliveryTimeout ();
	public int getMaxPoolSize ();
	public void setDeliveryTimeout (int milliseconds) throws JMSException;
	public void setMaxPoolSize (int size);
	
	public String getAlgorithm ();
	public String getTruststore ();
	public void setAlgorithm (String algorithm) throws JMSException; 
	public void setTrustStore (String trustStore) throws JMSException; 
	public void setTrustStorePassword (String password) throws JMSException; 
	public void setTrustStoreType (String trustStoreType) throws JMSException; 
}

The WrappedQueueConnectionFactory class will by default maintain a pool of the last ten Connections returned through createQueueConnection(). Please ensure that you call close() on Connections once they are no longer required in order to return them to the pool. Setting the pool size to zero disables pooling. Setting the pool size to any negative value sets the pool size to 'unlimited.' Apart from the pooling logic, and one security caveat discussed below, the parameters behave as the broker connection properties discussed in Configuring connections to messaging brokers.

Connecting to secure broker instances

Some additional properties must be provided when connecting to secure broker instances. The following code fragment shows an example of properties that must be provided in all cases: username, password, trustStore, trustStorePassword and trustStoreType. An encryption algorithm must be specified unless running on a Sun JRE, for which the default value works correctly.

// calls to qcf.createConnection() will use "user" and "password" by default
WrappedQueueConnectionFactory qcf = new WrappedQueueConnectionFactory ("brokerName", "hostName", port, "user", "password");
qcf.setTrustStore("keystore.jks");
qcf.setTrustStorePassword("trustStorePassword");
qcf.setTrustStoreType("JKS");

// Comment out the next line for when running on an IBM JRE
// qcf.setAlgorithm("IbmX509");

// The next, alternative line is permissible but not necessary on Sun JREs
// qcf.setAlgorithm("SunX509");

Transaction manager and XA support

The external client does not support the javax.jms.XA* classes that support the X/Open XA protocol for transaction processing. We do however provide a mechanism for applications to perform messaging operations as part of broader XA transactions. The external client includes a transaction manager which implements the Java Transaction API (JTA) version 1.0.1B. This implements the full java.transaction.* API. It also provides last participant support, a means by which a single one-phase resource may participate in a two-phase transaction. This support is integrated into Connection objects returned by the WrappedQueueConnectionFactory class. Sessions created within the scope of a transaction are automatically enrolled into that transaction, and are committed when the transaction is committed. The transaction manager provides two static methods returning JTA implementation objects:

com.ibm.tx.jta.TransactionManagerFactory.getTransactionManager()
com.ibm.tx.jta.UserTransactionFactory.getUserTransaction()

The following code fragment shows a message being sent under a two-phase XA transaction:

import java.sql.SQLException;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.JMSException;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;
import zero.messaging.externalclient.wrapper.QueueFactory;
import zero.messaging.externalclient.wrapper.WrappedQueueConnectionFactory;
import com.ibm.tx.jta.UserTransactionFactory;

// ...
// fragment begins

Queue destination = QueueFactory.createInstance("queueName");
WrappedQueueConnectionFactory qcf = new WrappedQueueConnectionFactory ("myBroker", "hostname", port_number);
QueueConnection conn;
try {
    conn = qcf.createConnection();

	// Transacted section begins
	UserTransaction utx = UserTransactionFactory.getUserTransaction(); 
    utx.begin();
	Session session = conn.createSession (true, Session.AUTO_ACKNOWLEDGE);
	MessageProducer producer = session.createProducer(destination);
	TextMessage messageOut = session.createTextMessage("message sent under xa tran");
	producer.send(messageOut);
	producer.close();
	
	// perform any other work under the transaction here
	
	// commit the transaction: session will be committed and closed; do not reuse it. 		
	utx.commit();
} catch (JMSException e) {
    e.printStackTrace();
} catch (SystemException se) { 
	se.printStackTrace();
} catch (NotSupportedException e) {
	e.printStackTrace();
} catch (IllegalStateException e) {
	e.printStackTrace();
} catch (SecurityException e) {
	e.printStackTrace();
} catch (HeuristicMixedException e) {
	e.printStackTrace();
} catch (HeuristicRollbackException e) {
	e.printStackTrace();
} catch (RollbackException e) {
	e.printStackTrace();
} catch (SQLException e) {
	e.printStackTrace();
} 

Starting and stopping the transaction manager

The transaction manager is started implicitly the first time the application begins a transaction (e.g. via the JTA UserTransaction interface). Recovery processing will be performed automatically at this point. The transaction manager may instead be explicitly started by an application or its runtime, in order to drive recovery before the application starts any new transactions. The following lifecycle API is provided:

com.ibm.tx.TMHelper.start(boolean waitForRecovery)
com.ibm.tx.TMHelper.shutdown(int timeout)

The start() method explicitly initializes the in-process transaction manager and kicks off recovery. The waitForRecovery parameter indicates whether to wait for all recovery processing to complete before returning and allowing new transactions to start. New transactions can run in parallel to recovery processing if this parameter is false. Note that start(false) is called implicitly the first time an application begins a transaction if no explicit call to start has previously been made.

It is advantageous to stop the transaction manager cleanly before the process ends since this will clean up all active transactions and therefore release all resource locks. If the transaction manager is not stopped cleanly, then resource locks may be held until the transaction manager is recovered (e.g. the next time the JVM is started). The shutdown() method explicitly ends all active transactions and stops the transaction manager. Shutdown processing runs in a separate thread from the calling thread. The timeout parameter indicates the maximum period to wait (in seconds) for all in-flight transactions to complete. The configured shutdown timeout is used if this parameter is 0. Immediate shutdown occurs if a negative value is provided.

Spring support in the transaction manager

Classes specifically for use within the Spring framework are provided in jtmSpring.jar. The transaction manager can be used as the JTA transaction manager by Spring beans - either via the usual APIs or by using the transaction manager supplied in jtmSpring.jar, com.ibm.tx.spring.JtaTransactionManagerImpl, which extends the Spring JtaTransactionManager. Either approach also needs the main jtm.jar. The latter approach includes a ConfigurationProvider for the Spring environment to enable transaction manager configuration to be specified in its bean definition. This definition can be added to a Spring application context as follows:

<bean id="JTM" class="com.ibm.tx.spring.JtaTransactionManagerImpl">
    <!-- Optional JET Configuration properties -->
    <!-- Specified values are the defaults     -->
    <property name="totalTransactionTimeout" value="120"/>
    <property name="traceLevel" value="OFF"/>
    <property name="heuristicRetryInterval" value="60"/>
    <property name="heuristicRetryLimit" value="5"/>
    <property name="transactionLogDirectory" value="logs/recovery"/>
    <property name="transactionLogSize" value="1024"/>
    <property name="loggingForHeuristicReportingEnabled" value="false"/>
    <property name="heuristicCompletionDirection" value="ROLLBACK"/>
    <property name="defaultMaximumShutdownDelay" value="2"/>
</bean>

Extended transaction manager support

com.ibm.tx.jta.TransactionManagerFactory.getTransactionManager() returns an object which implements the com.ibm.tx.jta.ExtendedTransactionManager class which in turn extends javax.transaction.TransactionManager. The ExtendedTransactionManager method operate on the transaction associated with the calling thread. There are two key methods:

package com.ibm.tx.jta;
interface ExtendedTransactionManager extends javax.transaction.TransactionManager {
	public int registerResourceInfo(String xaResFactoryClassName, Serializable xaResInfo);
	public boolean enlist(XAResource xaRes, int recoveryId) throws RollbackException, IllegalStateException, SystemException;
}

registerResourceInfo() pre-registers an XAResource provider before any XAResources are enlisted from this provider. It takes two parameters:

xaResFactoryClassName
A class that implements a getXAResource method
xaResInfo
Information necessary for producing an XAResource object using XAResourceFactory.

The enlist() method returns a resource recoveryId value associated with the factory/info which can be used on resource enlistment, or -1 if an error occurs. This special variation of enlist identifies the resource-provider with which the XAResource is associated. This may be used after a previous call to registerResourceInfo.It also takes two parameters:

xaRes
The XAResource object representing the resource to enlist.
recoveryId
The identifier returned from a call to registerResourceInfo associating the appropriate xaResFactoryClassName/xaResInfo necessary for produce a XAResource object.

The method returns true if the resource was enlisted successfully, otherwise false. XAResources enlisted in this way do not need to be serialized to the transaction log, which improves performance and throughput. (Note that resources enlisted via javax.transaction.TransactionManager.enlist() MUST be serializable.)

Performing JMS and JDBC operations under a single XA transaction

The transaction manager is most useful for coordinating transactions across more than one resource. A common requirement is to transactionally update a database and send an asynchronous message. In Java these operations are respectively perfomed via the JDBC and JMS APIs. Just as our WrappedQueueConnectionFactory class automatically enlists JMS Session objects in a transaction if one is running, the same must be done for JDBC Connection objects. We provide sample code which indicates how this may be done.

To get started with JDBC, we must create a database instance and instantiate a javax.sql.DataSource for that database. We must create a class that will function as a javax.sql.DataSource, but with a getConnection() method that ensures Connection objects are enlisted with the current transaction, if there is one. In order to wrapper a DataSource, we provide two sample classes: XAResourceInfoImpl a Serializable class that creates DataSource objects from a set of properties, and XADataSourceProxy to act as the wrappper around DataSource The next step is to instantiate a jdbc.wrapper.XADataSourceProxy object instance - the constructor takes the name of your XA JDBC driver class (such as, for example, "org.apache.derby.jdbc.EmbeddedXADataSource" and a Properties object containing the properties for that data source. A minimum for Derby would be a name/value pair for the "databaseName" property. All that remains is to obtain a Connection object from jdbc.wrapper.XADataSourceProxy.getConnection() within the try {} block of the sample code above, and to use it for JDBC operations as you would normally.

Version 1.0.0.3.25591