001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.ejb.plugins;
023:
024: import org.jboss.invocation.Invocation;
025: import org.jboss.invocation.InvocationType;
026: import org.jboss.metadata.BeanMetaData;
027: import org.jboss.metadata.MetaData;
028: import org.jboss.metadata.XmlLoadable;
029: import org.jboss.tm.JBossTransactionRolledbackException;
030: import org.jboss.tm.JBossTransactionRolledbackLocalException;
031: import org.jboss.tm.TransactionTimeoutConfiguration;
032: import org.jboss.util.NestedException;
033: import org.jboss.util.deadlock.ApplicationDeadlockException;
034: import org.w3c.dom.Element;
035:
036: import javax.ejb.EJBException;
037: import javax.ejb.TransactionRequiredLocalException;
038: import javax.transaction.HeuristicMixedException;
039: import javax.transaction.HeuristicRollbackException;
040: import javax.transaction.RollbackException;
041: import javax.transaction.Status;
042: import javax.transaction.SystemException;
043: import javax.transaction.Transaction;
044: import javax.transaction.TransactionRequiredException;
045: import javax.transaction.TransactionRolledbackException;
046: import java.lang.reflect.Method;
047: import java.rmi.RemoteException;
048: import java.util.HashMap;
049: import java.util.Map;
050: import java.util.Random;
051: import java.util.Iterator;
052: import java.util.ArrayList;
053:
054: /**
055: * This interceptor handles transactions for CMT beans.
056: *
057: * @author <a href="mailto:rickard.oberg@telkel.com">Rickard �berg</a>
058: * @author <a href="mailto:marc.fleury@telkel.com">Marc Fleury</a>
059: * @author <a href="mailto:sebastien.alborini@m4x.org">Sebastien Alborini</a>
060: * @author <a href="mailto:akkerman@cs.nyu.edu">Anatoly Akkerman</a>
061: * @author <a href="mailto:osh@sparre.dk">Ole Husgaard</a>
062: * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
063: * @version $Revision: 60400 $
064: */
065: public class TxInterceptorCMT extends AbstractTxInterceptor implements
066: XmlLoadable {
067:
068: // Constants -----------------------------------------------------
069:
070: public static int MAX_RETRIES = 5;
071: public static Random random = new Random();
072:
073: // Attributes ----------------------------------------------------
074:
075: /**
076: * Whether an exception should be thrown if the transaction is not
077: * active, even though the application doesn't throw an exception
078: */
079: private boolean exceptionRollback = true;
080:
081: private TxRetryExceptionHandler[] retryHandlers = null;
082:
083: // Static --------------------------------------------------------
084:
085: /**
086: * Detects exception contains is or a ApplicationDeadlockException.
087: */
088: public static ApplicationDeadlockException isADE(Throwable t) {
089: while (t != null) {
090: if (t instanceof ApplicationDeadlockException) {
091: return (ApplicationDeadlockException) t;
092: } else if (t instanceof RemoteException) {
093: t = ((RemoteException) t).detail;
094: } else if (t instanceof EJBException) {
095: t = ((EJBException) t).getCausedByException();
096: } else {
097: return null;
098: }
099: }
100: return null;
101: }
102:
103: // Constructors --------------------------------------------------
104:
105: // Public --------------------------------------------------------
106:
107: // XmlLoadable implementation ------------------------------------
108:
109: public void importXml(Element ielement) {
110: try {
111: Element element = MetaData.getOptionalChild(ielement,
112: "retry-handlers");
113: if (element == null)
114: return;
115: ArrayList list = new ArrayList();
116: Iterator handlers = MetaData.getChildrenByTagName(element,
117: "handler");
118: while (handlers.hasNext()) {
119: Element handler = (Element) handlers.next();
120: String className = MetaData.getElementContent(handler)
121: .trim();
122: Class clazz = SecurityActions.getContextClassLoader()
123: .loadClass(className);
124: list.add(clazz.newInstance());
125: }
126: retryHandlers = (TxRetryExceptionHandler[]) list
127: .toArray(new TxRetryExceptionHandler[list.size()]);
128: } catch (Exception ex) {
129: log
130: .warn(
131: "Unable to importXml for the TxInterceptorCMT",
132: ex);
133: }
134: }
135:
136: // Interceptor implementation ------------------------------------
137:
138: public void create() throws Exception {
139: super .create();
140: BeanMetaData bmd = getContainer().getBeanMetaData();
141: exceptionRollback = bmd.getExceptionRollback();
142: if (exceptionRollback == false)
143: exceptionRollback = bmd.getApplicationMetaData()
144: .getExceptionRollback();
145: }
146:
147: public Object invokeHome(Invocation invocation) throws Exception {
148: Transaction oldTransaction = invocation.getTransaction();
149: for (int i = 0; i < MAX_RETRIES; i++) {
150: try {
151: return runWithTransactions(invocation);
152: } catch (Exception ex) {
153: checkRetryable(i, ex, oldTransaction);
154: }
155: }
156: throw new RuntimeException("Unreachable");
157: }
158:
159: /**
160: * This method does invocation interpositioning of tx management
161: */
162: public Object invoke(Invocation invocation) throws Exception {
163: Transaction oldTransaction = invocation.getTransaction();
164: for (int i = 0; i < MAX_RETRIES; i++) {
165: try {
166: return runWithTransactions(invocation);
167: } catch (Exception ex) {
168: checkRetryable(i, ex, oldTransaction);
169: }
170: }
171: throw new RuntimeException("Unreachable");
172: }
173:
174: private void checkRetryable(int i, Exception ex,
175: Transaction oldTransaction) throws Exception {
176: // if oldTransaction != null this means tx was propagated over the wire
177: // and we cannot retry it
178: if (i + 1 >= MAX_RETRIES || oldTransaction != null)
179: throw ex;
180: // Keep ADE check for backward compatibility
181: ApplicationDeadlockException deadlock = isADE(ex);
182: if (deadlock != null) {
183: if (!deadlock.retryable())
184: throw deadlock;
185: log
186: .debug(deadlock.getMessage() + " retrying tx "
187: + (i + 1));
188: } else if (retryHandlers != null) {
189: boolean retryable = false;
190: for (int j = 0; j < retryHandlers.length; j++) {
191: retryable = retryHandlers[j].retry(ex);
192: if (retryable)
193: break;
194: }
195: if (!retryable)
196: throw ex;
197: log.debug(ex.getMessage() + " retrying tx " + (i + 1));
198: } else {
199: throw ex;
200: }
201: Thread.sleep(random.nextInt(1 + i), random.nextInt(1000));
202: }
203:
204: // Private ------------------------------------------------------
205:
206: private void printMethod(Method m, byte type) {
207: String txName;
208: switch (type) {
209: case MetaData.TX_MANDATORY:
210: txName = "TX_MANDATORY";
211: break;
212: case MetaData.TX_NEVER:
213: txName = "TX_NEVER";
214: break;
215: case MetaData.TX_NOT_SUPPORTED:
216: txName = "TX_NOT_SUPPORTED";
217: break;
218: case MetaData.TX_REQUIRED:
219: txName = "TX_REQUIRED";
220: break;
221: case MetaData.TX_REQUIRES_NEW:
222: txName = "TX_REQUIRES_NEW";
223: break;
224: case MetaData.TX_SUPPORTS:
225: txName = "TX_SUPPORTS";
226: break;
227: default:
228: txName = "TX_UNKNOWN";
229: }
230:
231: String methodName;
232: if (m != null)
233: methodName = m.getName();
234: else
235: methodName = "<no method>";
236:
237: if (log.isTraceEnabled()) {
238: if (m != null
239: && (type == MetaData.TX_REQUIRED || type == MetaData.TX_REQUIRES_NEW))
240: log.trace(txName
241: + " for "
242: + methodName
243: + " timeout="
244: + container.getBeanMetaData()
245: .getTransactionTimeout(methodName));
246: else
247: log.trace(txName + " for " + methodName);
248: }
249: }
250:
251: /*
252: * This method does invocation interpositioning of tx management.
253: *
254: * This is where the meat is. We define what to do with the Tx based
255: * on the declaration.
256: * The Invocation is always the final authority on what the Tx
257: * looks like leaving this interceptor. In other words, interceptors
258: * down the chain should not rely on the thread association with Tx but
259: * on the Tx present in the Invocation.
260: *
261: * @param remoteInvocation If <code>true</code> this is an invocation
262: * of a method in the remote interface, otherwise
263: * it is an invocation of a method in the home
264: * interface.
265: * @param invocation The <code>Invocation</code> of this call.
266: */
267: private Object runWithTransactions(Invocation invocation)
268: throws Exception {
269: // Old transaction is the transaction that comes with the MI
270: Transaction oldTransaction = invocation.getTransaction();
271: // New transaction is the new transaction this might start
272: Transaction newTransaction = null;
273:
274: boolean trace = log.isTraceEnabled();
275: if (trace)
276: log.trace("Current transaction in MI is " + oldTransaction);
277:
278: InvocationType type = invocation.getType();
279: byte transType = container.getBeanMetaData()
280: .getTransactionMethod(invocation.getMethod(), type);
281:
282: if (trace)
283: printMethod(invocation.getMethod(), transType);
284:
285: // Thread arriving must be clean (jboss doesn't set the thread
286: // previously). However optimized calls come with associated
287: // thread for example. We suspend the thread association here, and
288: // resume in the finally block of the following try.
289: Transaction threadTx = tm.suspend();
290: if (trace)
291: log.trace("Thread came in with tx " + threadTx);
292: try {
293: switch (transType) {
294: case MetaData.TX_NOT_SUPPORTED: {
295: // Do not set a transaction on the thread even if in MI, just run
296: try {
297: invocation.setTransaction(null);
298: return invokeNext(invocation, false);
299: } finally {
300: invocation.setTransaction(oldTransaction);
301: }
302: }
303: case MetaData.TX_REQUIRED: {
304: int oldTimeout = 0;
305: Transaction theTransaction = oldTransaction;
306: if (oldTransaction == null) { // No tx running
307: // Create tx
308: oldTimeout = startTransaction(invocation);
309:
310: // get the tx
311: newTransaction = tm.getTransaction();
312: if (trace)
313: log.trace("Starting new tx " + newTransaction);
314:
315: // Let the method invocation know
316: invocation.setTransaction(newTransaction);
317: theTransaction = newTransaction;
318: } else {
319: // We have a tx propagated
320: // Associate it with the thread
321: tm.resume(oldTransaction);
322: }
323:
324: // Continue invocation
325: try {
326: Object result = invokeNext(invocation,
327: oldTransaction != null);
328: checkTransactionStatus(theTransaction, type);
329: return result;
330: } finally {
331: if (trace)
332: log.trace("TxInterceptorCMT: In finally");
333:
334: // Only do something if we started the transaction
335: if (newTransaction != null)
336: endTransaction(invocation, newTransaction,
337: oldTransaction, oldTimeout);
338: else
339: tm.suspend();
340: }
341: }
342: case MetaData.TX_SUPPORTS: {
343: // Associate old transaction with the thread
344: // Some TMs cannot resume a null transaction and will throw
345: // an exception (e.g. Tyrex), so make sure it is not null
346: if (oldTransaction != null) {
347: tm.resume(oldTransaction);
348: }
349:
350: try {
351: Object result = invokeNext(invocation,
352: oldTransaction != null);
353: if (oldTransaction != null)
354: checkTransactionStatus(oldTransaction, type);
355: return result;
356: } finally {
357: tm.suspend();
358: }
359:
360: // Even on error we don't do anything with the tx,
361: // we didn't start it
362: }
363: case MetaData.TX_REQUIRES_NEW: {
364: // Always begin a transaction
365: int oldTimeout = startTransaction(invocation);
366:
367: // get it
368: newTransaction = tm.getTransaction();
369:
370: // Set it on the method invocation
371: invocation.setTransaction(newTransaction);
372: // Continue invocation
373: try {
374: Object result = invokeNext(invocation, false);
375: checkTransactionStatus(newTransaction, type);
376: return result;
377: } finally {
378: // We started the transaction for sure so we commit or roll back
379: endTransaction(invocation, newTransaction,
380: oldTransaction, oldTimeout);
381: }
382: }
383: case MetaData.TX_MANDATORY: {
384: if (oldTransaction == null) {
385: if (type == InvocationType.LOCAL
386: || type == InvocationType.LOCALHOME) {
387: throw new TransactionRequiredLocalException(
388: "Transaction Required");
389: } else {
390: throw new TransactionRequiredException(
391: "Transaction Required");
392: }
393: }
394:
395: // Associate it with the thread
396: tm.resume(oldTransaction);
397: try {
398: Object result = invokeNext(invocation, true);
399: checkTransactionStatus(oldTransaction, type);
400: return result;
401: } finally {
402: tm.suspend();
403: }
404: }
405: case MetaData.TX_NEVER: {
406: if (oldTransaction != null) {
407: throw new EJBException("Transaction not allowed");
408: }
409: return invokeNext(invocation, false);
410: }
411: default:
412: log.error("Unknown TX attribute " + transType
413: + " for method" + invocation.getMethod());
414: }
415: } finally {
416: // IN case we had a Tx associated with the thread reassociate
417: if (threadTx != null)
418: tm.resume(threadTx);
419: }
420:
421: return null;
422: }
423:
424: private int startTransaction(final Invocation invocation)
425: throws Exception {
426: // Get the old timeout and set any new timeout
427: int oldTimeout = -1;
428: if (tm instanceof TransactionTimeoutConfiguration) {
429: oldTimeout = ((TransactionTimeoutConfiguration) tm)
430: .getTransactionTimeout();
431: int newTimeout = container.getBeanMetaData()
432: .getTransactionTimeout(invocation.getMethod());
433: tm.setTransactionTimeout(newTimeout);
434: }
435: tm.begin();
436: return oldTimeout;
437: }
438:
439: private void endTransaction(final Invocation invocation,
440: final Transaction tx, final Transaction oldTx,
441: final int oldTimeout)
442: throws TransactionRolledbackException, SystemException {
443: // Assert the correct transaction association
444: Transaction current = tm.getTransaction();
445: if ((tx == null && current != null)
446: || tx.equals(current) == false)
447: throw new IllegalStateException(
448: "Wrong transaction association: expected " + tx
449: + " was " + current);
450:
451: try {
452: // Marked rollback
453: if (tx.getStatus() == Status.STATUS_MARKED_ROLLBACK) {
454: tx.rollback();
455: } else {
456: // Commit tx
457: // This will happen if
458: // a) everything goes well
459: // b) app. exception was thrown
460: tx.commit();
461: }
462: } catch (RollbackException e) {
463: throwJBossException(e, invocation.getType());
464: } catch (HeuristicMixedException e) {
465: throwJBossException(e, invocation.getType());
466: } catch (HeuristicRollbackException e) {
467: throwJBossException(e, invocation.getType());
468: } catch (SystemException e) {
469: throwJBossException(e, invocation.getType());
470:
471: } catch (IllegalStateException e) {
472: throwJBossException(e, invocation.getType());
473:
474: } finally {
475: // reassociate the oldTransaction with the Invocation (even null)
476: invocation.setTransaction(oldTx);
477: // Always drop thread association even if committing or
478: // rolling back the newTransaction because not all TMs
479: // will drop thread associations when commit() or rollback()
480: // are called through tx itself (see JTA spec that seems to
481: // indicate that thread assoc is required to be dropped only
482: // when commit() and rollback() are called through TransactionManager
483: // interface)
484: //tx has committed, so we can't throw txRolledbackException.
485: tm.suspend();
486: // Reset the transaction timeout (unless we didn't set it)
487: if (oldTimeout != -1)
488: tm.setTransactionTimeout(oldTimeout);
489: }
490: }
491:
492: // Protected ----------------------------------------------------
493:
494: /**
495: * Rethrow the exception as a rollback or rollback local
496: *
497: * @param e the exception
498: * @param type the invocation type
499: */
500: protected void throwJBossException(Exception e, InvocationType type)
501: throws TransactionRolledbackException {
502: // Unwrap a nested exception if possible. There is no
503: // point in the extra wrapping, and the EJB spec should have
504: // just used javax.transaction exceptions
505: if (e instanceof NestedException) {
506: NestedException rollback = (NestedException) e;
507: if (rollback.getCause() instanceof Exception) {
508: e = (Exception) rollback.getCause();
509: }
510: }
511: if (type == InvocationType.LOCAL
512: || type == InvocationType.LOCALHOME) {
513: throw new JBossTransactionRolledbackLocalException(e);
514: } else {
515: throw new JBossTransactionRolledbackException(e);
516: }
517: }
518:
519: /**
520: * The application has not thrown an exception, but...
521: * When exception-on-rollback is true,
522: * check whether the transaction is not active.
523: * If it did not throw an exception anyway.
524: *
525: * @param tx the transaction
526: * @param type the invocation type
527: * @throws TransactionRolledbackException if transaction is no longer active
528: */
529: protected void checkTransactionStatus(Transaction tx,
530: InvocationType type) throws TransactionRolledbackException {
531: if (exceptionRollback) {
532: if (log.isTraceEnabled())
533: log
534: .trace("No exception from ejb, checking transaction status: "
535: + tx);
536: int status = Status.STATUS_UNKNOWN;
537: try {
538: status = tx.getStatus();
539: } catch (Throwable t) {
540: log
541: .debug(
542: "Ignored error trying to retrieve transaction status",
543: t);
544: }
545: if (status != Status.STATUS_ACTIVE) {
546: Exception e = new Exception(
547: "Transaction cannot be committed (probably transaction timeout): "
548: + tx);
549: throwJBossException(e, type);
550: }
551: }
552: }
553:
554: // Inner classes -------------------------------------------------
555:
556: // Monitorable implementation ------------------------------------
557: public void sample(Object s) {
558: // Just here to because Monitorable request it but will be removed soon
559: }
560:
561: public Map retrieveStatistic() {
562: return null;
563: }
564:
565: public void resetStatistic() {
566: }
567: }
|