JTA 1.2 Implementation Work
paul.robinson Jul 24, 2013 7:03 AMIntroduction
This document discusses the outstanding issues and design decisions relating to our implementation of the JTA 1.2 specification.
The JTA 1.2 Spec and JavaDoc can be obtained from here. The implementation is being developed here.
The two major changes are @Transactional (EJB-like transaction annotations for managed beans) and @TransactionScoped (A CDI scope tied to the lifecycle of the active transaction). Each of these is covered in it's own section bellow. There are some smaller changes too, for which it is yet to be determined if they require any code changes. These are all discussed in the "Other Changes" section.
Current Status of Implementation
Preview implementation in Narayana 5.0.0.M3 and WildFly 8.0.0.Alpha3.
Certification
Test against Java EE 7 CTS. Passes with a modification to the CTS. This modification should no longer be required. See here: https://issues.jboss.org/browse/JBCTS-1268
Test against JTA 1.2 TCK (Only required to test that we comply with the "XAResource#end" issue). Awaiting confirmation that this passes.
@TransactionScoped
Implementing a new context in CDI is relatively straight-forward. You just need to implement the javax.enterprise.context.spi.Context interface. Here's the pseudo code for this impl:
public class TransactionContext implements Context { public Class<? extends Annotation> getScope() { return TransactionScoped.class; } public <T> T get(Contextual<T> contextual, CreationalContext<T> creationalContext) { bean = (PassivationCapabale) contextual if (bean already in TSR) { return bean } else if (creationalContext != null ) { create new bean add bean to TSR return bean } else { return null } } public <T> T get(Contextual<T> contextual) { return get(contextual, null); } public boolean isActive() { return true if current transaction status in {STATUS_ACTIVE, STATUS_MARKED_ROLLBACK, STATUS_PREPARED, STATUS_UNKNOWN, STATUS_PREPARING, STATUS_COMMITTING, STATUS_ROLLING_BACK } } }
Do we Implement from scratch or extend a Weld Abstract Context? [RESOLVED]
Weld implements many different Contexts (SessionScoped, ApplicationScoped, RequestScoped, etc) and as a result many of the common implementation details have been abstracted into a largish class hierarchy, making the actual implementations of the specific Contexts much simpler. We may be able to extend one of these Abstract Contexts. However, it's not clear to me exactly which one should be extended. Also, this would place a dependency on Weld, which I'm not sure is a good idea. I think it would be better to simply depend on the CDI API, so that our implementation remains portable. Also the implementation of the TransactionContext is quite simple, so we may be over-engineering the solution by extending a Weld Context.
We should implement this from scratch as any class we might extend is not part of a supported interface and so could be removed/changed without notice. See comment from @pmuir bellow.
How are the out-of-scope beans garbage-collected? [RESOLVED]
The 'isActive' method ensures that the context is recognized as inactive, outside of an active transaction. However, the TransactionalBeanStore will collect garbage over time and so needs clearing at some point after the Context is no longer active. These are the current options that I think we have:
- TransactionSynchronizationRegistry. We can use the putResource and getResource methods to store objects against the current transaction. These instances seem to be garbage collected when the transaction completes.
- AfterCompletion callback. We could register a synchronisation participant with the transaction and then clear the TransactionalBeanStore during afterCompletion. This should be fine as I don't think the Context is available in the afterCompletion callback. The spec does state that "Any Synchronization.afterCompletion methods will be invoked in an undefined context".
- Bespoke invocation from the TM. If we have any problems with the previous solutions, we could simply make a direct call from the transaction manager at an appropriate point in the lifecycle of the transaction.
We are going with the TSR approach.
What's the best way to enable this feature in WildFly? [RESOLVED]
The implementations asociated with @Transactional and @TransactionScoped are registered via a CDI Portable Extension. The naryana jar in the org.jboss.jts module needs to contain a refrence to the PE via the "/META-INF/services/javax.enterprise.inject.spi.Extension" file. The application can then have these PE registered by exporting the services when declaring the dependency on "org.jboss.jts". This is done by putting this line in the MANIFEST.MF: "Dependencies: org.jboss.jts export services".
@Transactional
Is something similar implemented elsewhere? [RESOLVED]
There's currently two implementations (that I am aware of) that implement something similar:
- EJB 3. This implementation manages a JTA transaction, but is more EJB specific.
- DeltaSpike. This implementation seems to manage a hibernate transaction, but is more CDI specific.
Neither seem close enough to be simply copied/re-used/etc. Therefore, my current thoughts are to implement our own, using the EJB 3 implementation to understand any corner-cases that I have not already thought of.
How are these interceptors enabled by default? [RESOLVED]
In CDI 1.0, all interceptors where disabled by default, with the application needing to specify which interceptors to enable. In CDI 1.1 interceptors are enabled by default by using @Priority. The JTA spec should tell us what value to use.
How do I detect invalid usages of UserTransaction?
According to the JTA 1.2 spec:
If an attempt is made to call any method of the UserTransaction interface from within the scope of a bean or method annotated with @Transactional and a Transactional.TxType other than NOT_SUPPORTED or NEVER, an IllegalStateException must be throw
In EJB 3 a deployment failure occurs if you attempt to inject a UserTransaction into a CMT bean. This is because EJB disallows the usage of UserTransaction for any method in a CMT bean, regardless of the transaction attribute used. However, we can't take this approach. With @Transactional, some methods may use the injected UserTransaction, when others may not. Therefore, I think we need to enable/disable the usage of UserTransaction at runtime.
I think the way to do it is to somehow "mark" the UserTransaction as unavailable/available (depending on the TxType) in the interceptor, prior to proceeding the invocation. The old value (unavailable/available) is restored in a finally block, to ensure it is always restored. Pseudo code here:
@AroundInvoke public Object intercept(InvocationContext ic) { oldState = //current availability of UserTransaction if (TxType in {NOT_SUPPORTED, NEVER}) { //Mark UserTransaction as available } else { //Mark UserTransaction as unavailable } try { ic.proceed(); } finally { //Restore UserTransaction availability via oldState variable } }
I've simplified the above code to just show the enabling/disabling of UserTransaction.
When injecting UserTransaction via the @Resource annotation, you get an instance of org.jboss.tm.usertx.client.ServerVMClientUserTransaction from JNDI. ServerVMClientUserTransaction lives in jboss-transaction-spi. Marking available/Unavailable could be done by calling methods on ServerVMClientUserTransaction (default is available, so this will not break any other places that use ServerVMClientUserTransaction). This would cause the availability to be stored in a ThreadLocal. Each method that implements UserTransaction would first check that the UserTransaction is available for use (throwing an IllegalStateException if not) and then continuing as before.
Other Spec Changes
Most other changes seem to be clarifications, that don't result in changes to the implementation.
There is one other change that may impact the implementation:
Added the following description to the end of Section 3.3.1, “ResourceEnlistment”: "A container only needs to call delistResource to explicitly dissociate a resource from a transaction and it is not a mandatory container requirement to do so as a precondition to transaction completion. A transaction manager is, however, required to implicitly insure the association of any associated XAResource is ended, via the appropriate XAResource.end call, immediately prior to completion; that is before prepare (or commit/rollback in the onephase-optimized case)."
It looks like we already call XAResource#end for all XAResources, just not all at the same time.
In com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple#commit (and #rollback), endSuspendedRMs() is invoked. This calls XAResource#end on all those in state TxInfo.ASSOCIATION_SUSPENDED.
Later, in com.arjuna.ats.internal.jta.resources.arjunacore.XAResourceRecord#topLevelPrepare (and also in topLevelOnePhaseCommit and topLevelAbort) XAResource#end is invoked for resources that are not in either of the states TxInfo.NOT_ASSOCIATED and TxInfo.FAILED (notice call to endAssociation()).
Also, it looks it was intentional to ensure end is always called on an XAResource prior to prepare, abort or onePhaseCommit. Notice the comment: "end has been called so we don't need to do it again!".
Therefore, we already comply to the spec statement that:
A transaction manager is, however, required to implicitly ensure the association of any associated XAResource is ended, via the appropriate XAResource.end call, immediately prior to completion; that is before prepare (or commit/rollback in the onephase-optimized case).
Impact on WildFly
Update the jboss-transaction-api_spec version [RESOLVED]
Shelly is in the process of updating jboss-transaction-api_spec to version 1.2. This should be done soon.
Java EE 7 Certification
The two major changes brought in JTA 1.2 are not covered by the JTA 1.2 TCK. Therefore, I will need to wait for the availability of the Java EE 7 CTS to certify this work. In the mean time, I'll write some simple tests to make sure I'm on track.
General Issues
When something goes wrong internally to the implementation in the TransactionalContext, what RuntimeException should I throw? [RESOLVED]
TransactionalException is the only RuntimeException in the JTA 1.2 spec, and that does not seem apropriate here. Therfore, I'll just use whatever general unchecked exception we use in the JTA 1.1 implementation.
Where should I place the implementation and tests? [RESOLVED]
The code for this work should be placed under "$NARAYANA_SRC/ArjunaJTA/cdi". This ensures that we don't pollute the pure JTA (ArjunaJTA/jta) code-base with dependencies that don't make sense in a standalone scenario. It also keeps the Arquillian tests seperate from the unit tests in ArjunaJTA/jta; which is good as they need to be ran later in the testing cycle (i.e. after we have built WildFly).