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.EntityContainer;
027: import org.jboss.ejb.EntityEnterpriseContext;
028: import org.jboss.ejb.InstanceCache;
029: import org.jboss.ejb.AllowedOperationsAssociation;
030: import org.jboss.ejb.GlobalTxEntityMap;
031: import org.jboss.ejb.InstancePool;
032: import org.jboss.invocation.Invocation;
033: import org.jboss.invocation.InvocationType;
034: import org.jboss.util.NestedRuntimeException;
035:
036: import javax.ejb.EJBException;
037: import javax.ejb.NoSuchObjectLocalException;
038: import javax.ejb.TimedObject;
039: import javax.ejb.Timer;
040: import javax.transaction.Transaction;
041: import java.lang.reflect.Method;
042: import java.rmi.NoSuchObjectException;
043: import java.rmi.RemoteException;
044:
045: /**
046: * The instance interceptors role is to acquire a context representing the
047: * target object from the cache.
048: *
049: * <p>This particular container interceptor implements pessimistic locking on
050: * the transaction that is associated with the retrieved instance. If there is
051: * a transaction associated with the target component and it is different from
052: * the transaction associated with the Invocation coming in then the policy is
053: * to wait for transactional commit.
054: *
055: * <p>We also implement serialization of calls in here (this is a spec
056: * requirement). This is a fine grained notify, notifyAll mechanism. We notify
057: * on ctx serialization locks and notifyAll on global transactional locks.
058: *
059: * <p><b>WARNING: critical code</b>, get approval from senior developers before
060: * changing.
061: * @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
062: * @author <a href="mailto:Scott.Stark@jboss.org">Scott Stark</a>
063: * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
064: * @author <a href="mailto:mkgarnek@hotmail.com">Jamie Burns</a>
065: * @version $Revision: 62278 $
066: */
067: public class EntityInstanceInterceptor extends AbstractInterceptor {
068: // Constants -----------------------------------------------------
069:
070: // Attributes ----------------------------------------------------
071:
072: protected EntityContainer container;
073:
074: // Static --------------------------------------------------------
075:
076: /** A reference to {@link javax.ejb.TimedObject#ejbTimeout}. */
077: protected static final Method ejbTimeout;
078:
079: static {
080: try {
081: ejbTimeout = TimedObject.class.getMethod("ejbTimeout",
082: new Class[] { Timer.class });
083: } catch (Exception e) {
084: throw new ExceptionInInitializerError(e);
085: }
086: }
087:
088: // Constructors --------------------------------------------------
089:
090: // Public --------------------------------------------------------
091:
092: public void setContainer(Container container) {
093: this .container = (EntityContainer) container;
094: }
095:
096: public Container getContainer() {
097: return container;
098: }
099:
100: // Interceptor implementation --------------------------------------
101:
102: public Object invokeHome(Invocation mi) throws Exception {
103: // Get context
104: EntityContainer container = (EntityContainer) getContainer();
105: EntityEnterpriseContext ctx = (EntityEnterpriseContext) container
106: .getInstancePool().get();
107: ctx.setTxAssociation(GlobalTxEntityMap.NOT_READY);
108: InstancePool pool = container.getInstancePool();
109:
110: // Pass it to the method invocation
111: mi.setEnterpriseContext(ctx);
112:
113: // Give it the transaction
114: ctx.setTransaction(mi.getTransaction());
115:
116: // Set the current security information
117: ctx.setPrincipal(mi.getPrincipal());
118:
119: AllowedOperationsAssociation.pushInMethodFlag(IN_EJB_HOME);
120:
121: // Invoke through interceptors
122:
123: Object obj = null;
124: Exception exception = null;
125:
126: try {
127: obj = getNext().invokeHome(mi);
128:
129: // Is the context now with an identity? in which case we need to insert
130: if (ctx.getId() != null) {
131: BeanLock lock = container.getLockManager().getLock(
132: ctx.getCacheKey());
133: lock.sync(); // lock all access to BeanLock
134: try {
135: // Check there isn't a context already in the cache
136: // e.g. commit-option B where the entity was
137: // created then removed externally
138: InstanceCache cache = container.getInstanceCache();
139: cache.remove(ctx.getCacheKey());
140:
141: // marcf: possible race on creation and usage
142: // insert instance in cache,
143: cache.insert(ctx);
144: } finally {
145: lock.releaseSync();
146: container.getLockManager().removeLockRef(
147: ctx.getCacheKey());
148: }
149:
150: // we are all done
151: return obj;
152: }
153: } catch (Exception e) {
154: exception = e;
155: } finally {
156: AllowedOperationsAssociation.popInMethodFlag();
157: }
158:
159: ctx.setTransaction(null);
160: // EntityCreateInterceptor will access ctx if it is not null and call postCreate
161: mi.setEnterpriseContext(null);
162:
163: // if we get to here with a null exception then our invocation is
164: // just a home invocation. Return our instance to the instance pool
165: if (exception == null) {
166: container.getInstancePool().free(ctx);
167: return obj;
168: }
169:
170: if (exception instanceof RuntimeException) {
171: // if we get to here with a RuntimeException, we have a system exception.
172: // EJB 2.1 section 18.3.1 says we need to discard our instance.
173: pool.discard(ctx);
174: } else {
175: // if we get to here with an Exception, we have an application exception.
176: // EJB 2.1 section 18.3.1 says we can keep the instance. We need to return
177: // our instance to the instance pool so we dont get a memory leak.
178: pool.free(ctx);
179: }
180:
181: throw exception;
182: }
183:
184: public Object invoke(Invocation mi) throws Exception {
185: boolean trace = log.isTraceEnabled();
186:
187: // The key
188: Object key = mi.getId();
189:
190: // The context
191: EntityEnterpriseContext ctx;
192: try {
193: ctx = (EntityEnterpriseContext) container
194: .getInstanceCache().get(key);
195: } catch (NoSuchObjectException e) {
196: if (mi.isLocal())
197: throw new NoSuchObjectLocalException(e.getMessage());
198: else
199: throw e;
200: } catch (EJBException e) {
201: throw e;
202: } catch (RemoteException e) {
203: throw e;
204: } catch (Exception e) {
205: InvocationType type = mi.getType();
206: boolean isLocal = (type == InvocationType.LOCAL || type == InvocationType.LOCALHOME);
207: if (isLocal)
208: throw new EJBException(
209: "Unable to get an instance from the pool/cache",
210: e);
211: else
212: throw new RemoteException(
213: "Unable to get an intance from the pool/cache",
214: e);
215: }
216:
217: if (trace)
218: log.trace("Begin invoke, key=" + key);
219:
220: // Associate transaction, in the new design the lock already has the transaction from the
221: // previous interceptor
222:
223: // Don't set the transction if a read-only method. With a read-only method, the ctx can be shared
224: // between multiple transactions.
225: Transaction tx = mi.getTransaction();
226: if (!container.isReadOnly()) {
227: Method method = mi.getMethod();
228: if (method == null
229: || !container.getBeanMetaData().isMethodReadOnly(
230: method.getName())) {
231: ctx.setTransaction(tx);
232: }
233: }
234:
235: // Set the current security information
236: ctx.setPrincipal(mi.getPrincipal());
237: // Set the JACC EnterpriseBean PolicyContextHandler data
238: EnterpriseBeanPolicyContextHandler.setEnterpriseBean(ctx
239: .getInstance());
240:
241: // Set context on the method invocation
242: mi.setEnterpriseContext(ctx);
243:
244: if (ejbTimeout.equals(mi.getMethod()))
245: AllowedOperationsAssociation
246: .pushInMethodFlag(IN_EJB_TIMEOUT);
247: else
248: AllowedOperationsAssociation
249: .pushInMethodFlag(IN_BUSINESS_METHOD);
250:
251: Throwable exceptionThrown = null;
252: boolean discardContext = false;
253: try {
254: Object obj = getNext().invoke(mi);
255: return obj;
256: } catch (RemoteException e) {
257: exceptionThrown = e;
258: discardContext = true;
259: throw e;
260: } catch (RuntimeException e) {
261: exceptionThrown = e;
262: discardContext = true;
263: throw e;
264: } catch (Error e) {
265: exceptionThrown = e;
266: discardContext = true;
267: throw e;
268: } catch (Exception e) {
269: exceptionThrown = e;
270: throw e;
271: } catch (Throwable e) {
272: exceptionThrown = e;
273: discardContext = true;
274: throw new NestedRuntimeException(e);
275: } finally {
276: AllowedOperationsAssociation.popInMethodFlag();
277:
278: // Make sure we clear the transaction on an error before synchronization.
279: // But avoid a race with a transaction rollback on a synchronization
280: // that may have moved the context onto a different transaction
281: if (exceptionThrown != null && tx != null) {
282: Transaction ctxTx = ctx.getTransaction();
283: if (tx.equals(ctxTx)
284: && ctx.hasTxSynchronization() == false)
285: ctx.setTransaction(null);
286: }
287:
288: // If an exception has been thrown,
289: if (exceptionThrown != null &&
290: // if tx, the ctx has been registered in an InstanceSynchronization.
291: // that will remove the context, so we shouldn't.
292: // if no synchronization then we need to do it by hand
293: // But not for application exceptions
294: !ctx.hasTxSynchronization() && discardContext) {
295: // Discard instance
296: // EJB 1.1 spec 12.3.1
297: container.getInstanceCache().remove(key);
298:
299: if (trace)
300: log.trace("Ending invoke, exceptionThrown, ctx="
301: + ctx, exceptionThrown);
302: } else if (ctx.getId() == null) {
303: // The key from the Invocation still identifies the right cachekey
304: container.getInstanceCache().remove(key);
305:
306: if (trace)
307: log.trace("Ending invoke, cache removal, ctx="
308: + ctx);
309: // no more pool return
310: }
311:
312: EnterpriseBeanPolicyContextHandler.setEnterpriseBean(null);
313:
314: if (trace)
315: log.trace("End invoke, key=" + key + ", ctx=" + ctx);
316: }// end finally
317: }
318: }
|