View Javadoc

1   /**
2    *
3    * Copyright 2004 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.geronimo.gbean.runtime;
18  
19  import org.apache.commons.logging.Log;
20  import org.apache.commons.logging.LogFactory;
21  import org.apache.geronimo.gbean.AbstractName;
22  import org.apache.geronimo.kernel.DependencyManager;
23  import org.apache.geronimo.kernel.GBeanNotFoundException;
24  import org.apache.geronimo.kernel.Kernel;
25  import org.apache.geronimo.kernel.management.State;
26  
27  import java.util.Iterator;
28  import java.util.Set;
29  
30  /**
31   * @version $Rev: 430508 $ $Date: 2006-08-10 12:56:47 -0700 (Thu, 10 Aug 2006) $
32   */
33  public class GBeanInstanceState {
34      private static final Log log = LogFactory.getLog(GBeanInstanceState.class);
35  
36      /**
37       * The GBeanInstance in which this server is registered.
38       */
39      private final GBeanInstance gbeanInstance;
40  
41      /**
42       * The kernel in which this server is registered.
43       */
44      private final Kernel kernel;
45  
46      /**
47       * The unique name of this service.
48       */
49      private final AbstractName abstractName;
50  
51      /**
52       * The dependency manager
53       */
54      private final DependencyManager dependencyManager;
55  
56      /**
57       * The broadcaster of lifecycle events
58       */
59      private final LifecycleBroadcaster lifecycleBroadcaster;
60  
61      // This must be volatile otherwise getState must be synchronized which will result in deadlock as dependent
62      // objects check if each other are in one state or another (i.e., classic A calls B while B calls A)
63      private volatile State state = State.STOPPED;
64  
65      GBeanInstanceState(AbstractName abstractName, Kernel kernel, DependencyManager dependencyManager, GBeanInstance gbeanInstance, LifecycleBroadcaster lifecycleBroadcaster) {
66          this.abstractName = abstractName;
67          this.kernel = kernel;
68          this.dependencyManager = dependencyManager;
69          this.gbeanInstance = gbeanInstance;
70          this.lifecycleBroadcaster = lifecycleBroadcaster;
71      }
72  
73      /**
74       * Moves this MBean to the {@link org.apache.geronimo.kernel.management.State#STARTING} state and then attempts to move this MBean immediately
75       * to the {@link org.apache.geronimo.kernel.management.State#RUNNING} state.
76       * <p/>
77       * Note:  This method cannot be called while the current thread holds a synchronized lock on this MBean,
78       * because this method sends JMX notifications. Sending a general notification from a synchronized block
79       * is a bad idea and therefore not allowed.
80       */
81      public final void start() {
82          assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
83  
84          // Move to the starting state
85          State originalState;
86          synchronized (this) {
87              originalState = getStateInstance();
88              if (originalState == State.RUNNING) {
89                  return;
90              }
91              // only try to change states if we are not already starting
92              if (originalState != State.STARTING) {
93                  setStateInstance(State.STARTING);
94              }
95          }
96  
97          // only fire a notification if we are not already starting
98          if (originalState != State.STARTING) {
99              lifecycleBroadcaster.fireStartingEvent();
100         }
101 
102         attemptFullStart();
103     }
104 
105     /**
106      * Starts this MBean and then attempts to start all of its start dependent children.
107      * <p/>
108      * Note:  This method cannot be call while the current thread holds a synchronized lock on this MBean,
109      * because this method sends JMX notifications.  Sending a general notification from a synchronized block
110      * is a bad idea and therefore not allowed.
111      */
112     public final void startRecursive() {
113         assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
114 
115         State state = getStateInstance();
116         if (state != State.STOPPED && state != State.FAILED && state != State.RUNNING) {
117             // Cannot startRecursive while in the stopping state
118             // Dain: I don't think we can throw an exception here because there is no way for the caller
119             // to lock the instance and check the state before calling
120             return;
121         }
122 
123         // get myself starting
124         start();
125 
126         // startRecursive all of objects that depend on me
127         Set dependents = dependencyManager.getChildren(abstractName);
128         for (Iterator iterator = dependents.iterator(); iterator.hasNext();) {
129             AbstractName dependent = (AbstractName) iterator.next();
130             try {
131                 kernel.startRecursiveGBean(dependent);
132             } catch (GBeanNotFoundException e) {
133                 // this is ok the gbean died before we could start it
134             } catch (Exception e) {
135                 // there is something wrong with this gbean... skip it
136             }
137         }
138     }
139 
140     /**
141      * Moves this MBean to the STOPPING state, calls stop on all start dependent children, and then attempt
142      * to move this MBean to the STOPPED state.
143      * <p/>
144      * Note:  This method can not be call while the current thread holds a syncronized lock on this MBean,
145      * because this method sends JMX notifications.  Sending a general notification from a synchronized block
146      * is a bad idea and therefore not allowed.
147      */
148     public final void stop() {
149         assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
150 
151         // move to the stopping state
152         State originalState;
153         synchronized (this) {
154             originalState = getStateInstance();
155             if (originalState == State.STOPPED) {
156                 return;
157             }
158 
159             // only try to change states if we are not already stopping
160             if (originalState != State.STOPPING) {
161                 setStateInstance(State.STOPPING);
162             }
163         }
164 
165         // only fire a notification if we are not already stopping
166         if (originalState != State.STOPPING) {
167             lifecycleBroadcaster.fireStoppingEvent();
168         }
169 
170         // Don't try to stop dependents from within a synchronized block... this should reduce deadlocks
171 
172         // stop all of my dependent objects
173         Set dependents = dependencyManager.getChildren(abstractName);
174         for (Iterator iterator = dependents.iterator(); iterator.hasNext();) {
175             AbstractName child = (AbstractName) iterator.next();
176             try {
177                 log.trace("Checking if child is running: child=" + child);
178                 if (kernel.getGBeanState(child) == State.RUNNING_INDEX) {
179                     log.trace("Stopping child: child=" + child);
180                     kernel.stopGBean(child);
181                     log.trace("Stopped child: child=" + child);
182                 }
183             } catch (Exception ignore) {
184                 // not a big deal... did my best
185             }
186         }
187 
188         attemptFullStop();
189     }
190 
191     /**
192      * Moves this MBean to the FAILED state.  There are no calls to dependent children, but they will be notified
193      * using standard J2EE management notification.
194      * <p/>
195      * Note:  This method can not be call while the current thread holds a syncronized lock on this MBean,
196      * because this method sends JMX notifications.  Sending a general notification from a synchronized block
197      * is a bad idea and therefore not allowed.
198      */
199     final void fail() {
200         assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
201 
202         synchronized (this) {
203             State state = getStateInstance();
204             if (state == State.STOPPED || state == State.FAILED) {
205                 return;
206             }
207         }
208 
209         try {
210             if (gbeanInstance.destroyInstance(false)) {
211                 // instance is not ready to destroyed... this is because another thread has
212                 // already killed the gbean.
213                 return;
214             }
215         } catch (Throwable e) {
216             log.warn("Problem in doFail", e);
217         }
218         setStateInstance(State.FAILED);
219         lifecycleBroadcaster.fireFailedEvent();
220     }
221 
222     /**
223      * Attempts to bring the component into {@link org.apache.geronimo.kernel.management.State#RUNNING} state. If an Exception occurs while
224      * starting the component, the component will be failed.
225      * <p/>
226      * <p/>
227      * Note: Do not call this from within a synchronized block as it makes may send a JMX notification
228      */
229     void attemptFullStart() {
230         assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
231 
232         synchronized (this) {
233             // if we are still trying to start and can start now... start
234             if (getStateInstance() != State.STARTING) {
235                 return;
236             }
237 
238             // check if all of the gbeans we depend on are running
239             Set parents = dependencyManager.getParents(abstractName);
240             for (Iterator i = parents.iterator(); i.hasNext();) {
241                 AbstractName parent = (AbstractName) i.next();
242                 if (!kernel.isLoaded(parent)) {
243                     log.trace("Cannot run because parent is not registered: parent=" + parent);
244                     return;
245                 }
246                 try {
247                     log.trace("Checking if parent is running: parent=" + parent);
248                     if (kernel.getGBeanState(parent) != State.RUNNING_INDEX) {
249                         log.trace("Cannot run because parent is not running: parent=" + parent);
250                         return;
251                     }
252                     log.trace("Parent is running: parent=" + parent);
253                 } catch (GBeanNotFoundException e) {
254                     // depended on instance was removed bewteen the register check and the invoke
255                     log.trace("Cannot run because parent is not registered: parent=" + parent);
256                     return;
257                 } catch (Exception e) {
258                     // problem getting the attribute, parent has most likely failed
259                     log.trace("Cannot run because an error occurred while checking if parent is running: parent=" + parent);
260                     return;
261                 }
262             }
263         }
264 
265         try {
266             // try to create the instance
267             if (!gbeanInstance.createInstance()) {
268                 // instance is not ready to start... this is normally caused by references
269                 // not being available, but could be because someone already started the gbean.
270                 // in another thread.  The reference will log a debug message about why
271                 // it could not start
272                 return;
273             }
274         } catch (Throwable t) {
275             // oops there was a problem and the gbean failed
276             log.error("Error while starting; GBean is now in the FAILED state: abstractName=\"" + abstractName + "\"", t);
277             setStateInstance(State.FAILED);
278             lifecycleBroadcaster.fireFailedEvent();
279 
280             if (t instanceof Exception) {
281                 // ignore - we only rethrow errors
282                 return;
283             } else if (t instanceof Error) {
284                 throw (Error) t;
285             } else {
286                 throw new Error(t);
287             }
288         }
289 
290         // started successfully... notify everyone else
291         setStateInstance(State.RUNNING);
292         lifecycleBroadcaster.fireRunningEvent();
293     }
294 
295     /**
296      * Attempt to bring the component into the fully stopped state.
297      * If an exception occurs while stopping the component, the component will be failed.
298      * <p/>
299      * <p/>
300      * Note: Do not call this from within a synchronized block as it may send a JMX notification
301      */
302     void attemptFullStop() {
303         assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
304 
305         // check if we are able to stop
306         synchronized (this) {
307             // if we are still trying to stop...
308             if (getStateInstance() != State.STOPPING) {
309                 return;
310             }
311 
312             // check if all of the mbeans depending on us are stopped
313             Set children = dependencyManager.getChildren(abstractName);
314             for (Iterator i = children.iterator(); i.hasNext();) {
315                 AbstractName child = (AbstractName) i.next();
316                 if (kernel.isLoaded(child)) {
317                     try {
318                         log.trace("Checking if child is stopped: child=" + child);
319                         int state = kernel.getGBeanState(child);
320                         if (state == State.RUNNING_INDEX) {
321                             log.trace("Cannot stop because child is still running: child=" + child);
322                             return;
323                         }
324                     } catch (GBeanNotFoundException e) {
325                         // depended on instance was removed between the register check and the invoke
326                     } catch (Exception e) {
327                         // problem getting the attribute, depended on bean has most likely failed
328                         log.trace("Cannot run because an error occurred while checking if child is stopped: child=" + child);
329                         return;
330                     }
331                 }
332             }
333         }
334 
335         // all is clear to stop... try to stop
336         try {
337             if (!gbeanInstance.destroyInstance(true)) {
338                 // instance is not ready to stop... this is because another thread has
339                 // already stopped the gbean.
340                 return;
341             }
342         } catch (Throwable t) {
343             log.error("Error while stopping; GBean is now in the FAILED state: abstractName=\"" + abstractName + "\"", t);
344             setStateInstance(State.FAILED);
345             lifecycleBroadcaster.fireFailedEvent();
346 
347             if (t instanceof Exception) {
348                 // ignore - we only rethrow errors
349                 return;
350             } else if (t instanceof Error) {
351                 throw (Error) t;
352             } else {
353                 throw new Error(t);
354             }
355         }
356 
357         // we successfully stopped, notify everyone else
358         setStateInstance(State.STOPPED);
359         lifecycleBroadcaster.fireStoppedEvent();
360     }
361 
362     public int getState() {
363         return state.toInt();
364     }
365 
366     public final State getStateInstance() {
367         return state;
368     }
369 
370     /**
371      * Set the Component state.
372      *
373      * @param newState the target state to transition
374      * @throws IllegalStateException Thrown if the transition is not supported by the J2EE Management lifecycle.
375      */
376     private synchronized void setStateInstance(State newState) throws IllegalStateException {
377         switch (state.toInt()) {
378             case State.STOPPED_INDEX:
379                 switch (newState.toInt()) {
380                     case State.STARTING_INDEX:
381                         break;
382                     case State.STOPPED_INDEX:
383                     case State.RUNNING_INDEX:
384                     case State.STOPPING_INDEX:
385                     case State.FAILED_INDEX:
386                         throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
387                 }
388                 break;
389 
390             case State.STARTING_INDEX:
391                 switch (newState.toInt()) {
392                     case State.RUNNING_INDEX:
393                     case State.FAILED_INDEX:
394                     case State.STOPPING_INDEX:
395                         break;
396                     case State.STOPPED_INDEX:
397                     case State.STARTING_INDEX:
398                         throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
399                 }
400                 break;
401 
402             case State.RUNNING_INDEX:
403                 switch (newState.toInt()) {
404                     case State.STOPPING_INDEX:
405                     case State.FAILED_INDEX:
406                         break;
407                     case State.STOPPED_INDEX:
408                     case State.STARTING_INDEX:
409                     case State.RUNNING_INDEX:
410                         throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
411                 }
412                 break;
413 
414             case State.STOPPING_INDEX:
415                 switch (newState.toInt()) {
416                     case State.STOPPED_INDEX:
417                     case State.FAILED_INDEX:
418                         break;
419                     case State.STARTING_INDEX:
420                     case State.RUNNING_INDEX:
421                     case State.STOPPING_INDEX:
422                         throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
423                 }
424                 break;
425 
426             case State.FAILED_INDEX:
427                 switch (newState.toInt()) {
428                     case State.STARTING_INDEX:
429                     case State.STOPPING_INDEX:
430                         break;
431                     case State.RUNNING_INDEX:
432                     case State.STOPPED_INDEX:
433                     case State.FAILED_INDEX:
434                         throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
435                 }
436                 break;
437         }
438         log.debug(toString() + " State changed from " + state + " to " + newState);
439         state = newState;
440     }
441 
442     public String toString() {
443         return "GBeanInstanceState for: " + abstractName;
444     }
445 
446 }