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 }