Recently I was called on to develop a Message-Driven Bean (MDB) that was to be deployed under Oracle Weblogic 10.3. Easy enough, right? The only problem was that the queue it was supposed to listen to was on a remote Unix machine, and was defined under IBM MQ Server 6.0. There are several how-to's on the internet about how this can be done, but none of them were complete enough to get it working. My goal in this article is to tie together all of the threads needed to get Weblogic 10.3 and MQ chatting away happily. The best how-to I could find is here. It provides a well-illustrated tutorial, but has a few omissions and makes a few assumptions.

Corrections/Observations

Setting up MQ

The above tutorial seems to assume that MQ and Weblogic are on the same machine; for many of us, that is not the case. In order to get MQ and Weblogic talking over the network, you must use a slightly different configuration. Instead of:

def xaqcf(MQSenderQCF) qmgr(MQTest)
def xaqcf(MQReceiverQCF) qmgr(MQTest)
def q(MQSenderQueue) qmgr(MQTest) queue(MQSenderQueue)
def q(MQReceiverQueue) qmgr(MQTest) queue(MQReceiverQueue)
end

You'd use something like this (adjusting the host, port, and channel parameters to taste):

def xaqcf(MQSenderQCF) qmgr(MQTest) HOST(mqhost.mydomain.com) PORT(1417) TRANSPORT(CLIENT) CHANNEL(HPT5.CLNT.WL)
def xaqcf(MQReceiverQCF) qmgr(MQTest) HOST(mqhost.mydomain.com) PORT(1417) TRANSPORT(CLIENT) CHANNEL(HPT5.CLNT.WL)
def q(MQSenderQueue) qmgr(MQTest) queue(MQSenderQueue)
def q(MQReceiverQueue) qmgr(MQTest) queue(MQReceiverQueue)
end

You must also copy the generated .bindings file from the directory specified in the variable PROVIDER_URL=file:/C:/MQ-JNDI on the MQ machine to the Weblogic machine and place it in a directory that makes sense to you with regard to your Weblogic installation, which leads us to ...

Setting up Weblogic

The section "Create JMS Foreign Server" mentions setting JNDI Connection URL: file:/C:/MQ-JNDI in the foreign JMS provider as the destination for the MQ-generated .bindings file. That will work, but using an absolute URL in the Weblogic configuration did not seem like a good idea to me. The domain directory would no longer be portable, and that file couldn't be wrapped up into a template using the template wizard. I looked around on the internet, but didn't find any definitive guide to relative file: URLs. I took a peek at the code that interprets file: URLs (thank you, open source!), and found that everything after the file: was used as the file path - that means file:MQ-JNDI would be a relative path! Relative to what, you might ask? In Weblogic's case, the directory is relative to the domain directory, so your MQ bindings file can reside in a directory alongside servers/, config/, etc. Note that the filename _must_ be .bindings; nothing else will work.

In section "Configuring Foreign JSM Server in BEA Weblogic Server", the tutorial mentions copying several jars into Weblogic's domain/servername/lib directory. The files fscontext.jar and providerutil.jar were available for download from Oracle at the time of this writing; to get your own copy, access this page, Click on the "Download JNDI 1.2.1 & More" button, agree to the "Software License Agreement" (if you do), and download the file fscontext-1_2-beta3.zip. I found MQ version 6.x.x jars available on this page (look on in right column for "Software Version" links).

If your application requires XA access, you must copy the jar com.ibm.mqetclient.jar into the same directory as the other MQ jars mentioned in "Configuring Foreign JSM Server in BEA Weblogic Server". Note that I'm told that the mqetclient jar is a separately-licensed product that can't be downloaded for free. You must also declare your Queue Connection Factories in MQ using xaqcf vs qcf for non-XA connection factories. The example defines XA-compliant QCFs and uses them as such. I've also declared them as non-XA (qcf) and declared the MDB transactional attribute to be NOT_SUPPORTED with good results. You will get a single warning message from Weblogic, but that's expected.

How It Works

The MQ utility that generates the .bindings file is actually creating a mini-JNDI environment for you. If you take a look at the Java docs for the RefFSContextFactory or this example, you'll see that you must store objects of type Reference, and that Reference object carries with it all of the information needed to reinflate objects stored in the .bindings file, which has been sorted for ease of use, below.

