With the current spec (1.4) it seems unsafe to use a stateful session bean (SFSB) from a stateless session bean (SLSB) or in certain cases even message driven beans (MDB) when using (propagated) transactions.
But I did not see any warnings or explicit limitations in this regard in the spec. Neither did I see any other users reporting and warning on this issue. Is any one else aware of this issue?
I used the samples that come with the
J2EE 1.4 reference implementation (FCS) to reproduce the problem. I chained together the "simple" mdb, stateless and stateful samples to get the following calling chain.
JMS->MDB->SLSB->SFSB
The SLSB instance acts as the SFSB client, i.e. creates a SFSB instance in its ejbCreate and removes it in the ejbRemove, so there is a 1-to-1 relationship between the SLSB instance and the SFSB. It keeps the reference as internal state.
I also modified the deployment descriptors to container managed transactions and ensured that the transaction attributes on the methods called were 'Required' so the same transaction is propagated to the MDB, to the SLSB and then the SFSB. The commit on the transaction in this scenario happens when the JMS resource adapter calls "afterDelivery" and the container completes the transaction.
To get to the point, it will result in "java.lang.IllegalStateException: EJB is already associated with an incomplete transaction" exceptions when running on the RI.
The spec states that:
- a stateful session bean stays associated with a transaction until it completes
- a stateful session bean can only participate in one transaction at a time, and should throw an exception if a client invokes it with a different transaction context whilst it is still associated
- a stateless session bean is returned to the method ready pool as soon as the business method invocation completes, and as soon as it is in the method ready pool, the container can delegate further work to it.
The above means that the SLSB is returned to the pool before the propagated transaction completes, ready for new work. But at this point the SFSB is still associated with the transaction. This transaction might not be completed for quite a while, e.g. the MDB could do more work in the onMessage before returning and the container commiting.
If the SLSB is then assigned work for another client (with a different transaction), it will try to call the potentially "already associated" stateful session bean - and the container will throw an exception as specified in the spec.
It should be noted that indirectly, the SLSB has kept client state across invocations, thanks to its SFSB staying associated with the previous transaction. This is obviously against the spec. But it should also be noted that I don't see any reasonable way to use this SFSB component from the SLSB in this scenario. For example, if the SLSB were to create an SFSB instance as part of the business method invoke (instead of ejbCreate), it would not be allowed to remove the SFSB before returning - the spec disallows ejbRemove to get called on the SFSB whilst it is still associated with a transaction (= again, an exception).
I believe that according to the spec, even the simple JMS->MDB->SFSB has the potential to fail, if the container sets the MDB to method-ready pool state right after the message listener invoke, before processing the transaction completion. In the current reference implementation, this is not a problem though as it only sets the MDB to pooled state in the afterDelivery() handling, and in there after completing the transaction.
However, if a Resource Adapter with transaction inflow contract was used instead, even in the reference implementation this scenario would fail as the completion of the transaction would not happen in the afterDelivery, only the return of the MDB to the method-ready pool.
RA (transaction inflow contract)->MDB->SFSB
Another simple sample scenario which should fail is
WAR (
JSP,
servlet)->SLSB->SFSB
If the transaction is started at the web component level and propagated to the EJBs.
Below is the full stack trace for the exception; when I feed 1000 messages to my scenario, the server.log easily grows to 20 MB full of these:
java.lang.IllegalStateException: EJB is already associated with an incomplete transaction
at com.sun.ejb.containers.BaseContainer.useClientTx(BaseContainer.java:2305)
at com.sun.ejb.containers.BaseContainer.preInvokeTx(BaseContainer.java:2141)
at com.sun.ejb.containers.BaseContainer.preInvoke(BaseContainer.java:726)
at com.sun.ejb.containers.EJBObjectInvocationHandler.invoke(EJBObjectInvocationHandler.java:126)
at $Proxy9.getContents(Unknown Source)
at samples.ejb.stateful.simple.ejb._Cart_Stub.getContents(Unknown Source)
at samples.ejb.stateless.simple.ejb.GreeterEJB.getGreeting(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at com.sun.enterprise.security.SecurityUtil$2.run(SecurityUtil.java:146)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.enterprise.security.application.EJBSecurityManager.doAsPrivileged(EJBSecurityManager.java:930)
at com.sun.enterprise.security.SecurityUtil.invoke(SecurityUtil.java:151)
at com.sun.ejb.containers.EJBObjectInvocationHandler.invoke(EJBObjectInvocationHandler.java:128)
at $Proxy7.getGreeting(Unknown Source)
at samples.ejb.stateless.simple.ejb._Greeter_Stub.getGreeting(Unknown Source)
at samples.ejb.mdb.simple.ejb.SimpleMessageBean.onMessage(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at com.sun.enterprise.security.SecurityUtil$2.run(SecurityUtil.java:146)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.enterprise.security.application.EJBSecurityManager.doAsPrivileged(EJBSecurityManager.java:930)
at com.sun.enterprise.security.SecurityUtil.invoke(SecurityUtil.java:151)
at com.sun.ejb.containers.MessageBeanContainer.deliverMessage(MessageBeanContainer.java:944)
at com.sun.ejb.containers.MessageBeanListenerImpl.deliverMessage(MessageBeanListenerImpl.java:42)
at com.sun.enterprise.connectors.inflow.MessageEndpointInvocationHandler.invoke(MessageEndpointInvocationHandler.java:130)
at $Proxy10.onMessage(Unknown Source)
at com.sun.messaging.jms.ra.OnMessageRunner.run(OnMessageRunner.java:165)
at com.sun.enterprise.connectors.work.OneWork.doWork(OneWork.java:45)
at com.sun.corba.ee.impl.orbutil.threadpool.ThreadPoolImpl$WorkerThread.run(ThreadPoolImpl.java:382)
javax.ejb.EJBException: nested exception is: java.lang.IllegalStateException: EJB is already associated with an incomplete transaction
at com.sun.ejb.containers.BaseContainer.preInvoke(BaseContainer.java:742)
at com.sun.ejb.containers.EJBObjectInvocationHandler.invoke(EJBObjectInvocationHandler.java:126)
at $Proxy9.getContents(Unknown Source)
at samples.ejb.stateful.simple.ejb._Cart_Stub.getContents(Unknown Source)
at samples.ejb.stateless.simple.ejb.GreeterEJB.getGreeting(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at com.sun.enterprise.security.SecurityUtil$2.run(SecurityUtil.java:146)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.enterprise.security.application.EJBSecurityManager.doAsPrivileged(EJBSecurityManager.java:930)
at com.sun.enterprise.security.SecurityUtil.invoke(SecurityUtil.java:151)
at com.sun.ejb.containers.EJBObjectInvocationHandler.invoke(EJBObjectInvocationHandler.java:128)
at $Proxy7.getGreeting(Unknown Source)
at samples.ejb.stateless.simple.ejb._Greeter_Stub.getGreeting(Unknown Source)
at samples.ejb.mdb.simple.ejb.SimpleMessageBean.onMessage(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at com.sun.enterprise.security.SecurityUtil$2.run(SecurityUtil.java:146)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.enterprise.security.application.EJBSecurityManager.doAsPrivileged(EJBSecurityManager.java:930)
at com.sun.enterprise.security.SecurityUtil.invoke(SecurityUtil.java:151)
at com.sun.ejb.containers.MessageBeanContainer.deliverMessage(MessageBeanContainer.java:944)
at com.sun.ejb.containers.MessageBeanListenerImpl.deliverMessage(MessageBeanListenerImpl.java:42)
at com.sun.enterprise.connectors.inflow.MessageEndpointInvocationHandler.invoke(MessageEndpointInvocationHandler.java:130)
at $Proxy10.onMessage(Unknown Source)
at com.sun.messaging.jms.ra.OnMessageRunner.run(OnMessageRunner.java:165)
at com.sun.enterprise.connectors.work.OneWork.doWork(OneWork.java:45)
at com.sun.corba.ee.impl.orbutil.threadpool.ThreadPoolImpl$WorkerThread.run(ThreadPoolImpl.java:382)
|#]