Using the JNBridge JMS BizTalk Adapter with Oracle RIB and AQ

Oracle RIB (Retail Information Bus) is a message-based integration platform peaked for retail outlets. The messaging layer is Java Message Service, and the messaging provider is Oracle AQ.  One of the enterprise solutions that’s frequently integrated with Oracle RIB is Microsoft’s BizTalk Server. While the JNBridge JMS Adapter for BizTalk is a key component enabling this integration, Oracle RIB presents an integration challenge for stand-alone JMS clients like the adapter. This blog entry will discuss how to integrate Oracle RIB to BizTalk Server using the JMS adapter, but first it might be worthwhile to understand the particular problems with integrating stand-alone JMS clients.

Stand-alone JMS clients

JMS clients usually execute in a Java EE container within an application server, for example a Message Driven Bean. The container provides services to the client including transaction enlistment and implicit naming environments. A generic stand-alone JMS client does not execute in a container and must explicitly use the Java Naming and Directory Interface (JNDI) to acquire connection factories and destinations. Because of this, stand-alone JMS clients often require special libraries—the JAR files in the classpath—and configuration properties that are different from those used for a MDB. Some vendors, like IBM, and some products, like WebLogic, provide special thin-client JAR files. For other vendors, it is necessary to choose a subset of JAR files from the Java EE server implementation, copying them to the JMS client machine. Most times, the vendor will actually publish the required JAR files for each version, other times it is necessary to build the class path one ClassNotFoundException at a time.

Depending on the implementation of the JMS server, a stand-alone client may not have access to a JNDI service. Some JMS implementations don’t provide JNDI, relying instead on the host Java EE application server to provide naming and directory services. If the JMS implementation is not integrated into an application server, but is running as a stand-alone broker, all clients must access connection factories and destinations through proprietary APIs. That’s fine, but it isn’t portable, which is the whole idea behind the JMS specification.

Oracle AQ is an example of a JMS implementation that can run as a stand-alone broker or run in a Java EE application server like Oracle WebLogic. This blog will address both scenarios: a stand-alone JMS client, in this case the JNBridge JMS Adapter for BizTalk Server, connecting to Oracle AQ as a stand-alone broker and as a JMS service provider within WebLogic. As we’ll see, both scenarios require some extra work to support a stand-alone JMS client.

Oracle AQ configured as a Foreign Server within WebLogic

Generally speaking, the JMS specification was intended as an API that provides a generic surface on proprietary messaging middleware. The JMS surface is provided by a Java EE application server which also provides JNDI, another generic API for obtaining connection factories and destinations. Usually, the integration of the messaging provider to JMS and JNDI within the application server is accomplished by a Java Connection Architecture resource adapter. However, the preferred method required by Oracle RIB for integrating Oracle AQ to WebLogic is to configure the message provider as a foreign server.

Oracle AQ uses an Oracle Database as its message store, mapping destinations to tables. Because of the transactional nature of the DB, a Java Data Source is used in the foreign server configuration. A data source automatically handles DB transactions by enlisting in a transaction handled by the Java EE transaction manager, a service provided by WebLogic. This works well when the JMS client is executing in a Java EE container because the data source provides the transactional support. However, a stand-alone client is unable to use a data source because there’s no transaction manager available—it’s executing outside the transactional scope. While a JMS client can use local transactions, either explicitly calling commit or rollback, it cannot participate in a distributed transaction.

Foreign Server using a Data Source

Using WebLogic 10.3 and Oracle AQ 11.2, the configuration as a foreign server starts with configuring the data source.  A data source for an Oracle DB is a transaction aware wrapper around the database connection URL. This screen shot shows the data source configuration within WebLogic. Note the standard Oracle connection URL at the top with the default SID, orcl.

Here’s a screen shot of the foreign server configuration using  the above data source which has been mapped to the JNDI name, jms/oracleDS.

This foreign server configuration will work fine for JMS clients executing inside containers in WebLogic. However, if a stand-alone JMS client attempts to connect to WebLogic, this exception is thrown.

cannot assign instance of weblogic.jdbc.common.internal.RmiDataSource_1033_WLStub to field oracle.jms.AQjmsConnectionFactory.data_source of type javax.sql.DataSource in instance of oracle.jms.AQjmsXATopicConnectionFactory

 Foreign Server using a Connection URL

The problem is the data source. If the foreign server configuration does not use a data source, but instead uses the connection URL, then the stand-alone JMS client will be able to connect. The following screen shot shows the foreign server configuration using a connection URL instead of a data source.

Mutually Exclusive: Requires Two Foreign Servers

Now, a stand-alone JMS client can connect to WebLogic and access Oracle AQ queues and topics. However, a JMS client running inside a container in WebLogic cannot use this foreign server configuration. The internal JMS client requires a data source to provide transaction enlistment. The solution is to configure two foreign servers, one using a data source for the WebLogic JMS clients, the other using a connection URL for the external stand-alone JMS clients. Each foreign server configuration will have its own connection factory with a unique JNDI name. Each foreign server will also point to the same Oracle AQ queues or topics, but will use unique JNDI names.

The configuration for the JNBridge JMS Adapter for BizTalk when using Oracle AQ configured as a WebLogic Foreign Server can be found here.

 Oracle AQ as a stand-alone broker.

