1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.ejb.plugins;
23
24 import org.jboss.invocation.Invocation;
25 import org.jboss.invocation.InvocationType;
26 import org.jboss.metadata.BeanMetaData;
27 import org.jboss.metadata.MetaData;
28 import org.jboss.metadata.XmlLoadable;
29 import org.jboss.tm.JBossTransactionRolledbackException;
30 import org.jboss.tm.JBossTransactionRolledbackLocalException;
31 import org.jboss.tm.TransactionTimeoutConfiguration;
32 import org.jboss.util.NestedException;
33 import org.jboss.util.deadlock.ApplicationDeadlockException;
34 import org.w3c.dom.Element;
35
36 import javax.ejb.EJBException;
37 import javax.ejb.TransactionRequiredLocalException;
38 import javax.transaction.HeuristicMixedException;
39 import javax.transaction.HeuristicRollbackException;
40 import javax.transaction.RollbackException;
41 import javax.transaction.Status;
42 import javax.transaction.SystemException;
43 import javax.transaction.Transaction;
44 import javax.transaction.TransactionRequiredException;
45 import javax.transaction.TransactionRolledbackException;
46 import java.lang.reflect.Method;
47 import java.rmi.RemoteException;
48 import java.util.HashMap;
49 import java.util.Map;
50 import java.util.Random;
51 import java.util.Iterator;
52 import java.util.ArrayList;
53
54 /**
55 * This interceptor handles transactions for CMT beans.
56 *
57 * @author <a href="mailto:rickard.oberg@telkel.com">Rickard Oberg</a>
58 * @author <a href="mailto:marc.fleury@telkel.com">Marc Fleury</a>
59 * @author <a href="mailto:sebastien.alborini@m4x.org">Sebastien Alborini</a>
60 * @author <a href="mailto:akkerman@cs.nyu.edu">Anatoly Akkerman</a>
61 * @author <a href="mailto:osh@sparre.dk">Ole Husgaard</a>
62 * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
63 * @version $Revision: 66439 $
64 */
65 public class TxInterceptorCMT extends AbstractTxInterceptor implements XmlLoadable
66 {
67
68 // Constants -----------------------------------------------------
69
70
71 public static int MAX_RETRIES = 5;
72 public static Random random = new Random();
73
74 // Attributes ----------------------------------------------------
75
76 /**
77 * Whether an exception should be thrown if the transaction is not
78 * active, even though the application doesn't throw an exception
79 */
80 private boolean exceptionRollback = true;
81
82 private TxRetryExceptionHandler[] retryHandlers = null;
83
84 // Static --------------------------------------------------------
85
86
87 /**
88 * Detects exception contains is or a ApplicationDeadlockException.
89 */
90 public static ApplicationDeadlockException isADE(Throwable t)
91 {
92 while (t!=null)
93 {
94 if (t instanceof ApplicationDeadlockException)
95 {
96 return (ApplicationDeadlockException)t;
97 }
98 else if (t instanceof RemoteException)
99 {
100 t = ((RemoteException)t).detail;
101 }
102 else if (t instanceof EJBException)
103 {
104 t = ((EJBException)t).getCausedByException();
105 }
106 else
107 {
108 return null;
109 }
110 }
111 return null;
112 }
113
114 // Constructors --------------------------------------------------
115
116 // Public --------------------------------------------------------
117
118 // XmlLoadable implementation ------------------------------------
119
120 public void importXml(Element ielement)
121 {
122 try
123 {
124 Element element = MetaData.getOptionalChild(ielement, "retry-handlers");
125 if (element == null) return;
126 ArrayList list = new ArrayList();
127 Iterator handlers = MetaData.getChildrenByTagName(element, "handler");
128 while (handlers.hasNext())
129 {
130 Element handler = (Element)handlers.next();
131 String className = MetaData.getElementContent(handler).trim();
132 Class clazz = SecurityActions.getContextClassLoader().loadClass(className);
133 list.add(clazz.newInstance());
134 }
135 retryHandlers = (TxRetryExceptionHandler[])list.toArray(new TxRetryExceptionHandler[list.size()]);
136 }
137 catch (Exception ex)
138 {
139 log.warn("Unable to importXml for the TxInterceptorCMT", ex);
140 }
141 }
142
143 // Interceptor implementation ------------------------------------
144
145 public void create() throws Exception
146 {
147 super.create();
148 BeanMetaData bmd = getContainer().getBeanMetaData();
149 exceptionRollback = bmd.getExceptionRollback();
150 if (exceptionRollback == false)
151 exceptionRollback = bmd.getApplicationMetaData().getExceptionRollback();
152 }
153
154 public Object invokeHome(Invocation invocation) throws Exception
155 {
156 Transaction oldTransaction = invocation.getTransaction();
157 for (int i = 0; i < MAX_RETRIES; i++)
158 {
159 try
160 {
161 return runWithTransactions(invocation);
162 }
163 catch (Exception ex)
164 {
165 checkRetryable(i, ex, oldTransaction);
166 }
167 }
168 throw new RuntimeException("Unreachable");
169 }
170
171 /**
172 * This method does invocation interpositioning of tx management
173 */
174 public Object invoke(Invocation invocation) throws Exception
175 {
176 Transaction oldTransaction = invocation.getTransaction();
177 for (int i = 0; i < MAX_RETRIES; i++)
178 {
179 try
180 {
181 return runWithTransactions(invocation);
182 }
183 catch (Exception ex)
184 {
185 checkRetryable(i, ex, oldTransaction);
186 }
187 }
188 throw new RuntimeException("Unreachable");
189 }
190
191 private void checkRetryable(int i, Exception ex, Transaction oldTransaction) throws Exception
192 {
193 // if oldTransaction != null this means tx was propagated over the wire
194 // and we cannot retry it
195 if (i + 1 >= MAX_RETRIES || oldTransaction != null) throw ex;
196 // Keep ADE check for backward compatibility
197 ApplicationDeadlockException deadlock = isADE(ex);
198 if (deadlock != null)
199 {
200 if (!deadlock.retryable()) throw deadlock;
201 log.debug(deadlock.getMessage() + " retrying tx " + (i + 1));
202 }
203 else if (retryHandlers != null)
204 {
205 boolean retryable = false;
206 for (int j = 0; j < retryHandlers.length; j++)
207 {
208 retryable = retryHandlers[j].retry(ex);
209 if (retryable) break;
210 }
211 if (!retryable) throw ex;
212 log.debug(ex.getMessage() + " retrying tx " + (i + 1));
213 }
214 else
215 {
216 throw ex;
217 }
218 Thread.sleep(random.nextInt(1 + i), random.nextInt(1000));
219 }
220
221 // Private ------------------------------------------------------
222
223 private void printMethod(Method m, byte type)
224 {
225 String txName;
226 switch(type)
227 {
228 case MetaData.TX_MANDATORY:
229 txName = "TX_MANDATORY";
230 break;
231 case MetaData.TX_NEVER:
232 txName = "TX_NEVER";
233 break;
234 case MetaData.TX_NOT_SUPPORTED:
235 txName = "TX_NOT_SUPPORTED";
236 break;
237 case MetaData.TX_REQUIRED:
238 txName = "TX_REQUIRED";
239 break;
240 case MetaData.TX_REQUIRES_NEW:
241 txName = "TX_REQUIRES_NEW";
242 break;
243 case MetaData.TX_SUPPORTS:
244 txName = "TX_SUPPORTS";
245 break;
246 default:
247 txName = "TX_UNKNOWN";
248 }
249
250 String methodName;
251 if(m != null)
252 methodName = m.getName();
253 else
254 methodName ="<no method>";
255
256 if (log.isTraceEnabled())
257 {
258 if (m != null && (type == MetaData.TX_REQUIRED || type == MetaData.TX_REQUIRES_NEW))
259 log.trace(txName + " for " + methodName + " timeout=" + container.getBeanMetaData().getTransactionTimeout(methodName));
260 else
261 log.trace(txName + " for " + methodName);
262 }
263 }
264
265 /*
266 * This method does invocation interpositioning of tx management.
267 *
268 * This is where the meat is. We define what to do with the Tx based
269 * on the declaration.
270 * The Invocation is always the final authority on what the Tx
271 * looks like leaving this interceptor. In other words, interceptors
272 * down the chain should not rely on the thread association with Tx but
273 * on the Tx present in the Invocation.
274 *
275 * @param remoteInvocation If <code>true</code> this is an invocation
276 * of a method in the remote interface, otherwise
277 * it is an invocation of a method in the home
278 * interface.
279 * @param invocation The <code>Invocation</code> of this call.
280 */
281 private Object runWithTransactions(Invocation invocation) throws Exception
282 {
283 // Old transaction is the transaction that comes with the MI
284 Transaction oldTransaction = invocation.getTransaction();
285 // New transaction is the new transaction this might start
286 Transaction newTransaction = null;
287
288 boolean trace = log.isTraceEnabled();
289 if( trace )
290 log.trace("Current transaction in MI is " + oldTransaction);
291
292 InvocationType type = invocation.getType();
293 byte transType = container.getBeanMetaData().getTransactionMethod(invocation.getMethod(), type);
294
295 if ( trace )
296 printMethod(invocation.getMethod(), transType);
297
298 // Thread arriving must be clean (jboss doesn't set the thread
299 // previously). However optimized calls come with associated
300 // thread for example. We suspend the thread association here, and
301 // resume in the finally block of the following try.
302 Transaction threadTx = tm.suspend();
303 if( trace )
304 log.trace("Thread came in with tx " + threadTx);
305 try
306 {
307 switch (transType)
308 {
309 case MetaData.TX_NOT_SUPPORTED:
310 {
311 // Do not set a transaction on the thread even if in MI, just run
312 try
313 {
314 invocation.setTransaction(null);
315 return invokeNext(invocation, false);
316 }
317 finally
318 {
319 invocation.setTransaction(oldTransaction);
320 }
321 }
322 case MetaData.TX_REQUIRED:
323 {
324 int oldTimeout = 0;
325 Transaction theTransaction = oldTransaction;
326 if (oldTransaction == null)
327 { // No tx running
328 // Create tx
329 oldTimeout = startTransaction(invocation);
330
331 // get the tx
332 newTransaction = tm.getTransaction();
333 if( trace )
334 log.trace("Starting new tx " + newTransaction);
335
336 // Let the method invocation know
337 invocation.setTransaction(newTransaction);
338 theTransaction = newTransaction;
339 }
340 else
341 {
342 // We have a tx propagated
343 // Associate it with the thread
344 tm.resume(oldTransaction);
345 }
346
347 // Continue invocation
348 try
349 {
350 Object result = invokeNext(invocation, oldTransaction != null);
351 checkTransactionStatus(theTransaction, type);
352 return result;
353 }
354 finally
355 {
356 if( trace )
357 log.trace("TxInterceptorCMT: In finally");
358
359 // Only do something if we started the transaction
360 if (newTransaction != null)
361 endTransaction(invocation, newTransaction, oldTransaction, oldTimeout);
362 else
363 tm.suspend();
364 }
365 }
366 case MetaData.TX_SUPPORTS:
367 {
368 // Associate old transaction with the thread
369 // Some TMs cannot resume a null transaction and will throw
370 // an exception (e.g. Tyrex), so make sure it is not null
371 if (oldTransaction != null)
372 {
373 tm.resume(oldTransaction);
374 }
375
376 try
377 {
378 Object result = invokeNext(invocation, oldTransaction != null);
379 if (oldTransaction != null)
380 checkTransactionStatus(oldTransaction, type);
381 return result;
382 }
383 finally
384 {
385 tm.suspend();
386 }
387
388 // Even on error we don't do anything with the tx,
389 // we didn't start it
390 }
391 case MetaData.TX_REQUIRES_NEW:
392 {
393 // Always begin a transaction
394 int oldTimeout = startTransaction(invocation);
395
396 // get it
397 newTransaction = tm.getTransaction();
398
399 // Set it on the method invocation
400 invocation.setTransaction(newTransaction);
401 // Continue invocation
402 try
403 {
404 Object result = invokeNext(invocation, false);
405 checkTransactionStatus(newTransaction, type);
406 return result;
407 }
408 finally
409 {
410 // We started the transaction for sure so we commit or roll back
411 endTransaction(invocation, newTransaction, oldTransaction, oldTimeout);
412 }
413 }
414 case MetaData.TX_MANDATORY:
415 {
416 if (oldTransaction == null)
417 {
418 if (type == InvocationType.LOCAL ||
419 type == InvocationType.LOCALHOME)
420 {
421 throw new TransactionRequiredLocalException(
422 "Transaction Required");
423 }
424 else
425 {
426 throw new TransactionRequiredException(
427 "Transaction Required");
428 }
429 }
430
431 // Associate it with the thread
432 tm.resume(oldTransaction);
433 try
434 {
435 Object result = invokeNext(invocation, true);
436 checkTransactionStatus(oldTransaction, type);
437 return result;
438 }
439 finally
440 {
441 tm.suspend();
442 }
443 }
444 case MetaData.TX_NEVER:
445 {
446 if (oldTransaction != null)
447 {
448 throw new EJBException("Transaction not allowed");
449 }
450 return invokeNext(invocation, false);
451 }
452 default:
453 log.error("Unknown TX attribute "+transType+" for method"+invocation.getMethod());
454 }
455 }
456 finally
457 {
458 // IN case we had a Tx associated with the thread reassociate
459 if (threadTx != null)
460 tm.resume(threadTx);
461 }
462
463 return null;
464 }
465
466 private int startTransaction(final Invocation invocation) throws Exception
467 {
468 // Get the old timeout and set any new timeout
469 int oldTimeout = -1;
470 if (tm instanceof TransactionTimeoutConfiguration)
471 {
472 oldTimeout = ((TransactionTimeoutConfiguration) tm).getTransactionTimeout();
473 int newTimeout = container.getBeanMetaData().getTransactionTimeout(invocation.getMethod());
474 tm.setTransactionTimeout(newTimeout);
475 }
476 tm.begin();
477 return oldTimeout;
478 }
479
480 private void endTransaction(final Invocation invocation, final Transaction tx, final Transaction oldTx, final int oldTimeout)
481 throws TransactionRolledbackException, SystemException
482 {
483 // Assert the correct transaction association
484 Transaction current = tm.getTransaction();
485 if ((tx == null && current != null) || tx.equals(current) == false)
486 throw new IllegalStateException("Wrong transaction association: expected " + tx + " was " + current);
487
488 try
489 {
490 // Marked rollback
491 if (tx.getStatus() == Status.STATUS_MARKED_ROLLBACK)
492 {
493 tx.rollback();
494 }
495 else
496 {
497 // Commit tx
498 // This will happen if
499 // a) everything goes well
500 // b) app. exception was thrown
501 tx.commit();
502 }
503 }
504 catch (RollbackException e)
505 {
506 throwJBossException(e, invocation.getType());
507 }
508 catch (HeuristicMixedException e)
509 {
510 throwJBossException(e, invocation.getType());
511 }
512 catch (HeuristicRollbackException e)
513 {
514 throwJBossException(e, invocation.getType());
515 }
516 catch (SystemException e)
517 {
518 throwJBossException(e, invocation.getType());
519 }
520 catch (IllegalStateException e)
521 {
522 throwJBossException(e, invocation.getType());
523 }
524 finally
525 {
526 // reassociate the oldTransaction with the Invocation (even null)
527 invocation.setTransaction(oldTx);
528 // Always drop thread association even if committing or
529 // rolling back the newTransaction because not all TMs
530 // will drop thread associations when commit() or rollback()
531 // are called through tx itself (see JTA spec that seems to
532 // indicate that thread assoc is required to be dropped only
533 // when commit() and rollback() are called through TransactionManager
534 // interface)
535 //tx has committed, so we can't throw txRolledbackException.
536 tm.suspend();
537 // Reset the transaction timeout (unless we didn't set it)
538 if (oldTimeout != -1)
539 tm.setTransactionTimeout(oldTimeout);
540 }
541 }
542
543
544 // Protected ----------------------------------------------------
545
546 /**
547 * Rethrow the exception as a rollback or rollback local
548 *
549 * @param e the exception
550 * @param type the invocation type
551 */
552 protected void throwJBossException(Exception e, InvocationType type)
553 throws TransactionRolledbackException
554 {
555 // Unwrap a nested exception if possible. There is no
556 // point in the extra wrapping, and the EJB spec should have
557 // just used javax.transaction exceptions
558 if (e instanceof NestedException)
559 {
560 NestedException rollback = (NestedException) e;
561 if(rollback.getCause() instanceof Exception)
562 {
563 e = (Exception) rollback.getCause();
564 }
565 }
566 if (type == InvocationType.LOCAL
567 || type == InvocationType.LOCALHOME)
568 {
569 throw new JBossTransactionRolledbackLocalException(e);
570 }
571 else
572 {
573 throw new JBossTransactionRolledbackException(e);
574 }
575 }
576
577 /**
578 * The application has not thrown an exception, but...
579 * When exception-on-rollback is true,
580 * check whether the transaction is not active.
581 * If it did not throw an exception anyway.
582 *
583 * @param tx the transaction
584 * @param type the invocation type
585 * @throws TransactionRolledbackException if transaction is no longer active
586 */
587 protected void checkTransactionStatus(Transaction tx, InvocationType type)
588 throws TransactionRolledbackException
589 {
590 if (exceptionRollback)
591 {
592 if (log.isTraceEnabled())
593 log.trace("No exception from ejb, checking transaction status: " + tx);
594 int status = Status.STATUS_UNKNOWN;
595 try
596 {
597 status = tx.getStatus();
598 }
599 catch (Throwable t)
600 {
601 log.debug("Ignored error trying to retrieve transaction status", t);
602 }
603 if (status != Status.STATUS_ACTIVE)
604 {
605 Exception e = new Exception("Transaction cannot be committed (probably transaction timeout): " + tx);
606 throwJBossException(e, type);
607 }
608 }
609 }
610
611 // Inner classes -------------------------------------------------
612
613 // Monitorable implementation ------------------------------------
614 public void sample(Object s)
615 {
616 // Just here to because Monitorable request it but will be removed soon
617 }
618 public Map retrieveStatistic()
619 {
620 return null;
621 }
622 public void resetStatistic()
623 {
624 }
625 }