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 }