001/*
002 * Copyright 2002-2012 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.transaction.jta;
018
019import javax.transaction.Status;
020import javax.transaction.Synchronization;
021import javax.transaction.TransactionManager;
022import javax.transaction.UserTransaction;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026
027import org.springframework.transaction.support.TransactionSynchronization;
028import org.springframework.transaction.support.TransactionSynchronizationManager;
029import org.springframework.util.Assert;
030
031/**
032 * Adapter that implements the JTA {@link javax.transaction.Synchronization}
033 * interface delegating to an underlying Spring
034 * {@link org.springframework.transaction.support.TransactionSynchronization}.
035 *
036 * <p>Useful for synchronizing Spring resource management code with plain
037 * JTA / EJB CMT transactions, despite the original code being built for
038 * Spring transaction synchronization.
039 *
040 * @author Juergen Hoeller
041 * @since 2.0
042 * @see javax.transaction.Transaction#registerSynchronization
043 * @see org.springframework.transaction.support.TransactionSynchronization
044 */
045public class SpringJtaSynchronizationAdapter implements Synchronization {
046
047        protected static final Log logger = LogFactory.getLog(SpringJtaSynchronizationAdapter.class);
048
049        private final TransactionSynchronization springSynchronization;
050
051        private UserTransaction jtaTransaction;
052
053        private boolean beforeCompletionCalled = false;
054
055
056        /**
057         * Create a new SpringJtaSynchronizationAdapter for the given Spring
058         * TransactionSynchronization and JTA TransactionManager.
059         * @param springSynchronization the Spring TransactionSynchronization to delegate to
060         */
061        public SpringJtaSynchronizationAdapter(TransactionSynchronization springSynchronization) {
062                Assert.notNull(springSynchronization, "TransactionSynchronization must not be null");
063                this.springSynchronization = springSynchronization;
064        }
065
066        /**
067         * Create a new SpringJtaSynchronizationAdapter for the given Spring
068         * TransactionSynchronization and JTA TransactionManager.
069         * <p>Note that this adapter will never perform a rollback-only call on WebLogic,
070         * since WebLogic Server is known to automatically mark the transaction as
071         * rollback-only in case of a {@code beforeCompletion} exception. Hence,
072         * on WLS, this constructor is equivalent to the single-arg constructor.
073         * @param springSynchronization the Spring TransactionSynchronization to delegate to
074         * @param jtaUserTransaction the JTA UserTransaction to use for rollback-only
075         * setting in case of an exception thrown in {@code beforeCompletion}
076         * (can be omitted if the JTA provider itself marks the transaction rollback-only
077         * in such a scenario, which is required by the JTA specification as of JTA 1.1).
078         */
079        public SpringJtaSynchronizationAdapter(
080                        TransactionSynchronization springSynchronization, UserTransaction jtaUserTransaction) {
081
082                this(springSynchronization);
083                if (jtaUserTransaction != null && !jtaUserTransaction.getClass().getName().startsWith("weblogic.")) {
084                        this.jtaTransaction = jtaUserTransaction;
085                }
086        }
087
088        /**
089         * Create a new SpringJtaSynchronizationAdapter for the given Spring
090         * TransactionSynchronization and JTA TransactionManager.
091         * <p>Note that this adapter will never perform a rollback-only call on WebLogic,
092         * since WebLogic Server is known to automatically mark the transaction as
093         * rollback-only in case of a {@code beforeCompletion} exception. Hence,
094         * on WLS, this constructor is equivalent to the single-arg constructor.
095         * @param springSynchronization the Spring TransactionSynchronization to delegate to
096         * @param jtaTransactionManager the JTA TransactionManager to use for rollback-only
097         * setting in case of an exception thrown in {@code beforeCompletion}
098         * (can be omitted if the JTA provider itself marks the transaction rollback-only
099         * in such a scenario, which is required by the JTA specification as of JTA 1.1)
100         */
101        public SpringJtaSynchronizationAdapter(
102                        TransactionSynchronization springSynchronization, TransactionManager jtaTransactionManager) {
103
104                this(springSynchronization);
105                if (jtaTransactionManager != null && !jtaTransactionManager.getClass().getName().startsWith("weblogic.")) {
106                        this.jtaTransaction = new UserTransactionAdapter(jtaTransactionManager);
107                }
108        }
109
110
111        /**
112         * JTA {@code beforeCompletion} callback: just invoked before commit.
113         * <p>In case of an exception, the JTA transaction will be marked as rollback-only.
114         * @see org.springframework.transaction.support.TransactionSynchronization#beforeCommit
115         */
116        @Override
117        public void beforeCompletion() {
118                try {
119                        boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
120                        this.springSynchronization.beforeCommit(readOnly);
121                }
122                catch (RuntimeException ex) {
123                        setRollbackOnlyIfPossible();
124                        throw ex;
125                }
126                catch (Error err) {
127                        setRollbackOnlyIfPossible();
128                        throw err;
129                }
130                finally {
131                        // Process Spring's beforeCompletion early, in order to avoid issues
132                        // with strict JTA implementations that issue warnings when doing JDBC
133                        // operations after transaction completion (e.g. Connection.getWarnings).
134                        this.beforeCompletionCalled = true;
135                        this.springSynchronization.beforeCompletion();
136                }
137        }
138
139        /**
140         * Set the underlying JTA transaction to rollback-only.
141         */
142        private void setRollbackOnlyIfPossible() {
143                if (this.jtaTransaction != null) {
144                        try {
145                                this.jtaTransaction.setRollbackOnly();
146                        }
147                        catch (UnsupportedOperationException ex) {
148                                // Probably Hibernate's WebSphereExtendedJTATransactionLookup pseudo JTA stuff...
149                                logger.debug("JTA transaction handle does not support setRollbackOnly method - " +
150                                                "relying on JTA provider to mark the transaction as rollback-only based on " +
151                                                "the exception thrown from beforeCompletion", ex);
152                        }
153                        catch (Throwable ex) {
154                                logger.error("Could not set JTA transaction rollback-only", ex);
155                        }
156                }
157                else {
158                        logger.debug("No JTA transaction handle available and/or running on WebLogic - " +
159                                                "relying on JTA provider to mark the transaction as rollback-only based on " +
160                                                "the exception thrown from beforeCompletion");
161                        }
162        }
163
164        /**
165         * JTA {@code afterCompletion} callback: invoked after commit/rollback.
166         * <p>Needs to invoke the Spring synchronization's {@code beforeCompletion}
167         * at this late stage in case of a rollback, since there is no corresponding
168         * callback with JTA.
169         * @see org.springframework.transaction.support.TransactionSynchronization#beforeCompletion
170         * @see org.springframework.transaction.support.TransactionSynchronization#afterCompletion
171         */
172        @Override
173        public void afterCompletion(int status) {
174                if (!this.beforeCompletionCalled) {
175                        // beforeCompletion not called before (probably because of JTA rollback).
176                        // Perform the cleanup here.
177                        this.springSynchronization.beforeCompletion();
178                }
179                // Call afterCompletion with the appropriate status indication.
180                switch (status) {
181                        case Status.STATUS_COMMITTED:
182                                this.springSynchronization.afterCompletion(TransactionSynchronization.STATUS_COMMITTED);
183                                break;
184                        case Status.STATUS_ROLLEDBACK:
185                                this.springSynchronization.afterCompletion(TransactionSynchronization.STATUS_ROLLED_BACK);
186                                break;
187                        default:
188                                this.springSynchronization.afterCompletion(TransactionSynchronization.STATUS_UNKNOWN);
189                }
190        }
191
192}