/**
*
* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.geronimo.gbean.runtime;
import java.util.Iterator;
import java.util.Set;
import javax.management.ObjectName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.geronimo.kernel.DependencyManager;
import org.apache.geronimo.kernel.GBeanNotFoundException;
import org.apache.geronimo.kernel.Kernel;
import org.apache.geronimo.kernel.lifecycle.LifecycleAdapter;
import org.apache.geronimo.kernel.lifecycle.LifecycleListener;
import org.apache.geronimo.kernel.management.State;
/**
* @version $Rev$ $Date$
*/
public class GBeanInstanceState {
private static final Log log = LogFactory.getLog(GBeanInstanceState.class);
/**
* The GBeanInstance in which this server is registered.
*/
private final GBeanInstance gbeanInstance;
/**
* The kernel in which this server is registered.
*/
private final Kernel kernel;
/**
* The unique name of this service.
*/
private final ObjectName objectName;
/**
* The dependency manager
*/
private final DependencyManager dependencyManager;
/**
* The broadcaster of lifecycle events
*/
private final LifecycleBroadcaster lifecycleBroadcaster;
/**
* The listener for the of the object blocking the start of this gbean.
* When the blocker dies we attempt to start.
*/
private LifecycleListener blockerListener;
// This must be volatile otherwise getState must be synchronized which will result in deadlock as dependent
// objects check if each other are in one state or another (i.e., classic A calls B while B calls A)
private volatile State state = State.STOPPED;
GBeanInstanceState(ObjectName objectName, Kernel kernel, DependencyManager dependencyManager, GBeanInstance gbeanInstance, LifecycleBroadcaster lifecycleBroadcaster) {
this.objectName = objectName;
this.kernel = kernel;
this.dependencyManager = dependencyManager;
this.gbeanInstance = gbeanInstance;
this.lifecycleBroadcaster = lifecycleBroadcaster;
}
/**
* Moves this MBean to the {@link org.apache.geronimo.kernel.management.State#STARTING} state and then attempts to move this MBean immediately
* to the {@link org.apache.geronimo.kernel.management.State#RUNNING} state.
* <p/>
* Note: This method cannot be called while the current thread holds a synchronized lock on this MBean,
* because this method sends JMX notifications. Sending a general notification from a synchronized block
* is a bad idea and therefore not allowed.
*/
public final void start() {
assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
// Move to the starting state
State originalState;
synchronized (this) {
originalState = getStateInstance();
if (originalState == State.RUNNING) {
return;
}
// only try to change states if we are not already starting
if (originalState != State.STARTING) {
setStateInstance(State.STARTING);
}
}
// only fire a notification if we are not already starting
if (originalState != State.STARTING) {
lifecycleBroadcaster.fireStartingEvent();
}
attemptFullStart();
}
/**
* Starts this MBean and then attempts to start all of its start dependent children.
* <p/>
* Note: This method cannot be call while the current thread holds a synchronized lock on this MBean,
* because this method sends JMX notifications. Sending a general notification from a synchronized block
* is a bad idea and therefore not allowed.
*/
public final void startRecursive() {
assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
State state = getStateInstance();
if (state != State.STOPPED && state != State.FAILED && state != State.RUNNING) {
// Cannot startRecursive while in the stopping state
// Dain: I don't think we can throw an exception here because there is no way for the caller
// to lock the instance and check the state before calling
return;
}
// get myself starting
start();
// startRecursive all of objects that depend on me
Set dependents = dependencyManager.getChildren(objectName);
for (Iterator iterator = dependents.iterator(); iterator.hasNext();) {
ObjectName dependent = (ObjectName) iterator.next();
try {
if (kernel.isGBeanEnabled(dependent)) {
kernel.startRecursiveGBean(dependent);
}
} catch (GBeanNotFoundException e) {
// this is ok the gbean died before we could start it
continue;
} catch (Exception e) {
// the is something wrong with this gbean... skip it
continue;
}
}
}
/**
* Moves this MBean to the STOPPING state, calls stop on all start dependent children, and then attempt
* to move this MBean to the STOPPED state.
* <p/>
* Note: This method can not be call while the current thread holds a syncronized lock on this MBean,
* because this method sends JMX notifications. Sending a general notification from a synchronized block
* is a bad idea and therefore not allowed.
*/
public final void stop() {
assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
// move to the stopping state
State originalState;
synchronized (this) {
originalState = getStateInstance();
if (originalState == State.STOPPED) {
return;
}
// only try to change states if we are not already stopping
if (originalState != State.STOPPING) {
setStateInstance(State.STOPPING);
}
}
// only fire a notification if we are not already stopping
if (originalState != State.STOPPING) {
lifecycleBroadcaster.fireStoppingEvent();
}
// Don't try to stop dependents from within a synchronized block... this should reduce deadlocks
// stop all of my dependent objects
Set dependents = dependencyManager.getChildren(objectName);
for (Iterator iterator = dependents.iterator(); iterator.hasNext();) {
ObjectName child = (ObjectName) iterator.next();
try {
log.trace("Checking if child is running: child=" + child);
if (kernel.getGBeanState(child) == State.RUNNING_INDEX) {
log.trace("Stopping child: child=" + child);
kernel.stopGBean(child);
log.trace("Stopped child: child=" + child);
}
} catch (Exception ignore) {
// not a big deal... did my best
}
}
attemptFullStop();
}
/**
* Moves this MBean to the FAILED state. There are no calls to dependent children, but they will be notified
* using standard J2EE management notification.
* <p/>
* Note: This method can not be call while the current thread holds a syncronized lock on this MBean,
* because this method sends JMX notifications. Sending a general notification from a synchronized block
* is a bad idea and therefore not allowed.
*/
final void fail() {
assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
synchronized (this) {
State state = getStateInstance();
if (state == State.STOPPED || state == State.FAILED) {
return;
}
}
try {
if (gbeanInstance.destroyInstance(false)) {
// instance is not ready to destroyed... this is because another thread has
// already killed the gbean.
return;
}
} catch (Throwable e) {
log.warn("Problem in doFail", e);
}
setStateInstance(State.FAILED);
lifecycleBroadcaster.fireFailedEvent();
}
/**
* Attempts to bring the component into {@link org.apache.geronimo.kernel.management.State#RUNNING} state. If an Exception occurs while
* starting the component, the component will be failed.
* <p/>
* <p/>
* Note: Do not call this from within a synchronized block as it makes may send a JMX notification
*/
void attemptFullStart() {
assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
synchronized (this) {
// if we are still trying to start and can start now... start
if (getStateInstance() != State.STARTING) {
return;
}
if (blockerListener != null) {
log.trace("Cannot run because gbean is still being blocked");
return;
}
// check if an gbean is blocking us from starting
final ObjectName blocker = dependencyManager.checkBlocker(objectName);
if (blocker != null) {
blockerListener = new LifecycleAdapter() {
public void stopped(ObjectName objectName) {
checkBlocker(objectName);
}
public void failed(ObjectName objectName) {
checkBlocker(objectName);
}
public void unloaded(ObjectName objectName) {
checkBlocker(objectName);
}
private void checkBlocker(ObjectName objectName) {
synchronized (GBeanInstanceState.this) {
if (!objectName.equals(blocker)) {
// it did not start so just exit this method
return;
}
// it started, so remove the blocker and attempt a full start
kernel.getLifecycleMonitor().removeLifecycleListener(this);
GBeanInstanceState.this.blockerListener = null;
}
try {
attemptFullStart();
} catch (Exception e) {
log.warn("A problem occured while attempting to start", e);
}
}
};
// register the listener and return
kernel.getLifecycleMonitor().addLifecycleListener(blockerListener, blocker);
return;
}
// check if all of the gbeans we depend on are running
Set parents = dependencyManager.getParents(objectName);
for (Iterator i = parents.iterator(); i.hasNext();) {
ObjectName parent = (ObjectName) i.next();
if (!kernel.isLoaded(parent)) {
log.trace("Cannot run because parent is not registered: parent=" + parent);
return;
}
try {
log.trace("Checking if parent is running: parent=" + parent);
if (kernel.getGBeanState(parent) != State.RUNNING_INDEX) {
log.trace("Cannot run because parent is not running: parent=" + parent);
return;
}
log.trace("Parent is running: parent=" + parent);
} catch (GBeanNotFoundException e) {
// depended on instance was removed bewteen the register check and the invoke
log.trace("Cannot run because parent is not registered: parent=" + parent);
return;
} catch (Exception e) {
// problem getting the attribute, parent has most likely failed
log.trace("Cannot run because an error occurred while checking if parent is running: parent=" + parent);
return;
}
}
}
try {
// try to create the instance
if (!gbeanInstance.createInstance()) {
// instance is not ready to start... this is normally caused by references
// not being available, but could be because someone already started the gbean.
// in another thread. The reference will log a debug message about why
// it could not start
return;
}
} catch (Throwable t) {
// oops there was a problem and the gbean failed
log.error("Error while starting; GBean is now in the FAILED state: objectName=\"" + objectName + "\"", t);
setStateInstance(State.FAILED);
lifecycleBroadcaster.fireFailedEvent();
if (t instanceof Exception) {
// ignore - we only rethrow errors
return;
} else if (t instanceof Error) {
throw (Error) t;
} else {
throw new Error(t);
}
}
// started successfully... notify everyone else
setStateInstance(State.RUNNING);
lifecycleBroadcaster.fireRunningEvent();
}
/**
* Attempt to bring the component into the fully stopped state.
* If an exception occurs while stopping the component, the component will be failed.
* <p/>
* <p/>
* Note: Do not call this from within a synchronized block as it may send a JMX notification
*/
void attemptFullStop() {
assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
// check if we are able to stop
synchronized (this) {
// if we are still trying to stop...
if (getStateInstance() != State.STOPPING) {
return;
}
// check if all of the mbeans depending on us are stopped
Set children = dependencyManager.getChildren(objectName);
for (Iterator i = children.iterator(); i.hasNext();) {
ObjectName child = (ObjectName) i.next();
if (kernel.isLoaded(child)) {
try {
log.trace("Checking if child is stopped: child=" + child);
int state = kernel.getGBeanState(child);
if (state == State.RUNNING_INDEX) {
log.trace("Cannot stop because child is still running: child=" + child);
return;
}
} catch (GBeanNotFoundException e) {
// depended on instance was removed between the register check and the invoke
} catch (Exception e) {
// problem getting the attribute, depended on bean has most likely failed
log.trace("Cannot run because an error occurred while checking if child is stopped: child=" + child);
return;
}
}
}
}
// all is clear to stop... try to stop
try {
if (!gbeanInstance.destroyInstance(true)) {
// instance is not ready to stop... this is because another thread has
// already stopped the gbean.
return;
}
} catch (Throwable t) {
log.error("Error while stopping; GBean is now in the FAILED state: objectName=\"" + objectName + "\"", t);
setStateInstance(State.FAILED);
lifecycleBroadcaster.fireFailedEvent();
if (t instanceof Exception) {
// ignore - we only rethrow errors
return;
} else if (t instanceof Error) {
throw (Error) t;
} else {
throw new Error(t);
}
}
// we successfully stopped, notify everyone else
setStateInstance(State.STOPPED);
lifecycleBroadcaster.fireStoppedEvent();
}
public int getState() {
return state.toInt();
}
public final State getStateInstance() {
return state;
}
/**
* Set the Component state.
*
* @param newState the target state to transition
* @throws IllegalStateException Thrown if the transition is not supported by the J2EE Management lifecycle.
*/
private synchronized void setStateInstance(State newState) throws IllegalStateException {
switch (state.toInt()) {
case State.STOPPED_INDEX:
switch (newState.toInt()) {
case State.STARTING_INDEX:
break;
case State.STOPPED_INDEX:
case State.RUNNING_INDEX:
case State.STOPPING_INDEX:
case State.FAILED_INDEX:
throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
}
break;
case State.STARTING_INDEX:
switch (newState.toInt()) {
case State.RUNNING_INDEX:
case State.FAILED_INDEX:
case State.STOPPING_INDEX:
break;
case State.STOPPED_INDEX:
case State.STARTING_INDEX:
throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
}
break;
case State.RUNNING_INDEX:
switch (newState.toInt()) {
case State.STOPPING_INDEX:
case State.FAILED_INDEX:
break;
case State.STOPPED_INDEX:
case State.STARTING_INDEX:
case State.RUNNING_INDEX:
throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
}
break;
case State.STOPPING_INDEX:
switch (newState.toInt()) {
case State.STOPPED_INDEX:
case State.FAILED_INDEX:
break;
case State.STARTING_INDEX:
case State.RUNNING_INDEX:
case State.STOPPING_INDEX:
throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
}
break;
case State.FAILED_INDEX:
switch (newState.toInt()) {
case State.STARTING_INDEX:
case State.STOPPING_INDEX:
break;
case State.RUNNING_INDEX:
case State.STOPPED_INDEX:
case State.FAILED_INDEX:
throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
}
break;
}
log.debug(toString() + " State changed from " + state + " to " + newState);
state = newState;
}
public String toString() {
return "GBeanInstanceState for: " + objectName;
}
}