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.RemoteException;
26 import javax.ejb.EJBObject;
27 import javax.ejb.EJBException;
28 import javax.transaction.Transaction;
29 import javax.transaction.Status;
30
31 import org.jboss.invocation.Invocation;
32 import org.jboss.invocation.InvocationType;
33 import org.jboss.ejb.Container;
34 import org.jboss.ejb.EntityEnterpriseContext;
35 import org.jboss.metadata.EntityMetaData;
36 import org.jboss.ejb.plugins.lock.Entrancy;
37 import org.jboss.ejb.plugins.lock.NonReentrantLock;
38 import org.jboss.ejb.plugins.cmp.jdbc.bridge.CMRInvocation;
39
40 /**
41 * The role of this interceptor is to check for reentrancy.
42 * Per the spec, throw an exception if instance is not marked
43 * as reentrant. We do not check to see if same Tx is
44 * accessing object at the same time as we assume that
45 * any transactional locks will handle this.
46 *
47 * <p><b>WARNING: critical code</b>, get approval from senior developers
48 * before changing.
49 *
50 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
51 * @version $Revision: 37459 $
52 */
53 public class EntityReentranceInterceptor
54 extends AbstractInterceptor
55 {
56 protected boolean reentrant = false;
57
58 // Public --------------------------------------------------------
59
60 public void setContainer(Container container)
61 {
62 super.setContainer(container);
63 if (container != null)
64 {
65 EntityMetaData meta = (EntityMetaData) container.getBeanMetaData();
66 reentrant = meta.isReentrant();
67 }
68 }
69
70 protected boolean isTxExpired(Transaction miTx) throws Exception
71 {
72 if (miTx != null && miTx.getStatus() == Status.STATUS_MARKED_ROLLBACK)
73 {
74 return true;
75 }
76 return false;
77 }
78
79 public Object invoke(Invocation mi)
80 throws Exception
81 {
82 // We are going to work with the context a lot
83 EntityEnterpriseContext ctx = (EntityEnterpriseContext) mi.getEnterpriseContext();
84 boolean nonReentrant = !(reentrant || isReentrantMethod(mi));
85
86 // Not a reentrant method like getPrimaryKey
87 NonReentrantLock methodLock = ctx.getMethodLock();
88 Transaction miTx = ctx.getTransaction();
89 boolean locked = false;
90 try
91 {
92 while (!locked)
93 {
94 if (methodLock.attempt(5000, miTx, nonReentrant))
95 {
96 locked = true;
97 }
98 else
99 {
100 if (isTxExpired(miTx))
101 {
102 log.error("Saw rolled back tx=" + miTx);
103 throw new RuntimeException("Transaction marked for rollback, possibly a timeout");
104 }
105 }
106 }
107 }
108 catch (NonReentrantLock.ReentranceException re)
109 {
110 if (mi.getType() == InvocationType.REMOTE)
111 {
112 throw new RemoteException("Reentrant method call detected: "
113 + container.getBeanMetaData().getEjbName() + " "
114 + ctx.getId().toString());
115 }
116 else
117 {
118 throw new EJBException("Reentrant method call detected: "
119 + container.getBeanMetaData().getEjbName() + " "
120 + ctx.getId().toString());
121 }
122 }
123 try
124 {
125 ctx.lock();
126 return getNext().invoke(mi);
127 }
128 finally
129 {
130 ctx.unlock();
131 methodLock.release(nonReentrant);
132 }
133 }
134
135 // Private ------------------------------------------------------
136
137 private static final Method getEJBHome;
138 private static final Method getHandle;
139 private static final Method getPrimaryKey;
140 private static final Method isIdentical;
141 private static final Method remove;
142
143 static
144 {
145 try
146 {
147 Class[] noArg = new Class[0];
148 getEJBHome = EJBObject.class.getMethod("getEJBHome", noArg);
149 getHandle = EJBObject.class.getMethod("getHandle", noArg);
150 getPrimaryKey = EJBObject.class.getMethod("getPrimaryKey", noArg);
151 isIdentical = EJBObject.class.getMethod("isIdentical", new Class[]{EJBObject.class});
152 remove = EJBObject.class.getMethod("remove", noArg);
153 }
154 catch (Exception e)
155 {
156 e.printStackTrace();
157 throw new ExceptionInInitializerError(e);
158 }
159 }
160
161 protected boolean isReentrantMethod(Invocation mi)
162 {
163 // is this a known non-entrant method
164 Method m = mi.getMethod();
165 if (m != null && (
166 m.equals(getEJBHome) ||
167 m.equals(getHandle) ||
168 m.equals(getPrimaryKey) ||
169 m.equals(isIdentical) ||
170 m.equals(remove)))
171 {
172 return true;
173 }
174
175 // if this is a non-entrant message to the container let it through
176 if (mi instanceof CMRInvocation)
177 {
178 Entrancy entrancy = ((CMRInvocation) mi).getEntrancy();
179 if (entrancy == Entrancy.NON_ENTRANT)
180 {
181 log.trace("NON_ENTRANT invocation");
182 return true;
183 }
184 }
185
186 return false;
187 }
188
189 }