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

280                        if (logger.isDebugEnabled()) {
281                                logger.debug("Standard JTA resume threw InvalidTransactionException: " + ex.getMessage() +
282                                        " - trying WebLogic JTA forceResume");
283                        }
284                        /*
285                        weblogic.transaction.TransactionManager wtm =
286                                        (weblogic.transaction.TransactionManager) getTransactionManager();
287                        wtm.forceResume(suspendedTransaction);
288                        */
289                        try {
290                                this.forceResumeMethod.invoke(getTransactionManager(), suspendedTransaction);
291                        }
292                        catch (InvocationTargetException ex2) {
293                                throw new TransactionSystemException(
294                                                "WebLogic's TransactionManager.forceResume(Transaction) method failed", ex2.getTargetException());
295                        }
296                        catch (Exception ex2) {
297                                throw new TransactionSystemException(
298                                                "Could not access WebLogic's TransactionManager.forceResume(Transaction) method", ex2);
299                        }
300                }
301        }
302
303        @Override
304        public Transaction createTransaction(String name, int timeout) throws NotSupportedException, SystemException {
305                if (this.weblogicUserTransactionAvailable && name != null) {
306                        try {
307                                if (timeout >= 0) {
308                                        this.beginWithNameAndTimeoutMethod.invoke(getUserTransaction(), name, timeout);
309                                }
310                                else {
311                                        this.beginWithNameMethod.invoke(getUserTransaction(), name);
312                                }
313                        }
314                        catch (InvocationTargetException ex) {
315                                if (ex.getTargetException() instanceof NotSupportedException) {
316                                        throw (NotSupportedException) ex.getTargetException();
317                                }
318                                else if (ex.getTargetException() instanceof SystemException) {
319                                        throw (SystemException) ex.getTargetException();
320                                }
321                                else if (ex.getTargetException() instanceof RuntimeException) {
322                                        throw (RuntimeException) ex.getTargetException();
323                                }
324                                else {
325                                        throw new SystemException(
326                                                        "WebLogic's begin() method failed with an unexpected error: " + ex.getTargetException());
327                                }
328                        }
329                        catch (Exception ex) {
330                                throw new SystemException("Could not invoke WebLogic's UserTransaction.begin() method: " + ex);
331                        }
332                        return new ManagedTransactionAdapter(getTransactionManager());
333                }
334
335                else {
336                        // No name specified - standard JTA is sufficient.
337                        return super.createTransaction(name, timeout);
338                }
339        }
340
341}