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.Container;
025: import org.jboss.invocation.Invocation;
026: import org.jboss.ha.framework.interfaces.GenericClusteringException;
027: import EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock;
028: import org.jboss.ejb.EjbModule;
029: import javax.management.NotificationListener;
030: import javax.management.Notification;
031: import javax.management.AttributeChangeNotification;
032: import javax.management.AttributeChangeNotificationFilter;
033: import java.util.Collection;
034: import java.util.Iterator;
035: import org.jboss.system.ServiceMBean;
036:
037: /**
038: * Track the incoming invocations and when shuting down a container (stop or
039: * destroy), waits for current invocations to finish before returning the
040: * stop or destroy call. This interceptor can be important in clustered environment
041: * where shuting down a node doesn't necessarly mean that an application cannot
042: * be reached: other nodes may still be servicing. Consequently, it is important
043: * to have a clean shutdown to keep a coherent behaviour cluster-wide.
044: *
045: * To avoid strange or inefficient behaviour, the facade session bean (if any)
046: * should be stopped first thus not blocking invocations in a middle-step (i.e.
047: * facade making multiple invocations to "sub-beans": if a "sub-bean" is
048: * shut down, the facade will get an exception in the middle of its activity)
049: *
050: * @see <related>
051: *
052: * @author <a href="mailto:sacha.labourey@cogito-info.ch">Sacha Labourey</a>.
053: * @version $Revision: 57188 $
054: *
055: * <p><b>Revisions:</b>
056: *
057: * <p><b>14 avril 2002 Sacha Labourey:</b>
058: * <ul>
059: * <li> First implementation </li>
060: * </ul>
061: */
062:
063: public class CleanShutdownInterceptor extends AbstractInterceptor {
064:
065: // Constants -----------------------------------------------------
066:
067: // Attributes ----------------------------------------------------
068:
069: protected Container container = null;
070:
071: protected EjbModule ejbModule = null;
072: protected String ejbModuleName = null;
073:
074: private static ThreadLocal currentModule = new ThreadLocal();
075:
076: protected boolean allowInvocations = false;
077: protected boolean allowRemoteInvocations = false;
078:
079: protected boolean isDebugEnabled = false;
080:
081: public long runningInvocations = 0;
082: public long runningHomeInvocations = 0;
083: public long shutdownTimeout = 60000;
084: public long readAcquireTimeMs = 10000;
085:
086: protected ReentrantWriterPreferenceReadWriteLock rwLock = new ReentrantWriterPreferenceReadWriteLock();
087:
088: // Static --------------------------------------------------------
089:
090: private static final String METHOD_INVOCATION_TAG = "WrappingEjbModuleName";
091:
092: // Constructors --------------------------------------------------
093:
094: public CleanShutdownInterceptor() {
095: }
096:
097: // Public --------------------------------------------------------
098:
099: public void onlyAllowLocalInvocations() {
100: if (isDebugEnabled)
101: log.debug("Only allow local invocation from now on: "
102: + this .container.getServiceName().toString());
103: this .allowRemoteInvocations = false;
104: }
105:
106: public void waitForNoMoreInvocations() {
107: this .log.debug("Waiting that the container "
108: + container.getJmxName()
109: + " finishes its running invocations. "
110: + this .runningHomeInvocations
111: + " current home invocations and "
112: + this .runningInvocations
113: + " current remote invocations.");
114:
115: purgeRunningInvocations();
116: if (isDebugEnabled)
117: log
118: .debug("... Done: no more remote invocations currently running in this container.");
119: }
120:
121: // Z implementation ----------------------------------------------
122:
123: // AbstractInterceptor overrides ---------------------------------------------------
124:
125: public void create() throws Exception {
126: super .create();
127: this .allowInvocations = false;
128: this .allowRemoteInvocations = false;
129:
130: this .isDebugEnabled = log.isDebugEnabled();
131:
132: ejbModuleName = ejbModule.getServiceName().toString();
133:
134: // we register our inner-class to retrieve STATE notifications from our container
135: //
136: AttributeChangeNotificationFilter filter = new AttributeChangeNotificationFilter();
137: filter.enableAttribute("State");
138:
139: this .container.getServer().addNotificationListener(
140: this .container.getEjbModule().getServiceName(),
141: new CleanShutdownInterceptor.StateChangeListener(),
142: filter, null);
143:
144: // we need a way to find all CleanShutDownInterceptor of an EjbModule
145: //
146: ejbModule.putModuleData("CleanShutDownInterceptor-"
147: + this .container.getServiceName().toString(), this );
148: }
149:
150: public void start() throws Exception {
151: super .start();
152: this .allowInvocations = true;
153: this .allowRemoteInvocations = true;
154: }
155:
156: public void stop() {
157: super .stop();
158:
159: this .log.debug("Stopping container " + container.getJmxName()
160: + ". " + this .runningHomeInvocations
161: + " current home invocations and "
162: + this .runningInvocations
163: + " current remote invocations.");
164:
165: forbidInvocations();
166: }
167:
168: public void destroy() {
169: super .destroy();
170:
171: this .log.debug("Destroying container "
172: + container.getJmxName().toString() + ". "
173: + this .runningHomeInvocations
174: + " current home invocations and "
175: + this .runningInvocations
176: + " current remote invocations.");
177:
178: forbidInvocations();
179: }
180:
181: public Object invokeHome(Invocation mi) throws Exception {
182: if (this .allowInvocations) {
183: String origin = getOrigin(mi);
184: boolean isAppLocalCall = ejbModuleName.equals(origin);
185:
186: if (!this .allowRemoteInvocations && !isAppLocalCall)
187: // it is a remote call and they are currently forbidden!
188: //
189: {
190: if (isDebugEnabled)
191: log
192: .debug("Refusing a remote home invocation. here= "
193: + ejbModuleName
194: + "; Origin= "
195: + origin);
196: throw new GenericClusteringException(
197: GenericClusteringException.COMPLETED_NO,
198: "This application does not accept remote calls any more");
199: }
200:
201: // we need to acquire the read lock. If we cannot directly, it means
202: // that the stop/destroy call has gotten the write lock in the meantime
203: //
204: try {
205: if (!isAppLocalCall) // we only consider remote calls => every local originates from a remote!
206: {
207: if (!rwLock.readLock().attempt(readAcquireTimeMs))
208: throw new GenericClusteringException(
209: GenericClusteringException.COMPLETED_NO,
210: "Container is shuting down on this node (timeout)");
211: }
212: } catch (java.lang.InterruptedException ie) {
213: throw new GenericClusteringException(
214: GenericClusteringException.COMPLETED_NO,
215: "Container is shuting down on this node");
216: }
217:
218: runningHomeInvocations++;
219: try {
220: if (!isAppLocalCall)
221: setOrigin(mi);
222: return this .getNext().invokeHome(mi);
223: } catch (GenericClusteringException gce) {
224: // a gce exception has be thrown somewhere else: we need to modify its flag
225: // and forward it. We could add optimisations at this level by having some
226: // "idempotent" flag at the container level
227: //
228: gce.setCompletionStatus(gce.COMPLETED_MAYBE);
229: throw gce;
230: } finally {
231: if (!isAppLocalCall)
232: revertOrigin(mi, origin);
233:
234: runningHomeInvocations--;
235: if (!isAppLocalCall) // we only consider remote calls => every local originates from a remote!
236: rwLock.readLock().release();
237: }
238: } else
239: throw new GenericClusteringException(
240: GenericClusteringException.COMPLETED_NO,
241: "Container is shuting down on this node");
242: }
243:
244: public Object invoke(Invocation mi) throws Exception {
245: if (this .allowInvocations) {
246: String origin = getOrigin(mi);
247: boolean isAppLocalCall = ejbModuleName.equals(origin);
248:
249: if (!this .allowRemoteInvocations && !isAppLocalCall)
250: // it is a remote call and they are currently forbidden!
251: //
252: {
253: if (isDebugEnabled)
254: log.debug("Refusing a remote invocation");
255: throw new GenericClusteringException(
256: GenericClusteringException.COMPLETED_NO,
257: "This application does not accept remote calls any more");
258: }
259:
260: // we need to acquire the read lock. If we cannot directly, it means
261: // that the stop/destroy call has gotten the write lock in the meantime
262: //
263: try {
264: if (!isAppLocalCall) // we only consider remote calls => every local originates from a remote!
265: {
266: if (!rwLock.readLock().attempt(readAcquireTimeMs))
267: throw new GenericClusteringException(
268: GenericClusteringException.COMPLETED_NO,
269: "Container is shuting down on this node (timeout)");
270: }
271: } catch (java.lang.InterruptedException ie) {
272: throw new GenericClusteringException(
273: GenericClusteringException.COMPLETED_NO,
274: "Container is shuting down on this node");
275: }
276:
277: runningInvocations++;
278: try {
279: if (!isAppLocalCall)
280: setOrigin(mi);
281: return this .getNext().invoke(mi);
282: } catch (GenericClusteringException gce) {
283: // a gce exception has be thrown somewhere else: we need to modify its flag
284: // and forward it. We could add optimisations at this level by having some
285: // "idempotent" flag at the container level
286: //
287: gce.setCompletionStatus(gce.COMPLETED_MAYBE);
288: throw gce;
289: } finally {
290: if (!isAppLocalCall)
291: revertOrigin(mi, origin);
292:
293: runningInvocations--;
294: if (!isAppLocalCall) // we only consider remote calls => every local originates from a remote!
295: rwLock.readLock().release();
296: }
297: } else
298: throw new GenericClusteringException(
299: GenericClusteringException.COMPLETED_NO,
300: "Container is shuting down on this node");
301: }
302:
303: public Container getContainer() {
304: return this .container;
305: }
306:
307: /** This callback is set by the container so that the plugin may access it
308: *
309: * @param con The container using this plugin.
310: */
311: public void setContainer(Container con) {
312: this .container = con;
313: if (con != null)
314: this .ejbModule = con.getEjbModule();
315: else
316: this .ejbModule = null;
317: }
318:
319: // Package protected ---------------------------------------------
320:
321: // Protected -----------------------------------------------------
322:
323: protected void forbidInvocations() {
324: this .allowInvocations = false;
325:
326: purgeRunningInvocations();
327: }
328:
329: protected void purgeRunningInvocations() {
330: try {
331: if (this .rwLock.writeLock().attempt(shutdownTimeout))
332: this .rwLock.writeLock().release();
333: else
334: log.info("Possible running invocations not terminated "
335: + "while leaving the container. Home: "
336: + runningHomeInvocations + ". Remote: "
337: + runningInvocations + ".");
338: } catch (Exception e) {
339: log.info("Exception while waiting for running invocations "
340: + "to leave container. Home: "
341: + runningHomeInvocations + ". Remote: "
342: + runningInvocations + ".", e);
343: } finally {
344:
345: }
346: }
347:
348: protected String getOrigin(Invocation mi) {
349: String value = (String) currentModule.get();
350: if (log.isTraceEnabled())
351: log.trace("GET_ORIGIN: " + value + " in "
352: + this .container.getServiceName().toString());
353: return value;
354: }
355:
356: protected void setOrigin(Invocation mi) {
357: currentModule.set(this .ejbModuleName);
358: }
359:
360: protected void revertOrigin(Invocation mi, String origin) {
361:
362: if (log.isTraceEnabled())
363: log.trace("Crossing ejbModule border from "
364: + this .ejbModuleName + " to " + origin);
365: currentModule.set(origin);
366: }
367:
368: // Private -------------------------------------------------------
369:
370: protected void containerIsAboutToStop() {
371: log
372: .debug("Container about to stop: disabling HA-RMI access to bean from interceptor");
373:
374: // This is bad: we should have some kind of code (manager) associated
375: // with this ejbModule. We mimic this by electing the first ProxyFactoryHA
376: // as a manager
377: //
378: boolean iAmTheManager = !Boolean.TRUE.equals(ejbModule
379: .getModuleData("ShutdownInterceptorElected"));
380:
381: if (iAmTheManager) {
382: ejbModule.putModuleData("ShutdownInterceptorElected",
383: Boolean.TRUE);
384:
385: if (isDebugEnabled)
386: log
387: .debug("Container is about to stop and I am the manager of the first step: blocking remote calls");
388: // in a first step, all interceptors must refuse/redirect remote invocations
389: //
390: Collection containers = ejbModule.getContainers();
391: Iterator containersIter = containers.iterator();
392: while (containersIter.hasNext()) {
393: Container otherContainer = (Container) containersIter
394: .next();
395: CleanShutdownInterceptor inter = (CleanShutdownInterceptor) ejbModule
396: .getModuleData("CleanShutDownInterceptor-"
397: + otherContainer.getServiceName()
398: .toString());
399: if (inter == null) {
400: log
401: .debug("Found an EJB that doesnt have a clean-shutdown interceptor: "
402: + otherContainer.getJmxName());
403: } else {
404: inter.onlyAllowLocalInvocations();
405: }
406: }
407: } else {
408: if (isDebugEnabled)
409: log
410: .debug("Container is about to stop but I am not the manager: I don't manage the first step of the process.");
411: }
412:
413: // in a second step, all container, manager or not, will wait that no more invocation
414: // are running through
415: // The cycling around other interceptor is managed by the JMX callbacks, not by us
416: //
417: waitForNoMoreInvocations();
418: }
419:
420: // Inner classes -------------------------------------------------
421:
422: class StateChangeListener implements NotificationListener {
423:
424: public void handleNotification(Notification notification,
425: java.lang.Object handback) {
426: if (notification instanceof AttributeChangeNotification) {
427: AttributeChangeNotification notif = (AttributeChangeNotification) notification;
428: int value = ((Integer) notif.getNewValue()).intValue();
429:
430: // Start management is handled by the ProxyFactoryHA, not here
431: if (value == ServiceMBean.STOPPING) {
432: containerIsAboutToStop();
433: }
434: }
435: }
436:
437: }
438:
439: }
|