001    /**
002     *
003     * Copyright 2004 The Apache Software Foundation
004     *
005     *  Licensed under the Apache License, Version 2.0 (the "License");
006     *  you may not use this file except in compliance with the License.
007     *  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.geronimo.gbean.runtime;
018    
019    import org.apache.commons.logging.Log;
020    import org.apache.commons.logging.LogFactory;
021    import org.apache.geronimo.gbean.AbstractName;
022    import org.apache.geronimo.kernel.DependencyManager;
023    import org.apache.geronimo.kernel.GBeanNotFoundException;
024    import org.apache.geronimo.kernel.Kernel;
025    import org.apache.geronimo.kernel.management.State;
026    
027    import java.util.Iterator;
028    import java.util.Set;
029    
030    /**
031     * @version $Rev: 430508 $ $Date: 2006-08-10 12:56:47 -0700 (Thu, 10 Aug 2006) $
032     */
033    public class GBeanInstanceState {
034        private static final Log log = LogFactory.getLog(GBeanInstanceState.class);
035    
036        /**
037         * The GBeanInstance in which this server is registered.
038         */
039        private final GBeanInstance gbeanInstance;
040    
041        /**
042         * The kernel in which this server is registered.
043         */
044        private final Kernel kernel;
045    
046        /**
047         * The unique name of this service.
048         */
049        private final AbstractName abstractName;
050    
051        /**
052         * The dependency manager
053         */
054        private final DependencyManager dependencyManager;
055    
056        /**
057         * The broadcaster of lifecycle events
058         */
059        private final LifecycleBroadcaster lifecycleBroadcaster;
060    
061        // This must be volatile otherwise getState must be synchronized which will result in deadlock as dependent
062        // objects check if each other are in one state or another (i.e., classic A calls B while B calls A)
063        private volatile State state = State.STOPPED;
064    
065        GBeanInstanceState(AbstractName abstractName, Kernel kernel, DependencyManager dependencyManager, GBeanInstance gbeanInstance, LifecycleBroadcaster lifecycleBroadcaster) {
066            this.abstractName = abstractName;
067            this.kernel = kernel;
068            this.dependencyManager = dependencyManager;
069            this.gbeanInstance = gbeanInstance;
070            this.lifecycleBroadcaster = lifecycleBroadcaster;
071        }
072    
073        /**
074         * Moves this MBean to the {@link org.apache.geronimo.kernel.management.State#STARTING} state and then attempts to move this MBean immediately
075         * to the {@link org.apache.geronimo.kernel.management.State#RUNNING} state.
076         * <p/>
077         * Note:  This method cannot be called while the current thread holds a synchronized lock on this MBean,
078         * because this method sends JMX notifications. Sending a general notification from a synchronized block
079         * is a bad idea and therefore not allowed.
080         */
081        public final void start() {
082            assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
083    
084            // Move to the starting state
085            State originalState;
086            synchronized (this) {
087                originalState = getStateInstance();
088                if (originalState == State.RUNNING) {
089                    return;
090                }
091                // only try to change states if we are not already starting
092                if (originalState != State.STARTING) {
093                    setStateInstance(State.STARTING);
094                }
095            }
096    
097            // only fire a notification if we are not already starting
098            if (originalState != State.STARTING) {
099                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    }