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: 542575 $ $Date: 2007-05-29 12:02:57 -0400 (Tue, 29 May 2007) $
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 }