The JMS specification is a generic interface, but there is no specified mechanism for obtaining connection factories or destinations. Generic access is usually provided by JNDI, however the specification does not preclude a non-JNDI mechanism, i.e. a proprietary extension. Oracle AQ is an example of a JMS implementation that doesn’t require JNDI if it is running as a stand-alone broker. However, because the API for creating connection factories and accessing destinations is proprietary, the JNBridge JMS Adapter for BizTalk can only support stand-alone Oracle AQ if there is a JNDI service available.

One way to get around this problem is to write a Java wrapper that mimics JNDI, but uses the Oracle AQ API to create and return connection factories and destinations. JNDI requires the implementation of two Java naming interfaces, InitialContextFactory and Context. When a Java EE client requires remote access to objects in the JNDI repository, it creates an InitialContext from the InitialContextFactory. The client then uses the InitialContext to look-up connection factories and destinations. The InitialContextFactory must implement the method getInitialContext(). Here’s the Java code for that method.

import java.util.Hashtable;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.NamingException;
import java.net.URI;
public Context getInitialContext(Hashtable<?, ?> environment)
throws NamingException
{
String dbURL = this.getDBURL(environment);
String hostname = this.getHostname(environment);
int port = this.getPort(environment);
String sid = this.getSID();
String username = this.getUsername(environment);
String password = this.getPassword(environment);
OAQContext oaqCtx = null;
try {
oaqCtx = new OAQContext(hostname, username, password, sid, port);
}
catch (Exception ex) {
throw new NamingException(“Unable to create OAQContext: ” + ex.getMessage());
}
return oaqCtx;
}

This method simply parses the Hashtable object, environment, that’s an argument to the InitialContext constructor. The JMS Adapter for BizTalk constructs the Hashtable object, populating it with connection values, passing it as an argument to the InitialContext constructor in the factory class where the  method getInitialContext() is invoked. The method then creates an instance of OAQContext passing in the connection properties, where they’re stored in instance variables, and returns it. The OAQContext object is an implementation of the Context interface and therefore must implement the lookup() method.

Here’s the implementation for lookup(), notice the casts of the interface javax.jms.Session to the subclass AQjmsSession. This allows the code to invoke the proprietary methods getQueue() and getTopic().

import java.util.Hashtable;
import java.util.Properties;
import javax.naming.Binding;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NameClassPair;
import javax.naming.NameParser;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import oracle.AQ.*;
import oracle.jms.*;
import javax.jms.*;
public Object lookup(String name) throws NamingException
{
ConnectionFactory cf = null;
Connection connection = null;
Session session = null;
Object rtn = null;
cf = this.getCF(name);
if (name.equalsIgnoreCase(“QueueConnectionFactory”) || name.equalsIgnoreCase(“TopicConnectionFactory”))
{
rtn = cf;
}
else {
try {
connection = cf.createConnection(this.username, this.password);
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
}
catch (Exception ex) {
throw new NamingException(“Unable to create a connection and session: “ + ex.getMessage());
}
try {
Queue q = ((AQjmsSession)session).getQueue(this.username, name);
rtn = q;
}
catch(Exception ex) { }
if ( rtn == null) {
try {
Topic t = ((AQjmsSession)session).getTopic(this.username, name);
rtn = t;
}
catch (Exception ex) { }
}
}
if ( session != null) {
try {
session.close();
}
catch (JMSException e) { }
}
if ( connection != null) {
try {
connection.close();
}
catch (JMSException e) { }
}
if (rtn == null) {
throw new NamingException(“Unable to find object: “ + name);
}
return rtn;
}

The private method getCF() creates a ConnectionFactory object using the Oracle AQ API. If the name of the object to look-up matches the the name “QueueConnectionFactory” or “TopicConnactionFactory”, then the appropriate connection factory is returned. In the lookup() method, if a queue or topic name is the target of the look-up, then the connection factory is used to create a connection followed by a session. This is the private method getCF().

private ConnectionFactory getCF(String name) throws NamingException
{
ConnectionFactory cf = null;
try
{
if (name.equalsIgnoreCase(“QueueConnectionFactory”)) {
cf = AQjmsFactory.getQueueConnectionFactory(this.hostname, this.sid, this.port, “thin” );
}
else if (name.equalsIgnoreCase(“TopicConnectionFactory”)) {
cf = AQjmsFactory.getTopicConnectionFactory(this.hostname, this.sid, this.port, “thin” );
}
else {
cf = AQjmsFactory.getConnectionFactory(this.hostname, this.sid, this.port, “thin” );
}
}
catch (Exception ex) {
throw new NamingException(“Unable to get OAQ connection factory: “ + ex.getMessage());
}
return cf;
}

The two classes, com.jnbridge.adapters.oaq.InitialContextFactory and com.jnbridge.adapters.oaq.OAQContext must be archived in a JAR file and added to the class path property in the BizTalk transport handlers. In order to correctly build the connection URL for the Oracle Database behind AQ, the Oracle SID must be used. For example, this connection URL,

jdbc:oracle:thin:@stravinsky:1521:etude

has a SID name of etude. While the other values in the connection URL can be obtained from the InitialContext constructor argument, the SID name must use a system property. System properties can be defined and set using arguments to the Java Virtual Machine. For that reason, when configuring the JMS Adapter in BizTalk, the JVM Arguments property in the BTS transport handlers must supply this argument:

-Dcom.jnbridge.adapters.oaq.dbname=[OracleSID]

For more information on configuring the JNBridge JMS Adapter for BizTalk using the JNDI wrapper, please go here.

To download the source for the JNDI wrapper, please go here.

For more information on configuring Oracle AQ as a foreign server in Oracle WebLogic (albeit using a data source), please go here.