Java EE (J2EE) application servers usually
are the ideal choice to host your back-end applications, especially if
your applications require transactions, security or state management.
Running in an application server, your application can easily connect
to external systems provided by Resource Adapters. E.g. if your
application would have to connect to a CRM (e.g. PeopleSoft), to an ERP
(e.g. SAP) or to a system such as JMS, or even to a database (e.g.
Oracle), it would use a Resource Adapter. Through this, the application
would use advanced features such as connection pooling, connection
failure detection and recovery, transactions, security propagation, etc.
As said, EE or J2EE application servers are ideal containers for
hosting business side logic. But it's not the best choice in literally
every situation. There are
cases where you would prefer to write your
application as a stand alone Java program. There's nothing strange
about that: you should always use the best tools for the job at hand,
and no single tool is best in all situations.
Now say that you need to write a stand-alone Java application, and
in that application you would need to connect to an external system.
Wouldn't it be nice to be able to use an off-the-shelf Resource Adapter
for this connectivity, so that you would not have to hand-code features
such as connection pooling, connection failure detection and recovery
etc? As I will show, this is not that difficult.
Hacking a Resource Adapter
Resource Adapters are distributed in RAR files, i.e. a file with a .rar extension. A RAR is
nothing more than a ZIP file. When you open up a RAR, you will see a
bunch of jars and a descriptor file with the name ra.xml. In a nutshell, this is
what you need to do to use a Resource Adapter:
- Add the jars to your application's classpath
- Instantiate the Resource Adapter classes (you can find out which
ones by
examining the ra.xml)
- Configure the Resource Adapter by calling a few setter methods (you can find which ones by examining the ra.xml)
- Activate the Resource Adapter (you can find out how by reading the Java Connector API specification, or just read on)
These four steps are basically what the application server does when
it loads a Resource Adapter. There's a lot of logic involved in this,
but in a stand-alone Java application we can take a lot of short-cuts
so that the logic that we need to write is not too involved.
There's quite a big difference in how a Resource Adapter is used to
provide outbound connectivity
versus inbound connectivity.
With outbound connectivity, I
mean a situation where an application obtains a connection to an
external system and
reads or writes data to it. With inbound
I mean a situation where the Resource Adapter listens for events from
the external system and calls into
your application when such an event
occurs.
As an example we will use the JMSJCA
resource adapter. This is an open-source adapter for JMS
connectivity to various JMS servers.
Outbound connectivity
In a nutshell what we need to do is instantiate the Resource Adapter
class, configure it, instanatiate a Managed Connection Factory, and
from that obtain the application-facing connection factory.
When you open up the ra.xml
file, you can find out class implements the ResourceAdapter interface:
<resourceadapter>
<resourceadapter-class>com.stc.jmsjca.unifiedjms.RAUnifiedResourceAdapter</resourceadapter-class>
The Resource Adapter class has per the specification a no-args
constructor
and should implement the javax.resource.spi.ResourceAdapter
interface. You could instantiate the ResourceAdapter simply by doing
this:
com.stc.jmsjca.unifiedjms.RAUnifiedResourceAdapter ra = new com.stc.jmsjca.unifiedjms.RAUnifiedResourceAdapter();
The drawback of this is that, although the chances of that being
small, if the classname changes in future versions of the Resource
Adapter, your
application would no longer compile. A better approach would be to read
the classname dynamically from the ra.xml. I'll leave that as "an
excercise for the reader".
The Resource Adapter is a Java Bean with getters and setters. This
is how you can configure the Resource Adapter. For example, we could
set the connection URL as follows:
ra.setConnectionURL("stcms://localhost:18007");
Next, we need to instantiate a ManagedConnectionFactory.
Again from the ra.xml,
you can find the
classname:
<outbound-resourceadapter>
<connection-definition>
<managedconnectionfactory-class>com.stc.jmsjca.core.XMCFUnifiedXA</managedconnectionfactory-class>
Again, the ManagedConnectionFactory
must have a no-arg constructor
and is a Java bean, so you can simply instantiate and configure one as
follows:
com.stc.jmsjca.core.XMCFUnifiedXA mcf = new com.stc.jmsjca.core.XMCFUnifiedXA();
mcf.setUserName("Administrator");
mcf.setPassword("STC");
Next, you may need to associate the newly created ManagedConnectionFactory with
the ResourceAdapter.
Those that require this association
(most likely all of them), implement the javax.resource.spi.ResourceAdapterAssociation
interface. This leads to the following code:
mcf.setResourceAdapter(ra);
Lastly, you create the application-facing connection factory. In
case of JMS, this is a javax.jmx.ConnectionFactory.
You can find evidence of this in the ra.xml:
<outbound-resourceadapter>
<connection-definition>
<managedconnectionfactory-class>com.stc.jmsjca.core.XMCFUnifiedXA</managedconnectionfactory-class>
...
<connectionfactory-interface>javax.jms.ConnectionFactory</connectionfactory-interface>
<connectionfactory-impl-class>com.stc.jmsjca.core.JConnectionFactoryXA</connectionfactory-impl-class>
<connection-interface>javax.jms.Connection</connection-interface>
</connection-definition>
This leads to the following code:
javax.jms.ConnectionFactory f = (javax.jms.ConnectionFactory) mcf.createConnectionFactory();
Now, putting it all together, this is what you would need to create
a JMS connection factory from the JMSJCA Resource Adapter:
com.stc.jmsjca.unifiedjms.RAUnifiedResourceAdapter ra = new com.stc.jmsjca.unifiedjms.RAUnifiedResourceAdapter();
com.stc.jmsjca.core.XMCFUnifiedXA mcf = new com.stc.jmsjca.core.XMCFUnifiedXA();
ra.setConnectionURL("stcms://localhost:18007");
ra.setUserName("Administrator");
ra.setPassword("STC");
ra.setOptions("JMSJCA.NoXA=true");
mcf.setResourceAdapter(ra);
javax.jms.ConnectionFactory f = (javax.jms.ConnectionFactory) mcf.createConnectionFactory();
And that's all there's to it
As I mentioned, one of the advantages of using a Resource Adapter
over using a client runtime directly, is that an Resource Adapter
typically provides connection pooling and other nifty features. JMSJCA
for instance provides a powerful and configurable connection manager
with blocking behavior, time-out behavior, connection failure
detection, etc. It even enlists the connection in the transaction if it
detects that there is a transaction active when the connection is
created.
Not all resource adapters will provide such a comprehensive
connection manager: check the documentation of the resource adapter
that you're planning to use. If it doesn't provide a connection manager
to your liking, you can provide your own connection manager. If you
need to write one, take a look at the one in JMSJCA: you may want to
use it as a starting point.
Inbound connectivity
Inbound connectivity is where a resource adapter is used to receive
messages from an external system; the resource adapter delivers these
messages to a Message Driven Bean. In the case of JMS, this is javax.jms.MessageListener, with
its void
onMessage(javax.jms.Message) method. For other types of resource
adapers, you will find other Message Driven Bean interfaces. Look in
the ra.xml to find out
which one.
Seting up inbound connectivity is a bit more involved than outbound
connectivity. That is because with inbound, you need to explain to
Resource Adapter how it should obtain a new instance of the Message
Driven Bean, and how to obtain a thread that will call the onMessage() method or
equivalent method.
We start with instantiating and configuring a ResourceAdapter object; the
class can be found in ra.xml
as I showed in the Outbound
Connectivity section:
com.stc.jmsjca.unifiedjms.RAUnifiedResourceAdapter ra = new com.stc.jmsjca.unifiedjms.RAUnifiedResourceAdapter();
ra.setConnectionURL("stcms://localhost:18007");
ra.setUserName("Administrator");
ra.setPassword("STC")
Next, we need to call start()
on the ResourceAdapter
object. This method takes a javax.resource.spi.BootstrapContext
object. You need to provide an implementation for this class; the most
important method that you need to implement is the public
javax.resource.spi.work.WorkManager getWorkManager() method. As
you can see, this method should return a WorkManager object. This class
has a number of methods that provide access to a threadpool. It would
help at this point if you have a bit of knowledge of the internals of
the Resource Adapter that you're trying to work with: some Adapters
don't use a WorkManager,
and the ones that do, typically only use one of the methods on the WorkManager object. For
instance, here's a WorkManager
that could be used with JMSJCA:
public class XWorkManager implements WorkManager {
private java.util.concurrent.Executor mPool;
public XWorkManager(int poolsize) {
mPool = new PooledExecutor(new LinkedQueue(), poolsize);
}
public void scheduleWork(Work work) throws WorkException {
try {
mPool.execute(work);
} catch (InterruptedException e) {
throw new WorkException(e);
}
}
// other methods just throw an exception
}
Fortunately we can make use
of the java.util.concurrent
tools introduced in JDK 5.0 for a threadpool. Next, we need to provide
an implementation for javax.resource.spi.endpoint.MessageEndpointFactory.
This is an interface with only two methods. One is there to indicate if
the message delivery should be transacted, and the other one to is
there to create a MessageEndpoint.
This is a class that is a proxy around the Message Driven Bean. This
proxy should implement the onMessage()
or equivalent method which should simply delegate to the MessageListener or equivalent
object in your application. The proxy should also implement three
additional methods:
void afterDelivery()
void beforeDelivery(Method method)
void release()
The beforeDelivery()
and afterDelivery()
methods are called just before the Resource Adapter calls the onMessage() or equivalent
method. You could start and commit a transaction in these methods; if
you're not using transactions, you can just leave these methods
unimplemented. The release()
method is called by the Resource Adapter when it's done using a Message
Driven Bean. If you don't implement some sort of pooling mechanism for
Message Driven Beans, and you probably won't, you can leave this method
empty as well.
Now that you have implementations for the BootstrapContext, WorkManager, MessageEndpointFactory, and MessageEndpoint, you can finally tell the Resource Adapter to start delivering messages to your Message Driven Bean. You do that by calling the void endpointActivation(javax.resource.spi.endpoint.MessageEndpointFactory, javax.resource.spi.ActivationSpec) method on the ResourceAdapter. The ActivationSpec object is implemented by the Resource Adapter; you can find the classname in the ra.xml file. It is a Java bean that is used to configure the message delivery. For instance, in the case of JMS, you specify from which queue or topic to get messages.
As you can see, the inbound connectivity case is not as straight
forward as the outbound connectivity case, but still very much doable.
Real examples
For a full sample source listing, take a look at the test suite in JMSJCA You can also look at the
JMSBC
as part of the Open
JBI Components project. This Binding Component uses the JMSJCA
Resource Adapter as described in this blog entry. By doing so, a lot of
development effort was saved dealing with all the idiosyncracies of
various JMS server implementations, connection management, etc.
9 comments:
thanks for the updates on jcaps.
Request for your help for the following problem.
http://forum.java.sun.com/thread.jspa?threadID=5139511
Sorry, I cannot be of much use with the problem at http://forum.java.sun.com/thread.jspa?threadID=5139511
Frank
Frank,
I tried playing with the JMSJCA and tried to configure with SonicMQ but little success using JCAPS. Do you think I can make calls from the JMSJCA from a JCD in JavaCAPS and ofcourse running on GlassFish?
BTW, for 5.1.3 you only need ESR 107235
Frank
hi, I've been trying to use this in a standalone program. how do I use it for inbound messaging? Thanks, Philip
Hi Philip,
For inbound messaging you need to implement a few JCA container classes. I'm adding this functionality to JMSJCA, and intend to blog about it; it should be available soon.
Frank
Why do we need Resource Adapters for JMS when we have spring JMS?
Is there any way to use the ra.xml file to configure the resource adaptor on standalone programs or does one need to use the setters?
We have tried changing the ra.xml, but it looks like the ra.xml file is not used in any way.
Re Arie:
In standalone programs, you do need to use the setters: there's no container that will read the ra.xml for you and call the setters.
Frank
Post a Comment