1 /*
2 * JBoss, Home of Professional Open Source.
3 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
4 * as indicated by the @author tags. See the copyright.txt file in the
5 * distribution for a 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.util.Hashtable;
25
26 import java.rmi.RemoteException;
27
28 import javax.transaction.Transaction;
29 import javax.transaction.Status;
30 import javax.transaction.SystemException;
31
32 import javax.naming.Context;
33 import javax.naming.InitialContext;
34 import javax.naming.Name;
35 import javax.naming.Reference;
36 import javax.naming.RefAddr;
37 import javax.naming.spi.ObjectFactory;
38
39 import org.jboss.ejb.EnterpriseContext;
40 import org.jboss.ejb.AllowedOperationsAssociation;
41 import org.jboss.invocation.Invocation;
42 import org.jboss.tm.TxUtils;
43
44 /**
45 * A common superclass for the BMT transaction interceptors.
46 *
47 * @author <a href="mailto:osh@sparre.dk">Ole Husgaard</a>
48 * @version $Revision: 73911 $
49 */
50 abstract class AbstractTxInterceptorBMT extends AbstractTxInterceptor
51 {
52 // Attributes ----------------------------------------------------
53
54 /**
55 * This associates the thread to the UserTransaction.
56 *
57 * It is used to redirect lookups on java:comp/UserTransaction to
58 * the <code>getUserTransaction()</code> method of the context.
59 */
60 private ThreadLocal userTransaction = new ThreadLocal();
61
62 /**
63 * If <code>false</code>, transactions may live across bean instance
64 * invocations, otherwise the bean instance should terminate any
65 * transaction before returning from the invocation.
66 * This attribute defaults to <code>true</code>.
67 */
68 protected boolean stateless = true;
69
70 // Static --------------------------------------------------------
71
72 // Constructors --------------------------------------------------
73
74 // Public --------------------------------------------------------
75
76 // Interceptor implementation --------------------------------------
77
78 public void create() throws Exception
79 {
80 // Do initialization in superclass.
81 super.create();
82
83 // bind java:comp/UserTransaction
84 RefAddr refAddr = new RefAddr("userTransaction")
85 {
86 /** This is never really serialized */
87 private static final long serialVersionUID = -8228448967597474960L;
88
89 public Object getContent()
90 {
91 return userTransaction;
92 }
93 };
94
95 Reference ref = new Reference("javax.transaction.UserTransaction", refAddr, new UserTxFactory().getClass()
96 .getName(), null);
97 ((Context) new InitialContext().lookup("java:comp/")).bind("UserTransaction", ref);
98 }
99
100 public void stop()
101 {
102 // bind java:comp/UserTransaction
103 try
104 {
105 ((Context) new InitialContext().lookup("java:comp/")).unbind("UserTransaction");
106 }
107 catch (Exception e)
108 {
109 //ignore
110 }
111 }
112
113 // Protected ----------------------------------------------------
114
115 /*
116 * This method calls the next interceptor in the chain.
117 *
118 * It handles the suspension of any client transaction, and the
119 * association of the calling thread with the instance transaction.
120 * And it takes care that any lookup of
121 * <code>java:comp/UserTransaction</code> will return the right
122 * UserTransaction for the bean instance.
123 *
124 * @param remoteInvocation If <code>true</code> this is an invocation
125 * of a method in the remote interface, otherwise
126 * it is an invocation of a method in the home
127 * interface.
128 * @param mi The <code>Invocation</code> of this call.
129 */
130 protected Object invokeNext(Invocation mi) throws Exception
131 {
132 // Save the transaction that comes with the MI
133 Transaction oldTransaction = mi.getTransaction();
134
135 // Get old threadlocal: It may be non-null if one BMT bean does a local
136 // call to another.
137 Object oldUserTx = userTransaction.get();
138
139 // Suspend any transaction associated with the thread: It may be
140 // non-null on optimized local calls.
141 Transaction threadTx = tm.suspend();
142
143 try
144 {
145 EnterpriseContext ctx = ((EnterpriseContext) mi.getEnterpriseContext());
146
147 // Set the threadlocal to the userTransaction of the instance
148 try
149 {
150 AllowedOperationsAssociation.pushInMethodFlag(IN_INTERCEPTOR_METHOD);
151 userTransaction.set(ctx.getEJBContext().getUserTransaction());
152 }
153 finally
154 {
155 AllowedOperationsAssociation.popInMethodFlag();
156 }
157
158 // Get the bean instance transaction
159 Transaction beanTx = ctx.getTransaction();
160
161 // Resume the bean instance transaction
162 // only if it not null, some TMs can't resume(null), e.g. Tyrex
163 if (beanTx != null)
164 tm.resume(beanTx);
165
166 // Let the MI know about our new transaction
167 mi.setTransaction(beanTx);
168
169 try
170 {
171 // Let the superclass call next interceptor and do the exception
172 // handling
173 return super.invokeNext(mi, false);
174 }
175 finally
176 {
177 try
178 {
179 if (stateless)
180 checkStatelessDone();
181 else
182 checkBadStateful();
183 }
184 finally
185 {
186 tm.suspend();
187 }
188 }
189 }
190 finally
191 {
192 // Reset threadlocal to its old value
193 userTransaction.set(oldUserTx);
194
195 // Restore old MI transaction
196 // OSH: Why ???
197 mi.setTransaction(oldTransaction);
198
199 // If we had a Tx associated with the thread reassociate
200 if (threadTx != null)
201 tm.resume(threadTx);
202 }
203 }
204
205 private void checkStatelessDone() throws RemoteException
206 {
207 int status = Status.STATUS_NO_TRANSACTION;
208
209 try
210 {
211 status = tm.getStatus();
212 }
213 catch (SystemException ex)
214 {
215 log.error("Failed to get status", ex);
216 }
217
218 try
219 {
220 switch (status)
221 {
222 case Status.STATUS_ACTIVE :
223 case Status.STATUS_COMMITTING :
224 case Status.STATUS_MARKED_ROLLBACK :
225 case Status.STATUS_PREPARING :
226 case Status.STATUS_ROLLING_BACK :
227 try
228 {
229 tm.rollback();
230 }
231 catch (Exception ex)
232 {
233 log.error("Failed to rollback", ex);
234 }
235 // fall through...
236 case Status.STATUS_PREPARED :
237 String msg = "Application error: BMT stateless bean " + container.getBeanMetaData().getEjbName()
238 + " should complete transactions before" + " returning (ejb1.1 spec, 11.6.1)";
239 log.error(msg);
240
241 // the instance interceptor will discard the instance
242 throw new RemoteException(msg);
243 }
244 }
245 finally
246 {
247 Transaction tx = null;
248 try
249 {
250 tx = tm.suspend();
251 }
252 catch (SystemException ex)
253 {
254 log.error("Failed to suspend transaction", ex);
255 }
256 if (tx != null)
257 {
258 String msg = "Application error: BMT stateless bean " + container.getBeanMetaData().getEjbName()
259 + " should complete transactions before " + " returning (ejb1.1 spec, 11.6.1), suspended tx=" + tx ;
260 log.error(msg);
261 throw new RemoteException(msg);
262 }
263 }
264 }
265
266 private void checkBadStateful() throws RemoteException
267 {
268 int status = Status.STATUS_NO_TRANSACTION;
269
270 try
271 {
272 status = tm.getStatus();
273 }
274 catch (SystemException ex)
275 {
276 log.error("Failed to get status", ex);
277 }
278 switch (status)
279 {
280 case Status.STATUS_COMMITTING :
281 case Status.STATUS_MARKED_ROLLBACK :
282 case Status.STATUS_PREPARING :
283 case Status.STATUS_ROLLING_BACK :
284 try
285 {
286 tm.rollback();
287 }
288 catch (Exception ex)
289 {
290 log.error("Failed to rollback", ex);
291 }
292 String msg = "BMT stateful bean '" + container.getBeanMetaData().getEjbName()
293 + "' did not complete user transaction properly status=" + TxUtils.getStatusAsString(status);
294 log.error(msg);
295 }
296 }
297
298 // Inner classes -------------------------------------------------
299
300 public static class UserTxFactory implements ObjectFactory
301 {
302 public Object getObjectInstance(Object ref, Name name, Context nameCtx, Hashtable environment) throws Exception
303 {
304 // The ref is a list with only one RefAddr ...
305 RefAddr refAddr = ((Reference) ref).get(0);
306 // ... whose content is the threadlocal
307 ThreadLocal threadLocal = (ThreadLocal) refAddr.getContent();
308
309 // The threadlocal holds the right UserTransaction
310 return threadLocal.get();
311 }
312 }
313
314 }