001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  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: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
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                gbeanInstance.setStateReason(e.getMessage());
217                log.warn("Problem in doFail", e);
218            }
219            setStateInstance(State.FAILED);
220            lifecycleBroadcaster.fireFailedEvent();
221        }
222    
223        /**
224         * Attempts to bring the component into {@link org.apache.geronimo.kernel.management.State#RUNNING} state. If an Exception occurs while
225         * starting the component, the component will be failed.
226         * <p/>
227         * <p/>
228         * Note: Do not call this from within a synchronized block as it makes may send a JMX notification
229         */
230        void attemptFullStart() {
231            assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
232    
233            synchronized (this) {
234                // if we are still trying to start and can start now... start
235                if (getStateInstance() != State.STARTING) {
236                    return;
237                }
238    
239                // check if all of the gbeans we depend on are running
240                Set parents = dependencyManager.getParents(abstractName);
241                for (Iterator i = parents.iterator(); i.hasNext();) {
242                    AbstractName parent = (AbstractName) i.next();
243                    if (!kernel.isLoaded(parent)) {
244                        log.trace("Cannot run because parent is not registered: parent=" + parent);
245                        return;
246                    }
247                    try {
248                        log.trace("Checking if parent is running: parent=" + parent);
249                        if (kernel.getGBeanState(parent) != State.RUNNING_INDEX) {
250                            log.trace("Cannot run because parent is not running: parent=" + parent);
251                            return;
252                        }
253                        log.trace("Parent is running: parent=" + parent);
254                    } catch (GBeanNotFoundException e) {
255                        // depended on instance was removed bewteen the register check and the invoke
256                        log.trace("Cannot run because parent is not registered: parent=" + parent);
257                        return;
258                    } catch (Exception e) {
259                        // problem getting the attribute, parent has most likely failed
260                        log.trace("Cannot run because an error occurred while checking if parent is running: parent=" + parent);
261                        return;
262                    }
263                }
264            }
265    
266            try {
267                // try to create the instance
268                if (!gbeanInstance.createInstance()) {
269                    // instance is not ready to start... this is normally caused by references
270                    // not being available, but could be because someone already started the gbean.
271                    // in another thread.  The reference will log a debug message about why
272                    // it could not start
273                    return;
274                }
275            } catch (Throwable t) {
276                // oops there was a problem and the gbean failed
277                log.error("Error while starting; GBean is now in the FAILED state: abstractName=\"" + abstractName + "\"", t);
278                setStateInstance(State.FAILED);
279                lifecycleBroadcaster.fireFailedEvent();
280    
281                if (t instanceof Exception) {
282                    // ignore - we only rethrow errors
283                    gbeanInstance.setStateReason(t.getMessage());
284                    return;
285                } else if (t instanceof Error) {
286                    throw (Error) t;
287                } else {
288                    throw new Error(t);
289                }
290            }
291    
292            // started successfully... notify everyone else
293            setStateInstance(State.RUNNING);
294            lifecycleBroadcaster.fireRunningEvent();
295        }
296    
297        /**
298         * Attempt to bring the component into the fully stopped state.
299         * If an exception occurs while stopping the component, the component will be failed.
300         * <p/>
301         * <p/>
302         * Note: Do not call this from within a synchronized block as it may send a JMX notification
303         */
304        void attemptFullStop() {
305            assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
306    
307            // check if we are able to stop
308            synchronized (this) {
309                // if we are still trying to stop...
310                if (getStateInstance() != State.STOPPING) {
311                    return;
312                }
313    
314                // check if all of the mbeans depending on us are stopped
315                Set children = dependencyManager.getChildren(abstractName);
316                for (Iterator i = children.iterator(); i.hasNext();) {
317                    AbstractName child = (AbstractName) i.next();
318                    if (kernel.isLoaded(child)) {
319                        try {
320                            log.trace("Checking if child is stopped: child=" + child);
321                            int state = kernel.getGBeanState(child);
322                            if (state == State.RUNNING_INDEX) {
323                                log.trace("Cannot stop because child is still running: child=" + child);
324                                return;
325                            }
326                        } catch (GBeanNotFoundException e) {
327                            // depended on instance was removed between the register check and the invoke
328                        } catch (Exception e) {
329                            // problem getting the attribute, depended on bean has most likely failed
330                            log.trace("Cannot run because an error occurred while checking if child is stopped: child=" + child);
331                            return;
332                        }
333                    }
334                }
335            }
336    
337            // all is clear to stop... try to stop
338            try {
339                if (!gbeanInstance.destroyInstance(true)) {
340                    // instance is not ready to stop... this is because another thread has
341                    // already stopped the gbean.
342                    return;
343                }
344            } catch (Throwable t) {
345                log.error("Error while stopping; GBean is now in the FAILED state: abstractName=\"" + abstractName + "\"", t);
346                setStateInstance(State.FAILED);
347                lifecycleBroadcaster.fireFailedEvent();
348    
349                if (t instanceof Exception) {
350                    // ignore - we only rethrow errors
351                    gbeanInstance.setStateReason(t.getMessage());
352                    return;
353                } else if (t instanceof Error) {
354                    throw (Error) t;
355                } else {
356                    throw new Error(t);
357                }
358            }
359    
360            // we successfully stopped, notify everyone else
361            setStateInstance(State.STOPPED);
362            lifecycleBroadcaster.fireStoppedEvent();
363        }
364    
365        public int getState() {
366            return state.toInt();
367        }
368    
369        public final State getStateInstance() {
370            return state;
371        }
372    
373        /**
374         * Set the Component state.
375         *
376         * @param newState the target state to transition
377         * @throws IllegalStateException Thrown if the transition is not supported by the J2EE Management lifecycle.
378         */
379        private synchronized void setStateInstance(State newState) throws IllegalStateException {
380            switch (state.toInt()) {
381                case State.STOPPED_INDEX:
382                    switch (newState.toInt()) {
383                        case State.STARTING_INDEX:
384                            break;
385                        case State.STOPPED_INDEX:
386                        case State.RUNNING_INDEX:
387                        case State.STOPPING_INDEX:
388                        case State.FAILED_INDEX:
389                            throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
390                    }
391                    break;
392    
393                case State.STARTING_INDEX:
394                    switch (newState.toInt()) {
395                        case State.RUNNING_INDEX:
396                        case State.FAILED_INDEX:
397                        case State.STOPPING_INDEX:
398                            break;
399                        case State.STOPPED_INDEX:
400                        case State.STARTING_INDEX:
401                            throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
402                    }
403                    break;
404    
405                case State.RUNNING_INDEX:
406                    switch (newState.toInt()) {
407                        case State.STOPPING_INDEX:
408                        case State.FAILED_INDEX:
409                            break;
410                        case State.STOPPED_INDEX:
411                        case State.STARTING_INDEX:
412                        case State.RUNNING_INDEX:
413                            throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
414                    }
415                    break;
416    
417                case State.STOPPING_INDEX:
418                    switch (newState.toInt()) {
419                        case State.STOPPED_INDEX:
420                        case State.FAILED_INDEX:
421                            break;
422                        case State.STARTING_INDEX:
423                        case State.RUNNING_INDEX:
424                        case State.STOPPING_INDEX:
425                            throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
426                    }
427                    break;
428    
429                case State.FAILED_INDEX:
430                    switch (newState.toInt()) {
431                        case State.STARTING_INDEX:
432                        case State.STOPPING_INDEX:
433                            break;
434                        case State.RUNNING_INDEX:
435                        case State.STOPPED_INDEX:
436                        case State.FAILED_INDEX:
437                            throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
438                    }
439                    break;
440            }
441            log.debug(toString() + " State changed from " + state + " to " + newState);
442            state = newState;
443        }
444    
445        public String toString() {
446            return "GBeanInstanceState for: " + abstractName;
447        }
448    
449    }