KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > ejb > plugins > CleanShutdownInterceptor


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 org.jboss.ejb.Container;
25 import org.jboss.invocation.Invocation;
26 import org.jboss.ha.framework.interfaces.GenericClusteringException;
27 import EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock;
28 import org.jboss.ejb.EjbModule;
29 import javax.management.NotificationListener JavaDoc;
30 import javax.management.Notification JavaDoc;
31 import javax.management.AttributeChangeNotification JavaDoc;
32 import javax.management.AttributeChangeNotificationFilter JavaDoc;
33 import java.util.Collection JavaDoc;
34 import java.util.Iterator JavaDoc;
35 import org.jboss.system.ServiceMBean;
36
37 /**
38  * Track the incoming invocations and when shuting down a container (stop or
39  * destroy), waits for current invocations to finish before returning the
40  * stop or destroy call. This interceptor can be important in clustered environment
41  * where shuting down a node doesn't necessarly mean that an application cannot
42  * be reached: other nodes may still be servicing. Consequently, it is important
43  * to have a clean shutdown to keep a coherent behaviour cluster-wide.
44  *
45  * To avoid strange or inefficient behaviour, the facade session bean (if any)
46  * should be stopped first thus not blocking invocations in a middle-step (i.e.
47  * facade making multiple invocations to "sub-beans": if a "sub-bean" is
48  * shut down, the facade will get an exception in the middle of its activity)
49  *
50  * @see <related>
51  *
52  * @author <a HREF="mailto:sacha.labourey@cogito-info.ch">Sacha Labourey</a>.
53  * @version $Revision: 37459 $
54  *
55  * <p><b>Revisions:</b>
56  *
57  * <p><b>14 avril 2002 Sacha Labourey:</b>
58  * <ul>
59  * <li> First implementation </li>
60  * </ul>
61  */

