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 java.io.Serializable;
020import java.lang.reflect.InvocationTargetException;
021import java.lang.reflect.Method;
022
023import javax.transaction.InvalidTransactionException;
024import javax.transaction.NotSupportedException;
025import javax.transaction.SystemException;
026import javax.transaction.Transaction;
027import javax.transaction.TransactionManager;
028import javax.transaction.UserTransaction;
029
030import org.springframework.lang.Nullable;
031import org.springframework.transaction.TransactionDefinition;
032import org.springframework.transaction.TransactionSystemException;
033import org.springframework.util.Assert;
034
035/**
036 * Special {@link JtaTransactionManager} variant for BEA WebLogic (9.0 and higher).
037 * Supports the full power of Spring's transaction definitions on WebLogic's
038 * transaction coordinator, <i>beyond standard JTA</i>: transaction names,
039 * per-transaction isolation levels, and proper resuming of transactions in all cases.
040 *
041 * <p>Uses WebLogic's special {@code begin(name)} method to start a JTA transaction,
042 * in order to make <b>Spring-driven transactions visible in WebLogic's transaction
043 * monitor</b>. In case of Spring's declarative transactions, the exposed name will
044 * (by default) be the fully-qualified class name + "." + method name.
045 *
046 * <p>Supports a <b>per-transaction isolation level</b> through WebLogic's corresponding
047 * JTA transaction property "ISOLATION LEVEL". This will apply the specified isolation
048 * level (e.g. ISOLATION_SERIALIZABLE) to all JDBC Connections that participate in the
049 * given transaction.
050 *
051 * <p>Invokes WebLogic's special {@code forceResume} method if standard JTA resume
052 * failed, to <b>also resume if the target transaction was marked rollback-only</b>.
053 * If you're not relying on this feature of transaction suspension in the first
054 * place, Spring's standard JtaTransactionManager will behave properly too.
055 *
056 * <p>By default, the JTA UserTransaction and TransactionManager handles are
057 * fetched directly from WebLogic's {@code TransactionHelper}. This can be
058 * overridden by specifying "userTransaction"/"userTransactionName" and
059 * "transactionManager"/"transactionManagerName", passing in existing handles
060 * or specifying corresponding JNDI locations to look up.
061 *
062 * <p><b>NOTE: This JtaTransactionManager is intended to refine specific transaction
063 * demarcation behavior on Spring's side. It will happily co-exist with independently
064 * configured WebLogic transaction strategies in your persistence provider, with no
065 * need to specifically connect those setups in any way.</b>
066 *
067 * @author Juergen Hoeller
068 * @since 1.1
069 * @see org.springframework.transaction.TransactionDefinition#getName
070 * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel
071 * @see weblogic.transaction.UserTransaction#begin(String)
072 * @see weblogic.transaction.Transaction#setProperty
073 * @see weblogic.transaction.TransactionManager#forceResume
074 * @see weblogic.transaction.TransactionHelper
075 */
076@SuppressWarnings("serial")
077public class WebLogicJtaTransactionManager extends JtaTransactionManager {
078
079        private static final String USER_TRANSACTION_CLASS_NAME = "weblogic.transaction.UserTransaction";
080
081        private static final String CLIENT_TRANSACTION_MANAGER_CLASS_NAME = "weblogic.transaction.ClientTransactionManager";
082
083        private static final String TRANSACTION_CLASS_NAME = "weblogic.transaction.Transaction";
084
085        private static final String TRANSACTION_HELPER_CLASS_NAME = "weblogic.transaction.TransactionHelper";
086
087        private static final String ISOLATION_LEVEL_KEY = "ISOLATION LEVEL";
088
089
090        private boolean weblogicUserTransactionAvailable;
091
092        @Nullable
093        private Method beginWithNameMethod;
094
095        @Nullable
096        private Method beginWithNameAndTimeoutMethod;
097
098        private boolean weblogicTransactionManagerAvailable;
099
100        @Nullable
101        private Method forceResumeMethod;
102
103        @Nullable
104        private Method setPropertyMethod;
105
106        @Nullable
107        private Object transactionHelper;
108
109
110        @Override
111        public void afterPropertiesSet() throws TransactionSystemException {
112                super.afterPropertiesSet();
113                loadWebLogicTransactionClasses();
114        }
115
116        @Override
117        @Nullable
118        protected UserTransaction retrieveUserTransaction() throws TransactionSystemException {
119                Object helper = loadWebLogicTransactionHelper();
120                try {
121                        logger.trace("Retrieving JTA UserTransaction from WebLogic TransactionHelper");
122                        Method getUserTransactionMethod = helper.getClass().getMethod("getUserTransaction");
123                        return (UserTransaction) getUserTransactionMethod.invoke(this.transactionHelper);
124                }
125                catch (InvocationTargetException ex) {
126                        throw new TransactionSystemException(
127                                        "WebLogic's TransactionHelper.getUserTransaction() method failed", ex.getTargetException());
128                }
129                catch (Exception ex) {
130                        throw new TransactionSystemException(
131                                        "Could not invoke WebLogic's TransactionHelper.getUserTransaction() method", ex);
132                }
133        }
134
135        @Override
136        @Nullable
137        protected TransactionManager retrieveTransactionManager() throws TransactionSystemException {
138                Object helper = loadWebLogicTransactionHelper();
139                try {
140                        logger.trace("Retrieving JTA TransactionManager from WebLogic TransactionHelper");
141                        Method getTransactionManagerMethod = helper.getClass().getMethod("getTransactionManager");
142                        return (TransactionManager) getTransactionManagerMethod.invoke(this.transactionHelper);
143                }
144                catch (InvocationTargetException ex) {
145                        throw new TransactionSystemException(
146                                        "WebLogic's TransactionHelper.getTransactionManager() method failed", ex.getTargetException());
147                }
148                catch (Exception ex) {
149                        throw new TransactionSystemException(
150                                        "Could not invoke WebLogic's TransactionHelper.getTransactionManager() method", ex);
151                }
152        }
153
154        private Object loadWebLogicTransactionHelper() throws TransactionSystemException {
155                Object helper = this.transactionHelper;
156                if (helper == null) {
157                        try {
158                                Class<?> transactionHelperClass = getClass().getClassLoader().loadClass(TRANSACTION_HELPER_CLASS_NAME);
159                                Method getTransactionHelperMethod = transactionHelperClass.getMethod("getTransactionHelper");
160                                helper = getTransactionHelperMethod.invoke(null);
161                                this.transactionHelper = helper;
162                                logger.trace("WebLogic TransactionHelper found");
163                        }
164                        catch (InvocationTargetException ex) {
165                                throw new TransactionSystemException(
166                                                "WebLogic's TransactionHelper.getTransactionHelper() method failed", ex.getTargetException());
167                        }
168                        catch (Exception ex) {
169                                throw new TransactionSystemException(
170                                                "Could not initialize WebLogicJtaTransactionManager because WebLogic API classes are not available",
171                                                ex);
172                        }
173                }
174                return helper;
175        }
176
177        private void loadWebLogicTransactionClasses() throws TransactionSystemException {
178                try {
179                        Class<?> userTransactionClass = getClass().getClassLoader().loadClass(USER_TRANSACTION_CLASS_NAME);
180                        this.weblogicUserTransactionAvailable = userTransactionClass.isInstance(getUserTransaction());
181                        if (this.weblogicUserTransactionAvailable) {
182                                this.beginWithNameMethod = userTransactionClass.getMethod("begin", String.class);
183                                this.beginWithNameAndTimeoutMethod = userTransactionClass.getMethod("begin", String.class, int.class);
184                                logger.debug("Support for WebLogic transaction names available");
185                        }
186                        else {
187                                logger.debug("Support for WebLogic transaction names not available");
188                        }
189
190                        // Obtain WebLogic ClientTransactionManager interface.
191                        Class<?> transactionManagerClass =
192                                        getClass().getClassLoader().loadClass(CLIENT_TRANSACTION_MANAGER_CLASS_NAME);
193                        logger.trace("WebLogic ClientTransactionManager found");
194
195                        this.weblogicTransactionManagerAvailable = transactionManagerClass.isInstance(getTransactionManager());
196                        if (this.weblogicTransactionManagerAvailable) {
197                                Class<?> transactionClass = getClass().getClassLoader().loadClass(TRANSACTION_CLASS_NAME);
198                                this.forceResumeMethod = transactionManagerClass.getMethod("forceResume", Transaction.class);
199                                this.setPropertyMethod = transactionClass.getMethod("setProperty", String.class, Serializable.class);
200                                logger.debug("Support for WebLogic forceResume available");
201                        }
202                        else {
203                                logger.debug("Support for WebLogic forceResume not available");
204                        }
205                }
206                catch (Exception ex) {
207                        throw new TransactionSystemException(
208                                        "Could not initialize WebLogicJtaTransactionManager because WebLogic API classes are not available",
209                                        ex);
210                }
211        }
212
213        private TransactionManager obtainTransactionManager() {
214                TransactionManager tm = getTransactionManager();
215                Assert.state(tm != null, "No TransactionManager set");
216                return tm;
217        }
218
219
220        @Override
221        protected void doJtaBegin(JtaTransactionObject txObject, TransactionDefinition definition)
222                        throws NotSupportedException, SystemException {
223
224                int timeout = determineTimeout(definition);
225
226                // Apply transaction name (if any) to WebLogic transaction.
227                if (this.weblogicUserTransactionAvailable && definition.getName() != null) {
228                        try {
229                                if (timeout > TransactionDefinition.TIMEOUT_DEFAULT) {
230                                        /*
231                                        weblogic.transaction.UserTransaction wut = (weblogic.transaction.UserTransaction) ut;
232                                        wut.begin(definition.getName(), timeout);
233                                        */
234                                        Assert.state(this.beginWithNameAndTimeoutMethod != null, "WebLogic JTA API not initialized");
235                                        this.beginWithNameAndTimeoutMethod.invoke(txObject.getUserTransaction(), definition.getName(), timeout);
236                                }
237                                else {
238                                        /*
239                                        weblogic.transaction.UserTransaction wut = (weblogic.transaction.UserTransaction) ut;
240                                        wut.begin(definition.getName());
241                                        */
242                                        Assert.state(this.beginWithNameMethod != null, "WebLogic JTA API not initialized");
243                                        this.beginWithNameMethod.invoke(txObject.getUserTransaction(), definition.getName());
244                                }
245                        }
246                        catch (InvocationTargetException ex) {
247                                throw new TransactionSystemException(
248                                                "WebLogic's UserTransaction.begin() method failed", ex.getTargetException());
249                        }
250                        catch (Exception ex) {
251                                throw new TransactionSystemException(
252                                                "Could not invoke WebLogic's UserTransaction.begin() method", ex);
253                        }
254                }
255                else {
256                        // No WebLogic UserTransaction available or no transaction name specified
257                        // -> standard JTA begin call.
258                        applyTimeout(txObject, timeout);
259                        txObject.getUserTransaction().begin();
260                }
261
262                // Specify isolation level, if any, through corresponding WebLogic transaction property.
263                if (this.weblogicTransactionManagerAvailable) {
264                        if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
265                                try {
266                                        Transaction tx = obtainTransactionManager().getTransaction();
267                                        Integer isolationLevel = definition.getIsolationLevel();
268                                        /*
269                                        weblogic.transaction.Transaction wtx = (weblogic.transaction.Transaction) tx;
270                                        wtx.setProperty(ISOLATION_LEVEL_KEY, isolationLevel);
271                                        */
272                                        Assert.state(this.setPropertyMethod != null, "WebLogic JTA API not initialized");
273                                        this.setPropertyMethod.invoke(tx, ISOLATION_LEVEL_KEY, isolationLevel);
274                                }
275                                catch (InvocationTargetException ex) {
276                                        throw new TransactionSystemException(
277                                                        "WebLogic's Transaction.setProperty(String, Serializable) method failed", ex.getTargetException());
278                                }
279                                catch (Exception ex) {
280                                        throw new TransactionSystemException(
281                                                        "Could not invoke WebLogic's Transaction.setProperty(String, Serializable) method", ex);
282                                }
283                        }
284                }
285                else {
286                        applyIsolationLevel(txObject, definition.getIsolationLevel());
287                }
288        }
289
290        @Override
291        protected void doJtaResume(@Nullable JtaTransactionObject txObject, Object suspendedTransaction)
292                        throws InvalidTransactionException, SystemException {
293
294                try {
295                        obtainTransactionManager().resume((Transaction) suspendedTransaction);
296                }
297                catch (InvalidTransactionException ex) {
298                        if (!this.weblogicTransactionManagerAvailable) {
299                                throw ex;
300                        }
301
302                        if (logger.isDebugEnabled()) {
303                                logger.debug("Standard JTA resume threw InvalidTransactionException: " + ex.getMessage() +
304                                        " - trying WebLogic JTA forceResume");
305                        }
306                        /*
307                        weblogic.transaction.TransactionManager wtm =
308                                        (weblogic.transaction.TransactionManager) getTransactionManager();
309                        wtm.forceResume(suspendedTransaction);
310                        */
311                        try {
312                                Assert.state(this.forceResumeMethod != null, "WebLogic JTA API not initialized");
313                                this.forceResumeMethod.invoke(getTransactionManager(), suspendedTransaction);
314                        }
315                        catch (InvocationTargetException ex2) {
316                                throw new TransactionSystemException(
317                                                "WebLogic's TransactionManager.forceResume(Transaction) method failed", ex2.getTargetException());
318                        }
319                        catch (Exception ex2) {
320                                throw new TransactionSystemException(
321                                                "Could not access WebLogic's TransactionManager.forceResume(Transaction) method", ex2);
322                        }
323                }
324        }
325
326        @Override
327        public Transaction createTransaction(@Nullable String name, int timeout) throws NotSupportedException, SystemException {
328                if (this.weblogicUserTransactionAvailable && name != null) {
329                        try {
330                                if (timeout >= 0) {
331                                        Assert.state(this.beginWithNameAndTimeoutMethod != null, "WebLogic JTA API not initialized");
332                                        this.beginWithNameAndTimeoutMethod.invoke(getUserTransaction(), name, timeout);
333                                }
334                                else {
335                                        Assert.state(this.beginWithNameMethod != null, "WebLogic JTA API not initialized");
336                                        this.beginWithNameMethod.invoke(getUserTransaction(), name);
337                                }
338                        }
339                        catch (InvocationTargetException ex) {
340                                if (ex.getTargetException() instanceof NotSupportedException) {
341                                        throw (NotSupportedException) ex.getTargetException();
342                                }
343                                else if (ex.getTargetException() instanceof SystemException) {
344                                        throw (SystemException) ex.getTargetException();
345                                }
346                                else if (ex.getTargetException() instanceof RuntimeException) {
347                                        throw (RuntimeException) ex.getTargetException();
348                                }
349                                else {
350                                        throw new SystemException(
351                                                        "WebLogic's begin() method failed with an unexpected error: " + ex.getTargetException());
352                                }
353                        }
354                        catch (Exception ex) {
355                                throw new SystemException("Could not invoke WebLogic's UserTransaction.begin() method: " + ex);
356                        }
357                        return new ManagedTransactionAdapter(obtainTransactionManager());
358                }
359
360                else {
361                        // No name specified - standard JTA is sufficient.
362                        return super.createTransaction(name, timeout);
363                }
364        }
365
366}