blob: 531b0d74fa6be9d79bdcd1a73f1ed48e3cde2232 [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.standard;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Collections;
import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicLong;
import edu.emory.mathcs.backport.java.util.concurrent.locks.ReentrantLock;
import org.gbean.kernel.ForcedStopException;
import org.gbean.kernel.IllegalServiceStateException;
import org.gbean.kernel.Kernel;
import org.gbean.kernel.KernelOperationInterruptedException;
import org.gbean.kernel.KernelOperationTimoutException;
import org.gbean.kernel.ServiceCondition;
import org.gbean.kernel.ServiceEvent;
import org.gbean.kernel.ServiceFactory;
import org.gbean.kernel.ServiceMonitor;
import org.gbean.kernel.ServiceName;
import org.gbean.kernel.ServiceNotFoundException;
import org.gbean.kernel.ServiceState;
import org.gbean.kernel.StartStrategies;
import org.gbean.kernel.StartStrategy;
import org.gbean.kernel.StopStrategy;
import org.gbean.kernel.UnregisterServiceException;
import org.gbean.kernel.UnsatisfiedConditionsException;
import org.gbean.kernel.InvalidServiceTypeException;
/**
* The ServiceManager handles the life cycle of a single service. The manager is responsible for gaurenteeing that
* all start conditions have been satisfied before the service is constructed, and that all stop conditions have been
* satisfied before the service is destroyed. The ServiceManager can be started and stopped several times, but once
* destroyed no methods may be called.
*
* @author Dain Sundstrom
* @version $Id$
* @since 1.0
*/
public class ServiceManager implements Comparable {
/**
* The kernel in which this service is registered.
*/
private final Kernel kernel;
/**
* The unique id of this service in the kernel.
*/
private final long serviceId;
/**
* The unique name of this service in the kernel.
*/
private final ServiceName serviceName;
/**
* The factory used to create and destroy the service instance.
*/
private final ServiceFactory serviceFactory;
/**
* The type of service this service manager will create. This value is cached from the serviceFactory.getT
*/
private final Set serviceTypes;
/**
* The class loader for this service.
*/
private final ClassLoader classLoader;
/**
* The monitor to which we fire service events. The ServiceManager requires an asynchronous monitor becuse events are
* fired from within the lock. This helps to reduce complexity but will cause more services to sit in the
* {@link ServiceState#STARTING} and {@link ServiceState#STOPPING} states since events are propagated in a separate
* thread.
*/
private final ServiceMonitor serviceMonitor;
/**
* The service context given to the service factory. This contans a reference to the kernel, serviceName and
* classloader.
*/
private final StandardServiceContext standardServiceContext;
/**
* Current state of this service.
*/
private volatile ServiceState state = ServiceState.STOPPED;
/**
* The time the service was started or 0 if not started.
*/
private volatile long startTime;
/**
* The {@link ServiceCondition) objects required to be ready before this service can be completely started.
*/
private AggregateCondition startCondition;
/**
* The {@link ServiceCondition) objects required to be ready before this service can be completely stopped.
*/
private AggregateCondition stopCondition;
/**
* The service instance.
*/
private volatile Object service;
/**
* The single lock we use.
*/
private final ReentrantLock lock = new ReentrantLock();
/**
* The maximum duration to wait for the lock.
*/
private final long timeoutDuration;
/**
* The unit of measure for the {@link #timeoutDuration}.
*/
private final TimeUnit timeoutUnits;
/**
* The name of the operation for which the lock is held; this is used in the reentrant exception message.
*/
private String currentLockHolderOperation = "NOT-HELD";
/**
* Sequence number for service event objects.
*/
private final AtomicLong eventId = new AtomicLong(0);
/**
* If true, when start is successful we will startRecusrive all of the services owned by this service.
*/
private boolean recursive = false;
/**
* Creates a service manager for a single service.
*
* @param kernel the kernel in which this wraper will be registered
* @param serviceId the unique id of this service in the kernel
* @param serviceName the unique name of this service in the kernel
* @param serviceFactory the factory used to create and destroy the service instance
* @param classLoader the class loader for this service
* @param serviceMonitor the monitor of service events
* @param timeoutDuration the maximum duration to wait for a lock
* @param timeoutUnits the unit of measure for the timeoutDuration
*/
public ServiceManager(Kernel kernel,
long serviceId,
ServiceName serviceName,
ServiceFactory serviceFactory,
ClassLoader classLoader,
ServiceMonitor serviceMonitor,
long timeoutDuration,
TimeUnit timeoutUnits) {
this.kernel = kernel;
this.serviceId = serviceId;
this.serviceName = serviceName;
this.serviceFactory = serviceFactory;
this.classLoader = classLoader;
this.serviceMonitor = serviceMonitor;
this.timeoutDuration = timeoutDuration;
this.timeoutUnits = timeoutUnits;
standardServiceContext = new StandardServiceContext(kernel, serviceName, classLoader);
serviceTypes = Collections.unmodifiableSet(new LinkedHashSet(Arrays.asList(serviceFactory.getTypes())));
}
/**
* Initializes the service.
*
* @throws IllegalServiceStateException if the service is not restartable and is disabled
* @throws UnsatisfiedConditionsException if the service is not restartable and there were unsatisfied start conditions
* @throws Exception if the service is not restartable and service construction threw an exception
* @see Kernel#registerService(ServiceName, ServiceFactory, ClassLoader)
*/
public void initialize() throws IllegalServiceStateException, UnsatisfiedConditionsException, Exception {
if (!serviceFactory.isRestartable() && !serviceFactory.isEnabled()) {
throw new IllegalServiceStateException("A disabled non-restartable service factory can not be initalized", serviceName);
}
serviceMonitor.serviceRegistered(createServiceEvent());
// if we are not restartable, we need to start immediately, otherwise we are not going to register this service
if (!serviceFactory.isRestartable()) {
try {
start(false, StartStrategies.UNREGISTER);
} catch (UnregisterServiceException e) {
serviceMonitor.serviceUnregistered(createServiceEvent());
Throwable cause = e.getCause();
if (cause instanceof Exception) {
throw (Exception) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
} else {
throw new AssertionError(cause);
}
}
// a non restartable service uses a special stop conditions object that picks up stop conditions as they
// are added. When the stop() method is called on a non-restartable service all of the stop conditions
// registered with the service factory are initialized (if not already initialized), and the isSatisfied
// method is called. This should cause the stop logic of a stop condition to fire.
lock("initialize");
try {
stopCondition = new NonRestartableStopCondition(kernel, serviceName, classLoader, lock, serviceFactory);
} finally {
unlock();
}
}
}
/**
* Attempts to stop and destroy the service.
*
* @param stopStrategy the strategy used to determine how to handle unsatisfied stop conditions
* @throws IllegalServiceStateException is the service did not stop
* @throws UnsatisfiedConditionsException if there were unsatisfied stop conditions
* @see Kernel#unregisterService(ServiceName, StopStrategy)
*/
public void destroy(StopStrategy stopStrategy) throws IllegalServiceStateException, UnsatisfiedConditionsException {
// if we are not restartable, we need to stop
try {
if (!stop(stopStrategy)) {
throw new IllegalServiceStateException("Service did not stop", serviceName);
}
} catch (UnsatisfiedConditionsException e) {
throw e;
}
if (!serviceFactory.isRestartable()) {
lock("destroy");
try {
if (state != ServiceState.STOPPED) {
state = ServiceState.STARTING;
serviceMonitor.serviceStopping(createServiceEvent());
if (service != null) {
try {
// destroy the service
serviceFactory.destroyService(standardServiceContext);
} catch (Throwable e) {
serviceMonitor.serviceStopError(createErrorServiceEvent(e));
}
}
destroyAllConditions(serviceMonitor);
service = null;
startTime = 0;
state = ServiceState.STOPPED;
serviceMonitor.serviceStopped(createServiceEvent());
}
} finally {
unlock();
}
}
// cool we can unregistered
serviceMonitor.serviceUnregistered(createServiceEvent());
}
/**
* Gets the unique id of this service in the kernel.
*
* @return the unique id of this service in the kernel
*/
public long getServiceId() {
return serviceId;
}
/**
* Gets the unique name of this service in the kernel.
*
* @return the unique name of this servce in the kernel
*/
public ServiceName getServiceName() {
return serviceName;
}
/**
* Gets the types of the service that will be managed by this service manager.
* @return the types of the service
*/
public Set getServiceTypes() {
return serviceTypes;
}
/**
* Gets the factory used to create and destroy the service instance.
*
* @return the factory for the service instance
* @see Kernel#getServiceFactory(ServiceName)
*/
public ServiceFactory getServiceFactory() {
return serviceFactory;
}
/**
* Gets the class loader for this service. This class loader is provided to the service factory in the
* ServiceContext object.
*
* @return the classloader for this service
* @see Kernel#getClassLoaderFor(ServiceName)
*/
public ClassLoader getClassLoader() {
return classLoader;
}
/**
* Gets the service instance.
*
* @return the service instance
* @see Kernel#getService(ServiceName)
*/
public Object getService() {
return service;
}
/**
* Gets the current state of this service.
*
* @return the current state of this service
* @see Kernel#getServiceState(ServiceName)
*/
public ServiceState getState() {
return state;
}
/**
* Gets the time at which this service entered the STARTING state or 0 if the service is STOPPED.
*
* @return the start time or 0 if the service is stopped
* @see Kernel#getServiceStartTime(ServiceName)
*/
public long getStartTime() {
return startTime;
}
/**
* Attempts to starts the service.
*
* @param recursive if start is successful should we start recursive the services owned by this servic
* @param startStrategy the strategy used to determine how to handle unsatisfied start conditions and start errors
* @throws IllegalServiceStateException if the service is in a state in which it can not be started
* @throws UnregisterServiceException if the kernel should unregister this service
* @throws UnsatisfiedConditionsException if there were unsatisfied start conditions
* @throws Exception it service creation threw an exception
* @see Kernel#startService(ServiceName)
* @see Kernel#startServiceRecursive(ServiceName)
*/
public void start(boolean recursive, StartStrategy startStrategy) throws IllegalServiceStateException, UnregisterServiceException, UnsatisfiedConditionsException, Exception {
// verify that it is possible to start this service in the current state before obtaining the lock
if (!verifyStartable(state)) {
if (recursive) {
startOwnedServices(startStrategy);
}
return;
}
boolean shouldStartRecursive = false;
lock("start");
try {
// update the recursive flag
this.recursive = this.recursive || recursive;
Throwable startError = null;
try {
//
// Loop until all start conditions have been satified. The start strategy can break this loop.
//
boolean satisfied = false;
while (!satisfied) {
// do we still want to start?
if (!verifyStartable(state)) {
// assume someone else called startOwnedServices
return;
}
// if we are in the STOPPED state, we need to move to the STARTING state
if (state == ServiceState.STOPPED) {
// we are now officially starting
state = ServiceState.STARTING;
serviceMonitor.serviceStarting(createServiceEvent());
// initialize the start conditions
startCondition = new AggregateCondition(kernel, serviceName, classLoader, lock, serviceFactory.getStartConditions());
startCondition.initialize();
}
// are we satisfied?
Set unsatisfiedConditions = startCondition.getUnsatisfied();
satisfied = unsatisfiedConditions.isEmpty();
if (!satisfied) {
// if the stragegy wants us to wait for conditions to be satisfied, it will return true
if (startStrategy.waitForUnsatisfiedConditions(serviceName, unsatisfiedConditions)) {
// wait for satisfaction and loop
startCondition.awaitSatisfaction();
} else {
// no wait, notify the monitor and exit
serviceMonitor.serviceWaitingToStart(createWaitingServiceEvent(unsatisfiedConditions));
return;
}
}
}
// we are ready to create the service
service = serviceFactory.createService(standardServiceContext);
// verify that the service implements all of the types
if (service == null) {
throw new NullPointerException("Service factory return null from createService for service " + serviceName);
}
for (Iterator iterator = serviceTypes.iterator(); iterator.hasNext();) {
Class type = (Class) iterator.next();
if (!type.isInstance(service)) {
throw new InvalidServiceTypeException(serviceName, type, service.getClass());
}
}
// success transition to running
startTime = System.currentTimeMillis();
state = ServiceState.RUNNING;
serviceMonitor.serviceRunning(createServiceEvent());
// should we recursively start our children
shouldStartRecursive = this.recursive || recursive;
this.recursive = false;
} catch (UnsatisfiedConditionsException e) {
// thrown from waitForUnsatisfiedConditions
throw e;
} catch (IllegalServiceStateException e) {
// this can be thrown while awaiting satisfaction
throw e;
} catch (Exception e) {
startError = e;
} catch (Error e) {
startError = e;
}
if (startError != null) {
try {
if (startError instanceof UnregisterServiceException) {
throw (UnregisterServiceException) startError;
} else {
// the strategy will normally rethrow the startError, but if it doesn't notify the service monitor
startStrategy.startError(serviceName, startError);
serviceMonitor.serviceStartError(createErrorServiceEvent(startError));
}
} finally {
// we are now STOPPING
state = ServiceState.STOPPING;
serviceMonitor.serviceStopping(createServiceEvent());
// clean up the conditons
destroyAllConditions(serviceMonitor);
// transition to the STOPPED state
service = null;
startTime = 0;
state = ServiceState.STOPPED;
serviceMonitor.serviceStopped(createServiceEvent());
}
}
} finally {
unlock();
}
// startRecursive all of the owned services
if (shouldStartRecursive) {
startOwnedServices(startStrategy);
}
}
private void startOwnedServices(StartStrategy startStrategy) throws IllegalServiceStateException, UnsatisfiedConditionsException, Exception {
Set ownedServices = serviceFactory.getOwnedServices();
if (ownedServices == null) throw new NullPointerException("serviceFactory.getOwnedServices() returned null");
for (Iterator iterator = ownedServices.iterator(); iterator.hasNext();) {
ServiceName ownedService = (ServiceName) iterator.next();
try {
kernel.startServiceRecursive(ownedService, startStrategy);
} catch (ServiceNotFoundException ignored) {
// this is ok -- service unregistered
} catch (IllegalServiceStateException ignored) {
// ownedService is disabled or stopping -- anyway we don't care
}
}
}
/**
* Verifies that the service is startable. This can be used out side a lock to avoid unecessary locking.
*
* @param state the state of the service
* @return true if it is possible to start a service in the specifiec state
* @throws IllegalServiceStateException if it is illegal to start a service in the specified state
*/
private boolean verifyStartable(ServiceState state) throws IllegalServiceStateException {
// if we are alredy in the running state, there is nothing to do
if (state == ServiceState.RUNNING) {
return false;
}
// if we are in the stopping states, that is an error
if (state == ServiceState.STOPPING) {
throw new IllegalServiceStateException("A stopping service can not be started", serviceName);
}
// is this service enabled?
if (state == ServiceState.STOPPED && !serviceFactory.isEnabled()) {
throw new IllegalServiceStateException("Service is disabled", serviceName);
}
return true;
}
/**
* Attempts to stop the service.
*
* @param stopStrategy the strategy used to determine how to handle unsatisfied stop conditions
* @return true if the service was sucessfully stopped; false otherwise
* @throws UnsatisfiedConditionsException if there were unsatisfied stop conditions
* @see Kernel#stopService(ServiceName)
*/
public boolean stop(StopStrategy stopStrategy) throws UnsatisfiedConditionsException {
// check that we aren't already stopped before attempting to acquire the lock
ServiceState initialState = state;
if (initialState == ServiceState.STOPPED) {
return true;
}
lock("stop");
try {
try {
//
// Loop until all stop conditions have been satified. The stop strategy can break this loop.
//
boolean satisfied = false;
while (!satisfied) {
// do we still want to stop?
if (state == ServiceState.STOPPED) {
return true;
}
// if we are not the STOPPING state, transition to it
// we check on the stopConditions variable because non-restartable services preset this in the
// intialization method
if (stopCondition == null) {
// we are not officially stopping
serviceMonitor.serviceStopping(createServiceEvent());
state = ServiceState.STOPPING;
// initialize all of the stop conditions
stopCondition = new AggregateCondition(kernel, serviceName, classLoader, lock, serviceFactory.getStopConditions());
stopCondition.initialize();
}
// are we satisfied?
Set unsatisfiedConditions = stopCondition.getUnsatisfied();
satisfied = unsatisfiedConditions.isEmpty();
if (!satisfied) {
// if the stragegy wants us to wait for conditions to be satisfied, it will return true
if (stopStrategy.waitForUnsatisfiedConditions(serviceName, unsatisfiedConditions)) {
// wait for satisfaction and loop
stopCondition.awaitSatisfaction();
} else {
// no wait, notify the monitor and exit
serviceMonitor.serviceWaitingToStop(createWaitingServiceEvent(unsatisfiedConditions));
return false;
}
}
}
} catch (UnsatisfiedConditionsException e) {
throw e;
} catch (ForcedStopException e) {
serviceMonitor.serviceStopError(createErrorServiceEvent(e));
} catch (Exception e) {
serviceMonitor.serviceStopError(createErrorServiceEvent(e));
} catch (Error e) {
serviceMonitor.serviceStopError(createErrorServiceEvent(e));
}
if (serviceFactory.isRestartable()) {
if (service != null) {
try {
// destroy the service
serviceFactory.destroyService(standardServiceContext);
} catch (Throwable e) {
serviceMonitor.serviceStopError(createErrorServiceEvent(e));
}
}
destroyAllConditions(serviceMonitor);
service = null;
startTime = 0;
state = ServiceState.STOPPED;
serviceMonitor.serviceStopped(createServiceEvent());
}
return true;
} finally {
unlock();
}
}
private void destroyAllConditions(ServiceMonitor monitor) {
if (!lock.isHeldByCurrentThread()) {
throw new IllegalStateException("Current thread must hold lock before calling destroyAllConditions");
}
if (startCondition != null) {
List errors = startCondition.destroy();
// errors from destroying the start conditions are stop errors because destroy is only called while
// stopping the service
for (Iterator iterator = errors.iterator(); iterator.hasNext();) {
Throwable stopError = (Throwable) iterator.next();
monitor.serviceStopError(createErrorServiceEvent(stopError));
}
startCondition = null;
}
if (stopCondition != null) {
List errors = stopCondition.destroy();
for (Iterator iterator = errors.iterator(); iterator.hasNext();) {
Throwable stopError = (Throwable) iterator.next();
monitor.serviceStopError(createErrorServiceEvent(stopError));
}
stopCondition = null;
}
}
/**
* Obtain the lock for the specified operation.
*
* @param operationName name of the operation that lock will be used for - this is only used for exception messages
* @throws IllegalStateException if thread tries to reenter while holding the lock
* @throws KernelOperationTimoutException if lock could not be obtained in {@link #timeoutDuration} {@link #timeoutUnits}
* @throws KernelOperationInterruptedException if the thread was interrupted while waiting for the lock
*/
private void lock(String operationName) throws IllegalStateException, KernelOperationTimoutException, KernelOperationInterruptedException {
if (lock.isHeldByCurrentThread()) {
throw new IllegalStateException("Current thread holds lock for " + currentLockHolderOperation +
" and lock can not be reacquired for " + operationName + " on " + serviceName);
}
try {
if (!lock.tryLock(timeoutDuration, timeoutUnits)) {
throw new KernelOperationTimoutException("Could not obtain lock for " + operationName + " operation on " +
serviceName + " within " + timeoutDuration + " " + timeoutUnits.toString().toLowerCase(),
serviceName,
operationName);
}
currentLockHolderOperation = operationName;
} catch (InterruptedException e) {
throw new KernelOperationInterruptedException("Interrupted while attempting to obtain lock for " + operationName +
" operation on " + serviceName,
e,
serviceName,
operationName);
}
}
/**
* Unlock the lock and clear the currentLockHolderOperation name.
*/
private void unlock() {
if (!lock.isHeldByCurrentThread()) {
throw new IllegalMonitorStateException("Not owner");
}
currentLockHolderOperation = "NOT-HELD";
lock.unlock();
}
private ServiceEvent createServiceEvent() {
return new ServiceEvent(eventId.getAndIncrement(), kernel, serviceName, serviceFactory, classLoader, service, null, null);
}
private ServiceEvent createWaitingServiceEvent(Set unsatisfiedConditions) {
return new ServiceEvent(eventId.getAndIncrement(), kernel, serviceName, serviceFactory, classLoader, service, null, unsatisfiedConditions);
}
private ServiceEvent createErrorServiceEvent(Throwable cause) {
return new ServiceEvent(eventId.getAndIncrement(), kernel, serviceName, serviceFactory, classLoader, null, cause, null);
}
public int hashCode() {
return (int) (serviceId ^ (serviceId >>> 32));
}
public boolean equals(Object o) {
if (o instanceof ServiceManager) {
return serviceId == ((ServiceManager)o).serviceId;
}
return false;
}
public int compareTo(Object o) {
ServiceManager serviceManager = (ServiceManager) o;
if (serviceId < serviceManager.serviceId) {
return -1;
} else if (serviceId > serviceManager.serviceId) {
return 1;
} else {
return 0;
}
}
public String toString() {
return "[ServiceManager: serviceId=" + serviceId + ", serviceName=" + serviceName + ", state=" + state + "]";
}
}