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.lang.reflect.Method;
25 import java.rmi.NoSuchObjectException;
26 import java.rmi.RemoteException;
27
28 import javax.ejb.EJBException;
29 import javax.ejb.NoSuchObjectLocalException;
30 import javax.ejb.TimedObject;
31 import javax.ejb.Timer;
32 import javax.transaction.Transaction;
33
34 import org.jboss.ejb.AllowedOperationsAssociation;
35 import org.jboss.ejb.BeanLock;
36 import org.jboss.ejb.Container;
37 import org.jboss.ejb.EntityContainer;
38 import org.jboss.ejb.EntityEnterpriseContext;
39 import org.jboss.ejb.GlobalTxEntityMap;
40 import org.jboss.ejb.InstanceCache;
41 import org.jboss.ejb.InstancePool;
42 import org.jboss.invocation.Invocation;
43 import org.jboss.invocation.InvocationType;
44 import org.jboss.util.NestedRuntimeException;
45
46 /**
47 * The instance interceptors role is to acquire a context representing the
48 * target object from the cache.
49 *
50 * <p>This particular container interceptor implements pessimistic locking on
51 * the transaction that is associated with the retrieved instance. If there is
52 * a transaction associated with the target component and it is different from
53 * the transaction associated with the Invocation coming in then the policy is
54 * to wait for transactional commit.
55 *
56 * <p>We also implement serialization of calls in here (this is a spec
57 * requirement). This is a fine grained notify, notifyAll mechanism. We notify
58 * on ctx serialization locks and notifyAll on global transactional locks.
59 *
60 * <p><b>WARNING: critical code</b>, get approval from senior developers before
61 * changing.
62 * @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
63 * @author <a href="mailto:Scott.Stark@jboss.org">Scott Stark</a>
64 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
65 * @author <a href="mailto:mkgarnek@hotmail.com">Jamie Burns</a>
66 * @version $Revision: 69133 $
67 */
68 public class EntityInstanceInterceptor
69 extends AbstractInterceptor
70 {
71 // Constants -----------------------------------------------------
72
73 // Attributes ----------------------------------------------------
74
75 protected EntityContainer container;
76
77 // Static --------------------------------------------------------
78
79 /** A reference to {@link javax.ejb.TimedObject#ejbTimeout}. */
80 protected static final Method ejbTimeout;
81
82 static
83 {
84 try
85 {
86 ejbTimeout = TimedObject.class.getMethod("ejbTimeout", new Class[]{Timer.class});
87 }
88 catch (Exception e)
89 {
90 throw new ExceptionInInitializerError(e);
91 }
92 }
93
94 // Constructors --------------------------------------------------
95
96 // Public --------------------------------------------------------
97
98 public void setContainer(Container container)
99 {
100 this.container = (EntityContainer) container;
101 }
102
103 public Container getContainer()
104 {
105 return container;
106 }
107
108 // Interceptor implementation --------------------------------------
109
110 public Object invokeHome(Invocation mi)
111 throws Exception
112 {
113 // Get context
114 EntityContainer container = (EntityContainer) getContainer();
115 EntityEnterpriseContext ctx = (EntityEnterpriseContext) container.getInstancePool().get();
116 ctx.setTxAssociation(GlobalTxEntityMap.NOT_READY);
117 InstancePool pool = container.getInstancePool();
118
119 // Pass it to the method invocation
120 mi.setEnterpriseContext(ctx);
121
122 // Give it the transaction
123 ctx.setTransaction(mi.getTransaction());
124
125 // Set the current security information
126 ctx.setPrincipal(mi.getPrincipal());
127
128 AllowedOperationsAssociation.pushInMethodFlag(IN_EJB_HOME);
129
130 // Invoke through interceptors
131
132 Object obj = null;
133 Exception exception = null;
134
135 try
136 {
137 obj = getNext().invokeHome(mi);
138
139 // Is the context now with an identity? in which case we need to insert
140 if (ctx.getId() != null)
141 {
142 BeanLock lock = container.getLockManager().getLock(ctx.getCacheKey());
143 lock.sync(); // lock all access to BeanLock
144 try
145 {
146 // Check there isn't a context already in the cache
147 // e.g. commit-option B where the entity was
148 // created then removed externally
149 InstanceCache cache = container.getInstanceCache();
150 cache.remove(ctx.getCacheKey());
151
152 // marcf: possible race on creation and usage
153 // insert instance in cache,
154 cache.insert(ctx);
155 }
156 finally
157 {
158 lock.releaseSync();
159 container.getLockManager().removeLockRef(ctx.getCacheKey());
160 }
161
162 // we are all done
163 return obj;
164 }
165 }
166 catch (Exception e)
167 {
168 exception = e;
169 }
170 finally
171 {
172 AllowedOperationsAssociation.popInMethodFlag();
173 }
174
175 ctx.setTransaction(null);
176 // EntityCreateInterceptor will access ctx if it is not null and call postCreate
177 mi.setEnterpriseContext(null);
178
179 // if we get to here with a null exception then our invocation is
180 // just a home invocation. Return our instance to the instance pool
181 if (exception == null)
182 {
183 container.getInstancePool().free(ctx);
184 return obj;
185 }
186
187 if (exception instanceof RuntimeException)
188 {
189 // if we get to here with a RuntimeException, we have a system exception.
190 // EJB 2.1 section 18.3.1 says we need to discard our instance.
191 pool.discard(ctx);
192 }
193 else
194 {
195 // if we get to here with an Exception, we have an application exception.
196 // EJB 2.1 section 18.3.1 says we can keep the instance. We need to return
197 // our instance to the instance pool so we dont get a memory leak.
198 pool.free(ctx);
199 }
200
201 throw exception;
202 }
203
204
205 public Object invoke(Invocation mi)
206 throws Exception
207 {
208 boolean trace = log.isTraceEnabled();
209
210 // The key
211 Object key = mi.getId();
212
213 // The context
214 EntityEnterpriseContext ctx;
215 try
216 {
217 ctx = (EntityEnterpriseContext) container.getInstanceCache().get(key);
218 }
219 catch (NoSuchObjectException e)
220 {
221 if (mi.isLocal())
222 throw new NoSuchObjectLocalException(e.getMessage());
223 else
224 throw e;
225 }
226 catch (EJBException e)
227 {
228 throw e;
229 }
230 catch (RemoteException e)
231 {
232 throw e;
233 }
234 catch (Exception e)
235 {
236 InvocationType type = mi.getType();
237 boolean isLocal = (type == InvocationType.LOCAL || type == InvocationType.LOCALHOME);
238 if (isLocal)
239 throw new EJBException("Unable to get an instance from the pool/cache", e);
240 else
241 throw new RemoteException("Unable to get an intance from the pool/cache", e);
242 }
243
244 if (trace) log.trace("Begin invoke, key=" + key);
245
246 // Associate transaction, in the new design the lock already has the transaction from the
247 // previous interceptor
248
249 // Don't set the transction if a read-only method. With a read-only method, the ctx can be shared
250 // between multiple transactions.
251 Transaction tx = mi.getTransaction();
252 if (!container.isReadOnly())
253 {
254 Method method = mi.getMethod();
255 if (method == null ||
256 !container.getBeanMetaData().isMethodReadOnly(method.getName()))
257 {
258 ctx.setTransaction(tx);
259 }
260 }
261
262 // Set the current security information
263 ctx.setPrincipal(mi.getPrincipal());
264 // Set the JACC EnterpriseBean PolicyContextHandler data
265 EnterpriseBeanPolicyContextHandler.setEnterpriseBean(ctx.getInstance());
266
267 // Set context on the method invocation
268 mi.setEnterpriseContext(ctx);
269
270 if (ejbTimeout.equals(mi.getMethod()))
271 AllowedOperationsAssociation.pushInMethodFlag(IN_EJB_TIMEOUT);
272 else
273 AllowedOperationsAssociation.pushInMethodFlag(IN_BUSINESS_METHOD);
274
275 Throwable exceptionThrown = null;
276 boolean discardContext = false;
277 try
278 {
279 Object obj = getNext().invoke(mi);
280 return obj;
281 }
282 catch (RemoteException e)
283 {
284 exceptionThrown = e;
285 discardContext = true;
286 throw e;
287 }
288 catch (RuntimeException e)
289 {
290 exceptionThrown = e;
291 discardContext = true;
292 throw e;
293 }
294 catch (Error e)
295 {
296 exceptionThrown = e;
297 discardContext = true;
298 throw e;
299 }
300 catch (Exception e)
301 {
302 exceptionThrown = e;
303 throw e;
304 }
305 catch (Throwable e)
306 {
307 exceptionThrown = e;
308 discardContext = true;
309 throw new NestedRuntimeException(e);
310 }
311 finally
312 {
313 AllowedOperationsAssociation.popInMethodFlag();
314
315 // Make sure we clear the transaction on an error before synchronization.
316 // But avoid a race with a transaction rollback on a synchronization
317 // that may have moved the context onto a different transaction
318 if (exceptionThrown != null && tx != null)
319 {
320 Transaction ctxTx = ctx.getTransaction();
321 if (tx.equals(ctxTx) && ctx.hasTxSynchronization() == false)
322 ctx.setTransaction(null);
323 }
324
325 // If an exception has been thrown,
326 if (exceptionThrown != null &&
327 // if tx, the ctx has been registered in an InstanceSynchronization.
328 // that will remove the context, so we shouldn't.
329 // if no synchronization then we need to do it by hand
330 // But not for application exceptions
331 !ctx.hasTxSynchronization() && discardContext)
332 {
333 // Discard instance
334 // EJB 1.1 spec 12.3.1
335 container.getInstanceCache().remove(key);
336
337 if (trace) log.trace("Ending invoke, exceptionThrown, ctx=" + ctx, exceptionThrown);
338 }
339 else if (ctx.getId() == null)
340 {
341 // The key from the Invocation still identifies the right cachekey
342 container.getInstanceCache().remove(key);
343
344 if (trace) log.trace("Ending invoke, cache removal, ctx=" + ctx);
345 // no more pool return
346 }
347
348 EnterpriseBeanPolicyContextHandler.setEnterpriseBean(null);
349
350 if (trace) log.trace("End invoke, key=" + key + ", ctx=" + ctx);
351 }// end finally
352 }
353 }
354