1. /*
  2. * Copyright 2002-2004 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.springframework.transaction.jta;
  17. import javax.naming.NamingException;
  18. import javax.transaction.HeuristicMixedException;
  19. import javax.transaction.HeuristicRollbackException;
  20. import javax.transaction.InvalidTransactionException;
  21. import javax.transaction.NotSupportedException;
  22. import javax.transaction.RollbackException;
  23. import javax.transaction.Status;
  24. import javax.transaction.SystemException;
  25. import javax.transaction.Transaction;
  26. import javax.transaction.TransactionManager;
  27. import javax.transaction.UserTransaction;
  28. import org.springframework.beans.factory.InitializingBean;
  29. import org.springframework.jndi.JndiTemplate;
  30. import org.springframework.transaction.CannotCreateTransactionException;
  31. import org.springframework.transaction.HeuristicCompletionException;
  32. import org.springframework.transaction.IllegalTransactionStateException;
  33. import org.springframework.transaction.InvalidIsolationLevelException;
  34. import org.springframework.transaction.NoTransactionException;
  35. import org.springframework.transaction.TransactionDefinition;
  36. import org.springframework.transaction.TransactionException;
  37. import org.springframework.transaction.TransactionSystemException;
  38. import org.springframework.transaction.UnexpectedRollbackException;
  39. import org.springframework.transaction.support.AbstractPlatformTransactionManager;
  40. import org.springframework.transaction.support.DefaultTransactionStatus;
  41. /**
  42. * PlatformTransactionManager implementation for JTA, i.e. J2EE container transactions.
  43. * Can also work with a locally configured JTA implementation.
  44. *
  45. * <p>This transaction manager is appropriate for handling distributed transactions,
  46. * i.e. transactions that span multiple resources, and for managing transactions
  47. * on a J2EE Connector (e.g. a persistence toolkit registered as JCA Connector).
  48. * For a single JDBC DataSource, DataSourceTransactionManager is perfectly sufficient,
  49. * and for accessing a single resource with Hibernate (including transactional cache),
  50. * HibernateTransactionManager is appropriate.
  51. *
  52. * <p>Transaction synchronization is active by default, to allow data access support
  53. * classes to register resources that are opened within the transaction for closing at
  54. * transaction completion time. Spring's support classes for JDBC, Hibernate and JDO
  55. * all perform such registration, allowing for reuse of the same Hibernate Session etc
  56. * within the transaction. Standard JTA does not even guarantee that for Connections
  57. * from a transactional JDBC DataSource: Spring's synchronization solves those issues.
  58. *
  59. * <p>Synchronization is also leveraged for transactional cache handling with Hibernate.
  60. * Therefore, as long as JtaTransactionManager drives the JTA transactions, there is
  61. * no need to configure Hibernate's JTATransaction strategy or a container-specific
  62. * Hibernate TransactionManagerLookup. However, certain JTA implementations are
  63. * restrictive in terms of what JDBC calls they allow after transaction completion,
  64. * complaining even on close calls: In that case, it is indeed necessary to configure a
  65. * Hibernate TransactionManagerLookup, potentially via Spring's LocalSessionFactoryBean.
  66. *
  67. * <p>If JtaTransactionManager participates in an existing JTA transaction, e.g. from
  68. * EJB CMT, synchronization will be triggered on finishing the nested transaction,
  69. * before passing transaction control back to the J2EE container. In this case, a
  70. * container-specific Hibernate TransactionManagerLookup is the only way to achieve
  71. * exact afterCompletion callbacks for transactional cache handling with Hibernate.
  72. * In such a scenario, use Hibernate >=2.1 which features automatic JTA detection.
  73. *
  74. * <p><b>For typical JTA transactions (REQUIRED, SUPPORTS, MANDATORY, NEVER),
  75. * a plain JtaTransactionManager definition is all you need, completely portable
  76. * across all J2EE servers.</b> This corresponds to the functionality of the JTA
  77. * UserTransaction, for which J2EE specifies a standard JNDI name
  78. * ("java:comp/UserTransaction"). There is no need to configure a server-specific
  79. * TransactionManager lookup for this kind of JTA usage.
  80. *
  81. * <p><b>Note: Advanced JTA usage below. Dealing with these mechanisms is not
  82. * necessary for typical usage scenarios.</b>
  83. *
  84. * <p>Transaction suspension (REQUIRES_NEW, NOT_SUPPORTED) is just available with
  85. * a JTA TransactionManager being registered, via the "transactionManagerName" or
  86. * "transactionManager" property. The location of this internal JTA object is
  87. * <i>not</i> specified by J2EE; it is individual for each J2EE server, often kept
  88. * in JNDI like the UserTransaction. Some well-known JNDI locations are:
  89. * <ul>
  90. * <li>"java:comp/UserTransaction" for Resin, Orion, JOnAS (JOTM)
  91. * <li>"java:/TransactionManager" for JBoss, JRun4
  92. * <li>"javax.transaction.TransactionManager" for BEA WebLogic
  93. * </ul>
  94. *
  95. * <p>"java:comp/UserTransaction" as JNDI name for the TransactionManager means that
  96. * the same JTA object implements both the UserTransaction and the TransactionManager
  97. * interface. As this is easy to test when looking up the UserTransaction, this will
  98. * be auto-detected on initialization of JtaTransactionManager. Thus, there's no need
  99. * to specify the "transactionManagerName" in this case (for Resin, Orion, JOnAS).
  100. *
  101. * <p>A JNDI lookup can also be factored out into a corresponding JndiObjectFactoryBean,
  102. * passed into JtaTransactionManager's "transactionManager" property. Such a bean
  103. * definition can then be reused by other objects, for example Spring's
  104. * LocalSessionFactoryBean for Hibernate (see below).
  105. *
  106. * <p>For IBM WebSphere and standalone JOTM, static accessor methods are required to
  107. * obtain the JTA TransactionManager: Therefore, WebSphere and JOTM have their own
  108. * FactoryBean implementations, to be wired with the "transactionManager" property.
  109. * In case of JotmFactoryBean, the same JTA object implements UserTransaction too:
  110. * Therefore, passing the object to the "userTransaction" property is sufficient.
  111. *
  112. * <p>The internal JTA TransactionManager can also be used to register custom
  113. * synchronizations with the JTA transaction itself instead of Spring's
  114. * transaction manager. This is particularly useful for closing resources with
  115. * rigid JTA implementations like Weblogic's or WebSphere's that do not allow
  116. * any access to resources after transaction completion, not even for cleanup.
  117. * For example, Hibernate access is affected by this issue, as outlined above
  118. * in the discussion of transaction synchronization.
  119. *
  120. * <p>Spring's LocalSessionFactoryBean for Hibernate supports plugging a given
  121. * JTA TransactionManager into Hibernate's TransactionManagerLookup mechanism,
  122. * for Hibernate-driven cache synchronization and proper cleanup without warnings.
  123. * The same JTA TransactionManager configuration as above can be used in this case
  124. * (with a JndiObjectFactoryBean for a JNDI lookup, or one of the FactoryBeans),
  125. * avoiding double configuration. Alternatively, specify corresponding Hibernate
  126. * properties (see Hibernate docs for details).
  127. *
  128. * <p>JtaTransactionManager supports timeouts but not custom isolation levels.
  129. * Custom subclasses can override applyIsolationLevel for specific JTA
  130. * implementations. Note that some resource-specific transaction managers like
  131. * DataSourceTransactionManager and HibernateTransactionManager do support timeouts,
  132. * custom isolation levels, and transaction suspension without JTA's restrictions.
  133. *
  134. * @author Juergen Hoeller
  135. * @since 24.03.2003
  136. * @see #setUserTransactionName
  137. * @see #setUserTransaction
  138. * @see #setTransactionManagerName
  139. * @see #setTransactionManager
  140. * @see #applyIsolationLevel
  141. * @see JotmFactoryBean
  142. * @see WebSphereTransactionManagerFactoryBean
  143. * @see org.springframework.jndi.JndiObjectFactoryBean
  144. * @see org.springframework.orm.hibernate.LocalSessionFactoryBean#setJtaTransactionManager
  145. * @see org.springframework.orm.hibernate.HibernateTransactionManager
  146. * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
  147. */
  148. public class JtaTransactionManager extends AbstractPlatformTransactionManager implements InitializingBean {
  149. public static final String DEFAULT_USER_TRANSACTION_NAME = "java:comp/UserTransaction";
  150. private JndiTemplate jndiTemplate = new JndiTemplate();
  151. private UserTransaction userTransaction;
  152. private String userTransactionName = DEFAULT_USER_TRANSACTION_NAME;
  153. private TransactionManager transactionManager;
  154. private String transactionManagerName;
  155. /**
  156. * Set the JndiTemplate to use for JNDI lookups.
  157. * A default one is used if not set.
  158. */
  159. public void setJndiTemplate(JndiTemplate jndiTemplate) {
  160. if (jndiTemplate == null) {
  161. throw new IllegalArgumentException("jndiTemplate must not be null");
  162. }
  163. this.jndiTemplate = jndiTemplate;
  164. }
  165. /**
  166. * Set the JTA UserTransaction to use as direct reference.
  167. * Typically just used for local JTA setups; in a J2EE environment,
  168. * the UserTransaction will always be fetched from JNDI.
  169. */
  170. public void setUserTransaction(UserTransaction userTransaction) {
  171. this.userTransaction = userTransaction;
  172. }
  173. /**
  174. * Set the JNDI name of the JTA UserTransaction.
  175. * The default one is used if not set.
  176. * @see #DEFAULT_USER_TRANSACTION_NAME
  177. */
  178. public void setUserTransactionName(String userTransactionName) {
  179. this.userTransactionName = userTransactionName;
  180. }
  181. /**
  182. * Set the JTA TransactionManager to use as direct reference.
  183. * <p>A TransactionManager is necessary for suspending and resuming transactions,
  184. * as this not supported by the UserTransaction interface.
  185. */
  186. public void setTransactionManager(TransactionManager transactionManager) {
  187. this.transactionManager = transactionManager;
  188. }
  189. /**
  190. * Set the JNDI name of the JTA TransactionManager.
  191. * <p>A TransactionManager is necessary for suspending and resuming transactions,
  192. * as this not supported by the UserTransaction interface.
  193. */
  194. public void setTransactionManagerName(String transactionManagerName) {
  195. this.transactionManagerName = transactionManagerName;
  196. }
  197. public void afterPropertiesSet() throws CannotCreateTransactionException {
  198. if (this.userTransaction == null) {
  199. if (this.userTransactionName != null) {
  200. this.userTransaction = lookupUserTransaction(this.userTransactionName);
  201. }
  202. else {
  203. throw new IllegalArgumentException("Either userTransaction or userTransactionName must be set");
  204. }
  205. }
  206. if (this.transactionManager == null) {
  207. if (this.transactionManagerName != null) {
  208. this.transactionManager = lookupTransactionManager(this.transactionManagerName);
  209. }
  210. else if (this.userTransaction instanceof TransactionManager) {
  211. if (logger.isInfoEnabled()) {
  212. logger.info("JTA UserTransaction object [" + this.userTransaction + "] implements TransactionManager");
  213. }
  214. this.transactionManager = (TransactionManager) this.userTransaction;
  215. }
  216. else {
  217. logger.info("No JTA TransactionManager specified - transaction suspension not available");
  218. }
  219. }
  220. }
  221. /**
  222. * Look up the JTA UserTransaction in JNDI via the configured name.
  223. * Called by afterPropertiesSet if no direct UserTransaction reference was set.
  224. * Can be overridden in subclasses to provide a different UserTransaction object.
  225. * @param userTransactionName the JNDI name of the UserTransaction
  226. * @return the UserTransaction object
  227. * @throws CannotCreateTransactionException if the JNDI lookup failed
  228. * @see #setJndiTemplate
  229. * @see #setUserTransactionName
  230. */
  231. protected UserTransaction lookupUserTransaction(String userTransactionName)
  232. throws CannotCreateTransactionException {
  233. try {
  234. Object jndiObj = this.jndiTemplate.lookup(userTransactionName);
  235. if (!(jndiObj instanceof UserTransaction)) {
  236. throw new CannotCreateTransactionException("Object [" + jndiObj + "] available at JNDI location [" +
  237. userTransactionName + "] does not implement " +
  238. "javax.transaction.UserTransaction");
  239. }
  240. UserTransaction ut = (UserTransaction) jndiObj;
  241. if (logger.isInfoEnabled()) {
  242. logger.info("Using JTA UserTransaction [" + ut + "] from JNDI location [" +
  243. userTransactionName + "]");
  244. }
  245. return ut;
  246. }
  247. catch (NamingException ex) {
  248. throw new CannotCreateTransactionException("JTA UserTransaction is not available at JNDI location [" +
  249. userTransactionName + "]", ex);
  250. }
  251. }
  252. /**
  253. * Look up the JTA TransactionManager in JNDI via the configured name.
  254. * Called by afterPropertiesSet if no direct TransactionManager reference was set.
  255. * Can be overridden in subclasses to provide a different TransactionManager object.
  256. * @param transactionManagerName the JNDI name of the TransactionManager
  257. * @return the UserTransaction object
  258. * @throws CannotCreateTransactionException if the JNDI lookup failed
  259. * @see #setJndiTemplate
  260. * @see #setTransactionManagerName
  261. */
  262. protected TransactionManager lookupTransactionManager(String transactionManagerName)
  263. throws CannotCreateTransactionException {
  264. try {
  265. Object jndiObj = this.jndiTemplate.lookup(transactionManagerName);
  266. if (!(jndiObj instanceof TransactionManager)) {
  267. throw new CannotCreateTransactionException("Object [" + jndiObj + "] available at JNDI location [" +
  268. transactionManagerName + "] does not implement " +
  269. "javax.transaction.TransactionManager");
  270. }
  271. TransactionManager tm = (TransactionManager) jndiObj;
  272. if (logger.isInfoEnabled()) {
  273. logger.info("Using JTA TransactionManager [" + tm + "] from JNDI location [" +
  274. transactionManagerName + "]");
  275. }
  276. return tm;
  277. }
  278. catch (NamingException ex) {
  279. throw new CannotCreateTransactionException("JTA TransactionManager is not available at JNDI location [" +
  280. transactionManagerName + "]", ex);
  281. }
  282. }
  283. protected Object doGetTransaction() {
  284. return this.userTransaction;
  285. }
  286. protected boolean isExistingTransaction(Object transaction) {
  287. try {
  288. int status = ((UserTransaction) transaction).getStatus();
  289. return (status != Status.STATUS_NO_TRANSACTION);
  290. }
  291. catch (SystemException ex) {
  292. throw new TransactionSystemException("JTA failure on getStatus", ex);
  293. }
  294. }
  295. protected void doBegin(Object transaction, TransactionDefinition definition) {
  296. if (logger.isDebugEnabled()) {
  297. logger.debug("Beginning JTA transaction [" + transaction + "] ");
  298. }
  299. UserTransaction ut = (UserTransaction) transaction;
  300. applyIsolationLevel(definition.getIsolationLevel());
  301. try {
  302. if (definition.getTimeout() > TransactionDefinition.TIMEOUT_DEFAULT) {
  303. ut.setTransactionTimeout(definition.getTimeout());
  304. }
  305. ut.begin();
  306. }
  307. catch (NotSupportedException ex) {
  308. // assume "nested transactions not supported"
  309. throw new IllegalTransactionStateException(
  310. "JTA implementation does not support nested transactions", ex);
  311. }
  312. catch (UnsupportedOperationException ex) {
  313. // assume "nested transactions not supported"
  314. throw new IllegalTransactionStateException(
  315. "JTA implementation does not support nested transactions", ex);
  316. }
  317. catch (SystemException ex) {
  318. throw new TransactionSystemException("JTA failure on begin", ex);
  319. }
  320. }
  321. /**
  322. * Apply the given transaction isolation level. Default implementation
  323. * will throw an exception for any level other than ISOLATION_DEFAULT.
  324. * To be overridden in subclasses for specific JTA implementations.
  325. * @param isolationLevel isolation level taken from transaction definition
  326. * @throws InvalidIsolationLevelException if the given isolation level
  327. * cannot be applied
  328. */
  329. protected void applyIsolationLevel(int isolationLevel) throws InvalidIsolationLevelException {
  330. if (isolationLevel != TransactionDefinition.ISOLATION_DEFAULT) {
  331. throw new InvalidIsolationLevelException("JtaTransactionManager does not support custom isolation levels");
  332. }
  333. }
  334. protected Object doSuspend(Object transaction) {
  335. if (this.transactionManager == null) {
  336. throw new IllegalTransactionStateException("JtaTransactionManager needs a JTA TransactionManager for " +
  337. "suspending a transaction - specify the 'transactionManager' " +
  338. "or 'transactionManagerName' property");
  339. }
  340. try {
  341. return this.transactionManager.suspend();
  342. }
  343. catch (SystemException ex) {
  344. throw new TransactionSystemException("JTA failure on suspend", ex);
  345. }
  346. }
  347. protected void doResume(Object transaction, Object suspendedResources) {
  348. if (this.transactionManager == null) {
  349. throw new IllegalTransactionStateException("JtaTransactionManager needs a JTA TransactionManager for " +
  350. "suspending a transaction - specify the 'transactionManager' " +
  351. "or 'transactionManagerName' property");
  352. }
  353. try {
  354. this.transactionManager.resume((Transaction) suspendedResources);
  355. }
  356. catch (InvalidTransactionException ex) {
  357. throw new IllegalTransactionStateException("Tried to resume invalid JTA transaction", ex);
  358. }
  359. catch (SystemException ex) {
  360. throw new TransactionSystemException("JTA failure on resume", ex);
  361. }
  362. }
  363. protected boolean isRollbackOnly(Object transaction) throws TransactionException {
  364. try {
  365. return ((UserTransaction) transaction).getStatus() == Status.STATUS_MARKED_ROLLBACK;
  366. }
  367. catch (SystemException ex) {
  368. throw new TransactionSystemException("JTA failure on getStatus", ex);
  369. }
  370. }
  371. protected void doCommit(DefaultTransactionStatus status) {
  372. if (status.isDebug()) {
  373. logger.debug("Committing JTA transaction [" + status.getTransaction() + "]");
  374. }
  375. try {
  376. ((UserTransaction) status.getTransaction()).commit();
  377. }
  378. catch (RollbackException ex) {
  379. throw new UnexpectedRollbackException("JTA transaction rolled back", ex);
  380. }
  381. catch (HeuristicMixedException ex) {
  382. throw new HeuristicCompletionException(HeuristicCompletionException.STATE_MIXED, ex);
  383. }
  384. catch (HeuristicRollbackException ex) {
  385. throw new HeuristicCompletionException(HeuristicCompletionException.STATE_ROLLED_BACK, ex);
  386. }
  387. catch (SystemException ex) {
  388. throw new TransactionSystemException("JTA failure on commit", ex);
  389. }
  390. }
  391. protected void doRollback(DefaultTransactionStatus status) {
  392. if (status.isDebug()) {
  393. logger.debug("Rolling back JTA transaction [" + status.getTransaction() + "]");
  394. }
  395. try {
  396. ((UserTransaction) status.getTransaction()).rollback();
  397. }
  398. catch (SystemException ex) {
  399. throw new TransactionSystemException("JTA failure on rollback", ex);
  400. }
  401. }
  402. protected void doSetRollbackOnly(DefaultTransactionStatus status) {
  403. if (status.isDebug()) {
  404. logger.debug("Setting JTA transaction [" + status.getTransaction() + "] rollback-only");
  405. }
  406. try {
  407. ((UserTransaction) status.getTransaction()).setRollbackOnly();
  408. }
  409. catch (IllegalStateException ex) {
  410. throw new NoTransactionException("No active JTA transaction");
  411. }
  412. catch (SystemException ex) {
  413. throw new TransactionSystemException("JTA failure on setRollbackOnly", ex);
  414. }
  415. }
  416. protected void doCleanupAfterCompletion(Object transaction) {
  417. // nothing to do here
  418. }
  419. }