/* * Copyright 2002-2008 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.transaction.jta; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.List; import java.util.Properties; import javax.naming.NamingException; import javax.transaction.HeuristicMixedException; import javax.transaction.HeuristicRollbackException; import javax.transaction.InvalidTransactionException; import javax.transaction.NotSupportedException; import javax.transaction.RollbackException; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import javax.transaction.TransactionSynchronizationRegistry; import javax.transaction.UserTransaction; import org.springframework.beans.factory.InitializingBean; import org.springframework.jndi.JndiTemplate; import org.springframework.transaction.CannotCreateTransactionException; import org.springframework.transaction.HeuristicCompletionException; import org.springframework.transaction.IllegalTransactionStateException; import org.springframework.transaction.InvalidIsolationLevelException; import org.springframework.transaction.NestedTransactionNotSupportedException; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionSuspensionNotSupportedException; import org.springframework.transaction.TransactionSystemException; import org.springframework.transaction.UnexpectedRollbackException; import org.springframework.transaction.support.AbstractPlatformTransactionManager; import org.springframework.transaction.support.DefaultTransactionStatus; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** * {@link org.springframework.transaction.PlatformTransactionManager} implementation * for JTA, delegating to a backend JTA provider. This is typically used to delegate * to a J2EE server's transaction coordinator, but may also be configured with a * local JTA provider which is embedded within the application. * * <p>This transaction manager is appropriate for handling distributed transactions, * i.e. transactions that span multiple resources, and for controlling transactions on * application server resources (e.g. JDBC DataSources available in JNDI) in general. * For a single JDBC DataSource, DataSourceTransactionManager is perfectly sufficient, * and for accessing a single resource with Hibernate (including transactional cache), * HibernateTransactionManager is appropriate, for example. * * <p>Transaction synchronization is active by default, to allow data access support * classes to register resources that are opened within the transaction for closing at * transaction completion time. Spring's support classes for JDBC, Hibernate, JDO etc * all perform such registration, allowing for reuse of the same Hibernate Session etc * within the transaction. Standard JTA does not even guarantee that for Connections * from a transactional JDBC DataSource: Spring's synchronization solves those issues. * * <p><b>For typical JTA transactions (REQUIRED, SUPPORTS, MANDATORY, NEVER), a plain * JtaTransactionManager definition is all you need, completely portable across all * J2EE servers.</b> This corresponds to the functionality of the JTA UserTransaction, * for which J2EE specifies a standard JNDI name ("java:comp/UserTransaction"). * There is no need to configure a server-specific TransactionManager lookup for this * kind of JTA usage. * * <p><b>Note: Advanced JTA usage below. Dealing with these mechanisms is not * necessary for typical usage scenarios.</b> * * <p>Transaction suspension (REQUIRES_NEW, NOT_SUPPORTED) is just available with * a JTA TransactionManager being registered, via the "transactionManagerName" or * "transactionManager" property. The location of this well-defined JTA object is * <i>not</i> specified by J2EE; it is specific to each J2EE server, often kept * in JNDI like the JTA UserTransaction. Some well-known JNDI locations are: * <ul> * <li>"java:comp/UserTransaction" for Resin 2.x, Oracle OC4J (Orion), * JOnAS (JOTM), BEA WebLogic * <li>"java:comp/TransactionManager" for Resin 3.x * <li>"java:appserver/TransactionManager" for GlassFish * <li>"java:pm/TransactionManager" for Borland Enterprise Server and * Sun Application Server (Sun ONE 7 and later) * <li>"java:/TransactionManager" for JBoss Application Server * </ul> * * <p>All of these cases are autodetected by JtaTransactionManager, provided that the * "autodetectTransactionManager" flag is set to "true" (which it is by default). * * <p><b>Note: Support for the JTA TransactionManager interface is not required by J2EE. * Almost all J2EE servers expose it, but do so as extension to J2EE. There might be some * issues with compatibility, despite the TransactionManager interface being part of JTA.</b> * As a consequence, Spring provides various vendor-specific PlatformTransactionManagers, * which are recommended to be used if appropriate: {@link WebLogicJtaTransactionManager}, * {@link WebSphereUowTransactionManager} and {@link OC4JJtaTransactionManager}. * For all other J2EE servers, the standard JtaTransactionManager is sufficient. * * <p><b>Consider using Spring 2.5's <code>tx:jta-transaction-manager</code> configuration * element for automatically picking the appropriate JTA platform transaction manager * (automatically detecting WebLogic, WebSphere and OC4J).</b> * * <p><b>This pure JtaTransactionManager supports timeouts but not per-transaction * isolation levels.</b> Custom subclasses may override {@link #doJtaBegin} for * specific JTA extensions in order to provide this functionality; Spring includes * corresponding {@link WebLogicJtaTransactionManager} and {@link OC4JJtaTransactionManager} * classes, for BEA's WebLogic Server and Oracle's OC4J, respectively. Such adapters * for specific J2EE transaction coordinators may also expose transaction names for * monitoring; with standard JTA, transaction names will simply be ignored. * * <p>JTA 1.1 adds the TransactionSynchronizationRegistry facility, as public Java EE 5 * API in addition to the standard JTA UserTransaction handle. As of Spring 2.5, this * JtaTransactionManager autodetects the TransactionSynchronizationRegistry and uses * it for registering Spring-managed synchronizations when participating in an existing * JTA transaction (e.g. controlled by EJB CMT). If no TransactionSynchronizationRegistry * is available (or the JTA 1.1 API isn't available), then such synchronizations * will be registered via the (non-J2EE) JTA TransactionManager handle. * * <p>This class is serializable. However, active synchronizations do not survive * serialization. * * @author Juergen Hoeller * @since 24.03.2003 * @see javax.transaction.UserTransaction * @see javax.transaction.TransactionManager * @see javax.transaction.TransactionSynchronizationRegistry * @see #setUserTransactionName * @see #setUserTransaction * @see #setTransactionManagerName * @see #setTransactionManager * @see JotmFactoryBean * @see WebSphereTransactionManagerFactoryBean * @see WebLogicJtaTransactionManager */ public class JtaTransactionManager extends AbstractPlatformTransactionManager implements TransactionFactory, InitializingBean, Serializable { /** * Default JNDI location for the JTA UserTransaction. Many J2EE servers * also provide support for the JTA TransactionManager interface there. * @see #setUserTransactionName * @see #setAutodetectTransactionManager */ public static final String DEFAULT_USER_TRANSACTION_NAME = "java:comp/UserTransaction"; /** * Fallback JNDI locations for the JTA TransactionManager. Applied if * the JTA UserTransaction does not implement the JTA TransactionManager * interface, provided that the "autodetectTransactionManager" flag is "true". * @see #setTransactionManagerName * @see #setAutodetectTransactionManager */ public static final String[] FALLBACK_TRANSACTION_MANAGER_NAMES = new String[] {"java:comp/TransactionManager", "java:appserver/TransactionManager", "java:pm/TransactionManager", "java:/TransactionManager"}; /** * Standard Java EE 5 JNDI location for the JTA TransactionSynchronizationRegistry. * Autodetected when available. */ public static final String DEFAULT_TRANSACTION_SYNCHRONIZATION_REGISTRY_NAME = "java:comp/TransactionSynchronizationRegistry"; private static final String TRANSACTION_SYNCHRONIZATION_REGISTRY_CLASS_NAME = "javax.transaction.TransactionSynchronizationRegistry"; private transient JndiTemplate jndiTemplate = new JndiTemplate(); private transient UserTransaction userTransaction; private String userTransactionName; private boolean autodetectUserTransaction = true; private boolean cacheUserTransaction = true; private boolean userTransactionObtainedFromJndi = false; private transient TransactionManager transactionManager; private String transactionManagerName; private boolean autodetectTransactionManager = true; private String transactionSynchronizationRegistryName; private transient Object transactionSynchronizationRegistry; private boolean allowCustomIsolationLevels = false; /** * Create a new JtaTransactionManager instance, to be configured as bean. * Invoke <code>afterPropertiesSet</code> to activate the configuration. * @see #setUserTransactionName * @see #setUserTransaction * @see #setTransactionManagerName * @see #setTransactionManager * @see #afterPropertiesSet() */ public JtaTransactionManager() { setNestedTransactionAllowed(true); } /** * Create a new JtaTransactionManager instance. * @param userTransaction the JTA UserTransaction to use as direct reference */ public JtaTransactionManager(UserTransaction userTransaction) { this(); Assert.notNull(userTransaction, "UserTransaction must not be null"); this.userTransaction = userTransaction; } /** * Create a new JtaTransactionManager instance. * @param userTransaction the JTA UserTransaction to use as direct reference * @param transactionManager the JTA TransactionManager to use as direct reference */ public JtaTransactionManager(UserTransaction userTransaction, TransactionManager transactionManager) { this(); Assert.notNull(userTransaction, "UserTransaction must not be null"); Assert.notNull(transactionManager, "TransactionManager must not be null"); this.userTransaction = userTransaction; this.transactionManager = transactionManager; } /** * Create a new JtaTransactionManager instance. * @param transactionManager the JTA TransactionManager to use as direct reference */ public JtaTransactionManager(TransactionManager transactionManager) { this(); Assert.notNull(transactionManager, "TransactionManager must not be null"); this.transactionManager = transactionManager; this.userTransaction = buildUserTransaction(transactionManager); } /** * Set the JndiTemplate to use for JNDI lookups. * A default one is used if not set. */ public void setJndiTemplate(JndiTemplate jndiTemplate) { if (jndiTemplate == null) { throw new IllegalArgumentException("jndiTemplate must not be null"); } this.jndiTemplate = jndiTemplate; } /** * Return the JndiTemplate used for JNDI lookups. */ public JndiTemplate getJndiTemplate() { return this.jndiTemplate; } /** * Set the JNDI environment to use for JNDI lookups. * Creates a JndiTemplate with the given environment settings. * @see #setJndiTemplate */ public void setJndiEnvironment(Properties jndiEnvironment) { this.jndiTemplate = new JndiTemplate(jndiEnvironment); } /** * Return the JNDI environment to use for JNDI lookups. */ public Properties getJndiEnvironment() { return this.jndiTemplate.getEnvironment(); } /** * Set the JTA UserTransaction to use as direct reference. * <p>Typically just used for local JTA setups; in a J2EE environment, * the UserTransaction will always be fetched from JNDI. * @see #setUserTransactionName * @see #setAutodetectUserTransaction */ public void setUserTransaction(UserTransaction userTransaction) { this.userTransaction = userTransaction; } /** * Return the JTA UserTransaction that this transaction manager uses. */ public UserTransaction getUserTransaction() { return this.userTransaction; } /** * Set the JNDI name of the JTA UserTransaction. * <p>Note that the UserTransaction will be autodetected at the J2EE default * location "java:comp/UserTransaction" if not specified explicitly. * @see #DEFAULT_USER_TRANSACTION_NAME * @see #setUserTransaction * @see #setAutodetectUserTransaction */ public void setUserTransactionName(String userTransactionName) { this.userTransactionName = userTransactionName; } /** * Set whether to autodetect the JTA UserTransaction at its default * JNDI location "java:comp/UserTransaction", as specified by J2EE. * Will proceed without UserTransaction if none found. * <p>Default is "true", autodetecting the UserTransaction unless * it has been specified explicitly. Turn this flag off to allow for * JtaTransactionManager operating against the TransactionManager only, * despite a default UserTransaction being available. * @see #DEFAULT_USER_TRANSACTION_NAME */ public void setAutodetectUserTransaction(boolean autodetectUserTransaction) { this.autodetectUserTransaction = autodetectUserTransaction; } /** * Set whether to cache the JTA UserTransaction object fetched from JNDI. * <p>Default is "true": UserTransaction lookup will only happen at startup, * reusing the same UserTransaction handle for all transactions of all threads. * This is the most efficient choice for all application servers that provide * a shared UserTransaction object (the typical case). * <p>Turn this flag off to enforce a fresh lookup of the UserTransaction * for every transaction. This is only necessary for application servers * that return a new UserTransaction for every transaction, keeping state * tied to the UserTransaction object itself rather than the current thread. * @see #setUserTransactionName */ public void setCacheUserTransaction(boolean cacheUserTransaction) { this.cacheUserTransaction = cacheUserTransaction; } /** * Set the JTA TransactionManager to use as direct reference. * <p>A TransactionManager is necessary for suspending and resuming transactions, * as this not supported by the UserTransaction interface. * <p>Note that the TransactionManager will be autodetected if the JTA * UserTransaction object implements the JTA TransactionManager interface too, * as well as autodetected at various well-known fallback JNDI locations. * @see #setTransactionManagerName * @see #setAutodetectTransactionManager */ public void setTransactionManager(TransactionManager transactionManager) { this.transactionManager = transactionManager; } /** * Return the JTA TransactionManager that this transaction manager uses. */ public TransactionManager getTransactionManager() { return this.transactionManager; } /** * Set the JNDI name of the JTA TransactionManager. * <p>A TransactionManager is necessary for suspending and resuming transactions, * as this not supported by the UserTransaction interface. * <p>Note that the TransactionManager will be autodetected if the JTA * UserTransaction object implements the JTA TransactionManager interface too, * as well as autodetected at various well-known fallback JNDI locations. * @see #setTransactionManager * @see #setAutodetectTransactionManager */ public void setTransactionManagerName(String transactionManagerName) { this.transactionManagerName = transactionManagerName; } /** * Set whether to autodetect a JTA UserTransaction object that implements * the JTA TransactionManager interface too (i.e. the JNDI location for the * TransactionManager is "java:comp/UserTransaction", same as for the UserTransaction). * Also checks the fallback JNDI locations "java:comp/TransactionManager" and * "java:/TransactionManager". Will proceed without TransactionManager if none found. * <p>Default is "true", autodetecting the TransactionManager unless it has been * specified explicitly. Can be turned off to deliberately ignore an available * TransactionManager, for example when there are known issues with suspend/resume * and any attempt to use REQUIRES_NEW or NOT_SUPPORTED should fail fast. * @see #FALLBACK_TRANSACTION_MANAGER_NAMES */ public void setAutodetectTransactionManager(boolean autodetectTransactionManager) { this.autodetectTransactionManager = autodetectTransactionManager; } /** * Set the JNDI name of the JTA 1.1 TransactionSynchronizationRegistry. * <p>Note that the TransactionSynchronizationRegistry will be autodetected * at the Java EE 5 default location "java:comp/TransactionSynchronizationRegistry" * if not specified explicitly. * @see #DEFAULT_TRANSACTION_SYNCHRONIZATION_REGISTRY_NAME */ public void setTransactionSynchronizationRegistryName(String transactionSynchronizationRegistryName) { this.transactionSynchronizationRegistryName = transactionSynchronizationRegistryName; } /** * Set whether to allow custom isolation levels to be specified. * <p>Default is "false", throwing an exception if a non-default isolation level * is specified for a transaction. Turn this flag on if affected resource adapters * check the thread-bound transaction context and apply the specified isolation * levels individually (e.g. through a IsolationLevelDataSourceRouter). * @see org.springframework.jdbc.datasource.lookup.IsolationLevelDataSourceRouter */ public void setAllowCustomIsolationLevels(boolean allowCustomIsolationLevels) { this.allowCustomIsolationLevels = allowCustomIsolationLevels; } /** * Initialize the UserTransaction as well as the TransactionManager handle. * @see #initUserTransactionAndTransactionManager() */ public void afterPropertiesSet() throws TransactionSystemException { initUserTransactionAndTransactionManager(); checkUserTransactionAndTransactionManager(); initTransactionSynchronizationRegistry(); } /** * Initialize the UserTransaction as well as the TransactionManager handle. * @throws TransactionSystemException if initialization failed */ protected void initUserTransactionAndTransactionManager() throws TransactionSystemException { // Fetch JTA UserTransaction from JNDI, if necessary. if (this.userTransaction == null) { if (StringUtils.hasLength(this.userTransactionName)) { this.userTransaction = lookupUserTransaction(this.userTransactionName); this.userTransactionObtainedFromJndi = true; } else { this.userTransaction = retrieveUserTransaction(); } } // Fetch JTA TransactionManager from JNDI, if necessary. if (this.transactionManager == null) { if (StringUtils.hasLength(this.transactionManagerName)) { this.transactionManager = lookupTransactionManager(this.transactionManagerName); } else { this.transactionManager = retrieveTransactionManager(); } } // Autodetect UserTransaction at its default JNDI location. if (this.userTransaction == null && this.autodetectUserTransaction) { this.userTransaction = findUserTransaction(); } // Autodetect UserTransaction object that implements TransactionManager, // and check fallback JNDI locations else. if (this.transactionManager == null && this.autodetectTransactionManager) { this.transactionManager = findTransactionManager(this.userTransaction); } // If only JTA TransactionManager specified, create UserTransaction handle for it. if (this.userTransaction == null && this.transactionManager != null) { this.userTransaction = buildUserTransaction(this.transactionManager); } } /** * Check the UserTransaction as well as the TransactionManager handle, * assuming standard JTA requirements. * @throws IllegalStateException if no sufficient handles are available */ protected void checkUserTransactionAndTransactionManager() throws IllegalStateException { // We at least need the JTA UserTransaction. if (this.userTransaction != null) { if (logger.isInfoEnabled()) { logger.info("Using JTA UserTransaction: " + this.userTransaction); } } else { throw new IllegalStateException("No JTA UserTransaction available - specify either " + "'userTransaction' or 'userTransactionName' or 'transactionManager' or 'transactionManagerName'"); } // For transaction suspension, the JTA TransactionManager is necessary too. if (this.transactionManager != null) { if (logger.isInfoEnabled()) { logger.info("Using JTA TransactionManager: " + this.transactionManager); } } else { logger.warn("No JTA TransactionManager found: " + "transaction suspension and synchronization with existing JTA transactions not available"); } } /** * Initialize the JTA 1.1 TransactionSynchronizationRegistry, if available. * <p>To be called after {@link #initUserTransactionAndTransactionManager()}, * since it may check the UserTransaction and TransactionManager handles. * @throws TransactionSystemException if initialization failed */ protected void initTransactionSynchronizationRegistry() { if (StringUtils.hasLength(this.transactionSynchronizationRegistryName)) { this.transactionSynchronizationRegistry = lookupTransactionSynchronizationRegistry(this.transactionSynchronizationRegistryName); } else { this.transactionSynchronizationRegistry = retrieveTransactionSynchronizationRegistry(); if (this.transactionSynchronizationRegistry == null) { this.transactionSynchronizationRegistry = findTransactionSynchronizationRegistry(this.userTransaction, this.transactionManager); } } if (this.transactionSynchronizationRegistry != null) { if (logger.isInfoEnabled()) { logger.info("Using JTA TransactionSynchronizationRegistry: " + this.transactionSynchronizationRegistry); } } } /** * Build a UserTransaction handle based on the given TransactionManager. * @param transactionManager the TransactionManager * @return a corresponding UserTransaction handle */ protected UserTransaction buildUserTransaction(TransactionManager transactionManager) { if (transactionManager instanceof UserTransaction) { return (UserTransaction) transactionManager; } else { return new UserTransactionAdapter(transactionManager); } } /** * Look up the JTA UserTransaction in JNDI via the configured name. * <p>Called by <code>afterPropertiesSet</code> if no direct UserTransaction reference was set. * Can be overridden in subclasses to provide a different UserTransaction object. * @param userTransactionName the JNDI name of the UserTransaction * @return the UserTransaction object * @throws TransactionSystemException if the JNDI lookup failed * @see #setJndiTemplate * @see #setUserTransactionName */ protected UserTransaction lookupUserTransaction(String userTransactionName) throws TransactionSystemException { try { if (logger.isDebugEnabled()) { logger.debug("Retrieving JTA UserTransaction from JNDI location [" + userTransactionName + "]"); } return (UserTransaction) getJndiTemplate().lookup(userTransactionName, UserTransaction.class); } catch (NamingException ex) { throw new TransactionSystemException( "JTA UserTransaction is not available at JNDI location [" + userTransactionName + "]", ex); } } /** * Look up the JTA TransactionManager in JNDI via the configured name. * <p>Called by <code>afterPropertiesSet</code> if no direct TransactionManager reference was set. * Can be overridden in subclasses to provide a different TransactionManager object. * @param transactionManagerName the JNDI name of the TransactionManager * @return the UserTransaction object * @throws TransactionSystemException if the JNDI lookup failed * @see #setJndiTemplate * @see #setTransactionManagerName */ protected TransactionManager lookupTransactionManager(String transactionManagerName) throws TransactionSystemException { try { if (logger.isDebugEnabled()) { logger.debug("Retrieving JTA TransactionManager from JNDI location [" + transactionManagerName + "]"); } return (TransactionManager) getJndiTemplate().lookup(transactionManagerName, TransactionManager.class); } catch (NamingException ex) { throw new TransactionSystemException( "JTA TransactionManager is not available at JNDI location [" + transactionManagerName + "]", ex); } } /** * Look up the JTA 1.1 TransactionSynchronizationRegistry in JNDI via the configured name. * <p>Can be overridden in subclasses to provide a different TransactionManager object. * @param registryName the JNDI name of the * TransactionSynchronizationRegistry * @return the TransactionSynchronizationRegistry object * @throws TransactionSystemException if the JNDI lookup failed * @see #setJndiTemplate * @see #setTransactionSynchronizationRegistryName */ protected Object lookupTransactionSynchronizationRegistry(String registryName) throws TransactionSystemException { try { if (logger.isDebugEnabled()) { logger.debug("Retrieving JTA TransactionSynchronizationRegistry from JNDI location [" + registryName + "]"); } Class registryClass = ClassUtils.forName(TRANSACTION_SYNCHRONIZATION_REGISTRY_CLASS_NAME, JtaTransactionManager.class.getClassLoader()); return getJndiTemplate().lookup(registryName, registryClass); } catch (ClassNotFoundException ex) { throw new TransactionSystemException( "JTA 1.1 [" + TRANSACTION_SYNCHRONIZATION_REGISTRY_CLASS_NAME + "] not available"); } catch (NamingException ex) { throw new TransactionSystemException( "JTA TransactionSynchronizationRegistry is not available at JNDI location [" + registryName + "]", ex); } } /** * Allows subclasses to retrieve the JTA UserTransaction in a vendor-specific manner. * Only called if no "userTransaction" or "userTransactionName" specified. * <p>The default implementation simply returns <code>null</code>. * @return the JTA UserTransaction handle to use, or <code>null</code> if none found * @throws TransactionSystemException in case of errors * @see #setUserTransaction * @see #setUserTransactionName */ protected UserTransaction retrieveUserTransaction() throws TransactionSystemException { return null; } /** * Allows subclasses to retrieve the JTA TransactionManager in a vendor-specific manner. * Only called if no "transactionManager" or "transactionManagerName" specified. * <p>The default implementation simply returns <code>null</code>. * @return the JTA TransactionManager handle to use, or <code>null</code> if none found * @throws TransactionSystemException in case of errors * @see #setTransactionManager * @see #setTransactionManagerName */ protected TransactionManager retrieveTransactionManager() throws TransactionSystemException { return null; } /** * Allows subclasses to retrieve the JTA 1.1 TransactionSynchronizationRegistry * in a vendor-specific manner. * <p>The default implementation simply returns <code>null</code>. * @return the JTA TransactionSynchronizationRegistry handle to use, * or <code>null</code> if none found * @throws TransactionSystemException in case of errors */ protected Object retrieveTransactionSynchronizationRegistry() throws TransactionSystemException { return null; } /** * Find the JTA UserTransaction through a default JNDI lookup: * "java:comp/UserTransaction". * @return the JTA UserTransaction reference, or <code>null</code> if not found * @see #DEFAULT_USER_TRANSACTION_NAME */ protected UserTransaction findUserTransaction() { String jndiName = DEFAULT_USER_TRANSACTION_NAME; try { UserTransaction ut = (UserTransaction) getJndiTemplate().lookup(jndiName, UserTransaction.class); if (logger.isDebugEnabled()) { logger.debug("JTA UserTransaction found at default JNDI location [" + jndiName + "]"); } this.userTransactionObtainedFromJndi = true; return ut; } catch (NamingException ex) { if (logger.isDebugEnabled()) { logger.debug("No JTA UserTransaction found at default JNDI location [" + jndiName + "]", ex); } return null; } } /** * Find the JTA TransactionManager through autodetection: checking whether the * UserTransaction object implements the TransactionManager, and checking the * fallback JNDI locations. * @param ut the JTA UserTransaction object * @return the JTA TransactionManager reference, or <code>null</code> if not found * @see #FALLBACK_TRANSACTION_MANAGER_NAMES */ protected TransactionManager findTransactionManager(UserTransaction ut) { if (ut instanceof TransactionManager) { if (logger.isDebugEnabled()) { logger.debug("JTA UserTransaction object [" + ut + "] implements TransactionManager"); } return (TransactionManager) ut; } // Check fallback JNDI locations. for (int i = 0; i < FALLBACK_TRANSACTION_MANAGER_NAMES.length; i++) { String jndiName = FALLBACK_TRANSACTION_MANAGER_NAMES[i]; try { TransactionManager tm = (TransactionManager) getJndiTemplate().lookup(jndiName, TransactionManager.class); if (logger.isDebugEnabled()) { logger.debug("JTA TransactionManager found at fallback JNDI location [" + jndiName + "]"); } return tm; } catch (NamingException ex) { if (logger.isDebugEnabled()) { logger.debug("No JTA TransactionManager found at fallback JNDI location [" + jndiName + "]", ex); } } } // OK, so no JTA TransactionManager is available... return null; } /** * Find the JTA 1.1 TransactionSynchronizationRegistry through autodetection: * checking whether the UserTransaction object or TransactionManager object * implements it, and checking Java EE 5's standard JNDI location. * <p>The default implementation simply returns <code>null</code>. * @param ut the JTA UserTransaction object * @param tm the JTA TransactionManager object * @return the JTA TransactionSynchronizationRegistry handle to use, * or <code>null</code> if none found * @throws TransactionSystemException in case of errors */ protected Object findTransactionSynchronizationRegistry(UserTransaction ut, TransactionManager tm) throws TransactionSystemException { try { Class registryClass = ClassUtils.forName(TRANSACTION_SYNCHRONIZATION_REGISTRY_CLASS_NAME, JtaTransactionManager.class.getClassLoader()); // If we came here, we might be on Java EE 5, since the JTA 1.1 API is present. if (this.userTransactionObtainedFromJndi) { // UserTransaction has already been obtained from JNDI, so the // TransactionSynchronizationRegistry probably sits there as well. String jndiName = DEFAULT_TRANSACTION_SYNCHRONIZATION_REGISTRY_NAME; try { Object tsr = getJndiTemplate().lookup(jndiName, registryClass); if (logger.isDebugEnabled()) { logger.debug("JTA TransactionSynchronizationRegistry found at default JNDI location [" + jndiName + "]"); } return tsr; } catch (NamingException ex) { if (logger.isDebugEnabled()) { logger.debug( "No JTA TransactionSynchronizationRegistry found at default JNDI location [" + jndiName + "]", ex); } } } // Check whether the UserTransaction or TransactionManager implements it... if (registryClass.isInstance(ut)) { return ut; } if (registryClass.isInstance(tm)) { return tm; } // OK, so no JTA 1.1 TransactionSynchronizationRegistry is available, // despite the API being present... return null; } catch (ClassNotFoundException ex) { logger.debug("JTA 1.1 [" + TRANSACTION_SYNCHRONIZATION_REGISTRY_CLASS_NAME + "] not available"); return null; } } /** * This implementation returns a JtaTransactionObject instance for the * JTA UserTransaction. * <p>The UserTransaction object will either be looked up freshly for the * current transaction, or the cached one looked up at startup will be used. * The latter is the default: Most application servers use a shared singleton * UserTransaction that can be cached. Turn off the "cacheUserTransaction" * flag to enforce a fresh lookup for every transaction. * @see #setCacheUserTransaction */ protected Object doGetTransaction() { UserTransaction ut = getUserTransaction(); if (ut == null) { throw new CannotCreateTransactionException("No JTA UserTransaction available - " + "programmatic PlatformTransactionManager.getTransaction usage not supported"); } if (!this.cacheUserTransaction) { ut = lookupUserTransaction( this.userTransactionName != null ? this.userTransactionName : DEFAULT_USER_TRANSACTION_NAME); } return doGetJtaTransaction(ut); } /** * Get a JTA transaction object for the given current UserTransaction. * <p>Subclasses can override this to provide a JtaTransactionObject * subclass, for example holding some additional JTA handle needed. * @param ut the UserTransaction handle to use for the current transaction * @return the JtaTransactionObject holding the UserTransaction */ protected JtaTransactionObject doGetJtaTransaction(UserTransaction ut) { return new JtaTransactionObject(ut); } protected boolean isExistingTransaction(Object transaction) { JtaTransactionObject txObject = (JtaTransactionObject) transaction; try { return (txObject.getUserTransaction().getStatus() != Status.STATUS_NO_TRANSACTION); } catch (SystemException ex) { throw new TransactionSystemException("JTA failure on getStatus", ex); } } /** * This implementation returns false to cause a further invocation * of doBegin despite an already existing transaction. * <p>JTA implementations might support nested transactions via further * <code>UserTransaction.begin()</code> invocations, but never support savepoints. * @see #doBegin * @see javax.transaction.UserTransaction#begin() */ protected boolean useSavepointForNestedTransaction() { return false; } protected void doBegin(Object transaction, TransactionDefinition definition) { JtaTransactionObject txObject = (JtaTransactionObject) transaction; try { doJtaBegin(txObject, definition); } catch (NotSupportedException ex) { // assume nested transaction not supported throw new NestedTransactionNotSupportedException( "JTA implementation does not support nested transactions", ex); } catch (UnsupportedOperationException ex) { // assume nested transaction not supported throw new NestedTransactionNotSupportedException( "JTA implementation does not support nested transactions", ex); } catch (SystemException ex) { throw new CannotCreateTransactionException("JTA failure on begin", ex); } } /** * Perform a JTA begin on the JTA UserTransaction or TransactionManager. * <p>This implementation only supports standard JTA functionality: * that is, no per-transaction isolation levels and no transaction names. * Can be overridden in subclasses, for specific JTA implementations. * <p>Calls <code>applyIsolationLevel</code> and <code>applyTimeout</code> * before invoking the UserTransaction's <code>begin</code> method. * @param txObject the JtaTransactionObject containing the UserTransaction * @param definition TransactionDefinition instance, describing propagation * behavior, isolation level, read-only flag, timeout, and transaction name * @throws NotSupportedException if thrown by JTA methods * @throws SystemException if thrown by JTA methods * @see #getUserTransaction * @see #getTransactionManager * @see #applyIsolationLevel * @see #applyTimeout * @see JtaTransactionObject#getUserTransaction() * @see javax.transaction.UserTransaction#setTransactionTimeout * @see javax.transaction.UserTransaction#begin */ protected void doJtaBegin(JtaTransactionObject txObject, TransactionDefinition definition) throws NotSupportedException, SystemException { applyIsolationLevel(txObject, definition.getIsolationLevel()); int timeout = determineTimeout(definition); applyTimeout(txObject, timeout); txObject.getUserTransaction().begin(); } /** * Apply the given transaction isolation level. The default implementation * will throw an exception for any level other than ISOLATION_DEFAULT. * <p>To be overridden in subclasses for specific JTA implementations, * as alternative to overriding the full {@link #doJtaBegin} method. * @param txObject the JtaTransactionObject containing the UserTransaction * @param isolationLevel isolation level taken from transaction definition * @throws InvalidIsolationLevelException if the given isolation level * cannot be applied * @throws SystemException if thrown by the JTA implementation * @see #doJtaBegin * @see JtaTransactionObject#getUserTransaction() * @see #getTransactionManager() */ protected void applyIsolationLevel(JtaTransactionObject txObject, int isolationLevel) throws InvalidIsolationLevelException, SystemException { if (!this.allowCustomIsolationLevels && isolationLevel != TransactionDefinition.ISOLATION_DEFAULT) { throw new InvalidIsolationLevelException( "JtaTransactionManager does not support custom isolation levels by default - " + "switch 'allowCustomIsolationLevels' to 'true'"); } } /** * Apply the given transaction timeout. The default implementation will call * <code>UserTransaction.setTransactionTimeout</code> for a non-default timeout value. * @param txObject the JtaTransactionObject containing the UserTransaction * @param timeout timeout value taken from transaction definition * @throws SystemException if thrown by the JTA implementation * @see #doJtaBegin * @see JtaTransactionObject#getUserTransaction() * @see javax.transaction.UserTransaction#setTransactionTimeout(int) */ protected void applyTimeout(JtaTransactionObject txObject, int timeout) throws SystemException { if (timeout > TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getUserTransaction().setTransactionTimeout(timeout); } } protected Object doSuspend(Object transaction) { JtaTransactionObject txObject = (JtaTransactionObject) transaction; try { return doJtaSuspend(txObject); } catch (SystemException ex) { throw new TransactionSystemException("JTA failure on suspend", ex); } } /** * Perform a JTA suspend on the JTA TransactionManager. * <p>Can be overridden in subclasses, for specific JTA implementations. * @param txObject the JtaTransactionObject containing the UserTransaction * @return the suspended JTA Transaction object * @throws SystemException if thrown by JTA methods * @see #getTransactionManager() * @see javax.transaction.TransactionManager#suspend() */ protected Object doJtaSuspend(JtaTransactionObject txObject) throws SystemException { if (getTransactionManager() == null) { throw new TransactionSuspensionNotSupportedException( "JtaTransactionManager needs a JTA TransactionManager for suspending a transaction: " + "specify the 'transactionManager' or 'transactionManagerName' property"); } return getTransactionManager().suspend(); } protected void doResume(Object transaction, Object suspendedResources) { JtaTransactionObject txObject = (JtaTransactionObject) transaction; try { doJtaResume(txObject, suspendedResources); } catch (InvalidTransactionException ex) { throw new IllegalTransactionStateException("Tried to resume invalid JTA transaction", ex); } catch (IllegalStateException ex) { throw new TransactionSystemException("Unexpected internal transaction state", ex); } catch (SystemException ex) { throw new TransactionSystemException("JTA failure on resume", ex); } } /** * Perform a JTA resume on the JTA TransactionManager. * <p>Can be overridden in subclasses, for specific JTA implementations. * @param txObject the JtaTransactionObject containing the UserTransaction * @param suspendedTransaction the suspended JTA Transaction object * @throws InvalidTransactionException if thrown by JTA methods * @throws SystemException if thrown by JTA methods * @see #getTransactionManager() * @see javax.transaction.TransactionManager#resume(javax.transaction.Transaction) */ protected void doJtaResume(JtaTransactionObject txObject, Object suspendedTransaction) throws InvalidTransactionException, SystemException { if (getTransactionManager() == null) { throw new TransactionSuspensionNotSupportedException( "JtaTransactionManager needs a JTA TransactionManager for suspending a transaction: " + "specify the 'transactionManager' or 'transactionManagerName' property"); } getTransactionManager().resume((Transaction) suspendedTransaction); } /** * This implementation returns "true": a JTA commit will properly handle * transactions that have been marked rollback-only at a global level. */ protected boolean shouldCommitOnGlobalRollbackOnly() { return true; } protected void doCommit(DefaultTransactionStatus status) { JtaTransactionObject txObject = (JtaTransactionObject) status.getTransaction(); try { int jtaStatus = txObject.getUserTransaction().getStatus(); if (jtaStatus == Status.STATUS_NO_TRANSACTION) { // Should never happen... would have thrown an exception before // and as a consequence led to a rollback, not to a commit call. // In any case, the transaction is already fully cleaned up. throw new UnexpectedRollbackException("JTA transaction already completed - probably rolled back"); } if (jtaStatus == Status.STATUS_ROLLEDBACK) { // Only really happens on JBoss 4.2 in case of an early timeout... // Explicit rollback call necessary to clean up the transaction. // IllegalStateException expected on JBoss; call still necessary. try { txObject.getUserTransaction().rollback(); } catch (IllegalStateException ex) { if (logger.isDebugEnabled()) { logger.debug("Rollback failure with transaction already marked as rolled back: " + ex); } } throw new UnexpectedRollbackException("JTA transaction already rolled back (probably due to a timeout)"); } txObject.getUserTransaction().commit(); } catch (RollbackException ex) { throw new UnexpectedRollbackException( "JTA transaction unexpectedly rolled back (maybe due to a timeout)", ex); } catch (HeuristicMixedException ex) { throw new HeuristicCompletionException(HeuristicCompletionException.STATE_MIXED, ex); } catch (HeuristicRollbackException ex) { throw new HeuristicCompletionException(HeuristicCompletionException.STATE_ROLLED_BACK, ex); } catch (IllegalStateException ex) { throw new TransactionSystemException("Unexpected internal transaction state", ex); } catch (SystemException ex) { throw new TransactionSystemException("JTA failure on commit", ex); } } protected void doRollback(DefaultTransactionStatus status) { JtaTransactionObject txObject = (JtaTransactionObject) status.getTransaction(); try { int jtaStatus = txObject.getUserTransaction().getStatus(); if (jtaStatus != Status.STATUS_NO_TRANSACTION) { try { txObject.getUserTransaction().rollback(); } catch (IllegalStateException ex) { if (jtaStatus == Status.STATUS_ROLLEDBACK) { // Only really happens on JBoss 4.2 in case of an early timeout... if (logger.isDebugEnabled()) { logger.debug("Rollback failure with transaction already marked as rolled back: " + ex); } } else { throw new TransactionSystemException("Unexpected internal transaction state", ex); } } } } catch (SystemException ex) { throw new TransactionSystemException("JTA failure on rollback", ex); } } protected void doSetRollbackOnly(DefaultTransactionStatus status) { JtaTransactionObject txObject = (JtaTransactionObject) status.getTransaction(); if (status.isDebug()) { logger.debug("Setting JTA transaction rollback-only"); } try { int jtaStatus = txObject.getUserTransaction().getStatus(); if (jtaStatus != Status.STATUS_NO_TRANSACTION && jtaStatus != Status.STATUS_ROLLEDBACK) { txObject.getUserTransaction().setRollbackOnly(); } } catch (IllegalStateException ex) { throw new TransactionSystemException("Unexpected internal transaction state", ex); } catch (SystemException ex) { throw new TransactionSystemException("JTA failure on setRollbackOnly", ex); } } protected void registerAfterCompletionWithExistingTransaction(Object transaction, List synchronizations) { JtaTransactionObject txObject = (JtaTransactionObject) transaction; logger.debug("Registering after-completion synchronization with existing JTA transaction"); try { doRegisterAfterCompletionWithJtaTransaction(txObject, synchronizations); } catch (RollbackException ex) { logger.debug("Participating in existing JTA transaction that has been marked for rollback: " + "cannot register Spring after-completion callbacks with outer JTA transaction - " + "immediately performing Spring after-completion callbacks with outcome status 'rollback'. " + "Original exception: " + ex); invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_ROLLED_BACK); } catch (IllegalStateException ex) { logger.debug("Participating in existing JTA transaction, but unexpected internal transaction " + "state encountered: cannot register Spring after-completion callbacks with outer JTA " + "transaction - processing Spring after-completion callbacks with outcome status 'unknown'" + "Original exception: " + ex); invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_UNKNOWN); } catch (SystemException ex) { throw new TransactionSystemException("JTA failure on registerSynchronization", ex); } } /** * Register a JTA synchronization on the JTA TransactionManager, for calling * <code>afterCompletion</code> on the given Spring TransactionSynchronizations. * <p>The default implementation registers the synchronizations on the * JTA 1.1 TransactionSynchronizationRegistry, if available, or on the * JTA TransactionManager's current Transaction - again, if available. * If none of the two is available, a warning will be logged. * <p>Can be overridden in subclasses, for specific JTA implementations. * @param txObject the current transaction object * @param synchronizations List of TransactionSynchronization objects * @throws RollbackException if thrown by JTA methods * @throws SystemException if thrown by JTA methods * @see #getTransactionManager() * @see javax.transaction.Transaction#registerSynchronization * @see javax.transaction.TransactionSynchronizationRegistry#registerInterposedSynchronization */ protected void doRegisterAfterCompletionWithJtaTransaction(JtaTransactionObject txObject, List synchronizations) throws RollbackException, SystemException { int jtaStatus = txObject.getUserTransaction().getStatus(); if (jtaStatus == Status.STATUS_NO_TRANSACTION) { throw new RollbackException("JTA transaction already completed - probably rolled back"); } if (jtaStatus == Status.STATUS_ROLLEDBACK) { throw new RollbackException("JTA transaction already rolled back (probably due to a timeout)"); } if (this.transactionSynchronizationRegistry != null) { // JTA 1.1 TransactionSynchronizationRegistry available - use it. new InterposedSynchronizationDelegate().registerInterposedSynchronization( new JtaAfterCompletionSynchronization(synchronizations)); } else if (getTransactionManager() != null) { // At least the JTA TransactionManager available - use that one. Transaction transaction = getTransactionManager().getTransaction(); if (transaction == null) { throw new IllegalStateException("No JTA Transaction available"); } transaction.registerSynchronization(new JtaAfterCompletionSynchronization(synchronizations)); } else { // No JTA TransactionManager available - log a warning. logger.warn("Participating in existing JTA transaction, but no JTA TransactionManager available: " + "cannot register Spring after-completion callbacks with outer JTA transaction - " + "processing Spring after-completion callbacks with outcome status 'unknown'"); invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_UNKNOWN); } } //--------------------------------------------------------------------- // Implementation of TransactionFactory interface //--------------------------------------------------------------------- public Transaction createTransaction(String name, int timeout) throws NotSupportedException, SystemException { TransactionManager tm = getTransactionManager(); Assert.state(tm != null, "No JTA TransactionManager available"); if (timeout >= 0) { tm.setTransactionTimeout(timeout); } tm.begin(); return tm.getTransaction(); } //--------------------------------------------------------------------- // Serialization support //--------------------------------------------------------------------- private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // Rely on default serialization; just initialize state after deserialization. ois.defaultReadObject(); // Create template for client-side JNDI lookup. this.jndiTemplate = new JndiTemplate(); // Perform a fresh lookup for JTA handles. initUserTransactionAndTransactionManager(); initTransactionSynchronizationRegistry(); } /** * Inner class to avoid a direct dependency on the JTA 1.1 API * (javax.transaction.TransactionSynchronizationRegistry interface). */ private class InterposedSynchronizationDelegate { public void registerInterposedSynchronization(Synchronization synch) { ((TransactionSynchronizationRegistry) transactionSynchronizationRegistry).registerInterposedSynchronization(synch); } } }