001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.ejb.plugins;
023:
024: import org.jboss.ejb.BeanLock;
025: import org.jboss.ejb.Container;
026: import org.jboss.ejb.EnterpriseContext;
027: import org.jboss.ejb.InstanceCache;
028: import org.jboss.ejb.InstancePool;
029: import org.jboss.ejb.StatefulSessionContainer;
030: import org.jboss.ejb.AllowedOperationsAssociation;
031: import org.jboss.invocation.Invocation;
032: import org.jboss.invocation.InvocationType;
033: import org.jboss.logging.Logger;
034: import org.jboss.metadata.SessionMetaData;
035:
036: import javax.ejb.EJBException;
037: import javax.ejb.EJBObject;
038: import javax.ejb.Handle;
039: import javax.ejb.NoSuchObjectLocalException;
040: import javax.ejb.TimedObject;
041: import javax.ejb.Timer;
042: import javax.transaction.RollbackException;
043: import javax.transaction.Status;
044: import javax.transaction.Synchronization;
045: import javax.transaction.Transaction;
046: import java.lang.reflect.Method;
047: import java.rmi.NoSuchObjectException;
048: import java.rmi.RemoteException;
049:
050: /**
051: * This container acquires the given instance.
052: *
053: * @author <a href="mailto:rickard.oberg@telkel.com">Rickard Oberg</a>
054: * @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
055: * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
056: * @author <a href="mailto:scott.stark@jboss.org">Scott Stark</a>
057: * @version $Revision: 61363 $
058: *
059: */
060: public class StatefulSessionInstanceInterceptor extends
061: AbstractInterceptor {
062: // Constants ----------------------------------------------------
063:
064: // Attributes ---------------------------------------------------
065:
066: /** Instance logger. */
067: protected Logger log = Logger.getLogger(this .getClass());
068:
069: protected StatefulSessionContainer container;
070:
071: // Static -------------------------------------------------------
072:
073: private static final Method getEJBHome;
074: private static final Method getHandle;
075: private static final Method getPrimaryKey;
076: private static final Method isIdentical;
077: private static final Method remove;
078: private static final Method getEJBObject;
079: private static final Method ejbTimeout;
080:
081: static {
082: try {
083: Class[] noArg = new Class[0];
084: getEJBHome = EJBObject.class.getMethod("getEJBHome", noArg);
085: getHandle = EJBObject.class.getMethod("getHandle", noArg);
086: getPrimaryKey = EJBObject.class.getMethod("getPrimaryKey",
087: noArg);
088: isIdentical = EJBObject.class.getMethod("isIdentical",
089: new Class[] { EJBObject.class });
090: remove = EJBObject.class.getMethod("remove", noArg);
091: getEJBObject = Handle.class
092: .getMethod("getEJBObject", noArg);
093: ejbTimeout = TimedObject.class.getMethod("ejbTimeout",
094: new Class[] { Timer.class });
095: } catch (Exception e) {
096: e.printStackTrace();
097: throw new ExceptionInInitializerError(e);
098: }
099: }
100:
101: // Constructors -------------------------------------------------
102:
103: // Public -------------------------------------------------------
104:
105: public void setContainer(Container container) {
106: this .container = (StatefulSessionContainer) container;
107: }
108:
109: public Container getContainer() {
110: return container;
111: }
112:
113: // Interceptor implementation -----------------------------------
114:
115: public Object invokeHome(Invocation mi) throws Exception {
116: // Invocation on the handle, we don't need a bean instance
117: if (getEJBObject.equals(mi.getMethod()))
118: return getNext().invokeHome(mi);
119:
120: // get a new context from the pool (this is a home method call)
121: InstancePool pool = container.getInstancePool();
122: EnterpriseContext ctx = pool.get();
123:
124: // set the context on the Invocation
125: mi.setEnterpriseContext(ctx);
126:
127: // It is a new context for sure so we can lock it
128: ctx.lock();
129:
130: // Set the current security information
131: ctx.setPrincipal(mi.getPrincipal());
132:
133: AllowedOperationsAssociation.pushInMethodFlag(IN_EJB_HOME);
134:
135: try {
136: // Invoke through interceptors
137: return getNext().invokeHome(mi);
138: } finally {
139: synchronized (ctx) {
140: AllowedOperationsAssociation.popInMethodFlag();
141:
142: // Release the lock
143: ctx.unlock();
144:
145: // Still free? Not free if create() was called successfully
146: if (ctx.getId() == null) {
147: pool.free(ctx);
148: }
149: }
150: }
151: }
152:
153: protected void register(EnterpriseContext ctx, Transaction tx,
154: BeanLock lock) {
155: // Create a new synchronization
156: InstanceSynchronization synch = new InstanceSynchronization(
157: ctx, lock, container, log);
158:
159: try {
160: // OSH: An extra check to avoid warning.
161: // Can go when we are sure that we no longer get
162: // the JTA violation warning.
163: if (tx.getStatus() == Status.STATUS_MARKED_ROLLBACK) {
164:
165: return;
166: }
167:
168: // We want to be notified when the transaction commits
169: try {
170: tx.registerSynchronization(synch);
171: } catch (Exception ex) {
172: // synch adds a reference to the lock, so we must release the ref
173: // because afterCompletion will never get called.
174: getContainer().getLockManager().removeLockRef(
175: lock.getId());
176: throw ex;
177: }
178:
179: // EJB 1.1, 6.5.3
180: synch.afterBegin();
181:
182: } catch (RollbackException e) {
183:
184: } catch (Exception e) {
185:
186: throw new EJBException(e);
187:
188: }
189: }
190:
191: public Object invoke(Invocation mi) throws Exception {
192: InstanceCache cache = container.getInstanceCache();
193: InstancePool pool = container.getInstancePool();
194: Object methodID = mi.getId();
195: EnterpriseContext ctx = null;
196:
197: BeanLock lock = container.getLockManager().getLock(methodID);
198: //JBAS-3781
199: boolean callerRunAsIdentityPresent = SecurityActions
200: .peekRunAsIdentity() != null;
201: try {
202: /* The security context must be established before the cache
203: lookup because the activation of a session should have the caller's
204: security context as ejbActivate is allowed to call other secured
205: resources. Since the pm makes the ejbActivate call, we need to
206: set the caller's security context. The only reason this shows up for
207: stateful session is that we moved the SecurityInterceptor to after
208: the instance interceptor to allow security exceptions to result in
209: invalidation of the session. This may be too literal an interpretation
210: of the ejb spec requirement that runtime exceptions should invalidate
211: the session.
212: */
213: if (!callerRunAsIdentityPresent)
214: SecurityActions.pushSubjectContext(mi.getPrincipal(),
215: mi.getCredential(), null);
216:
217: lock.sync();
218: try {
219: // Get context
220: try {
221: ctx = cache.get(methodID);
222: } catch (NoSuchObjectException e) {
223: if (mi.isLocal())
224: throw new NoSuchObjectLocalException(e
225: .getMessage());
226: else
227: throw e;
228: } catch (EJBException e) {
229: throw e;
230: } catch (RemoteException e) {
231: throw e;
232: } catch (Exception e) {
233: InvocationType type = mi.getType();
234: boolean isLocal = (type == InvocationType.LOCAL || type == InvocationType.LOCALHOME);
235: if (isLocal)
236: throw new EJBException(
237: "Unable to get an instance from the pool/cache",
238: e);
239: else
240: throw new RemoteException(
241: "Unable to get an intance from the pool/cache",
242: e);
243: }
244:
245: // Associate it with the method invocation
246: mi.setEnterpriseContext(ctx);
247: // Set the JACC EnterpriseBean PolicyContextHandler data
248: EnterpriseBeanPolicyContextHandler
249: .setEnterpriseBean(ctx.getInstance());
250:
251: // BMT beans will lock and replace tx no matter what, CMT do work on transaction
252: boolean isBMT = ((SessionMetaData) container
253: .getBeanMetaData()).isBeanManagedTx();
254: if (isBMT == false) {
255:
256: // Do we have a running transaction with the context
257: if (ctx.getTransaction() != null &&
258: // And are we trying to enter with another transaction
259: !ctx.getTransaction().equals(
260: mi.getTransaction())) {
261: // Calls must be in the same transaction
262: StringBuffer msg = new StringBuffer(
263: "Application Error: "
264: + "tried to enter Stateful bean with different tx context");
265: msg.append(", contextTx: "
266: + ctx.getTransaction());
267: msg
268: .append(", methodTx: "
269: + mi.getTransaction());
270: throw new EJBException(msg.toString());
271: }
272:
273: //If the instance will participate in a new transaction we register a sync for it
274: if (ctx.getTransaction() == null
275: && mi.getTransaction() != null) {
276: register(ctx, mi.getTransaction(), lock);
277: }
278: }
279:
280: if (!ctx.isLocked()) {
281:
282: //take it!
283: ctx.lock();
284: } else {
285: if (!isCallAllowed(mi)) {
286: // Concurent calls are not allowed
287: throw new EJBException(
288: "Application Error: no concurrent "
289: + "calls on stateful beans");
290: } else {
291: ctx.lock();
292: }
293: }
294: } finally {
295: lock.releaseSync();
296: }
297:
298: // Set the current security information
299: ctx.setPrincipal(mi.getPrincipal());
300:
301: if (ejbTimeout.equals(mi.getMethod()))
302: AllowedOperationsAssociation
303: .pushInMethodFlag(IN_EJB_TIMEOUT);
304: else
305: AllowedOperationsAssociation
306: .pushInMethodFlag(IN_BUSINESS_METHOD);
307:
308: boolean validContext = true;
309: try {
310: // Invoke through interceptors
311: Object ret = getNext().invoke(mi);
312: return ret;
313: } catch (RemoteException e) {
314: // Discard instance
315: cache.remove(methodID);
316: pool.discard(ctx);
317: validContext = false;
318:
319: throw e;
320: } catch (RuntimeException e) {
321: // Discard instance
322: cache.remove(methodID);
323: pool.discard(ctx);
324: validContext = false;
325:
326: throw e;
327: } catch (Error e) {
328: // Discard instance
329: cache.remove(methodID);
330: pool.discard(ctx);
331: validContext = false;
332:
333: throw e;
334: } finally {
335: AllowedOperationsAssociation.popInMethodFlag();
336:
337: if (validContext) {
338: // Still a valid instance
339: lock.sync();
340: try {
341:
342: // release it
343: ctx.unlock();
344:
345: // if removed, remove from cache
346: if (ctx.getId() == null) {
347: // Remove from cache
348: cache.remove(methodID);
349: pool.free(ctx);
350: }
351: } finally {
352: lock.releaseSync();
353: }
354: }
355: }
356: } finally {
357: container.getLockManager().removeLockRef(lock.getId());
358: if (!callerRunAsIdentityPresent)
359: SecurityActions.popSubjectContext();
360: EnterpriseBeanPolicyContextHandler.setEnterpriseBean(null);
361: }
362: }
363:
364: protected boolean isCallAllowed(Invocation mi) {
365: Method m = mi.getMethod();
366: if (m.equals(getEJBHome) || m.equals(getHandle)
367: || m.equals(getPrimaryKey) || m.equals(isIdentical)
368: || m.equals(remove)) {
369: return true;
370: }
371: return false;
372: }
373:
374: // Inner classes -------------------------------------------------
375:
376: private static class InstanceSynchronization implements
377: Synchronization {
378: /**
379: * The context we manage.
380: */
381: private EnterpriseContext ctx;
382: private Container container;
383:
384: // a utility boolean for session sync
385: private boolean notifySession = false;
386:
387: // Utility methods for the notifications
388: private Method afterBegin;
389: private Method beforeCompletion;
390: private Method afterCompletion;
391: private BeanLock lock;
392: private boolean beforeCompletionInvoked = false;
393:
394: // Shared log with the interceptor
395: private Logger log;
396:
397: /**
398: * Create a new instance synchronization instance.
399: */
400: InstanceSynchronization(EnterpriseContext ctx, BeanLock lock,
401: Container container, Logger log) {
402: this .ctx = ctx;
403: this .lock = lock;
404: this .lock.addRef();
405: this .log = log;
406: this .container = ctx.getContainer();
407:
408: // Let's compute it now, to speed things up we could
409: notifySession = (ctx.getInstance() instanceof javax.ejb.SessionSynchronization);
410:
411: if (notifySession) {
412: try {
413: // Get the class we are working on
414: Class sync = Class
415: .forName("javax.ejb.SessionSynchronization");
416:
417: // Lookup the methods on it
418: afterBegin = sync.getMethod("afterBegin",
419: new Class[0]);
420: beforeCompletion = sync.getMethod(
421: "beforeCompletion", new Class[0]);
422: afterCompletion = sync.getMethod("afterCompletion",
423: new Class[] { boolean.class });
424: } catch (Exception e) {
425: log.error(
426: "failed to setup InstanceSynchronization",
427: e);
428: }
429: }
430: }
431:
432: // Synchronization implementation -----------------------------
433:
434: public void afterBegin() {
435: if (notifySession) {
436: try {
437: AllowedOperationsAssociation
438: .pushInMethodFlag(IN_AFTER_BEGIN);
439: afterBegin.invoke(ctx.getInstance(), new Object[0]);
440: } catch (Exception e) {
441: log.error("failed to invoke afterBegin", e);
442: } finally {
443: AllowedOperationsAssociation.popInMethodFlag();
444: }
445: }
446: }
447:
448: public void beforeCompletion() {
449: if (log.isTraceEnabled())
450: log.trace("beforeCompletion called");
451:
452: // lock the context the transaction is being commited (no need for sync)
453: ctx.lock();
454: beforeCompletionInvoked = true;
455:
456: if (notifySession) {
457: try {
458: AllowedOperationsAssociation
459: .pushInMethodFlag(IN_BEFORE_COMPLETION);
460: beforeCompletion.invoke(ctx.getInstance(),
461: new Object[0]);
462: } catch (Exception e) {
463: log.error("failed to invoke beforeCompletion", e);
464: } finally {
465: AllowedOperationsAssociation.popInMethodFlag();
466: }
467: }
468: }
469:
470: public void afterCompletion(int status) {
471: if (log.isTraceEnabled())
472: log.trace("afterCompletion called");
473:
474: lock.sync();
475: try {
476: // finish the transaction association
477: ctx.setTransaction(null);
478:
479: if (beforeCompletionInvoked)
480: ctx.unlock();
481:
482: if (notifySession) {
483:
484: try {
485: AllowedOperationsAssociation
486: .pushInMethodFlag(IN_AFTER_COMPLETION);
487: if (status == Status.STATUS_COMMITTED) {
488: afterCompletion.invoke(ctx.getInstance(),
489: new Object[] { Boolean.TRUE });
490: } else {
491: afterCompletion.invoke(ctx.getInstance(),
492: new Object[] { Boolean.FALSE });
493: }
494: } catch (Exception e) {
495: log
496: .error(
497: "failed to invoke afterCompletion",
498: e);
499: } finally {
500: AllowedOperationsAssociation.popInMethodFlag();
501: }
502: }
503: } finally {
504: lock.releaseSync();
505: container.getLockManager().removeLockRef(lock.getId());
506: // Clear refs that could lead to application classes, as the
507: // transaction manager may hold a ref to this Synchronization
508: // for a while
509: lock = null;
510: ctx = null;
511: container = null;
512: }
513: }
514: }
515: }
|