| /** |
| * |
| * 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 edu.emory.mathcs.backport.java.util.concurrent.Executor; |
| 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; |
| |
| /** |
| * 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 { |
| /** |
| * The kernel in which this service is registered. |
| */ |
| private final Kernel kernel; |
| |
| /** |
| * 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 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 AsyncServiceMonitor 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 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 |
| * @param executor the executor that is used to deliver the asynchronous service events to the serviceMonitor |
| */ |
| public ServiceManager(Kernel kernel, |
| ServiceName serviceName, |
| ServiceFactory serviceFactory, |
| ClassLoader classLoader, |
| ServiceMonitor serviceMonitor, |
| Executor executor, |
| long timeoutDuration, |
| TimeUnit timeoutUnits) { |
| |
| this.kernel = kernel; |
| this.serviceName = serviceName; |
| this.serviceFactory = serviceFactory; |
| this.classLoader = classLoader; |
| this.serviceMonitor = new AsyncServiceMonitor(serviceMonitor, executor); |
| this.timeoutDuration = timeoutDuration; |
| this.timeoutUnits = timeoutUnits; |
| standardServiceContext = new StandardServiceContext(kernel, serviceName, classLoader); |
| } |
| |
| /** |
| * 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 name of this service in the kernel. |
| * |
| * @return the unque name of this servce in the kernel |
| */ |
| public ServiceName getServiceName() { |
| return serviceName; |
| } |
| |
| /** |
| * 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); |
| |
| // 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(); |
| 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); |
| } |
| |
| private ServiceEvent createWaitingServiceEvent(Set unsatisfiedConditions) { |
| return new ServiceEvent(eventId.getAndIncrement(), kernel, serviceName, serviceFactory, classLoader, service, unsatisfiedConditions); |
| } |
| |
| private ServiceEvent createErrorServiceEvent(Throwable cause) { |
| return new ServiceEvent(eventId.getAndIncrement(), kernel, serviceName, serviceFactory, classLoader, cause); |
| } |
| } |