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