62
63 public class CleanShutdownInterceptor extends AbstractInterceptor
64 {
65
66    // Constants -----------------------------------------------------
67

68    // Attributes ----------------------------------------------------
69

70    protected Container container = null;
71    
72    protected EjbModule ejbModule = null;
73    protected String JavaDoc ejbModuleName = null;
74    
75    private static ThreadLocal JavaDoc currentModule = new ThreadLocal JavaDoc ();
76    
77    protected boolean allowInvocations = false;
78    protected boolean allowRemoteInvocations = false;
79    
80    protected boolean isDebugEnabled = false;
81
82    public long runningInvocations = 0;
83    public long runningHomeInvocations = 0;
84    public long shutdownTimeout = 60000;
85    public long readAcquireTimeMs = 10000;
86
87    protected ReentrantWriterPreferenceReadWriteLock rwLock = new ReentrantWriterPreferenceReadWriteLock();
88
89    // Static --------------------------------------------------------
90

91    private static final String JavaDoc METHOD_INVOCATION_TAG = "WrappingEjbModuleName";
92
93    // Constructors --------------------------------------------------
94

95    public CleanShutdownInterceptor ()
96    {
97    }
98
99    // Public --------------------------------------------------------
100

101    public void onlyAllowLocalInvocations ()
102    {
103       if (isDebugEnabled) log.debug ("Only allow local invocation from now on: " + this.container.getServiceName ().toString ());
104       this.allowRemoteInvocations = false;
105    }
106
107    public void waitForNoMoreInvocations ()
108    {
109       this.log.debug ("Waiting that the container " + container.getJmxName () + " finishes its running invocations. " +
110                         this.runningHomeInvocations + " current home invocations and " +
111                         this.runningInvocations + " current remote invocations.");
112
113       purgeRunningInvocations ();
114       if (isDebugEnabled) log.debug ("... Done: no more remote invocations currently running in this container.");
115    }
116    
117    // Z implementation ----------------------------------------------
118

119    // AbstractInterceptor overrides ---------------------------------------------------
120

121    public void create() throws Exception JavaDoc {
122       super.create ();
123       this.allowInvocations = false;
124       this.allowRemoteInvocations = false;
125       
126       this.isDebugEnabled = log.isDebugEnabled ();
127       
128       ejbModuleName = ejbModule.getServiceName().toString();
129       
130       // we register our inner-class to retrieve STATE notifications from our container
131
//
132
AttributeChangeNotificationFilter JavaDoc filter = new AttributeChangeNotificationFilter JavaDoc ();
133       filter.enableAttribute ("State");
134       
135       this.container.getServer ().
136          addNotificationListener (this.container.getEjbModule ().getServiceName (),
137                                   new CleanShutdownInterceptor.StateChangeListener (),
138                                   filter,
139                                   null);
140
141       // we need a way to find all CleanShutDownInterceptor of an EjbModule
142
//
143
ejbModule.putModuleData ("CleanShutDownInterceptor-" + this.container.getServiceName ().toString (), this);
144    }
145
146    public void start() throws Exception JavaDoc {
147       super.start();
148       this.allowInvocations = true;
149       this.allowRemoteInvocations = true;
150    }
151
152    public void stop() {
153       super.stop ();
154
155       this.log.debug ("Stopping container " + container.getJmxName () + ". " +
156                         this.runningHomeInvocations + " current home invocations and " +
157                         this.runningInvocations + " current remote invocations.");
158
159       forbidInvocations ();
160    }
161
162    public void destroy() {
163       super.destroy ();
164
165       this.log.debug ("Destroying container " + container.getJmxName ().toString () + ". " +
166                         this.runningHomeInvocations + " current home invocations and " +
167                         this.runningInvocations + " current remote invocations.");
168
169       forbidInvocations() ;
170    }
171
172    public Object JavaDoc invokeHome (Invocation mi)
173    throws Exception JavaDoc
174    {
175       if (this.allowInvocations)
176       {
177          String JavaDoc origin = getOrigin (mi);
178          boolean isAppLocalCall = ejbModuleName.equals (origin);
179          
180          if (!this.allowRemoteInvocations && !isAppLocalCall)
181             // it is a remote call and they are currently forbidden!
182
//
183
{
184             if (isDebugEnabled) log.debug ("Refusing a remote home invocation. here= " + ejbModuleName + "; Origin= " + origin);
185             throw new GenericClusteringException (GenericClusteringException.COMPLETED_NO,
186                                                   "This application does not accept remote calls any more");
187          }
188          
189          // we need to acquire the read lock. If we cannot directly, it means
190
// that the stop/destroy call has gotten the write lock in the meantime
191
//
192
try
193          {
194             if (!isAppLocalCall) // we only consider remote calls => every local originates from a remote!
195
{
196                if (!rwLock.readLock ().attempt (readAcquireTimeMs))
197                   throw new GenericClusteringException (GenericClusteringException.COMPLETED_NO,
198                                                         "Container is shuting down on this node (timeout)");
199             }
200          }
201          catch (java.lang.InterruptedException JavaDoc ie)
202          {
203             throw new GenericClusteringException (GenericClusteringException.COMPLETED_NO,
204                                                   "Container is shuting down on this node");
205          }
206
207          runningHomeInvocations++;
208          try
209          {
210             if (!isAppLocalCall)
211                setOrigin (mi);
212             return this.getNext ().invokeHome (mi);
213          }
214          catch (GenericClusteringException gce)
215          {
216             // a gce exception has be thrown somewhere else: we need to modify its flag
217
// and forward it. We could add optimisations at this level by having some
218
// "idempotent" flag at the container level
219
//
220
gce.setCompletionStatus (gce.COMPLETED_MAYBE);
221             throw gce;
222          }
223          finally
224          {
225             if (!isAppLocalCall)
226                revertOrigin (mi, origin);
227             
228             runningHomeInvocations--;
229             if (!isAppLocalCall) // we only consider remote calls => every local originates from a remote!
230
rwLock.readLock ().release ();
231          }
232       }
233       else
234          throw new GenericClusteringException (GenericClusteringException.COMPLETED_NO,
235                                                "Container is shuting down on this node");
236    }
237
238    public Object JavaDoc invoke (Invocation mi)
239    throws Exception JavaDoc
240    {
241       if (this.allowInvocations)
242       {
243          String JavaDoc origin = getOrigin (mi);
244          boolean isAppLocalCall = ejbModuleName.equals (origin);
245          
246          if (!this.allowRemoteInvocations && !isAppLocalCall)
247             // it is a remote call and they are currently forbidden!
248
//
249
{
250             if (isDebugEnabled) log.debug ("Refusing a remote invocation");
251             throw new GenericClusteringException (GenericClusteringException.COMPLETED_NO,
252                                                   "This application does not accept remote calls any more");
253          }
254
255          // we need to acquire the read lock. If we cannot directly, it means
256
// that the stop/destroy call has gotten the write lock in the meantime
257
//
258
try
259          {
260             if (!isAppLocalCall) // we only consider remote calls => every local originates from a remote!
261
{
262                if (!rwLock.readLock ().attempt (readAcquireTimeMs))
263                   throw new GenericClusteringException (GenericClusteringException.COMPLETED_NO,
264                                                         "Container is shuting down on this node (timeout)");
265             }
266          }
267          catch (java.lang.InterruptedException JavaDoc ie)
268          {
269             throw new GenericClusteringException (GenericClusteringException.COMPLETED_NO,
270                                                   "Container is shuting down on this node");
271          }
272
273          runningInvocations++;
274          try
275          {
276             if (!isAppLocalCall)
277                setOrigin (mi);
278             return this.getNext ().invoke (mi);
279          }
280          catch (GenericClusteringException gce)
281          {
282             // a gce exception has be thrown somewhere else: we need to modify its flag
283
// and forward it. We could add optimisations at this level by having some
284
// "idempotent" flag at the container level
285
//
286
gce.setCompletionStatus (gce.COMPLETED_MAYBE);
287             throw gce;
288          }
289          finally
290          {
291             if (!isAppLocalCall)
292                revertOrigin (mi, origin);
293
294             runningInvocations--;
295             if (!isAppLocalCall) // we only consider remote calls => every local originates from a remote!
296
rwLock.readLock ().release ();
297          }
298       }
299       else
300          throw new GenericClusteringException (GenericClusteringException.COMPLETED_NO,
301                                                "Container is shuting down on this node");
302    }
303
304    public Container getContainer ()
305    {
306       return this.container;
307    }
308
309    /** This callback is set by the container so that the plugin may access it
310     *
311     * @param con The container using this plugin.
312     */

313    public void setContainer (Container con)
314    {
315       this.container = con;
316       if (con != null)
317          this.ejbModule = con.getEjbModule ();
318       else
319          this.ejbModule = null;
320    }
321
322    // Package protected ---------------------------------------------
323

324    // Protected -----------------------------------------------------
325

326    protected void forbidInvocations ()
327    {
328       this.allowInvocations = false;
329
330       purgeRunningInvocations();
331    }
332    
333    protected void purgeRunningInvocations ()
334    {
335       try
336       {
337          if (this.rwLock.writeLock ().attempt (shutdownTimeout))
338             this.rwLock.writeLock ().release ();
339          else
340             log.info ("Possible running invocations not terminated " +
341                "while leaving the container. Home: " + runningHomeInvocations +
342                ". Remote: " + runningInvocations + ".");
343       }
344       catch (Exception JavaDoc e)
345       {
346          log.info ("Exception while waiting for running invocations " +
347             "to leave container. Home: " + runningHomeInvocations +
348             ". Remote: " + runningInvocations + ".", e);
349       }
350       finally
351       {
352          
353       }
354    }
355    
356    protected String JavaDoc getOrigin (Invocation mi)
357    {
358       String JavaDoc value = (String JavaDoc)currentModule.get ();
359       if (log.isTraceEnabled())
360          log.trace ("GET_ORIGIN: " + value + " in " + this.container.getServiceName ().toString ());
361       return value;
362    }
363    
364    protected void setOrigin (Invocation mi)
365    {
366       currentModule.set (this.ejbModuleName);
367    }
368       
369    protected void revertOrigin (Invocation mi, String JavaDoc origin)
370    {
371       
372       if (log.isTraceEnabled()) log.trace ("Crossing ejbModule border from " + this.ejbModuleName + " to " + origin);
373       currentModule.set (origin);
374    }
375    
376
377    // Private -------------------------------------------------------
378

379    protected void containerIsAboutToStop ()
380    {
381       log.debug ("Container about to stop: disabling HA-RMI access to bean from interceptor");
382       
383       // This is bad: we should have some kind of code (manager) associated
384
// with this ejbModule. We mimic this by electing the first ProxyFactoryHA
385
// as a manager
386
//
387
boolean iAmTheManager = !Boolean.TRUE.equals (ejbModule.getModuleData ("ShutdownInterceptorElected"));
388       
389       if (iAmTheManager)
390       {
391          ejbModule.putModuleData ("ShutdownInterceptorElected", Boolean.TRUE);
392          
393          if (isDebugEnabled) log.debug ("Container is about to stop and I am the manager of the first step: blocking remote calls");
394          // in a first step, all interceptors must refuse/redirect remote invocations
395
//
396
Collection JavaDoc containers = ejbModule.getContainers ();
397          Iterator JavaDoc containersIter = containers.iterator ();
398          while (containersIter.hasNext ())
399          {
400             Container otherContainer = (Container)containersIter.next ();
401             CleanShutdownInterceptor inter = (CleanShutdownInterceptor)
402                ejbModule.getModuleData ("CleanShutDownInterceptor-" + otherContainer.getServiceName ().toString ());
403             if (inter == null)
404             {
405                log.debug ("Found an EJB that doesnt have a clean-shutdown interceptor: " + otherContainer.getJmxName ());
406             }
407             else
408             {
409                inter.onlyAllowLocalInvocations ();
410             }
411          }
412       }
413       else
414       {
415          if (isDebugEnabled) log.debug ("Container is about to stop but I am not the manager: I don't manage the first step of the process.");
416       }
417       
418       // in a second step, all container, manager or not, will wait that no more invocation
419
// are running through
420
// The cycling around other interceptor is managed by the JMX callbacks, not by us
421
//
422
waitForNoMoreInvocations ();
423    }
424    
425    // Inner classes -------------------------------------------------
426

427    class StateChangeListener implements NotificationListener JavaDoc
428    {
429       
430       public void handleNotification (Notification JavaDoc notification, java.lang.Object JavaDoc handback)
431       {
432          if (notification instanceof AttributeChangeNotification JavaDoc)
433          {
434             AttributeChangeNotification JavaDoc notif = (AttributeChangeNotification JavaDoc) notification;
435             int value = ((Integer JavaDoc)notif.getNewValue()).intValue ();
436             
437             // Start management is handled by the ProxyFactoryHA, not here
438
if (value == ServiceMBean.STOPPING)
439             {
440                containerIsAboutToStop ();
441             }
442          }
443       }
444       
445    }
446    
447    }
448
Popular Tags