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:
- The name of a normal queue, containing up to 79 characters and not beginning with the characters
storeAndForward. - A valid store-and-forward queue name.
See the
zero.messaging.brokerdeveloper's guide for more information about store-and-forward processing.
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.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);
try {
QueueConnection 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.