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 java.lang.reflect.Method;
025: import java.rmi.RemoteException;
026: import javax.ejb.EJBObject;
027: import javax.ejb.EJBException;
028: import javax.transaction.Transaction;
029: import javax.transaction.Status;
030:
031: import org.jboss.invocation.Invocation;
032: import org.jboss.invocation.InvocationType;
033: import org.jboss.ejb.Container;
034: import org.jboss.ejb.EntityEnterpriseContext;
035: import org.jboss.metadata.EntityMetaData;
036: import org.jboss.ejb.plugins.lock.Entrancy;
037: import org.jboss.ejb.plugins.lock.NonReentrantLock;
038: import org.jboss.ejb.plugins.cmp.jdbc.bridge.CMRInvocation;
039:
040: /**
041: * The role of this interceptor is to check for reentrancy.
042: * Per the spec, throw an exception if instance is not marked
043: * as reentrant. We do not check to see if same Tx is
044: * accessing object at the same time as we assume that
045: * any transactional locks will handle this.
046: *
047: * <p><b>WARNING: critical code</b>, get approval from senior developers
048: * before changing.
049: *
050: * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
051: * @version $Revision: 57209 $
052: */
053: public class EntityReentranceInterceptor extends AbstractInterceptor {
054: protected boolean reentrant = false;
055:
056: // Public --------------------------------------------------------
057:
058: public void setContainer(Container container) {
059: super .setContainer(container);
060: if (container != null) {
061: EntityMetaData meta = (EntityMetaData) container
062: .getBeanMetaData();
063: reentrant = meta.isReentrant();
064: }
065: }
066:
067: protected boolean isTxExpired(Transaction miTx) throws Exception {
068: if (miTx != null
069: && miTx.getStatus() == Status.STATUS_MARKED_ROLLBACK) {
070: return true;
071: }
072: return false;
073: }
074:
075: public Object invoke(Invocation mi) throws Exception {
076: // We are going to work with the context a lot
077: EntityEnterpriseContext ctx = (EntityEnterpriseContext) mi
078: .getEnterpriseContext();
079: boolean nonReentrant = !(reentrant || isReentrantMethod(mi));
080:
081: // Not a reentrant method like getPrimaryKey
082: NonReentrantLock methodLock = ctx.getMethodLock();
083: Transaction miTx = ctx.getTransaction();
084: boolean locked = false;
085: try {
086: while (!locked) {
087: if (methodLock.attempt(5000, miTx, nonReentrant)) {
088: locked = true;
089: } else {
090: if (isTxExpired(miTx)) {
091: log.error("Saw rolled back tx=" + miTx);
092: throw new RuntimeException(
093: "Transaction marked for rollback, possibly a timeout");
094: }
095: }
096: }
097: } catch (NonReentrantLock.ReentranceException re) {
098: if (mi.getType() == InvocationType.REMOTE) {
099: throw new RemoteException(
100: "Reentrant method call detected: "
101: + container.getBeanMetaData()
102: .getEjbName() + " "
103: + ctx.getId().toString());
104: } else {
105: throw new EJBException(
106: "Reentrant method call detected: "
107: + container.getBeanMetaData()
108: .getEjbName() + " "
109: + ctx.getId().toString());
110: }
111: }
112: try {
113: ctx.lock();
114: return getNext().invoke(mi);
115: } finally {
116: ctx.unlock();
117: methodLock.release(nonReentrant);
118: }
119: }
120:
121: // Private ------------------------------------------------------
122:
123: private static final Method getEJBHome;
124: private static final Method getHandle;
125: private static final Method getPrimaryKey;
126: private static final Method isIdentical;
127: private static final Method remove;
128:
129: static {
130: try {
131: Class[] noArg = new Class[0];
132: getEJBHome = EJBObject.class.getMethod("getEJBHome", noArg);
133: getHandle = EJBObject.class.getMethod("getHandle", noArg);
134: getPrimaryKey = EJBObject.class.getMethod("getPrimaryKey",
135: noArg);
136: isIdentical = EJBObject.class.getMethod("isIdentical",
137: new Class[] { EJBObject.class });
138: remove = EJBObject.class.getMethod("remove", noArg);
139: } catch (Exception e) {
140: e.printStackTrace();
141: throw new ExceptionInInitializerError(e);
142: }
143: }
144:
145: protected boolean isReentrantMethod(Invocation mi) {
146: // is this a known non-entrant method
147: Method m = mi.getMethod();
148: if (m != null
149: && (m.equals(getEJBHome) || m.equals(getHandle)
150: || m.equals(getPrimaryKey)
151: || m.equals(isIdentical) || m.equals(remove))) {
152: return true;
153: }
154:
155: // if this is a non-entrant message to the container let it through
156: if (mi instanceof CMRInvocation) {
157: Entrancy entrancy = ((CMRInvocation) mi).getEntrancy();
158: if (entrancy == Entrancy.NON_ENTRANT) {
159: log.trace("NON_ENTRANT invocation");
160: return true;
161: }
162: }
163:
164: return false;
165: }
166:
167: }
|