blob: 4f7b2a6c3fdfa0027b31c294caae96daa9a2b432 [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.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.management.ObjectName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.gbean.kernel.DependencyManager;
import org.gbean.kernel.Kernel;
import org.gbean.kernel.ServiceNotFoundException;
import org.gbean.service.ServiceContext;
import org.gbean.service.ServiceFactory;
/**
* @version $Rev: 106387 $ $Date: 2004-11-23 22:16:54 -0800 (Tue, 23 Nov 2004) $
*/
public final class ServiceInstance {
private static final Log log = LogFactory.getLog(ServiceInstance.class);
private static final int DESTROYED = 0;
private static final int CREATING = 1;
private static final int RUNNING = 2;
private static final int DESTROYING = 3;
/**
* The kernel in which this server is registered.
*/
private final Kernel kernel;
/**
* The factory used to create the actual object instance.
*/
private ServiceFactory serviceFactory;
/**
* This handles all state transiitions for this instance.
*/
private final ServiceInstanceState serviceInstanceState;
/**
* The single listener to which we broadcast lifecycle change events.
*/
private final LifecycleBroadcaster lifecycleBroadcaster;
/**
* The context given to the instance
*/
private final ServiceContext serviceContext;
/**
* The classloader used for all invocations and creating targets.
*/
private final ClassLoader classLoader;
/**
* Has this instance been destroyed?
*/
private boolean dead = false;
/**
* The state of the internal service instance that we are wrapping.
*/
private int instanceState = DESTROYED;
/**
* Target instance of this service instance
*/
private Object target;
/**
* The time this application started.
*/
private long startTime;
private Set dependencies;
private final ObjectName objectName;
public ServiceInstance(ObjectName objectName,
ServiceFactory serviceFactory,
Kernel kernel,
DependencyManager dependencyManager,
LifecycleBroadcaster lifecycleBroadcaster,
ClassLoader classLoader) {
this.objectName = objectName;
this.serviceFactory = serviceFactory;
// add the dependencies
Set tempDependencies = new HashSet();
for (Iterator iterator = serviceFactory.getDependencies().entrySet().iterator(); iterator.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
String dependencyName = (String) entry.getKey();
Set patterns = (Set) entry.getValue();
tempDependencies.add(new ServiceDependency(this, dependencyName, patterns, kernel, dependencyManager));
}
this.dependencies = Collections.unmodifiableSet(tempDependencies);
this.kernel = kernel;
this.lifecycleBroadcaster = lifecycleBroadcaster;
this.serviceInstanceState = new ServiceInstanceState(objectName, kernel, dependencyManager, this, lifecycleBroadcaster);
this.classLoader = classLoader;
serviceContext = new ServiceInstanceContext(this);
}
public void init() {
lifecycleBroadcaster.fireLoadedEvent();
}
public void destroy() throws ServiceNotFoundException {
synchronized (this) {
if (dead) {
// someone beat us to the punch... this instance should have never been found in the first place
throw new ServiceNotFoundException(objectName.getCanonicalName());
}
dead = true;
}
// if the bean is already stopped or failed, this will do nothing; otherwise it will shutdown the bean
serviceInstanceState.stop();
// tell everyone we are done
lifecycleBroadcaster.fireUnloadedEvent();
}
/**
* The kernel in which this instance is mounted
* @return the kernel
*/
public Kernel getKernel() {
return kernel;
}
/**
* The class loader used to build this service. This class loader is set into the thread context
* class loader before callint the target instace.
*
* @return the class loader used to build this service
*/
public ClassLoader getClassLoader() {
return classLoader;
}
public synchronized Object getInstance() {
return target;
}
/**
* Has this service instance been destroyed. An destroyed service can no longer be used.
*
* @return true if the service has been destroyed
*/
public synchronized boolean isDead() {
return dead;
}
public final ObjectName getObjectName() {
return objectName;
}
public ServiceFactory getServiceFactory() {
return serviceFactory;
}
/**
* Is this service enabled. A disabled service can not be started.
*
* @return true if the service is enabled and can be started
*/
public synchronized final boolean isEnabled() {
return serviceFactory.isEnabled();
}
/**
* Changes the enabled status.
*
* @param enabled the new enabled flag
*/
public synchronized final void setEnabled(boolean enabled) {
serviceFactory.setEnabled(enabled);
}
public synchronized final long getStartTime() {
return startTime;
}
public int getState() {
return serviceInstanceState.getState();
}
/**
* Moves this service to the starting state and then attempts to move this service immediately
* to the running state.
*
* @throws IllegalStateException If the service is disabled
*/
public final void start() {
synchronized (this) {
if (dead) {
throw new IllegalStateException("A dead service can not be started: objectName=" + objectName);
}
if (!isEnabled()) {
throw new IllegalStateException("A disabled service can not be started: objectName=" + objectName);
}
}
serviceInstanceState.start();
}
/**
* Starts this ServiceInstance and then attempts to start all of its start dependent children.
*
* @throws IllegalStateException If the service is disabled
*/
public final void startRecursive() {
synchronized (this) {
if (dead) {
throw new IllegalStateException("A dead service can not be started: objectName=" + objectName);
}
if (!isEnabled()) {
throw new IllegalStateException("A disabled service can not be started: objectName=" + objectName);
}
}
serviceInstanceState.startRecursive();
}
/**
* 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.
*/
public final void stop() {
serviceInstanceState.stop();
}
boolean createInstance() throws Exception {
synchronized (this) {
// first check we are still in the correct state to start
if (instanceState == CREATING || instanceState == RUNNING) {
// another thread already completed starting
return false;
} else if (instanceState == DESTROYING) {
// this should never ever happen... this method is protected by the ServiceInstanceState class which should
// prevent stuff like this happening, but check anyway
throw new IllegalStateException("A stopping instance can not be started until fully stopped");
}
assert instanceState == DESTROYED;
// Call all start on every reference. This way the dependecies are held until we can start
boolean allStarted = true;
for (Iterator iterator = dependencies.iterator(); iterator.hasNext();) {
ServiceDependency dependency = (ServiceDependency) iterator.next();
allStarted = dependency.start() && allStarted;
}
if (!allStarted) {
return false;
}
// we are definately going to (try to) start... if this fails the must clean up these variables
instanceState = CREATING;
startTime = System.currentTimeMillis();
}
Object instance = null;
try {
instance = serviceFactory.createService(serviceContext);
// all done... we are now fully running
synchronized (this) {
target = instance;
instanceState = RUNNING;
this.notifyAll();
}
return true;
} catch (Throwable t) {
// something went wrong... we need to destroy this instance
synchronized (this) {
instanceState = DESTROYING;
}
serviceFactory.destroyService(serviceContext, instance);
// bean has been notified... drop our reference
synchronized (this) {
for (Iterator iterator = dependencies.iterator(); iterator.hasNext();) {
ServiceDependency dependency = (ServiceDependency) iterator.next();
dependency.stop();
}
target = null;
instanceState = DESTROYED;
startTime = 0;
this.notifyAll();
}
if (t instanceof Exception) {
throw (Exception) t;
} else if (t instanceof Error) {
throw (Error) t;
} else {
throw new Error(t);
}
}
}
boolean destroyInstance() throws Exception {
Object instance;
synchronized (this) {
// if the instance is being created we need to wait
// for it to finish before we can try to stop it
while (instanceState == CREATING) {
// todo should we limit this wait? If so, how do we configure the wait time?
try {
this.wait();
} catch (InterruptedException e) {
// clear the interrupted flag
Thread.interrupted();
// rethrow the interrupted exception.... someone was sick of us waiting
throw e;
}
}
if (instanceState == DESTROYING || instanceState == DESTROYED) {
// another thread is already stopping or has already stopped
return false;
}
assert instanceState == RUNNING;
// we are definately going to stop... if this fails we must clean up these variables
instanceState = DESTROYING;
instance = target;
}
try {
// we notify the bean before removing our dependencies so the dependencies can be called back while stopping
serviceFactory.destroyService(serviceContext, instance);
} finally {
// bean has been notified... drop the dependencies
synchronized (this) {
for (Iterator iterator = dependencies.iterator(); iterator.hasNext();) {
ServiceDependency dependency = (ServiceDependency) iterator.next();
dependency.stop();
}
target = null;
instanceState = DESTROYED;
startTime = 0;
}
}
return true;
}
private static final class ServiceInstanceContext implements ServiceContext {
/**
* The ServiceInstance which owns the target.
*/
private final ServiceInstance serviceInstance;
/**
* Creates a new context for a target.
*
* @param serviceInstance the ServiceInstance
*/
public ServiceInstanceContext(ServiceInstance serviceInstance) {
this.serviceInstance = serviceInstance;
}
public Kernel getKernel() {
return serviceInstance.getKernel();
}
public String getObjectName() {
return serviceInstance.getObjectName().getCanonicalName();
}
public ClassLoader getClassLoader() {
return serviceInstance.getClassLoader();
}
public int getState() {
return serviceInstance.getState();
}
public void stop() throws Exception {
synchronized (serviceInstance) {
if (serviceInstance.instanceState == CREATING) {
throw new IllegalStateException("Stop can not be called until instance is fully started");
} else if (serviceInstance.instanceState == DESTROYING) {
log.debug("Stop ignored. Service is already being stopped");
return;
} else if (serviceInstance.instanceState == DESTROYED) {
log.debug("Stop ignored. Service is already stopped");
return;
}
}
serviceInstance.stop();
}
}
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj instanceof ServiceInstance == false) return false;
return objectName.equals(((ServiceInstance) obj).objectName);
}
public int hashCode() {
return objectName.hashCode();
}
public String toString() {
return objectName.getCanonicalName();
}
}