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 java.io.PrintWriter;
25 import java.io.StringWriter;
26
27 import java.rmi.RemoteException;
28 import java.rmi.ServerException;
29 import java.rmi.NoSuchObjectException;
30 import java.lang.reflect.Method;
31
32 import javax.transaction.TransactionManager;
33 import javax.transaction.TransactionRolledbackException;
34 import javax.transaction.SystemException;
35 import javax.transaction.Transaction;
36 import javax.transaction.Synchronization;
37 import javax.transaction.RollbackException;
38
39 import javax.ejb.EJBException;
40 import javax.ejb.NoSuchEntityException;
41 import javax.ejb.NoSuchObjectLocalException;
42 import javax.ejb.TransactionRolledbackLocalException;
43 import javax.ejb.TimedObject;
44 import javax.ejb.Timer;
45
46 import org.jboss.invocation.Invocation;
47 import org.jboss.invocation.InvocationType;
48 import org.jboss.tm.TxUtils;
49
50 /**
51 * A common superclass for the transaction interceptors.
52 * <p/>
53 * The only important method in this class is invokeNext which is incharge
54 * of invoking the next interceptor and if an exception is thrown, it must
55 * follow the rules in the EJB 2.0 specification section 18.3. These
56 * rules specify if the transaction is rolled back and what exception
57 * should be thrown.
58 *
59 * @author <a href="mailto:osh@sparre.dk">Ole Husgaard</a>
60 * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
61 * @version $Revision: 37459 $
62 */
63 abstract class AbstractTxInterceptor
64 extends AbstractInterceptor
65 {
66
67 /** A reference to {@link javax.ejb.TimedObject#ejbTimeout}. */
68 protected static final Method ejbTimeout;
69 static
70 {
71 try
72 {
73 ejbTimeout = TimedObject.class.getMethod("ejbTimeout", new Class[]{Timer.class});
74 }
75 catch (Exception e)
76 {
77 throw new ExceptionInInitializerError(e);
78 }
79 }
80
81 /**
82 * Local reference to the container's TransactionManager.
83 */
84 protected TransactionManager tm;
85
86 public void create() throws Exception
87 {
88 super.create();
89 tm = getContainer().getTransactionManager();
90 }
91
92 /**
93 * This method calls the next interceptor in the chain.
94 * <p/>
95 * All Throwables are caught and divided into two groups: application
96 * exceptions and system exceptions. Application exception are simply
97 * rethrown. System exceptions result in the transaction being marked
98 * for rollback only. If the transaction was not started by the container
99 * (i.e., it was inherited from the client) the system exception is wrapped
100 * in a TransactionRolledBack[Local]Exception.
101 *
102 * @param invocation The <code>Invocation</code> of this call.
103 * @param inheritedTx If <code>true</code> the transaction has just been started
104 * in this interceptor.
105 * @throws Exception if an exception occures in the interceptor chain. The
106 * actual exception throw is governed by the rules in the EJB 2.0
107 * specification section 18.3
108 */
109 protected Object invokeNext(Invocation invocation, boolean inheritedTx)
110 throws Exception
111 {
112 InvocationType type = invocation.getType();
113 try
114 {
115 if (type == InvocationType.REMOTE || type == InvocationType.LOCAL || type == InvocationType.SERVICE_ENDPOINT)
116 {
117 // register the Timer with the transaction
118 if (ejbTimeout.equals(invocation.getMethod()))
119 registerTimer(invocation);
120
121 return getNext().invoke(invocation);
122 }
123 else
124 {
125 return getNext().invokeHome(invocation);
126 }
127 }
128 catch (Throwable e)
129 {
130 // if this is an ApplicationException, just rethrow it
131 if (e instanceof Exception &&
132 !(e instanceof RuntimeException || e instanceof RemoteException))
133 {
134 throw (Exception) e;
135 }
136
137 // attempt to rollback the transaction
138 Transaction tx = invocation.getTransaction();
139 if (tx == null)
140 {
141 // Look for a hanging active user transaction that we should mark rollback
142 try
143 {
144 tx = tm.getTransaction();
145 if (TxUtils.isActive(tx) == false)
146 tx = null;
147 }
148 catch (Exception ex)
149 {
150 log.warn("Unable to determine transaction context", ex);
151 }
152 }
153 if (tx != null)
154 {
155 try
156 {
157 tx.setRollbackOnly();
158 }
159 catch (SystemException ex)
160 {
161 log.error("SystemException while setting transaction " +
162 "for rollback only", ex);
163 }
164 catch (IllegalStateException ex)
165 {
166 log.error("IllegalStateException while setting transaction " +
167 "for rollback only", ex);
168 }
169 }
170
171 // is this a local invocation
172 boolean isLocal =
173 type == InvocationType.LOCAL ||
174 type == InvocationType.LOCALHOME;
175
176 // if this transaction was NOT inherited from the caller we simply
177 // rethrow the exception, and LogInterceptor will handle
178 // all exception conversions.
179 if (!inheritedTx)
180 {
181 if (e instanceof Exception)
182 {
183 throw (Exception) e;
184 }
185 if (e instanceof Error)
186 {
187 throw (Error) e;
188 }
189
190 // we have some funky throwable, wrap it
191 if (isLocal)
192 {
193 String msg = formatException("Unexpected Throwable", e);
194 throw new EJBException(msg);
195 }
196 else
197 {
198 ServerException ex = new ServerException("Unexpected Throwable");
199 ex.detail = e;
200 throw ex;
201 }
202 }
203
204 // to be nice we coerce the execption to an interface friendly type
205 // before wrapping it with a transaction rolled back exception
206 Throwable cause;
207 if (e instanceof NoSuchEntityException)
208 {
209 NoSuchEntityException nsee = (NoSuchEntityException) e;
210 if (isLocal)
211 {
212 cause = new NoSuchObjectLocalException(nsee.getMessage(),
213 nsee.getCausedByException());
214 }
215 else
216 {
217 cause = new NoSuchObjectException(nsee.getMessage());
218
219 // set the detil of the exception
220 ((NoSuchObjectException) cause).detail =
221 nsee.getCausedByException();
222 }
223 }
224 else
225 {
226 if (isLocal)
227 {
228 // local transaction rolled back exception can only wrap
229 // an exception so we create an EJBException for the cause
230 if (e instanceof Exception)
231 {
232 cause = e;
233 }
234 else if (e instanceof Error)
235 {
236 String msg = formatException("Unexpected Error", e);
237 cause = new EJBException(msg);
238 }
239 else
240 {
241 String msg = formatException("Unexpected Throwable", e);
242 cause = new EJBException(msg);
243 }
244 }
245 else
246 {
247 // remote transaction rolled back exception can wrap
248 // any throwable so we are ok
249 cause = e;
250 }
251 }
252
253 // We inherited tx: Tell caller we marked for rollback only.
254 if (isLocal)
255 {
256 if (cause instanceof TransactionRolledbackLocalException)
257 {
258 throw (TransactionRolledbackLocalException) cause;
259 }
260 else
261 {
262 throw new TransactionRolledbackLocalException(cause.getMessage(),
263 (Exception) cause);
264 }
265 }
266 else
267 {
268 if (cause instanceof TransactionRolledbackException)
269 {
270 throw (TransactionRolledbackException) cause;
271 }
272 else
273 {
274 TransactionRolledbackException ex =
275 new TransactionRolledbackException(cause.getMessage());
276 ex.detail = cause;
277 throw ex;
278 }
279 }
280 }
281 }
282
283 private void registerTimer(Invocation invocation)
284 throws RollbackException, SystemException
285 {
286 Timer timer = (Timer) invocation.getArguments()[0];
287 Transaction transaction = invocation.getTransaction();
288 if (transaction != null && timer instanceof Synchronization)
289 transaction.registerSynchronization((Synchronization) timer);
290 }
291
292 private String formatException(String msg, Throwable t)
293 {
294 StringWriter sw = new StringWriter();
295 PrintWriter pw = new PrintWriter(sw);
296 if (msg != null)
297 {
298 pw.println(msg);
299 }
300 t.printStackTrace(pw);
301 return sw.toString();
302 }
303 }