blob: dcd3bed64197760fc2f62b55d38e6045d44cb762 [file] [log] [blame]
/**
*
* Copyright 2005 the original author or authors.
*
* 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.gbean.kernel.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.gbean.kernel.Kernel;
import org.gbean.kernel.DependencyManager;
import org.gbean.kernel.LifecycleListener;
import org.gbean.kernel.ServiceNotFoundException;
import org.gbean.kernel.LifecycleAdapter;
/**
* @version $Rev$ $Date$
*/
public final class ServiceInstanceState {
private static final Log log = LogFactory.getLog(ServiceInstanceState.class);
/**
* The ServiceInstance in which this server is registered.
*/
private final ServiceInstance serviceInstance;
/**
* 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 service.
* 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 ServiceState state = ServiceState.STOPPED;
ServiceInstanceState(ObjectName objectName, Kernel kernel, DependencyManager dependencyManager, ServiceInstance serviceInstance, LifecycleBroadcaster lifecycleBroadcaster) {
this.objectName = objectName;
this.kernel = kernel;
this.dependencyManager = dependencyManager;
this.serviceInstance = serviceInstance;
this.lifecycleBroadcaster = lifecycleBroadcaster;
}
/**
* Moves this service to the starting state and then attempts to move this service immediately
* to the running state.
* <p/>
* Note: This method cannot be called while the current thread holds a synchronized lock on this service,
* because this method sends lifecycle notifications. Sending a general notification from a synchronized block
* is a bad idea and therefore not allowed.
*/
public void start() {
assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
// Move to the starting state
ServiceState originalState;
synchronized (this) {
originalState = getStateInstance();
if (originalState == ServiceState.RUNNING) {
return;
}
// only try to change states if we are not already starting
if (originalState != ServiceState.STARTING) {
setStateInstance(ServiceState.STARTING);
}
}
// only fire a notification if we are not already starting
if (originalState != ServiceState.STARTING) {
lifecycleBroadcaster.fireStartingEvent();
}
attemptFullStart();
}
/**
* Starts this service 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 service,
* because this method sends lifecycle notifications. Sending a general notification from a synchronized block
* is a bad idea and therefore not allowed.
*/
public void startRecursive() {
assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
ServiceState state = getStateInstance();
if (state != ServiceState.STOPPED) {
// 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.isServiceEnabled(dependent)) {
kernel.startRecursiveService(dependent);
}
} catch (ServiceNotFoundException e) {
// this is ok the service died before we could start it
continue;
} catch (Exception e) {
// the is something wrong with this service... skip it
continue;
}
}
}
/**
* Moves this service to the STOPPING state, calls stop on all start dependent children, and then attempt
* to move this service to the STOPPED state.
* <p/>
* Note: This method can not be call while the current thread holds a syncronized lock on this service,
* because this method sends lifecycle notifications. Sending a general notification from a synchronized block
* is a bad idea and therefore not allowed.
*/
public void stop() {
assert !Thread.holdsLock(this): "This method cannot be called while holding a synchronized lock on this";
// move to the stopping state
ServiceState originalState;
synchronized (this) {
originalState = getStateInstance();
if (originalState == ServiceState.STOPPED) {
return;
}
// only try to change states if we are not already stopping
if (originalState != ServiceState.STOPPING) {
setStateInstance(ServiceState.STOPPING);
}
}
// only fire a notification if we are not already stopping
if (originalState != ServiceState.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.getServiceState(child) == ServiceState.RUNNING_INDEX) {
log.trace("Stopping child: child=" + child);
kernel.stopService(child);
log.trace("Stopped child: child=" + child);
}
} catch (Exception ignore) {
// not a big deal... did my best
}
}
attemptFullStop();
}
/**
* Attempts to bring the component into 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 lifecycle 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() != ServiceState.STARTING) {
return;
}
if (blockerListener != null) {
log.trace("Cannot run because service is still being blocked");
return;
}
// check if an service 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 unloaded(ObjectName objectName) {
checkBlocker(objectName);
}
private void checkBlocker(ObjectName objectName) {
synchronized (ServiceInstanceState.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.removeLifecycleListener(this);
ServiceInstanceState.this.blockerListener = null;
}
try {
attemptFullStart();
} catch (Exception e) {
log.warn("A problem occured while attempting to start", e);
}
}
};
// register the listener and return
kernel.addLifecycleListener(blockerListener, blocker);
return;
}
// check if all of the services 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.getServiceState(parent) != ServiceState.RUNNING_INDEX) {
log.trace("Cannot run because parent is not running: parent=" + parent);
return;
}
log.trace("Parent is running: parent=" + parent);
} catch (ServiceNotFoundException 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 (!serviceInstance.createInstance()) {
// instance is not ready to start... this is normally caused by references
// not being available, but could be because someone alreayd started the service.
// 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, stop the service
setStateInstance(ServiceState.STOPPING);
lifecycleBroadcaster.fireStoppingEvent();
setStateInstance(ServiceState.STOPPED);
lifecycleBroadcaster.fireStoppedEvent();
log.error("Error while starting; Service is now in the STOPPED state: objectName=\"" + objectName + "\"", t);
return;
}
// started successfully... notify everyone else
setStateInstance(ServiceState.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 lifecycle 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() != ServiceState.STOPPING) {
return;
}
// check if all of the service 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.getServiceState(child);
if (state == ServiceState.RUNNING_INDEX) {
log.trace("Cannot stop because child is still running: child=" + child);
return;
}
} catch (ServiceNotFoundException 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 (!serviceInstance.destroyInstance()) {
// instance is not ready to stop... this is because another thread has
// already stopped the service.
return;
}
} catch (Throwable t) {
log.error("Error while stopping; Service is now in the STOPPED state: objectName=\"" + objectName + "\"", t);
} finally {
// we are always stopped at this point
setStateInstance(ServiceState.STOPPED);
lifecycleBroadcaster.fireStoppedEvent();
}
}
public int getState() {
return state.getIndex();
}
public ServiceState 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 lifecycle contract.
*/
private synchronized void setStateInstance(ServiceState newState) throws IllegalStateException {
switch (state.getIndex()) {
case ServiceState.STOPPED_INDEX:
switch (newState.getIndex()) {
case ServiceState.STARTING_INDEX:
break;
case ServiceState.STOPPED_INDEX:
case ServiceState.RUNNING_INDEX:
case ServiceState.STOPPING_INDEX:
throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
}
break;
case ServiceState.STARTING_INDEX:
switch (newState.getIndex()) {
case ServiceState.RUNNING_INDEX:
case ServiceState.STOPPING_INDEX:
break;
case ServiceState.STOPPED_INDEX:
case ServiceState.STARTING_INDEX:
throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
}
break;
case ServiceState.RUNNING_INDEX:
switch (newState.getIndex()) {
case ServiceState.STOPPING_INDEX:
break;
case ServiceState.STOPPED_INDEX:
case ServiceState.STARTING_INDEX:
case ServiceState.RUNNING_INDEX:
throw new IllegalStateException("Cannot transition to " + newState + " state from " + state);
}
break;
case ServiceState.STOPPING_INDEX:
switch (newState.getIndex()) {
case ServiceState.STOPPED_INDEX:
break;
case ServiceState.STARTING_INDEX:
case ServiceState.RUNNING_INDEX:
case ServiceState.STOPPING_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 "ServiceInstanceState for: " + objectName;
}
}