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.EJBObject;
30 import javax.ejb.Handle;
31 import javax.ejb.NoSuchObjectLocalException;
32 import javax.ejb.TimedObject;
33 import javax.ejb.Timer;
34 import javax.transaction.RollbackException;
35 import javax.transaction.Status;
36 import javax.transaction.Synchronization;
37 import javax.transaction.Transaction;
38
39 import org.jboss.ejb.AllowedOperationsAssociation;
40 import org.jboss.ejb.BeanLock;
41 import org.jboss.ejb.Container;
42 import org.jboss.ejb.EnterpriseContext;
43 import org.jboss.ejb.InstanceCache;
44 import org.jboss.ejb.InstancePool;
45 import org.jboss.ejb.StatefulSessionContainer;
46 import org.jboss.invocation.Invocation;
47 import org.jboss.invocation.InvocationType;
48 import org.jboss.logging.Logger;
49 import org.jboss.metadata.SessionMetaData;
50 import org.jboss.security.AuthenticationManager;
51 import org.jboss.security.SecurityConstants;
52
53 /**
54 * This container acquires the given instance.
55 *
56 * @author <a href="mailto:rickard.oberg@telkel.com">Rickard Oberg</a>
57 * @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
58 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
59 * @author <a href="mailto:scott.stark@jboss.org">Scott Stark</a>
60 * @version $Revision: 69133 $
61 *
62 */
63 public class StatefulSessionInstanceInterceptor
64 extends AbstractInterceptor
65 {
66 // Constants ----------------------------------------------------
67
68 // Attributes ---------------------------------------------------
69
70 /** Instance logger. */
71 protected Logger log = Logger.getLogger(this.getClass());
72
73 protected StatefulSessionContainer container;
74
75 // Static -------------------------------------------------------
76
77 private static final Method getEJBHome;
78 private static final Method getHandle;
79 private static final Method getPrimaryKey;
80 private static final Method isIdentical;
81 private static final Method remove;
82 private static final Method getEJBObject;
83 private static final Method ejbTimeout;
84
85 static
86 {
87 try
88 {
89 Class[] noArg = new Class[0];
90 getEJBHome = EJBObject.class.getMethod("getEJBHome", noArg);
91 getHandle = EJBObject.class.getMethod("getHandle", noArg);
92 getPrimaryKey = EJBObject.class.getMethod("getPrimaryKey", noArg);
93 isIdentical = EJBObject.class.getMethod("isIdentical", new Class[]{EJBObject.class});
94 remove = EJBObject.class.getMethod("remove", noArg);
95 getEJBObject = Handle.class.getMethod("getEJBObject", noArg);
96 ejbTimeout = TimedObject.class.getMethod("ejbTimeout", new Class[]{Timer.class});
97 }
98 catch (Exception e)
99 {
100 e.printStackTrace();
101 throw new ExceptionInInitializerError(e);
102 }
103 }
104
105 // Constructors -------------------------------------------------
106
107 // Public -------------------------------------------------------
108
109 public void setContainer(Container container)
110 {
111 this.container = (StatefulSessionContainer)container;
112 }
113
114 public Container getContainer()
115 {
116 return container;
117 }
118
119 // Interceptor implementation -----------------------------------
120
121 public Object invokeHome(Invocation mi)
122 throws Exception
123 {
124 // Invocation on the handle, we don't need a bean instance
125 if (getEJBObject.equals(mi.getMethod()))
126 return getNext().invokeHome(mi);
127
128 // get a new context from the pool (this is a home method call)
129 InstancePool pool = container.getInstancePool();
130 EnterpriseContext ctx = pool.get();
131
132 // set the context on the Invocation
133 mi.setEnterpriseContext(ctx);
134
135 // It is a new context for sure so we can lock it
136 ctx.lock();
137
138 // Set the current security information
139 /**
140 * JBAS-3976: Setting principal on the context has been moved to a separate interceptor
141 */
142
143 AllowedOperationsAssociation.pushInMethodFlag(IN_EJB_HOME);
144
145 try
146 {
147 // Invoke through interceptors
148 return getNext().invokeHome(mi);
149 }
150 finally
151 {
152 synchronized (ctx)
153 {
154 AllowedOperationsAssociation.popInMethodFlag();
155
156 // Release the lock
157 ctx.unlock();
158
159 // Still free? Not free if create() was called successfully
160 if (ctx.getId() == null)
161 {
162 pool.free(ctx);
163 }
164 }
165 }
166 }
167
168 protected void register(EnterpriseContext ctx, Transaction tx, BeanLock lock)
169 {
170 // Create a new synchronization
171 InstanceSynchronization synch = new InstanceSynchronization(ctx, lock);
172
173 try
174 {
175 // OSH: An extra check to avoid warning.
176 // Can go when we are sure that we no longer get
177 // the JTA violation warning.
178 if (tx.getStatus() == Status.STATUS_MARKED_ROLLBACK)
179 {
180
181 return;
182 }
183
184 // We want to be notified when the transaction commits
185 try
186 {
187 tx.registerSynchronization(synch);
188 }
189 catch (Exception ex)
190 {
191 // synch adds a reference to the lock, so we must release the ref
192 // because afterCompletion will never get called.
193 getContainer().getLockManager().removeLockRef(lock.getId());
194 throw ex;
195 }
196
197 // EJB 1.1, 6.5.3
198 synch.afterBegin();
199
200 } catch (RollbackException e)
201 {
202
203 } catch (Exception e)
204 {
205
206 throw new EJBException(e);
207
208 }
209 }
210
211 public Object invoke(Invocation mi)
212 throws Exception
213 {
214 InstanceCache cache = container.getInstanceCache();
215 InstancePool pool = container.getInstancePool();
216 Object methodID = mi.getId();
217 EnterpriseContext ctx = null;
218
219 BeanLock lock = container.getLockManager().getLock(methodID);
220 boolean callerRunAsIdentityPresent = SecurityActions.peekRunAsIdentity() != null;
221 boolean pushSecurityContext = SecurityActions.getSecurityContext() == null;
222 try
223 {
224 /* The security context must be established before the cache
225 lookup because the activation of a session should have the caller's
226 security context as ejbActivate is allowed to call other secured
227 resources. Since the pm makes the ejbActivate call, we need to
228 set the caller's security context. The only reason this shows up for
229 stateful session is that we moved the SecurityInterceptor to after
230 the instance interceptor to allow security exceptions to result in
231 invalidation of the session. This may be too literal an interpretation
232 of the ejb spec requirement that runtime exceptions should invalidate
233 the session.
234 */
235 if(!callerRunAsIdentityPresent && pushSecurityContext)
236 {
237 AuthenticationManager am = container.getSecurityManager();
238 String securityDomain = SecurityConstants.DEFAULT_APPLICATION_POLICY;
239 if(am != null)
240 securityDomain = am.getSecurityDomain();
241 SecurityActions.createAndSetSecurityContext(mi.getPrincipal(), mi.getCredential(),
242 securityDomain , null);
243 //SecurityActions.pushSubjectContext(mi.getPrincipal(), mi.getCredential(), null);
244 }
245
246 lock.sync();
247 try
248 {
249 // Get context
250 try
251 {
252 ctx = cache.get(methodID);
253 }
254 catch (NoSuchObjectException e)
255 {
256 if (mi.isLocal())
257 throw new NoSuchObjectLocalException(e.getMessage());
258 else
259 throw e;
260 }
261 catch (EJBException e)
262 {
263 throw e;
264 }
265 catch (RemoteException e)
266 {
267 throw e;
268 }
269 catch (Exception e)
270 {
271 InvocationType type = mi.getType();
272 boolean isLocal = (type == InvocationType.LOCAL || type == InvocationType.LOCALHOME);
273 if (isLocal)
274 throw new EJBException("Unable to get an instance from the pool/cache", e);
275 else
276 throw new RemoteException("Unable to get an intance from the pool/cache", e);
277 }
278
279 // Associate it with the method invocation
280 mi.setEnterpriseContext(ctx);
281 // Set the JACC EnterpriseBean PolicyContextHandler data
282 EnterpriseBeanPolicyContextHandler.setEnterpriseBean(ctx.getInstance());
283
284 // BMT beans will lock and replace tx no matter what, CMT do work on transaction
285 boolean isBMT = ((SessionMetaData)container.getBeanMetaData()).isBeanManagedTx();
286 if (isBMT == false)
287 {
288
289 // Do we have a running transaction with the context
290 if (ctx.getTransaction() != null &&
291 // And are we trying to enter with another transaction
292 !ctx.getTransaction().equals(mi.getTransaction()))
293 {
294 // Calls must be in the same transaction
295 StringBuffer msg = new StringBuffer("Application Error: " +
296 "tried to enter Stateful bean with different tx context");
297 msg.append(", contextTx: " + ctx.getTransaction());
298 msg.append(", methodTx: " + mi.getTransaction());
299 throw new EJBException(msg.toString());
300 }
301
302 //If the instance will participate in a new transaction we register a sync for it
303 if (ctx.getTransaction() == null && mi.getTransaction() != null)
304 {
305 register(ctx, mi.getTransaction(), lock);
306 }
307 }
308
309 if (!ctx.isLocked())
310 {
311
312 //take it!
313 ctx.lock();
314 }
315 else
316 {
317 if (!isCallAllowed(mi))
318 {
319 // Concurent calls are not allowed
320 throw new EJBException("Application Error: no concurrent " +
321 "calls on stateful beans");
322 }
323 else
324 {
325 ctx.lock();
326 }
327 }
328 }
329 finally
330 {
331 lock.releaseSync();
332 }
333
334 // Set the current security information
335 /**
336 * JBAS-3976: Setting principal on the context has been moved to a separate interceptor
337 */
338
339 if (ejbTimeout.equals(mi.getMethod()))
340 AllowedOperationsAssociation.pushInMethodFlag(IN_EJB_TIMEOUT);
341 else
342 AllowedOperationsAssociation.pushInMethodFlag(IN_BUSINESS_METHOD);
343
344 boolean validContext = true;
345 try
346 {
347 // Invoke through interceptors
348 Object ret = getNext().invoke(mi);
349 return ret;
350 }
351 catch (RemoteException e)
352 {
353 // Discard instance
354 cache.remove(methodID);
355 pool.discard(ctx);
356 validContext = false;
357
358 throw e;
359 }
360 catch (RuntimeException e)
361 {
362 // Discard instance
363 cache.remove(methodID);
364 pool.discard(ctx);
365 validContext = false;
366
367 throw e;
368 }
369 catch (Error e)
370 {
371 // Discard instance
372 cache.remove(methodID);
373 pool.discard(ctx);
374 validContext = false;
375
376 throw e;
377 }
378 finally
379 {
380 AllowedOperationsAssociation.popInMethodFlag();
381
382 if (validContext)
383 {
384 // Still a valid instance
385 lock.sync();
386 try
387 {
388
389 // release it
390 ctx.unlock();
391
392 // if removed, remove from cache
393 if (ctx.getId() == null)
394 {
395 // Remove from cache
396 cache.remove(methodID);
397 pool.free(ctx);
398 }
399 }
400 finally
401 {
402 lock.releaseSync();
403 }
404 }
405 }
406 }
407 finally
408 {
409 container.getLockManager().removeLockRef(lock.getId());
410 if(!callerRunAsIdentityPresent && pushSecurityContext)
411 SecurityActions.clearSecurityContext();
412 EnterpriseBeanPolicyContextHandler.setEnterpriseBean(null);
413 }
414 }
415
416 protected boolean isCallAllowed(Invocation mi)
417 {
418 Method m = mi.getMethod();
419 if (m.equals(getEJBHome) ||
420 m.equals(getHandle) ||
421 m.equals(getPrimaryKey) ||
422 m.equals(isIdentical) ||
423 m.equals(remove))
424 {
425 return true;
426 }
427 return false;
428 }
429
430 // Inner classes -------------------------------------------------
431
432 private class InstanceSynchronization
433 implements Synchronization
434 {
435 /**
436 * The context we manage.
437 */
438 private EnterpriseContext ctx;
439
440 // a utility boolean for session sync
441 private boolean notifySession = false;
442
443 // Utility methods for the notifications
444 private Method afterBegin;
445 private Method beforeCompletion;
446 private Method afterCompletion;
447 private BeanLock lock;
448 private boolean beforeCompletionInvoked = false;
449
450 /**
451 * Create a new instance synchronization instance.
452 */
453 InstanceSynchronization(EnterpriseContext ctx, BeanLock lock)
454 {
455 this.ctx = ctx;
456 this.lock = lock;
457 this.lock.addRef();
458
459 // Let's compute it now, to speed things up we could
460 notifySession = (ctx.getInstance() instanceof javax.ejb.SessionSynchronization);
461
462 if (notifySession)
463 {
464 try
465 {
466 // Get the class we are working on
467 Class sync = Class.forName("javax.ejb.SessionSynchronization");
468
469 // Lookup the methods on it
470 afterBegin = sync.getMethod("afterBegin", new Class[0]);
471 beforeCompletion = sync.getMethod("beforeCompletion", new Class[0]);
472 afterCompletion = sync.getMethod("afterCompletion", new Class[]
473 {boolean.class});
474 }
475 catch (Exception e)
476 {
477 log.error("failed to setup InstanceSynchronization", e);
478 }
479 }
480 }
481
482 // Synchronization implementation -----------------------------
483
484 public void afterBegin()
485 {
486 if (notifySession)
487 {
488 try
489 {
490 AllowedOperationsAssociation.pushInMethodFlag(IN_AFTER_BEGIN);
491 afterBegin.invoke(ctx.getInstance(), new Object[0]);
492 }
493 catch (Exception e)
494 {
495 log.error("failed to invoke afterBegin", e);
496 }
497 finally{
498 AllowedOperationsAssociation.popInMethodFlag();
499 }
500 }
501 }
502
503 public void beforeCompletion()
504 {
505 if( log.isTraceEnabled() )
506 log.trace("beforeCompletion called");
507
508 // lock the context the transaction is being commited (no need for sync)
509 ctx.lock();
510 beforeCompletionInvoked = true;
511
512 if (notifySession)
513 {
514 try
515 {
516 AllowedOperationsAssociation.pushInMethodFlag(IN_BEFORE_COMPLETION);
517 beforeCompletion.invoke(ctx.getInstance(), new Object[0]);
518 }
519 catch (Exception e)
520 {
521 log.error("failed to invoke beforeCompletion", e);
522 }
523 finally{
524 AllowedOperationsAssociation.popInMethodFlag();
525 }
526 }
527 }
528
529 public void afterCompletion(int status)
530 {
531 if( log.isTraceEnabled() )
532 log.trace("afterCompletion called");
533
534 lock.sync();
535 try
536 {
537 // finish the transaction association
538 ctx.setTransaction(null);
539
540 if (beforeCompletionInvoked)
541 ctx.unlock();
542
543 if (notifySession)
544 {
545
546 try
547 {
548 AllowedOperationsAssociation.pushInMethodFlag(IN_AFTER_COMPLETION);
549 if (status == Status.STATUS_COMMITTED)
550 {
551 afterCompletion.invoke(ctx.getInstance(), new Object[]{Boolean.TRUE});
552 }
553 else
554 {
555 afterCompletion.invoke(ctx.getInstance(), new Object[]{Boolean.FALSE});
556 }
557 }
558 catch (Exception e)
559 {
560 log.error("failed to invoke afterCompletion", e);
561 }
562 finally{
563 AllowedOperationsAssociation.popInMethodFlag();
564 }
565 }
566 }
567 finally
568 {
569 lock.releaseSync();
570 container.getLockManager().removeLockRef(lock.getId());
571 }
572 }
573 }
574 }
575