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