One of the things I've struggled with most when I was developing the
JMSJCA Resource Adapter at SeeBeyond, was the life cycle of the
ManagedConnection. I
wasted numerous hours going into fruitless
directions simply because I did not fully grasp the intricacies of the
ManagedConnection life
cycle. If you're involved in developing Resource
Adapters, you're likely to stumble onto the same problems, so read on!
What do you mean, close() should not close?
Let's take a look at how you might use a JMS Connection in an EJB:
@Resource(name="jms/cf") ConnectionFactory cf;
@Resource(name = "jmx/q1") Queue q;
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void myBusinessMethod() throws Exception {
Connection c = cf.createConnection();
Session s = c.createSession(false, Session.AUTO_ACKNOWLEDGE);
s.createProducer(q).send(s.createTextMessage("Hello world"));
c.close();
}
Should c.close()
really close the connection?
Keep in mind that since J2EE 1.4, JMS providers interface with the
application server using JCA. JMS connections should be pooled so that
repeated execution of the statement fact.createConnection() is
cheap. So the answer is no,
the connection should not really be closed.
Should c.close()
return the connection to the pool then, so that another EJB could use
it? Remember that there is a transaction in progress, so the container
should hold on to the connection until the transaction is completed.
Only then should the container return the connection to the pool.
Are you appreciating yet the complexities that the application
server
has to deal with? If not, let's add the following to the method above:
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void myBusinessMethod() throws Exception {
ConnectionFactory fact = (ConnectionFactory) new InitialContext().lookup("jms/cf");
Connection c = fact.createConnection();
Session s = c.createSession(false, Session.AUTO_ACKNOWLEDGE);
s.createProducer(s.createQueue("q")).send(s.createTextMessage("Hello world"));
c.close();
otherMethod();
}
public void otherMethod() throws Exception {
ConnectionFactory fact = (ConnectionFactory) new InitialContext().lookup("jms/cf");
Connection c = fact.createConnection();
Session s = c.createSession(false, Session.AUTO_ACKNOWLEDGE);
s.createProducer(s.createQueue("q")).send(s.createTextMessage("Goodbye world"));
c.close();
}
When myBusinessMethod()
is called, which in turn calls otherMethod(),
how many connections are created? Good application servers like the
Java CAPS Integration Server, the Sun Java System Application Server,
and Glassfish use only one connection in this example. The
connection that is used in otherMethod()
is the same connection that was created in myBusinessMethod().
Let's look in more detail at what is happening under the covers,
and why that is important.
Alphabet soup
Before we begin, let me reiterate some of the terms and
abbreviations used. JCA stands for the Java Connector Architecture. It
provides the interfacing between applications running in an application
server, and external systems such as CRM packages, but also systems
such as JMS.
A Resource Adapter is a set of classes that implement the JCA. The
central interface for outbound communications is the ManagedConnection. An
application, e.g. an EJB, never gets access to a a ManagedConnection directly.
Instead, it gets a connection handle.
The handle in the above example is the JMS Connection (actually, it is the
JMS Session -- but let's
not
go into that right now). This Connection
object is not the JMS Connection
that is implemented by the JMS provider, but is a wrapper around such a
connection. The wrapper is implemented by the Resource Adapter.
A ManagedConnection
holds the physical connection to the external system, e.g. the JMS Connection and Session. Because the physical
connection and hence the ManagedConnection
is expensive to create, the application server tries to pool the ManagedConnections.
State transitions, and why they are important
We've already seen that a ManagedConnection
can be in an idle state or pooled state and it can be in use. It would
be
very nice if the application server would tell the ManagedConnection when these
state transitions
happen. But it doesn't. Why would the ManagedConnection
care to know when it is being used or when it is being returned to the
pool? Let's look at an example in JMS. Let's change the example a
little bit:
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void usingTemp() throws Exception {
ConnectionFactory fact = (ConnectionFactory) new InitialContext().lookup("jms/cf");
Connection c = fact.createConnection();
Session s = c.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue temp = s.createTemporaryQueue();
Message request = s.createTextMessage("541897-9841");
request.setJMSReplyTo(temp);
sendRequest(request);
Message reply = s.createConsumer(temp).receive();
c.close();
}
The sendRequest()
method would somehow start a new transaction and send the request
message. The question is when the temporary destination should be
deleted. According to the JMS spec, the temporary destination should be
invalidated as soon as the connection is closed. However, a transaction
is still in progress, so the JMS provider will throw an exception if
the
temporary destination is deleted when c.close() is called. Can't we
just ignore the temporary destination? After all, it will be deleted some time, e.g. when the physical
JMS connection is closed. However, since the connection is pooled, it
may take a long time before the physical JMS connection is closed.
During that time temporary destinations just keep piling up. This
approach will exhaust the JMS server.
The temporary destination can be deleted safely when the ManagedConnection is returned
to the pool. And
that's why it's important to know the state transitions.
How to detect state transitions
What hints does the application server give the ManagedConnection about the
state transitions?
Let's take a look at the ManagedConnection
interface:
public interface ManagedConnection {
Object getConnection(Subject subject, ConnectionRequestInfo connectionRequestInfo) throws ResourceException;
void destroy() throws ResourceException;
void cleanup() throws ResourceException;
void associateConnection(Object object) throws ResourceException;
void addConnectionEventListener(ConnectionEventListener connectionEventListener);
void removeConnectionEventListener(ConnectionEventListener connectionEventListener);
XAResource getXAResource() throws ResourceException;
LocalTransaction getLocalTransaction() throws ResourceException;
ManagedConnectionMetaData getMetaData() throws ResourceException;
void setLogWriter(PrintWriter printWriter) throws ResourceException;
PrintWriter getLogWriter() throws ResourceException;
}
The getConnection()
method tells the ManagedConnection
to
create a new connection handle, so after that method the ManagedConnection knows that it
is not in the
pooled state.
The destroy() method
destroys the ManagedConnection
so it
signals a state transition to "non-existent".
Doesn't the cleanup()
method indicate that a connection is no longer used and will be
returned to the pool? It depends on the application server when exactly
this method is called. Most application servers will call cleanup() immediately when the
application calls Connection.close().
At that moment the connection may still be enlisted in a transaction,
and may be reused as we've seen in the examples above.
As it turns out, there are two states that the ManagedConnection needs to keep
track of so
that it can detect a state transition from "in-use" to "pooled".
The two diagrams keep track of whether the application has access to
the ManagedConnection
through a
connection handle. In other words, it keeps track of whether the ManagedConnection has any
outstanding
connection handles. See the figure below.