#This file is used by the JNDI FSContext.
#Tue Jun 08 13:57:31 EDT 2010
MQSenderQCF/ClassName=com.ibm.mq.jms.MQXAQueueConnectionFactory
MQSenderQCF/FactoryName=com.ibm.mq.jms.MQXAQueueConnectionFactoryFactory
MQSenderQCF/RefAddr/0/Content=6
MQSenderQCF/RefAddr/0/Encoding=String
MQSenderQCF/RefAddr/0/Type=VER
...
MQSenderQCF/RefAddr/2/Content=MQTest
MQSenderQCF/RefAddr/2/Encoding=String
MQSenderQCF/RefAddr/2/Type=QMGR
MQSenderQCF/RefAddr/3/Content=mqhost.mydomain.com
MQSenderQCF/RefAddr/3/Encoding=String
MQSenderQCF/RefAddr/3/Type=HOST
MQSenderQCF/RefAddr/4/Content=1417
MQSenderQCF/RefAddr/4/Encoding=String
MQSenderQCF/RefAddr/4/Type=PORT
MQSenderQCF/RefAddr/5/Content=HPT5.CLNT.WL
MQSenderQCF/RefAddr/5/Encoding=String
MQSenderQCF/RefAddr/5/Type=CHAN
...

See the pattern? Each Reference entry has a ClassName and optional FactoryName entry as well as some number of associated RefAddrs that correspond to the parameters used in the MQ utility that generated the .bindings file. Each RefAddr is stored with its index number, Type, encoding (String, Binary, etc), and Content. In the above example RefAddr #0 is of type "VER" (version), and the String value is "6". Sure enough, the MQ utility that generated this file was from MQ 6. Similarly the QMGR, HOST, PORT, and CHAN RefAddrs are all represented, and have the expected values. Looking up a Reference object using the JNDI initial context RefFSContextFactory reanimates the object for you using the ClassName, factory, and addresses stored in the .bindings file!

Here's a related example of how to recreate a DataSource that's been bound to a RefFS JNDI file. The following code creates a very simple Reference, stores it, and retrieves it. SInce there is no factory defined, the Reference itself is returned, _not_ the String object.

Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
env.put(Context.PROVIDER_URL, "file:MQ-JNDI");
 
try {
 Context ctx = new InitialContext(env);
 Reference ref = new Reference("java.lang.String");
 ref.add(new StringRefAddr("PO Box", "#12345"));
 ctx.rebind("Greeting", ref);
 
 ref = (Reference) ctx.lookup("Greeting");
 System.out.println(ref.getClassName());
 RefAddr ra = ref.get(0);
 System.out.println(ra.getType());
 System.out.println(ra.getContent().toString());
} catch (Exception e) {
 System.err.println("Ouch: " + e);
}

Assuming the directory "MQ-JNDI" exists in the current directory, the output of this program is:

java.lang.String
PO Box
#12345

The contents of the .bindings file in the "MQ-JNDI" directory is:

#This file is used by the JNDI FSContext.
#Mon Jun 21 16:23:02 EDT 2010
Greeting/ClassName=java.lang.String
Greeting/RefAddr/0/Content=\#12345
Greeting/RefAddr/0/Encoding=String
Greeting/RefAddr/0/Type=PO Box

Caveats

Using Wireshark to sniff the network while my MDB was active revealed that there were more TCP connections created than I could explain. It seemed that n+7 connections were created where 'n' is the number of MDBs in the pool. I also observed that the queue connections seemed to poll (horror, gasp!) the MQ server, although the default is 5000 mS and I didn't see that sort of delay in receiving messages form the queue.

I also noticed that redeploying the code caused the following sort of exception:

The Message-Driven EJB: xxxxMDB is unable to connect to the JMS destination: yyyyyyyyyyy.
The Error was: java.lang.IllegalArgumentException: Existing timer manager has different work manager.
TimerManager requested: JMSPoller-xxxxMDB
TimerManager obtained: JMSPoller-xxxxMDB
WorkManager requested:weblogic.timers.internal.TimerManagerFactoryImpl$WorkManagerExecutor@1cd924e
WorkManager obtained: weblogic.timers.internal.TimerManagerFactoryImpl$WorkManagerExecutor@173b5c0

It appears that either Weblogic or the MQ jars uses a J2EE timer to poll the MQ queue, and that either MQ or Weblogic does not clean up the timer and work managers. A reboot of the instance seems to clear this up.