The second state is a transactional state: it keeps
track of whether the ManagedConnection
is enlisted in a transaction. See the figure below:

Let's look at a few examples that illustrate this concept:
@Resource EJBContext ctx;
@Resource(name="jms/cf") ConnectionFactory cf;
@Resource(name = "jmx/q1") Queue q;
public void ex1() {
ctx.getUserTransaction().begin();
Connection c = cf.createConnection();
Session s = c.createSession(false, Session.AUTO_ACKNOWLEDGE);
s.createProducer(q).send(s.createTextMessage("Hello world"));
c.close();
ctx.getUserTransaction().commit();
}
In this example the getConnection()
method is called upon cf.createConnection().createSession():
the ManagedConnection is
in use. At the
same time the connection is enlisted in the transaction; this is done
through the XAResource
obtained through ManagedConnection.getXAResource().
When c.close() is called,
the ManagedConnection is
no longer
accessible to the application: all the connection handles are closed.
The application server may and probably will call ManagedConnection.cleanup().
However, the connection is still enlisted in the transaction, so the
connection is not returned to the pool yet. That happens when getUserTransaction().commit()
is called. While the connection is still enlisted, the resource adapter
should not try to delete any objects such as temporary destinations in
the example mentioned above.
In the following example the enlistment happens a little later. See
the inline comments for the state transitions.
public void ex2() {
Connection c = cf.createConnection();
Session s = c.createSession(false, Session.AUTO_ACKNOWLEDGE);
ctx.getUserTransaction().begin();
s.createProducer(q).send(s.createTextMessage("Hello world"));
c.close();
ctx.getUserTransaction().commit();
}
There is also a possible transition from "Inaccessible and enlisted"
back to "Accessible and enlisted":
public void ex3() {
Connection c = cf.createConnection();
Session s = c.createSession(false, Session.AUTO_ACKNOWLEDGE);
ctx.getUserTransaction().begin();
s.createProducer(q).send(s.createTextMessage("Hello world"));
c.close();
c = cf.createConnection();
s = c.createSession(false, Session.AUTO_ACKNOWLEDGE);
s.createProducer(q).send(s.createTextMessage("Hello world"));
c.close();
ctx.getUserTransaction().commit();
}
The following example shows that there can be multiple connection
handles:
public void ex4() {
Connection c = cf.createConnection();
Session s = c.createSession(false, Session.AUTO_ACKNOWLEDGE);
ctx.getUserTransaction().begin();
s.createProducer(q).send(s.createTextMessage("Hello world"));
Connection c2 = cf.createConnection();
Session s2 = c.createSession(false, Session.AUTO_ACKNOWLEDGE);
s2.createProducer(q).send(s2.createTextMessage("Hello world"));
c2.close();
ctx.getUserTransaction().commit();
c.close();
}
How to monitor transaction enlistment
To monitor enlistment and the commit()
or rollback() method on
the XAResource it is
possible to write a wrapper around the XAResource of the underlying
provider. There are some drawbacks associated with that, and it is
better to monitor the transaction through a javax.transaction.Synchronization
object. I've described this in more detail in my blog of July
23, 2006: J2EE JCA Resource Adapters: The problem with XAResource
wrappers .
Conclusion
By keeping track of both the number of outstanding connection
handles given out to the application and the enlistment in a
transaction, the ManagedConnection
can figure out when it is returned to the pool so that it can undertake
necessary actions such as destruction of objects indirectly created by
the application.