Services are now indexed by type and can be looked up by type.
Moved event broadcast code to standard implementation package.
Added server module which contains generic services for running GBean as a standalone server.
Moved class implementations to server module.
Added parameter verification checks to public APIs.
Code base now has 100% JavaDocs coverage.


git-svn-id: https://svn.apache.org/repos/asf/geronimo/xbean/branches/new@380372 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/kernel/src/java/org/gbean/kernel/ForcedStopException.java b/kernel/src/java/org/gbean/kernel/ForcedStopException.java
index d6f9b2a..76195c4 100644
--- a/kernel/src/java/org/gbean/kernel/ForcedStopException.java
+++ b/kernel/src/java/org/gbean/kernel/ForcedStopException.java
@@ -41,6 +41,8 @@
         super("Forced stop and ignored unsatisfied conditons:" +
                 " serviceName=" + serviceName +
                 ", unsatisfiedConditions=" + unsatisfiedConditions);
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
+        if (unsatisfiedConditions == null) throw new NullPointerException("unsatisfiedConditions is null");
         this.serviceName = serviceName;
         this.unsatisfiedConditions = Collections.unmodifiableSet(unsatisfiedConditions);
     }
diff --git a/kernel/src/java/org/gbean/kernel/IllegalServiceStateException.java b/kernel/src/java/org/gbean/kernel/IllegalServiceStateException.java
index eca40a0..665fb53 100644
--- a/kernel/src/java/org/gbean/kernel/IllegalServiceStateException.java
+++ b/kernel/src/java/org/gbean/kernel/IllegalServiceStateException.java
@@ -34,6 +34,7 @@
      */
     public IllegalServiceStateException(String message, ServiceName serviceName) {
         super(message + ": " + serviceName);
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
         this.serviceName = serviceName;
     }
 
diff --git a/kernel/src/java/org/gbean/kernel/InvalidServiceTypeException.java b/kernel/src/java/org/gbean/kernel/InvalidServiceTypeException.java
new file mode 100644
index 0000000..32f0e64
--- /dev/null
+++ b/kernel/src/java/org/gbean/kernel/InvalidServiceTypeException.java
@@ -0,0 +1,74 @@
+/**
+ *
+ * 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;
+
+/**
+ * Indicates that the service factory returned an object from the createService method that is not an instance of every
+ * declared type.
+ *
+ * @author Dain Sundstrom
+ * @version $Id$
+ * @since 1.0
+ */
+public class InvalidServiceTypeException extends Exception {
+    private final ServiceName serviceName;
+    private final Class expectedType;
+    private final Class serviceType;
+
+    /**
+     * Creates an InvalidServiceType caused by the service with the specified name, which returned an object from the
+     * createService method of the specified type that is not an instance of the expected type.
+     * @param serviceName the name of the service that returned an object of the wrong type
+     * @param expectedType the type that was expected
+     * @param serviceType the actual type of the service returned from the factory
+     */
+    // todo add servicefacotory to the parameters
+    public InvalidServiceTypeException(ServiceName serviceName, Class expectedType, Class serviceType) {
+        super("Expected service type " + expectedType.getName() + ", but service factory created a " +
+                serviceType.getName() + " for service " + serviceName);
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
+        if (expectedType == null) throw new NullPointerException("expectedType is null");
+        if (serviceType == null) throw new NullPointerException("serviceType is null");
+        this.serviceName = serviceName;
+        this.expectedType = expectedType;
+        this.serviceType = serviceType;
+    }
+
+    /**
+     * Gets the name of the service that returned an object of the wrong type.
+     * @return the name of the service that returned an object of the wrong type
+     */
+    public ServiceName getServiceName() {
+        return serviceName;
+    }
+
+    /**
+     * Gets the type that was expected.
+     * @return the type that was expected
+     */
+    public Class getExpectedType() {
+        return expectedType;
+    }
+
+    /**
+     * Gets the actual type of the service returned from the factory.
+     * @return  the actual type of the service returned from the factory
+     */
+    public Class getServiceType() {
+        return serviceType;
+    }
+}
diff --git a/kernel/src/java/org/gbean/kernel/Kernel.java b/kernel/src/java/org/gbean/kernel/Kernel.java
index 2995b14..6e09261 100644
--- a/kernel/src/java/org/gbean/kernel/Kernel.java
+++ b/kernel/src/java/org/gbean/kernel/Kernel.java
@@ -16,6 +16,8 @@
  */
 package org.gbean.kernel;
 
+import java.util.List;
+
 /**
  * This iterface defines the API for managing and monitoring service life-cycle. A kernel can be constructed with the
  * following code:
@@ -257,13 +259,31 @@
      * or STARTING state this method will throw an IllegalArgumentException.
      *
      * @param serviceName the unique name of the service
-     * @return the service factory associated with the specified name
+     * @return the service associated with the specified name
      * @throws ServiceNotFoundException if there is no service registered under the specified name
      * @throws IllegalArgumentException if the service is not in the RUNNING, or STARTING state
      */
     Object getService(ServiceName serviceName) throws ServiceNotFoundException, IllegalArgumentException;
 
     /**
+     * Gets the first running service registered with the kernel that is an instance of the specified type.  If no
+     * running services are instances of the specified type, null is returned.
+     *
+     * @param type the of the desired service
+     * @return the first registered service that is an instance of the specified type and is running
+     */
+    Object getService(Class type);
+
+    /**
+     * Gets the all of running service registered with the kernel that are an instances of the specified type.  If no
+     * running services are instances of the specified type, an empty list is returned
+     *
+     * @param type the of the desired service
+     * @return the registered services that are instances of the specified type and are running 
+     */
+    List getServices(Class type);
+
+    /**
      * Gets the service factory registered under the specified name.
      *
      * @param serviceName the unique name of the service
@@ -273,6 +293,24 @@
     ServiceFactory getServiceFactory(ServiceName serviceName) throws ServiceNotFoundException;
 
     /**
+     * Gets the first service factory registered with the kernel that creates an instance of the specified type.
+     * If no service factories create an instance of the specified type, null is returned.
+     *
+     * @param type the of the desired service
+     * @return the first service factory registered with the kernel that creates an instance of the specified type
+     */
+    ServiceFactory getServiceFactory(Class type);
+
+    /**
+     * Gets the all of the service factories registered with the kernel that create an instances of the specified type.
+     * If no service factories create an instance of the specified type, an empty list is returned.
+     *
+     * @param type the of the desired service
+     * @return the registered services that are instances of the specified type and are running
+     */
+    List getServiceFactories(Class type);
+
+    /**
      * Gets the class loader associated with the specifed service.
      *
      * @param serviceName the unique name of the service
diff --git a/kernel/src/java/org/gbean/kernel/KernelAlreadyExistsException.java b/kernel/src/java/org/gbean/kernel/KernelAlreadyExistsException.java
index d2c9942..ab13dac 100644
--- a/kernel/src/java/org/gbean/kernel/KernelAlreadyExistsException.java
+++ b/kernel/src/java/org/gbean/kernel/KernelAlreadyExistsException.java
@@ -33,6 +33,7 @@
      */
     public KernelAlreadyExistsException(String name) {
         super("A kernel is already registered with the name " + name);
+        if (name == null) throw new NullPointerException("name is null");
         this.name = name;
     }
 
diff --git a/kernel/src/java/org/gbean/kernel/KernelFactory.java b/kernel/src/java/org/gbean/kernel/KernelFactory.java
index c0649e4..f3a2220 100644
--- a/kernel/src/java/org/gbean/kernel/KernelFactory.java
+++ b/kernel/src/java/org/gbean/kernel/KernelFactory.java
@@ -51,6 +51,7 @@
      * @return the kernel or null if no kernel is registered under the specified name
      */
     public static Kernel getKernel(String name) {
+        if (name == null) throw new NullPointerException("name is null");
         return (Kernel) kernels.get(name);
     }
 
@@ -139,6 +140,8 @@
      * @throws KernelAlreadyExistsException is a kernel already exists with the specified name
      */
     public final Kernel createKernel(String name) throws KernelAlreadyExistsException {
+        if (name == null) throw new NullPointerException("name is null");
+
         // quick check to see if a kernel already registerd wit the name
         if (kernels.containsKey(name)) {
             throw new KernelAlreadyExistsException(name);
diff --git a/kernel/src/java/org/gbean/kernel/KernelOperationInterruptedException.java b/kernel/src/java/org/gbean/kernel/KernelOperationInterruptedException.java
index 8e0bea9..32b8189 100644
--- a/kernel/src/java/org/gbean/kernel/KernelOperationInterruptedException.java
+++ b/kernel/src/java/org/gbean/kernel/KernelOperationInterruptedException.java
@@ -37,6 +37,8 @@
      */
     public KernelOperationInterruptedException(InterruptedException cause, ServiceName serviceName, String operationName) {
         super(cause);
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
+        if (operationName == null) throw new NullPointerException("operationName is null");
         this.serviceName = serviceName;
         this.operationName = operationName;
     }
@@ -51,6 +53,8 @@
      */
     public KernelOperationInterruptedException(String message, InterruptedException cause, ServiceName serviceName, String operationName) {
         super(message, cause);
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
+        if (operationName == null) throw new NullPointerException("operationName is null");
         this.serviceName = serviceName;
         this.operationName = operationName;
     }
diff --git a/kernel/src/java/org/gbean/kernel/KernelOperationTimoutException.java b/kernel/src/java/org/gbean/kernel/KernelOperationTimoutException.java
index e8921fa..629e290 100644
--- a/kernel/src/java/org/gbean/kernel/KernelOperationTimoutException.java
+++ b/kernel/src/java/org/gbean/kernel/KernelOperationTimoutException.java
@@ -36,6 +36,8 @@
      */
     public KernelOperationTimoutException(ServiceName serviceName, String operationName) {
         super("Kernel operation timed out: serviceName=" + serviceName + ", operationName=" + operationName);
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
+        if (operationName == null) throw new NullPointerException("operationName is null");
         this.serviceName = serviceName;
         this.operationName = operationName;
     }
@@ -49,6 +51,8 @@
      */
     public KernelOperationTimoutException(String message, ServiceName serviceName, String operationName) {
         super(message);
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
+        if (operationName == null) throw new NullPointerException("operationName is null");
         this.serviceName = serviceName;
         this.operationName = operationName;
     }
diff --git a/kernel/src/java/org/gbean/kernel/RunningServiceCondition.java b/kernel/src/java/org/gbean/kernel/RunningServiceCondition.java
index 81b1e45..f5cdca7 100644
--- a/kernel/src/java/org/gbean/kernel/RunningServiceCondition.java
+++ b/kernel/src/java/org/gbean/kernel/RunningServiceCondition.java
@@ -41,6 +41,7 @@
      * @param stopOnServiceShutdown if the our service should be stopped when the specified service shutsdown
      */
     public RunningServiceCondition(ServiceName dependency, boolean ownedRelationship, boolean stopOnServiceShutdown) {
+        if (dependency == null) throw new NullPointerException("dependency is null");
         this.dependency = dependency;
         this.ownedRelationship = ownedRelationship;
         this.stopOnServiceShutdown = stopOnServiceShutdown;
diff --git a/kernel/src/java/org/gbean/kernel/ServiceAlreadyExistsException.java b/kernel/src/java/org/gbean/kernel/ServiceAlreadyExistsException.java
index adf55ce..d20cf01 100644
--- a/kernel/src/java/org/gbean/kernel/ServiceAlreadyExistsException.java
+++ b/kernel/src/java/org/gbean/kernel/ServiceAlreadyExistsException.java
@@ -32,6 +32,7 @@
      * @param serviceName the name of the service that already exists
      */
     public ServiceAlreadyExistsException(ServiceName serviceName) {
+        if (serviceName == null) throw new NullPointerException("name is null");
         this.serviceName = serviceName;
     }
 
diff --git a/kernel/src/java/org/gbean/kernel/ServiceEvent.java b/kernel/src/java/org/gbean/kernel/ServiceEvent.java
index 8820bee..93a098a 100644
--- a/kernel/src/java/org/gbean/kernel/ServiceEvent.java
+++ b/kernel/src/java/org/gbean/kernel/ServiceEvent.java
@@ -16,7 +16,6 @@
  */
 package org.gbean.kernel;
 
-import java.util.Collections;
 import java.util.Set;
 
 /**
@@ -45,59 +44,24 @@
      * @param serviceFactory the factory for the service
      * @param classLoader the class loader for the service
      * @param service the service instance if it exists
+     * @param cause the exception that caused the event if this is an exception event
+     * @param unsatisfiedConditions the unsatified conditions that caused the event if this is a waiting event
      */
-    public ServiceEvent(long eventId, Kernel kernel, ServiceName serviceName, ServiceFactory serviceFactory, ClassLoader classLoader, Object service) {
+    public ServiceEvent(long eventId, Kernel kernel, ServiceName serviceName, ServiceFactory serviceFactory, ClassLoader classLoader, Object service, Throwable cause, Set unsatisfiedConditions) {
+        if (kernel == null) throw new NullPointerException("kernel is null");
+        if (serviceName == null) throw new NullPointerException("name is null");
+        if (serviceFactory == null) throw new NullPointerException("serviceFactory is null");
+        if (classLoader == null) throw new NullPointerException("classLoader is null");
+        if (unsatisfiedConditions != null && cause != null) throw new IllegalArgumentException("Either unsatisfiedConditions or cause must be null");
+        if (cause != null && service != null) throw new IllegalArgumentException("A ServiceEvent can not carry both a cause and a service");
         this.eventId = eventId;
         this.kernel = kernel;
         this.serviceName = serviceName;
         this.serviceFactory = serviceFactory;
         this.classLoader = classLoader;
         this.service = service;
-        cause = null;
-        unsatisfiedConditions = null;
-    }
-
-    /**
-     * Creates an error service event.
-     *
-     * @param eventId the sequence number for this event
-     * @param kernel the kernel in which the service is registered
-     * @param serviceName the name of the service
-     * @param serviceFactory the factory for the service
-     * @param classLoader the class loader for the service
-     * @param cause the error that occured
-     */
-    public ServiceEvent(long eventId, Kernel kernel, ServiceName serviceName, ServiceFactory serviceFactory, ClassLoader classLoader, Throwable cause) {
-        this.eventId = eventId;
-        this.kernel = kernel;
-        this.serviceName = serviceName;
-        this.serviceFactory = serviceFactory;
-        this.classLoader = classLoader;
         this.cause = cause;
-        service = null;
-        unsatisfiedConditions = null;
-    }
-
-    /**
-     * Creates a waiting service event.
-     *
-     * @param eventId the sequence number for this event
-     * @param kernel the kernel in which the service is registered
-     * @param serviceName the name of the service
-     * @param serviceFactory the factory for the service
-     * @param classLoader the class loader for the service
-     * @param service the service instance if it exists
-     * @param unsatisfiedConditions the unsatified conditions
-     */
-    public ServiceEvent(long eventId, Kernel kernel, ServiceName serviceName, ServiceFactory serviceFactory, ClassLoader classLoader, Object service, Set unsatisfiedConditions) {
-        this.eventId = eventId;
-        this.kernel = kernel;
-        this.serviceName = serviceName;
-        this.serviceFactory = serviceFactory;
-        this.classLoader = classLoader;
-        this.service = service;
-        this.unsatisfiedConditions = Collections.unmodifiableSet(unsatisfiedConditions);
-        cause = null;
+        this.unsatisfiedConditions = unsatisfiedConditions;
     }
 
     /**
diff --git a/kernel/src/java/org/gbean/kernel/ServiceFactory.java b/kernel/src/java/org/gbean/kernel/ServiceFactory.java
index f86a87a..ee27b7c 100644
--- a/kernel/src/java/org/gbean/kernel/ServiceFactory.java
+++ b/kernel/src/java/org/gbean/kernel/ServiceFactory.java
@@ -28,6 +28,16 @@
  */
 public interface ServiceFactory {
     /**
+     * Gets the types of the service this service factory will create.  These types is used to index the service within
+     * the kernel.  It is a start error to return an object from create service that is not an instance of every type.
+     * This is the only type used to index the service, so if the service factory returns a subclass of this type from
+     * createService, the subtypes will now be reflected in the index.
+     *
+     * @return the type of the service this service factory will create
+     */
+    Class[] getTypes();
+
+    /**
      * A restartable service can be started and stopped repeatedly in the kernel.  A service that is not restartable
      * immediately enters the RUNNING state when registered with the kernel, and can not be started or stopped.
      *
diff --git a/kernel/src/java/org/gbean/kernel/ServiceNotFoundException.java b/kernel/src/java/org/gbean/kernel/ServiceNotFoundException.java
index 483bd4b..6d35361 100644
--- a/kernel/src/java/org/gbean/kernel/ServiceNotFoundException.java
+++ b/kernel/src/java/org/gbean/kernel/ServiceNotFoundException.java
@@ -32,6 +32,7 @@
      * @param serviceName the name of the service that was not found.
      */
     public ServiceNotFoundException(ServiceName serviceName) {
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
         this.serviceName = serviceName;
     }
 
diff --git a/kernel/src/java/org/gbean/kernel/ServiceRegistrationException.java b/kernel/src/java/org/gbean/kernel/ServiceRegistrationException.java
index df9dac5..f4e33fa 100644
--- a/kernel/src/java/org/gbean/kernel/ServiceRegistrationException.java
+++ b/kernel/src/java/org/gbean/kernel/ServiceRegistrationException.java
@@ -34,6 +34,7 @@
      */
     public ServiceRegistrationException(ServiceName serviceName, Throwable cause) {
         super(cause);
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
         this.serviceName = serviceName;
     }
 
diff --git a/kernel/src/java/org/gbean/kernel/ServiceState.java b/kernel/src/java/org/gbean/kernel/ServiceState.java
index d7d3046..8b32436 100644
--- a/kernel/src/java/org/gbean/kernel/ServiceState.java
+++ b/kernel/src/java/org/gbean/kernel/ServiceState.java
@@ -59,8 +59,10 @@
     static {
         for (int i = 0; i < serviceStateIndex.length; i++) {
             ServiceState serviceState = serviceStateIndex[i];
-            assert serviceState.getIndex() != i : serviceState + " state index is " + serviceState.getIndex() +
-                    ", but is located at index " + i + " in the serviceStateIndex";
+            if (serviceState.getIndex() != i) {
+                throw new AssertionError(serviceState + " state index is " + serviceState.getIndex() +
+                    ", but is located at index " + i + " in the serviceStateIndex");
+            }
         }
     }
 
@@ -86,6 +88,7 @@
      * @throws IllegalArgumentException if the state index is not STARTING, RUNNING, STOPPING or FAILED
      */
     public static ServiceState parseServiceState(String state) {
+        if (state == null) throw new NullPointerException("state is null");
         if (STARTING.toString().equalsIgnoreCase(state)) {
             return STARTING;
         } else if (RUNNING.toString().equalsIgnoreCase(state)) {
diff --git a/kernel/src/java/org/gbean/kernel/StaticServiceFactory.java b/kernel/src/java/org/gbean/kernel/StaticServiceFactory.java
index 57f1ee3..b81959a 100644
--- a/kernel/src/java/org/gbean/kernel/StaticServiceFactory.java
+++ b/kernel/src/java/org/gbean/kernel/StaticServiceFactory.java
@@ -45,6 +45,10 @@
         this.service = service;
     }
 
+    public Class[] getTypes() {
+        return new Class[]{service.getClass()};
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/kernel/src/java/org/gbean/kernel/StoppedServiceCondition.java b/kernel/src/java/org/gbean/kernel/StoppedServiceCondition.java
index 8fb6c05..b7cd567 100644
--- a/kernel/src/java/org/gbean/kernel/StoppedServiceCondition.java
+++ b/kernel/src/java/org/gbean/kernel/StoppedServiceCondition.java
@@ -36,6 +36,7 @@
      * @param dependency the service that must be stopped
      */
     public StoppedServiceCondition(ServiceName dependency) {
+        if (dependency == null) throw new NullPointerException("dependency is null");
         this.dependency = dependency;
     }
 
diff --git a/kernel/src/java/org/gbean/kernel/StringServiceName.java b/kernel/src/java/org/gbean/kernel/StringServiceName.java
index 082fde5..26c533a 100644
--- a/kernel/src/java/org/gbean/kernel/StringServiceName.java
+++ b/kernel/src/java/org/gbean/kernel/StringServiceName.java
@@ -36,6 +36,7 @@
      */
     public StringServiceName(String name) {
         if (name == null) throw new NullPointerException("name is null");
+        if (name.length() == 0) throw new IllegalArgumentException("name must be atleast one character long");
         this.name = name;
     }
 
diff --git a/kernel/src/java/org/gbean/kernel/UnregisterServiceException.java b/kernel/src/java/org/gbean/kernel/UnregisterServiceException.java
index 54d629d..1d19226 100644
--- a/kernel/src/java/org/gbean/kernel/UnregisterServiceException.java
+++ b/kernel/src/java/org/gbean/kernel/UnregisterServiceException.java
@@ -35,6 +35,7 @@
      */
     public UnregisterServiceException(ServiceName serviceName, Throwable cause) {
         super(cause);
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
         this.serviceName = serviceName;
     }
 
diff --git a/kernel/src/java/org/gbean/kernel/UnsatisfiedConditionsException.java b/kernel/src/java/org/gbean/kernel/UnsatisfiedConditionsException.java
index ddb997f..8813e14 100644
--- a/kernel/src/java/org/gbean/kernel/UnsatisfiedConditionsException.java
+++ b/kernel/src/java/org/gbean/kernel/UnsatisfiedConditionsException.java
@@ -40,6 +40,8 @@
      */
     public UnsatisfiedConditionsException(String message, ServiceName serviceName, Set unsatisfiedConditions) {
         super(message + ": serviceName=" + serviceName + ": unsatisfiedConditions=" + unsatisfiedConditions);
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
+        if (unsatisfiedConditions == null) throw new NullPointerException("unsatisfiedConditions is null");
         this.serviceName = serviceName;
         this.unsatisfiedConditions = Collections.unmodifiableSet(unsatisfiedConditions);
     }
diff --git a/kernel/src/java/org/gbean/kernel/standard/AggregateCondition.java b/kernel/src/java/org/gbean/kernel/standard/AggregateCondition.java
index 7b4b66a..993eb2a 100644
--- a/kernel/src/java/org/gbean/kernel/standard/AggregateCondition.java
+++ b/kernel/src/java/org/gbean/kernel/standard/AggregateCondition.java
@@ -63,6 +63,7 @@
         satisfiedSignal = lock.newCondition();
 
         // add the conditions to the registry
+        if (conditions == null) throw new NullPointerException("conditions is null");
         for (Iterator iterator = conditions.iterator(); iterator.hasNext();) {
             ServiceCondition serviceCondition = (ServiceCondition) iterator.next();
             addCondition(serviceCondition);
diff --git a/kernel/src/java/org/gbean/kernel/KernelMonitorBroadcaster.java b/kernel/src/java/org/gbean/kernel/standard/KernelMonitorBroadcaster.java
similarity index 92%
rename from kernel/src/java/org/gbean/kernel/KernelMonitorBroadcaster.java
rename to kernel/src/java/org/gbean/kernel/standard/KernelMonitorBroadcaster.java
index 0f7177c..7cab5bb 100644
--- a/kernel/src/java/org/gbean/kernel/KernelMonitorBroadcaster.java
+++ b/kernel/src/java/org/gbean/kernel/standard/KernelMonitorBroadcaster.java
@@ -14,13 +14,17 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package org.gbean.kernel;
+package org.gbean.kernel.standard;
 
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 
 import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArrayList;
+import org.gbean.kernel.KernelMonitor;
+import org.gbean.kernel.ServiceMonitor;
+import org.gbean.kernel.ServiceEvent;
+import org.gbean.kernel.KernelErrorsError;
 
 /**
  * The KernelMonitorBroadcaster broadcasts kernel events to registered kernel monitors.
diff --git a/kernel/src/java/org/gbean/kernel/standard/RegistryFutureTask.java b/kernel/src/java/org/gbean/kernel/standard/RegistryFutureTask.java
new file mode 100644
index 0000000..7a1883e
--- /dev/null
+++ b/kernel/src/java/org/gbean/kernel/standard/RegistryFutureTask.java
@@ -0,0 +1,142 @@
+/**
+ *
+ * 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 edu.emory.mathcs.backport.java.util.concurrent.Callable;
+import edu.emory.mathcs.backport.java.util.concurrent.FutureTask;
+import org.gbean.kernel.ServiceName;
+import org.gbean.kernel.StopStrategy;
+
+/**
+ * RegistryFutureTask preforms service registration and unregistration in a FutureTask.
+ * @author Dain Sundstrom
+ * @version $Id$
+ * @since 1.0
+ */
+class RegistryFutureTask extends FutureTask implements Comparable {
+    private final long serviceId;
+    private final ServiceName serviceName;
+    private final String taskType;
+    private Throwable throwable;
+
+    static RegistryFutureTask createRegisterTask(ServiceManager serviceManager) {
+        RegisterCallable registerCallable = new RegisterCallable(serviceManager);
+        RegistryFutureTask registryFutureTask = new RegistryFutureTask(serviceManager.getServiceId(),
+                        serviceManager.getServiceName(),
+                        "RegisterServiceManager",
+                        registerCallable);
+        return registryFutureTask;
+    }
+
+    static RegistryFutureTask createUnregisterTask(ServiceManager serviceManager, StopStrategy stopStrategy) {
+        UnregisterCallable unregisterCallable = new UnregisterCallable(serviceManager, stopStrategy);
+        RegistryFutureTask registryFutureTask = new RegistryFutureTask(serviceManager.getServiceId(),
+                        serviceManager.getServiceName(),
+                        "UnregisterServiceManager",
+                        unregisterCallable);
+        unregisterCallable.setRegistryFutureTask(registryFutureTask);
+        return registryFutureTask;
+    }
+
+    private RegistryFutureTask(long serviceId, ServiceName serviceName, String taskType, Callable callable) {
+        super(callable);
+        this.serviceId = serviceId;
+        this.serviceName = serviceName;
+        this.taskType = taskType;
+    }
+
+    public ServiceName getServiceName() {
+        return serviceName;
+    }
+
+    public synchronized Throwable getThrowable() {
+        return throwable;
+    }
+
+    private synchronized void setThrowable(Throwable throwable) {
+        this.throwable = throwable;
+    }
+
+    public int hashCode() {
+        return (int) (serviceId ^ (serviceId >>> 32));
+    }
+
+    public boolean equals(Object o) {
+        if (o instanceof RegistryFutureTask) {
+            return serviceId == ((RegistryFutureTask) o).serviceId;
+        }
+        return false;
+    }
+
+    public int compareTo(Object o) {
+        RegistryFutureTask registryFutureTask = (RegistryFutureTask) o;
+
+        if (serviceId < registryFutureTask.serviceId) {
+            return -1;
+        } else if (serviceId > registryFutureTask.serviceId) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    public String toString() {
+        return "[RegistryFutureTask: task=" + taskType + ", serviceName=" + serviceName + "]";
+    }
+
+
+    private static class RegisterCallable implements Callable {
+        private final ServiceManager serviceManager;
+
+        private RegisterCallable(ServiceManager serviceManager) {
+            this.serviceManager = serviceManager;
+        }
+
+        public Object call() throws Exception {
+            serviceManager.initialize();
+            return serviceManager;
+        }
+    }
+
+    private static class UnregisterCallable implements Callable {
+        private final ServiceManager serviceManager;
+        private final StopStrategy stopStrategy;
+        private RegistryFutureTask registryFutureTask;
+
+        private UnregisterCallable(ServiceManager serviceManager, StopStrategy stopStrategy) {
+            this.serviceManager = serviceManager;
+            this.stopStrategy = stopStrategy;
+        }
+
+        public void setRegistryFutureTask(RegistryFutureTask registryFutureTask) {
+            this.registryFutureTask = registryFutureTask;
+        }
+
+        public Object call() {
+            try {
+                serviceManager.destroy(stopStrategy);
+                return null;
+            } catch (Throwable e) {
+                // Destroy failed, save the exception so it can be rethrown from the unregister method
+                registryFutureTask.setThrowable(e);
+
+                // return the service manager so the service remains registered
+                return serviceManager;
+            }
+        }
+    }
+}
diff --git a/kernel/src/java/org/gbean/kernel/standard/ServiceManager.java b/kernel/src/java/org/gbean/kernel/standard/ServiceManager.java
index 6731b37..531b0d7 100644
--- a/kernel/src/java/org/gbean/kernel/standard/ServiceManager.java
+++ b/kernel/src/java/org/gbean/kernel/standard/ServiceManager.java
@@ -19,8 +19,10 @@
 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.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;
@@ -41,6 +43,7 @@
 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
@@ -52,13 +55,18 @@
  * @version $Id$
  * @since 1.0
  */
-public class ServiceManager {
+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;
@@ -69,6 +77,11 @@
     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;
@@ -79,7 +92,7 @@
      * {@link ServiceState#STARTING} and {@link ServiceState#STOPPING} states since events are propagated in a separate
      * thread.
      */
-    private final AsyncServiceMonitor serviceMonitor;
+    private final ServiceMonitor serviceMonitor;
 
     /**
      * The service context given to the service factory.  This contans a reference to the kernel, serviceName and
@@ -146,31 +159,33 @@
      * 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
-     * @param executor the executor that is used to deliver the asynchronous service events to the serviceMonitor
      */
     public ServiceManager(Kernel kernel,
+            long serviceId,
             ServiceName serviceName,
             ServiceFactory serviceFactory,
             ClassLoader classLoader,
             ServiceMonitor serviceMonitor,
-            Executor executor,
             long timeoutDuration,
             TimeUnit timeoutUnits) {
 
         this.kernel = kernel;
+        this.serviceId = serviceId;
         this.serviceName = serviceName;
         this.serviceFactory = serviceFactory;
         this.classLoader = classLoader;
-        this.serviceMonitor = new AsyncServiceMonitor(serviceMonitor, executor);
+        this.serviceMonitor = serviceMonitor;
         this.timeoutDuration = timeoutDuration;
         this.timeoutUnits = timeoutUnits;
         standardServiceContext = new StandardServiceContext(kernel, serviceName, classLoader);
+        serviceTypes = Collections.unmodifiableSet(new LinkedHashSet(Arrays.asList(serviceFactory.getTypes())));
     }
 
     /**
@@ -267,15 +282,32 @@
     }
 
     /**
+     * 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 unque name of this servce 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
@@ -396,6 +428,17 @@
                 // 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;
@@ -453,6 +496,7 @@
 
     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 {
@@ -650,14 +694,41 @@
     }
 
     private ServiceEvent createServiceEvent() {
-        return new ServiceEvent(eventId.getAndIncrement(), kernel, serviceName, serviceFactory, classLoader, service);
+        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, 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, 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 + "]";
     }
 }
diff --git a/kernel/src/java/org/gbean/kernel/standard/ServiceManagerFactory.java b/kernel/src/java/org/gbean/kernel/standard/ServiceManagerFactory.java
index 6d9a37c..1bb00d5 100644
--- a/kernel/src/java/org/gbean/kernel/standard/ServiceManagerFactory.java
+++ b/kernel/src/java/org/gbean/kernel/standard/ServiceManagerFactory.java
@@ -20,7 +20,6 @@
 import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
 import org.gbean.kernel.Kernel;
 import org.gbean.kernel.ServiceFactory;
-import org.gbean.kernel.ServiceMonitorBroadcaster;
 import org.gbean.kernel.ServiceName;
 
 /**
@@ -76,18 +75,19 @@
     /**
      * Creates a ServiceManager.
      *
+     * @param serviceId the id of the service
      * @param serviceName the name of the service
      * @param serviceFactory the factory for the service
      * @param classLoader the classloader for the service
      * @return a new service manager
      */
-    public ServiceManager createServiceManager(ServiceName serviceName, ServiceFactory serviceFactory, ClassLoader classLoader) {
+    public ServiceManager createServiceManager(long serviceId, ServiceName serviceName, ServiceFactory serviceFactory, ClassLoader classLoader) {
         return new ServiceManager(kernel,
+                serviceId,
                 serviceName,
                 serviceFactory,
                 classLoader,
-                serviceMonitor,
-                serviceExecutor,
+                new AsyncServiceMonitor(serviceMonitor, serviceExecutor),
                 timeoutDuration,
                 timeoutUnits);
     }
diff --git a/kernel/src/java/org/gbean/kernel/standard/ServiceManagerRegistry.java b/kernel/src/java/org/gbean/kernel/standard/ServiceManagerRegistry.java
index c65e622..8fdfde2 100644
--- a/kernel/src/java/org/gbean/kernel/standard/ServiceManagerRegistry.java
+++ b/kernel/src/java/org/gbean/kernel/standard/ServiceManagerRegistry.java
@@ -17,15 +17,19 @@
 package org.gbean.kernel.standard;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
 
-import edu.emory.mathcs.backport.java.util.concurrent.Callable;
 import edu.emory.mathcs.backport.java.util.concurrent.ExecutionException;
-import edu.emory.mathcs.backport.java.util.concurrent.Future;
-import edu.emory.mathcs.backport.java.util.concurrent.FutureTask;
+import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicLong;
 import org.gbean.kernel.IllegalServiceStateException;
 import org.gbean.kernel.KernelErrorsError;
 import org.gbean.kernel.KernelOperationInterruptedException;
@@ -47,6 +51,11 @@
  */
 public class ServiceManagerRegistry {
     /**
+     * The sequence used for the serviceId assigned to service managers.
+     */
+    private final AtomicLong serviceId = new AtomicLong(1);
+
+    /**
      * The factory used to create service managers.
      */
     private final ServiceManagerFactory serviceManagerFactory;
@@ -57,6 +66,11 @@
     private final Map serviceManagers = new HashMap();
 
     /**
+     * The service managers indexed by the service type.  This map is populated when a service enters the running state.
+     */
+    private final Map serviceManagersByType = new HashMap();
+
+    /**
      * Creates a ServiceManagerRegistry that uses the specified service manager factory to create new service managers.
      *
      * @param serviceManagerFactory the factory for new service managers
@@ -83,9 +97,9 @@
 
         List managers = new ArrayList(managerFutures.size());
         for (Iterator iterator = managerFutures.iterator(); iterator.hasNext();) {
-            Future future = (Future) iterator.next();
+            RegistryFutureTask registryFutureTask = (RegistryFutureTask) iterator.next();
             try {
-                managers.add(future.get());
+                managers.add(registryFutureTask.get());
             } catch (InterruptedException e) {
                 // ignore -- this should not happen
                 errors.add(new AssertionError(e));
@@ -151,15 +165,15 @@
      * @return true if there is a service registered with the specified name; false otherwise
      */
     public boolean isRegistered(ServiceName serviceName) {
-        assert serviceName != null : "serviceName is null";
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
 
-        Future serviceManagerFuture;
+        RegistryFutureTask registryFutureTask;
         synchronized (serviceManagers) {
-            serviceManagerFuture = (Future) serviceManagers.get(serviceName);
+            registryFutureTask = (RegistryFutureTask) serviceManagers.get(serviceName);
         }
         try {
             // the service is registered if we have a non-null future value
-            return serviceManagerFuture != null && serviceManagerFuture.get() != null;
+            return registryFutureTask != null && registryFutureTask.get() != null;
         } catch (InterruptedException e) {
             throw new KernelOperationInterruptedException(e, serviceName, "isRegistered");
         } catch (ExecutionException e) {
@@ -175,20 +189,20 @@
      * @throws ServiceNotFoundException if there is no service registered under the specified name
      */
     public ServiceManager getServiceManager(ServiceName serviceName) throws ServiceNotFoundException {
-        assert serviceName != null : "serviceName is null";
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
 
-        Future serviceManagerFuture;
+        RegistryFutureTask registryFutureTask;
         synchronized (serviceManagers) {
-            serviceManagerFuture = (Future) serviceManagers.get(serviceName);
+            registryFutureTask = (RegistryFutureTask) serviceManagers.get(serviceName);
         }
 
         // this service has no future
-        if (serviceManagerFuture == null) {
+        if (registryFutureTask == null) {
             throw new ServiceNotFoundException(serviceName);
         }
 
         try {
-            ServiceManager serviceManager = (ServiceManager) serviceManagerFuture.get();
+            ServiceManager serviceManager = (ServiceManager) registryFutureTask.get();
             if (serviceManager == null) {
                 throw new ServiceNotFoundException(serviceName);
             }
@@ -202,6 +216,120 @@
     }
 
     /**
+     * Gets the first registered service manager that creates an instance of the specified type, or null if no service
+     * managers create an instance of the specified type.
+     *
+     * @param type the of the desired service
+     * @return the first registered service manager that creates an instance of the specified type, or null if none found
+     */
+    public ServiceManager getServiceManager(Class type) {
+        SortedSet serviceManagerFutures = getServiceManagerFutures(type);
+        for (Iterator iterator = serviceManagerFutures.iterator(); iterator.hasNext();) {
+            RegistryFutureTask registryFutureTask = (RegistryFutureTask) iterator.next();
+            try {
+                ServiceManager serviceManager = (ServiceManager) registryFutureTask.get();
+                if (serviceManager != null) {
+                    return serviceManager;
+                }
+            } catch (InterruptedException e) {
+                throw new KernelOperationInterruptedException(e, registryFutureTask.getServiceName(), "getServiceManagers(java.lang.Class)");
+            } catch (ExecutionException ignored) {
+                // registration threw an exception which means it didn't register
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets all service managers that create an instances of the specified type, or an empty list if no service
+     * managers create an instance of the specified type.
+     *
+     * @param type the of the desired service managers
+     * @return all service managers that create an instances of the specified type, or an empty list if none found
+     */
+    public List getServiceManagers(Class type) {
+        SortedSet serviceManagerFutures = getServiceManagerFutures(type);
+        List serviceManagers = new ArrayList(serviceManagerFutures.size());
+        for (Iterator iterator = serviceManagerFutures.iterator(); iterator.hasNext();) {
+            RegistryFutureTask registryFutureTask = (RegistryFutureTask) iterator.next();
+            try {
+                ServiceManager serviceManager = (ServiceManager) registryFutureTask.get();
+                if (serviceManager != null) {
+                    serviceManagers.add(serviceManager);
+                }
+            } catch (InterruptedException e) {
+                throw new KernelOperationInterruptedException(e, registryFutureTask.getServiceName(), "getServiceManagers(java.lang.Class)");
+            } catch (ExecutionException ignored) {
+                // registration threw an exception which means it didn't register
+            }
+        }
+        return serviceManagers;
+    }
+
+    /**
+     * Gets the first registed and running service that is an instance of the specified type, or null if no instances
+     * of the specified type are running.
+     *
+     * @param type the of the desired service
+     * @return the first registed and running service that is an instance of the specified type or null if none found
+     */
+    public synchronized Object getService(Class type) {
+        SortedSet serviceManagerFutures = getServiceManagerFutures(type);
+        for (Iterator iterator = serviceManagerFutures.iterator(); iterator.hasNext();) {
+            RegistryFutureTask registryFutureTask = (RegistryFutureTask) iterator.next();
+            try {
+                ServiceManager serviceManager = (ServiceManager) registryFutureTask.get();
+                if (serviceManager != null) {
+                    Object service = serviceManager.getService();
+                    if (service != null) {
+                        return service;
+                    }
+                }
+            } catch (InterruptedException e) {
+                throw new KernelOperationInterruptedException(e, registryFutureTask.getServiceName(), "getService(java.lang.Class)");
+            } catch (ExecutionException ignored) {
+                // registration threw an exception which means it didn't register
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets the all of running service that are an instances of the specified type, or an empty list if no instances
+     * of the specified type are running.
+     *
+     * @param type the of the desired service
+     * @return the all of running service that are an instances of the specified type, or an empty list if none found
+     */
+    public synchronized List getServices(Class type) {
+        List serviceManagers = getServiceManagers(type);
+        List services = new ArrayList(serviceManagers.size());
+        for (Iterator iterator = serviceManagers.iterator(); iterator.hasNext();) {
+            ServiceManager serviceManager = (ServiceManager) iterator.next();
+            if (serviceManager != null) {
+                Object service = serviceManager.getService();
+                if (service != null) {
+                    services.add(service);
+                }
+            }
+        }
+        return services;
+    }
+
+    private SortedSet getServiceManagerFutures(Class type) {
+        SortedSet serviceManagerFutures;
+        synchronized (serviceManagers) {
+            serviceManagerFutures = (SortedSet) serviceManagersByType.get(type);
+            if (serviceManagerFutures != null) {
+                serviceManagerFutures = new TreeSet(serviceManagerFutures);
+            } else {
+                serviceManagerFutures = new TreeSet();
+            }
+        }
+        return serviceManagerFutures;
+    }
+
+    /**
      * Creates a ServiceManager and registers it under the specified name.  If the service is restartable, it will
      * enter the server in the STOPPED state.  If a service is not restartable, the service manager will assure that all
      * dependencies are satisfied and service will immediately enter in the  RUNNING state.  If a
@@ -215,16 +343,16 @@
      * @throws ServiceRegistrationException if the service is not restartable and an error occured while starting the service
      */
     public void registerService(ServiceName serviceName, ServiceFactory serviceFactory, ClassLoader classLoader) throws ServiceAlreadyExistsException, ServiceRegistrationException {
-        assert serviceName != null : "serviceName is null";
-        assert serviceFactory != null : "serviceFactory is null";
-        assert classLoader != null : "classLoader is null";
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
+        if (serviceFactory == null) throw new NullPointerException("serviceFactory is null");
+        if (classLoader == null) throw new NullPointerException("classLoader is null");
 
         if (!serviceFactory.isEnabled()) {
             throw new ServiceRegistrationException(serviceName,
                     new IllegalServiceStateException("A disabled non-restartable service factory can not be registered", serviceName));
         }
 
-        FutureTask registrationTask = null;
+        RegistryFutureTask registrationTask = null;
 
         //
         // This loop will continue until we put our registrationTask in the serviceManagers map.  If at any point,
@@ -232,9 +360,9 @@
         // a ServiceAlreadyExistsException exiting this method.
         //
         while (registrationTask == null) {
-            Future existingRegistration;
+            RegistryFutureTask existingRegistration;
             synchronized (serviceManagers) {
-                existingRegistration = (Future) serviceManagers.get(serviceName);
+                existingRegistration = (RegistryFutureTask) serviceManagers.get(serviceName);
 
                 // if we do not have an existing registration or the existing registration task is complete
                 // we can create the new registration task; otherwise we need to wait for the existing registration to
@@ -256,11 +384,13 @@
 
                     // we are ready to register our serviceManager
                     existingRegistration = null;
-                    registrationTask = new FutureTask(new RegisterServiceManager(serviceManagerFactory,
+                    ServiceManager serviceManager = serviceManagerFactory.createServiceManager(serviceId.getAndIncrement(),
                             serviceName,
                             serviceFactory,
-                            classLoader));
+                            classLoader);
+                    registrationTask = RegistryFutureTask.createRegisterTask(serviceManager);
                     serviceManagers.put(serviceName, registrationTask);
+                    addTypeIndex(serviceManager, registrationTask);
                 }
             }
 
@@ -291,6 +421,7 @@
                 // make sure our task is still the registered one
                 if (serviceManagers.get(serviceName) == registrationTask) {
                     serviceManagers.remove(serviceName);
+                    removeTypeIndex(registrationTask);
                 }
             }
             throw new ServiceRegistrationException(serviceName, e.getCause());
@@ -309,11 +440,10 @@
      * @throws ServiceRegistrationException if the service could not be stopped
      */
     public void unregisterService(ServiceName serviceName, StopStrategy stopStrategy) throws ServiceNotFoundException, ServiceRegistrationException {
-        assert serviceName != null : "serviceName is null";
-        assert stopStrategy != null : "stopStrategy is null";
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
+        if (stopStrategy == null) throw new NullPointerException("stopStrategy is null");
 
-        FutureTask unregistrationTask = null;
-        UnregisterServiceManager unregisterCallable = null;
+        RegistryFutureTask unregistrationTask = null;
 
         //
         // This loop will continue until we put our unregistrationTask in the serviceManagers map.  If at any point,
@@ -321,9 +451,9 @@
         // a ServiceNotFoundException exiting this method.
         //
         while (unregistrationTask == null) {
-            Future existingRegistration;
+            RegistryFutureTask existingRegistration;
             synchronized (serviceManagers) {
-                existingRegistration = (Future) serviceManagers.get(serviceName);
+                existingRegistration = (RegistryFutureTask) serviceManagers.get(serviceName);
                 if (existingRegistration == null) {
                     throw new ServiceNotFoundException(serviceName);
                 }
@@ -346,9 +476,9 @@
 
                     // we are ready to register our serviceManager
                     existingRegistration = null;
-                    unregisterCallable = new UnregisterServiceManager(serviceManager, stopStrategy);
-                    unregistrationTask = new FutureTask(unregisterCallable);
+                    unregistrationTask = RegistryFutureTask.createUnregisterTask(serviceManager, stopStrategy);
                     serviceManagers.put(serviceName, unregistrationTask);
+                    addTypeIndex(serviceManager, unregistrationTask);
                 }
             }
 
@@ -376,12 +506,13 @@
                     // make sure our task is still the registered one
                     if (serviceManagers.get(serviceName) == unregistrationTask) {
                         serviceManagers.remove(serviceName);
+                        removeTypeIndex(unregistrationTask);
                     }
                 }
             } else {
-                synchronized (unregisterCallable) {
+                synchronized (unregistrationTask) {
                     // the root exception is contained in the exception handle
-                    throw new ServiceRegistrationException(serviceName, unregisterCallable.getThrowable());
+                    throw new ServiceRegistrationException(serviceName, unregistrationTask.getThrowable());
                 }
             }
         } catch (InterruptedException e) {
@@ -392,52 +523,70 @@
         }
     }
 
-    private static class RegisterServiceManager implements Callable {
-        private final ServiceManagerFactory serviceManagerFactory;
-        private final ServiceName serviceName;
-        private final ServiceFactory serviceFactory;
-        private final ClassLoader classLoader;
+    private void addTypeIndex(ServiceManager serviceManager, RegistryFutureTask registryFutureTask) {
+        if (serviceManager == null) throw new NullPointerException("serviceManager is null");
+        if (registryFutureTask == null) throw new NullPointerException("serviceManagerFuture is null");
 
-        private RegisterServiceManager(ServiceManagerFactory serviceManagerFactory, ServiceName serviceName, ServiceFactory serviceFactory, ClassLoader classLoader) {
-            this.serviceManagerFactory = serviceManagerFactory;
-            this.serviceName = serviceName;
-            this.serviceFactory = serviceFactory;
-            this.classLoader = classLoader;
+        Set allTypes = new LinkedHashSet();
+        for (Iterator iterator = serviceManager.getServiceTypes().iterator(); iterator.hasNext();) {
+            Class serviceType = (Class) iterator.next();
+
+            if (serviceType.isArray()) {
+                throw new IllegalArgumentException("Service is an array: serviceName=" + serviceManager.getServiceName() +
+                        ", serviceType=" + serviceManager.getServiceTypes());
+            }
+
+            allTypes.add(serviceType);
+            allTypes.addAll(getAllSuperClasses(serviceType));
+            allTypes.addAll(getAllInterfaces(serviceType));
         }
 
-        public Object call() throws Exception {
-            ServiceManager serviceManager = serviceManagerFactory.createServiceManager(serviceName, serviceFactory, classLoader);
-            serviceManager.initialize();
-            return serviceManager;
+        synchronized (serviceManagers) {
+            for (Iterator iterator = allTypes.iterator(); iterator.hasNext();) {
+                Class type = (Class) iterator.next();
+                Set futureServiceManagers = (Set) serviceManagersByType.get(type);
+                if (futureServiceManagers == null) {
+                    futureServiceManagers = new TreeSet();
+                    serviceManagersByType.put(type, futureServiceManagers);
+                }
+                futureServiceManagers.add(registryFutureTask);
+            }
         }
     }
 
-    private static class UnregisterServiceManager implements Callable {
-        private final ServiceManager serviceManager;
-        private final StopStrategy stopStrategy;
-        private Throwable throwable;
-
-        private UnregisterServiceManager(ServiceManager serviceManager, StopStrategy stopStrategy) {
-            this.serviceManager = serviceManager;
-            this.stopStrategy = stopStrategy;
-        }
-
-        public Object call() {
-            try {
-                serviceManager.destroy(stopStrategy);
-                return null;
-            } catch (Throwable e) {
-                // Destroy failed, save the exception so it can be rethrown from the unregister method
-                synchronized (this) {
-                    throwable = e;
+    private void removeTypeIndex(RegistryFutureTask registryFutureTask) {
+        if (registryFutureTask == null) throw new NullPointerException("serviceManagerFuture is null");
+        synchronized (serviceManagers) {
+            for (Iterator iterator = serviceManagersByType.entrySet().iterator(); iterator.hasNext();) {
+                Map.Entry entry = (Map.Entry) iterator.next();
+                Set serviceManagers = (Set) entry.getValue();
+                serviceManagers.remove(registryFutureTask);
+                if (serviceManagers.isEmpty()) {
+                    iterator.remove();
                 }
-                // return the service manager so the service remains registered
-                return serviceManager;
             }
         }
+    }
 
-        private synchronized Throwable getThrowable() {
-            return throwable;
+    private static Set getAllSuperClasses(Class clazz) {
+        Set allSuperClasses = new LinkedHashSet();
+        for (Class superClass = clazz.getSuperclass(); superClass != null; superClass = superClass.getSuperclass()) {
+            allSuperClasses.add(superClass);
         }
+        return allSuperClasses;
+    }
+
+    private static Set getAllInterfaces(Class clazz) {
+        Set allInterfaces = new LinkedHashSet();
+        LinkedList stack = new LinkedList();
+        stack.addAll(Arrays.asList(clazz.getInterfaces()));
+        while (!stack.isEmpty()) {
+            Class intf = (Class) stack.removeFirst();
+            if (!allInterfaces.contains(intf)) {
+                allInterfaces.add(intf);
+                stack.addAll(Arrays.asList(intf.getInterfaces()));
+            }
+        }
+        return allInterfaces;
     }
 }
diff --git a/kernel/src/java/org/gbean/kernel/ServiceMonitorBroadcaster.java b/kernel/src/java/org/gbean/kernel/standard/ServiceMonitorBroadcaster.java
similarity index 95%
rename from kernel/src/java/org/gbean/kernel/ServiceMonitorBroadcaster.java
rename to kernel/src/java/org/gbean/kernel/standard/ServiceMonitorBroadcaster.java
index e40c0ba..05ad634 100644
--- a/kernel/src/java/org/gbean/kernel/ServiceMonitorBroadcaster.java
+++ b/kernel/src/java/org/gbean/kernel/standard/ServiceMonitorBroadcaster.java
@@ -14,7 +14,7 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package org.gbean.kernel;
+package org.gbean.kernel.standard;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -25,6 +25,12 @@
 import java.util.Map;
 import java.util.Set;
 
+import org.gbean.kernel.ServiceMonitor;
+import org.gbean.kernel.KernelMonitor;
+import org.gbean.kernel.ServiceName;
+import org.gbean.kernel.ServiceEvent;
+import org.gbean.kernel.KernelErrorsError;
+
 /**
  * The ServiceMonitorBroadcaster broadcasts kernel events to registered service monitors.
  *
@@ -50,6 +56,7 @@
      * @param kernelMonitor the monitor to notify when an error occurs while notifying the registered service monitors
      */
     public ServiceMonitorBroadcaster(KernelMonitor kernelMonitor) {
+        if (kernelMonitor == null) throw new NullPointerException("kernelMonitor is null");
         this.kernelMonitor = kernelMonitor;
     }
 
@@ -62,6 +69,7 @@
      * @param serviceName the unique name of the service to monitor or null to monitor all services
      */
     public void addServiceMonitor(ServiceMonitor serviceMonitor, ServiceName serviceName) {
+        if (serviceMonitor == null) throw new NullPointerException("serviceMonitor is null");
         synchronized (serviceMonitors) {
             Set monitors = (Set) serviceMonitors.get(serviceName);
             if (monitors == null) {
@@ -78,6 +86,7 @@
      * @param serviceMonitor the service monitor to remove
      */
     public void removeServiceMonitor(ServiceMonitor serviceMonitor) {
+        if (serviceMonitor == null) throw new NullPointerException("serviceMonitor is null");
         synchronized (serviceMonitors) {
             for (Iterator iterator = serviceMonitors.values().iterator(); iterator.hasNext();) {
                 Set monitors = (Set) iterator.next();
diff --git a/kernel/src/java/org/gbean/kernel/standard/StandardKernel.java b/kernel/src/java/org/gbean/kernel/standard/StandardKernel.java
index 93c019e..0073fcd 100644
--- a/kernel/src/java/org/gbean/kernel/standard/StandardKernel.java
+++ b/kernel/src/java/org/gbean/kernel/standard/StandardKernel.java
@@ -16,6 +16,10 @@
  */
 package org.gbean.kernel.standard;
 
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+
 import edu.emory.mathcs.backport.java.util.concurrent.Executor;
 import edu.emory.mathcs.backport.java.util.concurrent.Executors;
 import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
@@ -24,11 +28,9 @@
 import org.gbean.kernel.Kernel;
 import org.gbean.kernel.KernelErrorsError;
 import org.gbean.kernel.KernelMonitor;
-import org.gbean.kernel.KernelMonitorBroadcaster;
 import org.gbean.kernel.ServiceAlreadyExistsException;
 import org.gbean.kernel.ServiceFactory;
 import org.gbean.kernel.ServiceMonitor;
-import org.gbean.kernel.ServiceMonitorBroadcaster;
 import org.gbean.kernel.ServiceName;
 import org.gbean.kernel.ServiceNotFoundException;
 import org.gbean.kernel.ServiceRegistrationException;
@@ -92,6 +94,11 @@
      * @param timeoutUnits the unit of measure for the timeoutDuration
      */
     public StandardKernel(String kernelName, Executor serviceExecutor, long timeoutDuration, TimeUnit timeoutUnits) {
+        if (kernelName == null) throw new NullPointerException("kernelName is null");
+        if (kernelName.length() ==0) throw new IllegalArgumentException("kernelName must be atleast one character long");
+        if (serviceExecutor == null) throw new NullPointerException("serviceExecutor is null");
+        if (timeoutUnits == null) throw new NullPointerException("timeoutUnits is null");
+
         this.kernelName = kernelName;
         serviceManagerFactory = new ServiceManagerFactory(this, serviceMonitor, serviceExecutor, timeoutDuration, timeoutUnits);
         serviceManagerRegistry = new ServiceManagerRegistry(serviceManagerFactory);
@@ -141,6 +148,7 @@
      * {@inheritDoc}
      */
     public void unregisterService(ServiceName serviceName) throws ServiceNotFoundException, ServiceRegistrationException {
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
         unregisterService(serviceName, StopStrategies.SYNCHRONOUS);
     }
 
@@ -173,6 +181,7 @@
      * {@inheritDoc}
      */
     public ServiceState getServiceState(ServiceName serviceName) throws ServiceNotFoundException {
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
         ServiceManager serviceManager = getServiceManager(serviceName);
         return serviceManager.getState();
     }
@@ -181,6 +190,7 @@
      * {@inheritDoc}
      */
     public long getServiceStartTime(ServiceName serviceName) throws ServiceNotFoundException {
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
         ServiceManager serviceManager = getServiceManager(serviceName);
         return serviceManager.getStartTime();
     }
@@ -189,6 +199,7 @@
      * {@inheritDoc}
      */
     public void startService(ServiceName serviceName) throws ServiceNotFoundException, IllegalServiceStateException, UnsatisfiedConditionsException, Exception {
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
         startService(serviceName, false, StartStrategies.SYNCHRONOUS);
     }
 
@@ -196,6 +207,8 @@
      * {@inheritDoc}
      */
     public void startService(ServiceName serviceName, StartStrategy startStrategy) throws ServiceNotFoundException, IllegalServiceStateException, UnsatisfiedConditionsException, Exception {
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
+        if (startStrategy == null) throw new NullPointerException("startStrategy is null");
         startService(serviceName, false, startStrategy);
     }
 
@@ -203,6 +216,7 @@
      * {@inheritDoc}
      */
     public void startServiceRecursive(ServiceName serviceName) throws ServiceNotFoundException, IllegalServiceStateException, UnsatisfiedConditionsException, Exception {
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
         startService(serviceName, true, StartStrategies.SYNCHRONOUS);
     }
 
@@ -210,6 +224,8 @@
      * {@inheritDoc}
      */
     public void startServiceRecursive(ServiceName serviceName, StartStrategy startStrategy) throws ServiceNotFoundException, IllegalServiceStateException, UnsatisfiedConditionsException, Exception {
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
+        if (startStrategy == null) throw new NullPointerException("startStrategy is null");
         startService(serviceName, true, startStrategy);
     }
 
@@ -241,6 +257,7 @@
      * {@inheritDoc}
      */
     public void stopService(ServiceName serviceName) throws ServiceNotFoundException, UnsatisfiedConditionsException {
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
         stopService(serviceName, StopStrategies.SYNCHRONOUS);
     }
 
@@ -248,6 +265,7 @@
      * {@inheritDoc}
      */
     public void stopService(ServiceName serviceName, StopStrategy stopStrategy) throws ServiceNotFoundException, UnsatisfiedConditionsException {
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
         if (stopStrategy == null) throw new NullPointerException("stopStrategy is null");
         ServiceManager serviceManager = getServiceManager(serviceName);
         serviceManager.stop(stopStrategy);
@@ -257,6 +275,7 @@
      * {@inheritDoc}
      */
     public boolean isServiceEnabled(ServiceName serviceName) throws ServiceNotFoundException {
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
         ServiceManager serviceManager = getServiceManager(serviceName);
         ServiceFactory serviceFactory = serviceManager.getServiceFactory();
         return serviceFactory.isEnabled();
@@ -266,6 +285,7 @@
      * {@inheritDoc}
      */
     public void setServiceEnabled(ServiceName serviceName, boolean enabled) throws ServiceNotFoundException {
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
         ServiceManager serviceManager = getServiceManager(serviceName);
         ServiceFactory serviceFactory = serviceManager.getServiceFactory();
         serviceFactory.setEnabled(enabled);
@@ -275,6 +295,7 @@
      * {@inheritDoc}
      */
     public Object getService(ServiceName serviceName) throws ServiceNotFoundException, IllegalArgumentException {
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
         ServiceManager serviceManager = getServiceManager(serviceName);
         return serviceManager.getService();
     }
@@ -282,7 +303,34 @@
     /**
      * {@inheritDoc}
      */
+    public Object getService(Class type) {
+        if (type == null) throw new NullPointerException("type is null");
+        if (!isRunning()) {
+            return null;
+        }
+
+        Object service = serviceManagerRegistry.getService(type);
+        return service;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List getServices(Class type) {
+        if (type == null) throw new NullPointerException("type is null");
+        if (!isRunning()) {
+            return null;
+        }
+
+        List services = serviceManagerRegistry.getServices(type);
+        return services;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public ServiceFactory getServiceFactory(ServiceName serviceName) throws ServiceNotFoundException {
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
         ServiceManager serviceManager = getServiceManager(serviceName);
         return serviceManager.getServiceFactory();
     }
@@ -290,13 +338,44 @@
     /**
      * {@inheritDoc}
      */
+    public ServiceFactory getServiceFactory(Class type) {
+        if (type == null) throw new NullPointerException("type is null");
+        if (!isRunning()) {
+            return null;
+        }
+
+        ServiceManager serviceManager = serviceManagerRegistry.getServiceManager(type);
+        return serviceManager.getServiceFactory();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List getServiceFactories(Class type) {
+        if (type == null) throw new NullPointerException("type is null");
+        if (!isRunning()) {
+            return null;
+        }
+
+        List serviceManagers = serviceManagerRegistry.getServiceManagers(type);
+        List serviceFactories = new ArrayList(serviceManagers.size());
+        for (Iterator iterator = serviceManagers.iterator(); iterator.hasNext();) {
+            ServiceManager serviceManager = (ServiceManager) iterator.next();
+            serviceFactories.add(serviceManager.getServiceFactory());
+        }
+        return serviceFactories;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public ClassLoader getClassLoaderFor(ServiceName serviceName) throws ServiceNotFoundException {
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
         ServiceManager serviceManager = getServiceManager(serviceName);
         return serviceManager.getClassLoader();
     }
 
     private ServiceManager getServiceManager(ServiceName serviceName) throws ServiceNotFoundException {
-        if (serviceName == null) throw new NullPointerException("serviceName is null");
         if (!isRunning()) {
             throw new ServiceNotFoundException(serviceName);
         }
@@ -310,7 +389,9 @@
      */
     public void addKernelMonitor(KernelMonitor kernelMonitor) {
         if (kernelMonitor == null) throw new NullPointerException("kernelMonitor is null");
-        checkKernelState();
+        if (!running.get()) {
+            throw new IllegalStateException("Kernel is stopped");
+        }
         this.kernelMonitor.addKernelMonitor(kernelMonitor);
     }
 
@@ -327,7 +408,9 @@
      */
     public void addServiceMonitor(ServiceMonitor serviceMonitor) {
         if (serviceMonitor == null) throw new NullPointerException("serviceMonitor is null");
-        checkKernelState();
+        if (!running.get()) {
+            throw new IllegalStateException("Kernel is stopped");
+        }
         addServiceMonitor(serviceMonitor, null);
     }
 
@@ -336,8 +419,11 @@
      */
     public void addServiceMonitor(ServiceMonitor serviceMonitor, ServiceName serviceName) {
         if (serviceMonitor == null) throw new NullPointerException("serviceMonitor is null");
-        checkKernelState();
-        this.serviceMonitor.removeServiceMonitor(serviceMonitor);
+        if (serviceName == null) throw new NullPointerException("serviceName is null");
+        if (!running.get()) {
+            throw new IllegalStateException("Kernel is stopped");
+        }
+        this.serviceMonitor.addServiceMonitor(serviceMonitor, serviceName);
     }
 
     /**
@@ -347,10 +433,4 @@
         if (serviceMonitor == null) throw new NullPointerException("serviceMonitor is null");
         this.serviceMonitor.removeServiceMonitor(serviceMonitor);
     }
-
-    private void checkKernelState() {
-        if (!running.get()) {
-            throw new IllegalStateException("Kernel is stopped");
-        }
-    }
 }
diff --git a/kernel/src/java/org/gbean/kernel/standard/StandardKernelFactory.java b/kernel/src/java/org/gbean/kernel/standard/StandardKernelFactory.java
index 6749d70..7408b16 100644
--- a/kernel/src/java/org/gbean/kernel/standard/StandardKernelFactory.java
+++ b/kernel/src/java/org/gbean/kernel/standard/StandardKernelFactory.java
@@ -31,6 +31,7 @@
      * {@inheritDoc}
      */
     protected Kernel createKernelInternal(String name) {
+        if (name == null) throw new NullPointerException("name is null");
         return new StandardKernel(name);
     }
 }
diff --git a/kernel/src/test/org/gbean/kernel/KernelMonitorBroadcasterTest.java b/kernel/src/test/org/gbean/kernel/KernelMonitorBroadcasterTest.java
index 942998a..e42e0c8 100644
--- a/kernel/src/test/org/gbean/kernel/KernelMonitorBroadcasterTest.java
+++ b/kernel/src/test/org/gbean/kernel/KernelMonitorBroadcasterTest.java
@@ -18,6 +18,7 @@
 
 import junit.framework.TestCase;
 import org.gbean.kernel.standard.StandardKernel;
+import org.gbean.kernel.standard.KernelMonitorBroadcaster;
 
 /**
  * Tests the KernelMonitorBroadcaster.
@@ -32,7 +33,10 @@
             new StandardKernel("test"),
             new StringServiceName("service-name"),
             new StaticServiceFactory(new Object()),
-            ClassLoader.getSystemClassLoader(), null);
+            ClassLoader.getSystemClassLoader(), 
+            null,
+            null,
+            null);
     private static final Throwable THROWABLE = new Throwable("test throwable");
 
     private static final int MONITOR_COUNT = 4;
diff --git a/kernel/src/test/org/gbean/kernel/ServiceStateTest.java b/kernel/src/test/org/gbean/kernel/ServiceStateTest.java
index 3c48545..54917ae 100644
--- a/kernel/src/test/org/gbean/kernel/ServiceStateTest.java
+++ b/kernel/src/test/org/gbean/kernel/ServiceStateTest.java
@@ -21,11 +21,15 @@
 import junit.framework.TestCase;
 
 /**
+ * Tests that the ServiceState constants are consistent.
  * @author Dain Sundstrom
  * @version $Id$
  * @since 1.0
  */
 public class ServiceStateTest extends TestCase {
+    /**
+     * Tests that the constances are .equals to them selves and no other constants.
+     */
     public void testEquals() {
         assertTrue(ServiceState.STARTING.equals(ServiceState.STARTING));
         assertFalse(ServiceState.STARTING.equals(ServiceState.RUNNING));
@@ -48,6 +52,9 @@
         assertTrue(ServiceState.STOPPED.equals(ServiceState.STOPPED));
     }
 
+    /**
+     * Tests that the constants create a hashCode that is equal to their own hashCode an no other constants hashCode.
+     */
     public void testHashCode() {
         assertTrue(ServiceState.STARTING.hashCode() == ServiceState.STARTING.hashCode());
         assertFalse(ServiceState.STARTING.hashCode() == ServiceState.RUNNING.hashCode());
@@ -70,6 +77,10 @@
         assertTrue(ServiceState.STOPPED.hashCode() == ServiceState.STOPPED.hashCode());
     }
 
+    /**
+     * Tests that getServiceState returns the same constant as the constant from which the index was gotten, and that
+     * getServiceState throws an exception if an attempt is make to get an unknown constant.
+     */
     public void testGetServiceState() {
         assertSame(ServiceState.STARTING, ServiceState.getServiceState(ServiceState.STARTING.getIndex()));
         assertSame(ServiceState.RUNNING, ServiceState.getServiceState(ServiceState.RUNNING.getIndex()));
@@ -91,7 +102,11 @@
         }
     }
 
-    public void testFromString() {
+    /**
+     * Tests that parseServiceState returns the same state when called with getName on a state, that it throws an exception
+     * for an unknown state and that the parsing is done using a case insensitive match.
+     */
+    public void testParseServiceState() {
         assertSame(ServiceState.STARTING, ServiceState.parseServiceState(ServiceState.STARTING.getName()));
         assertSame(ServiceState.RUNNING, ServiceState.parseServiceState(ServiceState.RUNNING.getName()));
         assertSame(ServiceState.STOPPING, ServiceState.parseServiceState(ServiceState.STOPPING.getName()));
@@ -110,6 +125,11 @@
         assertSame(ServiceState.STOPPED, ServiceState.parseServiceState("StoppeD"));
     }
 
+    /**
+     * Tests that when a state is serialized an deserialized it returns the same state object.  This test shoudl assure
+     * that there is only one state instance with a specific index in existant at one time.
+     * @throws Exception if a problem occurs
+     */
     public void testSerialization() throws Exception {
         assertSame(ServiceState.STARTING, copyServiceState(ServiceState.STARTING));
         assertSame(ServiceState.RUNNING, copyServiceState(ServiceState.RUNNING));
diff --git a/kernel/src/test/org/gbean/kernel/StaticServiceFactoryTest.java b/kernel/src/test/org/gbean/kernel/StaticServiceFactoryTest.java
index b789759..6dac9d7 100644
--- a/kernel/src/test/org/gbean/kernel/StaticServiceFactoryTest.java
+++ b/kernel/src/test/org/gbean/kernel/StaticServiceFactoryTest.java
@@ -17,19 +17,25 @@
 package org.gbean.kernel;
 
 import java.util.Set;
+import java.util.TreeSet;
 
 import junit.framework.TestCase;
 
 /**
+ * Tests the StaticServiceFactory.
  * @author Dain Sundstrom
  * @version $Id$
  * @since 1.0
  */
 public class StaticServiceFactoryTest extends TestCase {
-    private static final Object SERVICE = new Object();
+    private static final Object SERVICE = new TreeSet();
     private static final ServiceContext SERVICE_CONTEXT = new MockServiceContext();
     private static final ServiceCondition START_CONDITION = new MockStartCondition();
+    private static final ServiceCondition STOP_CONDITION = new MockStopCondition();
 
+    /**
+     * Tests that the constructor works when called with an Object and fails when called with null.
+     */
     public void testConstructor() {
         new StaticServiceFactory(SERVICE);
         try {
@@ -39,14 +45,23 @@
         }
     }
 
+    /**
+     * Tests that create service returns the same object that was passed to the constructor.
+     */
     public void testCreateService() {
         assertSame(SERVICE, new StaticServiceFactory(SERVICE).createService(SERVICE_CONTEXT));
     }
 
+    /**
+     * Tests that the service factory is not restartable.
+     */
     public void testIsRestartable() {
         assertEquals(false, new StaticServiceFactory(SERVICE).isRestartable());
     }
 
+    /**
+     * Tests that geting and setting the enalbe flag.
+     */
     public void testEnabled() {
         StaticServiceFactory serviceFactory = new StaticServiceFactory(SERVICE);
         assertEquals(true, serviceFactory.isEnabled());
@@ -56,8 +71,12 @@
         assertEquals(true, serviceFactory.isEnabled());
     }
 
-    public void testDependency() {
+    /**
+     * Tests getting and setting start and stop conditions.
+     */
+    public void testConditions() {
         StaticServiceFactory serviceFactory = new StaticServiceFactory(SERVICE);
+
         // get the dependency set
         Set dependencies = serviceFactory.getStartConditions();
         assertNotNull(dependencies);
@@ -80,6 +99,40 @@
             fail("dependencies.clear() should have thrown an Exception");
         } catch (Exception expected) {
         }
+
+        // get the dependency set
+        dependencies = serviceFactory.getStopConditions();
+        assertNotNull(dependencies);
+        // it should be initially empty
+        assertTrue(dependencies.isEmpty());
+
+        serviceFactory.addStopCondition(STOP_CONDITION);
+        // old dependency set should still be empty... it is a snapshot
+        assertTrue(dependencies.isEmpty());
+
+        // get a new dependency set
+        dependencies = serviceFactory.getStopConditions();
+        assertNotNull(dependencies);
+        // should have our dependency in it
+        assertEquals(1, dependencies.size());
+        assertTrue(dependencies.contains(STOP_CONDITION));
+
+        try {
+            dependencies.clear();
+            fail("dependencies.clear() should have thrown an Exception");
+        } catch (Exception expected) {
+        }
+    }
+
+    /**
+     * Tests that getTypes returns an array containing a single class which is the class of the service passed to the constuctor.
+     */
+    public void testGetTypes() {
+        StaticServiceFactory serviceFactory = new StaticServiceFactory(SERVICE);
+        Class[] types = serviceFactory.getTypes();
+        assertNotNull(types);
+        assertEquals(1, types.length);
+        assertSame(SERVICE.getClass(), types[0]);
     }
 
     private static class MockServiceContext implements ServiceContext {
@@ -109,4 +162,18 @@
             throw new UnsupportedOperationException();
         }
     }
+
+    private static class MockStopCondition implements ServiceCondition {
+        public void initialize(ServiceConditionContext context) {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean isSatisfied() {
+            throw new UnsupportedOperationException();
+        }
+
+        public void destroy() {
+            throw new UnsupportedOperationException();
+        }
+    }
 }
diff --git a/kernel/src/test/org/gbean/kernel/StringServiceNameTest.java b/kernel/src/test/org/gbean/kernel/StringServiceNameTest.java
index 5a2438e..8dbb15a 100644
--- a/kernel/src/test/org/gbean/kernel/StringServiceNameTest.java
+++ b/kernel/src/test/org/gbean/kernel/StringServiceNameTest.java
@@ -19,11 +19,15 @@
 import junit.framework.TestCase;
 
 /**
+ * Tests StringServiceName.
  * @author Dain Sundstrom
  * @version $Id$
  * @since 1.0
  */
 public class StringServiceNameTest extends TestCase {
+    /**
+     * Tests that the constuctor works when passed a string and fails when passed null.
+     */
     public void testConstructor() {
         new StringServiceName("foo");
 
@@ -34,15 +38,24 @@
         }
     }
 
+    /**
+     * Tests that toString returns equivalent String as the one passed to the construcor.
+     */
     public void testToString() {
         assertEquals("foo", new StringServiceName("foo").toString());
     }
 
+    /**
+     * Tests that equals works when comparing two names created with equivalent strings, and fails when not.
+     */
     public void testEquals() {
         assertEquals(new StringServiceName("foo"), new StringServiceName("foo"));
         assertFalse(new StringServiceName("bar").equals(new StringServiceName("foo")));
     }
 
+    /**
+     * Tests that hashCode creates the same value when used on two names created with equivalent strings, and fails when not.
+     */
     public void testHashCode() {
         assertEquals(new StringServiceName("foo").hashCode(), new StringServiceName("foo").hashCode());
         assertFalse(new StringServiceName("bar").hashCode() == new StringServiceName("foo").hashCode());
diff --git a/kernel/src/test/org/gbean/kernel/standard/ServiceManagerRegistryTest.java b/kernel/src/test/org/gbean/kernel/standard/ServiceManagerRegistryTest.java
index 042fdea..864aec7 100644
--- a/kernel/src/test/org/gbean/kernel/standard/ServiceManagerRegistryTest.java
+++ b/kernel/src/test/org/gbean/kernel/standard/ServiceManagerRegistryTest.java
@@ -22,6 +22,15 @@
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Arrays;
+import java.util.AbstractCollection;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.io.Serializable;
 
 import edu.emory.mathcs.backport.java.util.concurrent.Callable;
 import edu.emory.mathcs.backport.java.util.concurrent.CountDownLatch;
@@ -41,6 +50,7 @@
 import org.gbean.kernel.StopStrategy;
 import org.gbean.kernel.StringServiceName;
 import org.gbean.kernel.UnsatisfiedConditionsException;
+import org.gbean.kernel.NullServiceMonitor;
 
 /**
  * Test the ServiceManagerRegistry.
@@ -56,6 +66,18 @@
     private static final StringServiceName SERVICE_NAME = new StringServiceName("Service");
     private static final StaticServiceFactory SERVICE_FACTORY = new StaticServiceFactory(new Object());
     private static final ClassLoader CLASS_LOADER = new URLClassLoader(new URL[0]);
+    private static final Class[] EXPECTED_TYPES =  new Class[] {
+       TreeSet.class,
+       AbstractSet.class,
+       AbstractCollection.class,
+       Object.class,
+       SortedSet.class,
+       Set.class,
+       Collection.class,
+       Cloneable.class,
+       Serializable.class,
+       List.class
+    };
     private final MockServiceManager serviceManager = new MockServiceManager();
     private final MockServiceManagerFactory serviceManagerFactory = new MockServiceManagerFactory();
     private final ServiceManagerRegistry registry = new ServiceManagerRegistry(serviceManagerFactory);
@@ -580,7 +602,11 @@
 
         if (throwable == null) {
             assertTrue(registry.isRegistered(SERVICE_NAME));
-            assertEquals(serviceManager, registry.getServiceManager(SERVICE_NAME));
+            assertSame(serviceManager, registry.getServiceManager(SERVICE_NAME));
+            for (int i = 0; i < EXPECTED_TYPES.length; i++) {
+                assertSame(serviceManager, registry.getServiceManager(EXPECTED_TYPES[i]));
+                assertTrue(registry.getServiceManagers(EXPECTED_TYPES[i]).contains(serviceManager));
+            }
         } else {
             assertFalse(registry.isRegistered(SERVICE_NAME));
             try {
@@ -590,6 +616,10 @@
                 // expected
                 assertEquals(SERVICE_NAME, expected.getServiceName());
             }
+            for (int i = 0; i < EXPECTED_TYPES.length; i++) {
+                assertNull(registry.getServiceManager(EXPECTED_TYPES[i]));
+                assertTrue(registry.getServiceManagers(EXPECTED_TYPES[i]).isEmpty());
+            }
         }
         assertTrue(serviceManager.isInitializeCalled());
         assertFalse(serviceManager.isDestroyCalled());
@@ -620,9 +650,17 @@
                 // expected
                 assertEquals(SERVICE_NAME, expected.getServiceName());
             }
+            for (int i = 0; i < EXPECTED_TYPES.length; i++) {
+                assertNull(registry.getServiceManager(EXPECTED_TYPES[i]));
+                assertTrue(registry.getServiceManagers(EXPECTED_TYPES[i]).isEmpty());
+            }
         } else {
             assertTrue(registry.isRegistered(SERVICE_NAME));
-            assertEquals(serviceManager, registry.getServiceManager(SERVICE_NAME));
+            assertSame(serviceManager, registry.getServiceManager(SERVICE_NAME));
+            for (int i = 0; i < EXPECTED_TYPES.length; i++) {
+                assertSame(serviceManager, registry.getServiceManager(EXPECTED_TYPES[i]));
+                assertTrue(registry.getServiceManagers(EXPECTED_TYPES[i]).contains(serviceManager));
+            }
         }
         assertFalse(serviceManager.isInitializeCalled());
         assertTrue(serviceManager.isDestroyCalled());
@@ -684,7 +722,7 @@
             super(null, null, null, 0, null);
         }
 
-        public ServiceManager createServiceManager(ServiceName serviceName, ServiceFactory serviceFactory, ClassLoader classLoader) {
+        public ServiceManager createServiceManager(long serviceId, ServiceName serviceName, ServiceFactory serviceFactory, ClassLoader classLoader) {
             assertEquals(SERVICE_NAME, serviceName);
             assertEquals(SERVICE_FACTORY, serviceFactory);
             assertEquals(CLASS_LOADER, classLoader);
@@ -709,10 +747,19 @@
         private Throwable stopException;
         private CountDownLatch enterWaiting = new CountDownLatch(1);
         private CountDownLatch exitWaiting = new CountDownLatch(0);
+        private static final Set TYPES = Collections.unmodifiableSet(new HashSet(Arrays.asList(
+                new Class[] {TreeSet.class, List.class} )));
 
 
         private MockServiceManager() {
-            super(null, null, null, null, null, null, 0, null);
+            super(null,
+                    0,
+                    new StringServiceName("MockService"),
+                    new StaticServiceFactory(new Object()),
+                    null,
+                    new NullServiceMonitor(),
+                    0,
+                    null);
         }
 
         private synchronized void reset() {
@@ -724,6 +771,10 @@
             stopException = null;
         }
 
+        public Set getServiceTypes() {
+            return TYPES;
+        }
+
         public void initialize() throws IllegalServiceStateException, UnsatisfiedConditionsException, Exception {
             synchronized (this) {
                 assertFalse(initializeCalled);
diff --git a/kernel/src/test/org/gbean/kernel/standard/ServiceManagerTest.java b/kernel/src/test/org/gbean/kernel/standard/ServiceManagerTest.java
index e65aa63..2616559 100644
--- a/kernel/src/test/org/gbean/kernel/standard/ServiceManagerTest.java
+++ b/kernel/src/test/org/gbean/kernel/standard/ServiceManagerTest.java
@@ -26,7 +26,6 @@
 
 import edu.emory.mathcs.backport.java.util.concurrent.Callable;
 import edu.emory.mathcs.backport.java.util.concurrent.CountDownLatch;
-import edu.emory.mathcs.backport.java.util.concurrent.Executor;
 import edu.emory.mathcs.backport.java.util.concurrent.FutureTask;
 import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
 import junit.framework.TestCase;
@@ -55,6 +54,7 @@
 import org.gbean.kernel.UnsatisfiedConditionsException;
 
 /**
+ * Test ServiceManager.
  * @author Dain Sundstrom
  * @version $Id$
  * @since 1.0
@@ -71,6 +71,9 @@
     private final MockServiceMonitor serviceMonitor = new MockServiceMonitor();
     private ServiceManager serviceManager;
 
+    /**
+     * Tests that the initial state of the service manager is as expected.
+     */
     public void testInitialState() {
         assertSame(serviceName, serviceManager.getServiceName());
         assertSame(serviceFactory, serviceManager.getServiceFactory());
@@ -80,6 +83,10 @@
         assertSame(ServiceState.STOPPED, serviceManager.getState());
     }
 
+    /**
+     * Tests that initialize and destroy work without exception.
+     * @throws Exception if a problem occurs
+     */
     public void testInitializeDestroy() throws Exception {
         initialize();
         destroy(StopStrategies.SYNCHRONOUS);
@@ -87,6 +94,10 @@
         destroy(StopStrategies.SYNCHRONOUS);
     }
 
+    /**
+     * Tests that start and stop work without exception.
+     * @throws Exception if a problem occurs
+     */
     public void testStartStop() throws Exception {
         startStop(StartStrategies.ASYNCHRONOUS, false);
         startStop(StartStrategies.SYNCHRONOUS, false);
@@ -99,6 +110,10 @@
         startStop(StartStrategies.BLOCK, false);
     }
 
+    /**
+     * Tests that startRecursive results in recursive start calls on the owned services.
+     * @throws Exception if a problem occurs
+     */
     public void testStartRecursive() throws Exception {
         startStop(StartStrategies.ASYNCHRONOUS, true);
         startStop(StartStrategies.SYNCHRONOUS, true);
@@ -121,6 +136,10 @@
         stop(StopStrategies.SYNCHRONOUS);
     }
 
+    /**
+     * Tests how the start strategies respond when an Exception is thrown.
+     * @throws Exception if a problem occurs
+     */
     public void testStartException() throws Exception {
         startException(StartStrategies.ASYNCHRONOUS, false);
         startException(StartStrategies.SYNCHRONOUS, false);
@@ -133,6 +152,10 @@
         startException(StartStrategies.BLOCK, false);
     }
 
+    /**
+     * Tests how startRecursive start strategies respond when an Exception is thrown.
+     * @throws Exception if a problem occurs
+     */
     public void testStartExceptionRecursive() throws Exception {
         startException(StartStrategies.ASYNCHRONOUS, true);
         startException(StartStrategies.SYNCHRONOUS, true);
@@ -160,6 +183,10 @@
         stop(StopStrategies.SYNCHRONOUS);
     }
 
+    /**
+     * Tests how the start strategies respond when confronted with an unsatisfied start condition.
+     * @throws Exception if a problem occurs
+     */
     public void testStartWaiting() throws Exception {
         startWaiting(StartStrategies.ASYNCHRONOUS, false);
         startWaiting(StartStrategies.SYNCHRONOUS, false);
@@ -170,6 +197,10 @@
         startWaiting(StartStrategies.UNREGISTER, false);
     }
 
+    /**
+     * Tests how the startRecursive start strategies responed when confronted with an unsatisfied start condition.
+     * @throws Exception
+     */
     public void testStartWaitingRecursive() throws Exception {
         startWaiting(StartStrategies.ASYNCHRONOUS, true);
         startWaiting(StartStrategies.SYNCHRONOUS, true);
@@ -198,6 +229,11 @@
         stop(StopStrategies.SYNCHRONOUS);
     }
 
+
+    /**
+     * Tests how the start strategies respond once a once unsatisfied start condition becomes satisfied.
+     * @throws Exception if a problem occurs
+     */
     public void testStartWaitingStart() throws Exception {
         startWaitingStart(StartStrategies.ASYNCHRONOUS, false);
         startWaitingStart(StartStrategies.SYNCHRONOUS, false);
@@ -208,6 +244,10 @@
         startWaitingStart(StartStrategies.UNREGISTER, false);
     }
 
+    /**
+     * Tests how startRecursive start strategies respond once a once unsatisfied start condition becomes satisfied.
+     * @throws Exception if a problem occurs
+     */
     public void testStartWaitingStartRecursive() throws Exception {
         startWaitingStart(StartStrategies.ASYNCHRONOUS, true);
         startWaitingStart(StartStrategies.SYNCHRONOUS, true);
@@ -252,9 +292,13 @@
         stop(StopStrategies.SYNCHRONOUS);
     }
 
+    /**
+     * Tests the BLOCK start stragegy.
+     * @throws Exception if a problem occurs
+     */
     public void testBlockStartWaiting() throws Exception {
         startCondition.satisfied = false;
-        startCondition.initializedSignal = new CountDownLatch(1);
+        startCondition.isSatisfiedSignal = new CountDownLatch(1);
         FutureTask startTask = new FutureTask(new Callable() {
             public Object call() throws Exception {
                 start(false, StartStrategies.BLOCK);
@@ -266,7 +310,7 @@
         startThread.start();
 
         // wait for the start thread to reach the startContion initialize method
-        assertTrue(startCondition.initializedSignal.await(5, TimeUnit.SECONDS));
+        assertTrue(startCondition.isSatisfiedSignal.await(5, TimeUnit.SECONDS));
 
         // we should not have a service instance and be in the starting state
         assertSame(ServiceState.STARTING, serviceManager.getState());
@@ -311,6 +355,10 @@
         stop(StopStrategies.SYNCHRONOUS);
     }
 
+    /**
+     * Tests how start responds when the service factory is not enabled.
+     * @throws Exception if a problem occurs
+     */
     public void testDisabledStart() throws Exception {
         disabledStart(StartStrategies.ASYNCHRONOUS);
         disabledStart(StartStrategies.SYNCHRONOUS);
@@ -352,6 +400,10 @@
         }
     }
 
+    /**
+     * Tests how the stop strategies respond when destroyService throws an Exception.
+     * @throws Exception if a problem occurs
+     */
     public void testStopException() throws Exception {
         stopException(StopStrategies.ASYNCHRONOUS);
         stopException(StopStrategies.SYNCHRONOUS);
@@ -365,6 +417,10 @@
         stop(stopStrategy);
     }
 
+    /**
+     * Tests how the start and stop strategies work when both create and destroy throw exceptions.
+     * @throws Exception if a problem occurs
+     */
     public void testStartStopsException() throws Exception {
         startStopsException(StartStrategies.ASYNCHRONOUS, StopStrategies.ASYNCHRONOUS);
         startStopsException(StartStrategies.ASYNCHRONOUS, StopStrategies.SYNCHRONOUS);
@@ -400,13 +456,17 @@
         stop(stopStrategy);
     }
 
+    /**
+     * Tests how stop strategies respond when confronted with an unsatisfied stop condition.
+     * @throws Exception if a problem occurs
+     */
     public void testWaitingStop() throws Exception {
         waitingStop(StopStrategies.ASYNCHRONOUS);
         waitingStop(StopStrategies.SYNCHRONOUS);
         waitingStop(StopStrategies.FORCE);
     }
 
-    public void waitingStop(StopStrategy stopStrategy) throws Exception {
+    private void waitingStop(StopStrategy stopStrategy) throws Exception {
         start(false, StartStrategies.SYNCHRONOUS);
         stopCondition.satisfied = false;
         try {
@@ -428,10 +488,14 @@
         stop(stopStrategy);
     }
 
+    /**
+     * Tests the BLOCK stop strategy.
+     * @throws Exception if a problem occurs
+     */
     public void testBlockStopWaiting() throws Exception {
         start(false, StartStrategies.SYNCHRONOUS);
         stopCondition.satisfied = false;
-        stopCondition.initializedSignal = new CountDownLatch(1);
+        stopCondition.isStatisfiedSignal = new CountDownLatch(1);
         FutureTask stopTask = new FutureTask(new Callable() {
             public Object call() throws Exception {
                 stop(StopStrategies.BLOCK);
@@ -443,7 +507,7 @@
         stopThread.start();
 
         // wait for the stop thread to reach the stopContion initialize method
-        assertTrue(stopCondition.initializedSignal.await(5, TimeUnit.SECONDS));
+        assertTrue(stopCondition.isStatisfiedSignal.await(5, TimeUnit.SECONDS));
 
         // we should blocked waiting to stop
         assertSame(ServiceState.STOPPING, serviceManager.getState());
@@ -484,6 +548,10 @@
     }
 
 
+    /**
+     * Tests that start throws an exception when the service is in the stoping state.
+     * @throws Exception if a problem occurs
+     */
     public void testStartFromStopping() throws Exception {
         startFromStopping(StartStrategies.ASYNCHRONOUS);
         startFromStopping(StartStrategies.SYNCHRONOUS);
@@ -506,6 +574,10 @@
         stop(StopStrategies.ASYNCHRONOUS);
     }
 
+    /**
+     * Tests that a non-restartable servie is immedately started when initialized and stopped in destroy.
+     * @throws Exception if a problem occurs
+     */
     public void testNotRestartableInitDestroy() throws Exception {
         notRestartableInitDestroy(StopStrategies.ASYNCHRONOUS);
         notRestartableInitDestroy(StopStrategies.SYNCHRONOUS);
@@ -519,24 +591,40 @@
         destroy(stopStrategy);
     }
 
+    /**
+     * Tests how initialize on a non-restartable service responds when an Exception is thrown from create service.
+     * @throws Exception if a problem occurs
+     */
     public void testNotRestartableInitException() throws Exception {
         serviceFactory.throwExceptionFromCreate = true;
         serviceFactory.restartable = false;
         initialize();
     }
 
+    /**
+     * Tests how initialize on a non-restartable service responds when confronted with an unsatisfied start condition.
+     * @throws Exception if a problem occurs
+     */
     public void testNotRestartableInitWaiting() throws Exception {
         startCondition.satisfied = false;
         serviceFactory.restartable = false;
         initialize();
     }
 
+    /**
+     * Tests how initialize on a non-restartable service responds when the service factory is disabled.
+     * @throws Exception if a problem occurs
+     */
     public void testNotRestartableInitDisabled() throws Exception {
         serviceFactory.setEnabled(false);
         serviceFactory.restartable = false;
         initialize();
     }
 
+    /**
+     * Tests how destroy on a non-restartable service responds when destroyService throws an exception.
+     * @throws Exception if a problem occurs
+     */
     public void testNotRestartableDestroyException() throws Exception {
         notRestartableDestroyException(StopStrategies.ASYNCHRONOUS);
         notRestartableDestroyException(StopStrategies.SYNCHRONOUS);
@@ -551,6 +639,11 @@
         destroy(stopStrategy);
     }
 
+
+    /**
+     * Tests how destroy on a non-restartable service responds when createService and destroyService throws an exception.
+     * @throws Exception if a problem occurs
+     */
     public void testNotRestartableInitDestroyException() throws Exception {
         notRestartableInitDestroyException(StopStrategies.ASYNCHRONOUS);
         notRestartableInitDestroyException(StopStrategies.SYNCHRONOUS);
@@ -566,13 +659,17 @@
         destroy(stopStrategy);
     }
 
+    /**
+     * Tests how destroy on a non-restartable service responds when confronted with an unsatisfied stop condition.
+     * @throws Exception if a problem occurs
+     */
     public void testNotRestartableWaitingStop() throws Exception {
         notRestartableWaitingDestroy(StopStrategies.ASYNCHRONOUS);
         notRestartableWaitingDestroy(StopStrategies.SYNCHRONOUS);
         notRestartableWaitingDestroy(StopStrategies.FORCE);
     }
 
-    public void notRestartableWaitingDestroy(StopStrategy stopStrategy) throws Exception {
+    private void notRestartableWaitingDestroy(StopStrategy stopStrategy) throws Exception {
         serviceFactory.restartable = false;
         initialize();
         stopCondition.satisfied = false;
@@ -582,11 +679,15 @@
         destroy(stopStrategy);
     }
 
+    /**
+     * Tests the BLOCK stop strategy on a non-restartable service.
+     * @throws Exception if a problem occurs
+     */
     public void testNotRestartableBlockWaitingStop() throws Exception {
         serviceFactory.restartable = false;
         initialize();
         stopCondition.satisfied = false;
-        stopCondition.initializedSignal = new CountDownLatch(1);
+        stopCondition.isStatisfiedSignal = new CountDownLatch(1);
         FutureTask destroyTask = new FutureTask(new Callable() {
             public Object call() throws Exception {
                 destroy(StopStrategies.BLOCK);
@@ -598,7 +699,7 @@
         destroyThread.start();
 
         // wait for the stop thread to reach the stopContion initialize method
-        assertTrue(stopCondition.initializedSignal.await(5, TimeUnit.SECONDS));
+        assertTrue(stopCondition.isStatisfiedSignal.await(5, TimeUnit.SECONDS));
 
         // we should blocked waiting to stop
         assertSame(ServiceState.RUNNING, serviceManager.getState());
@@ -1252,15 +1353,11 @@
     protected void setUp() throws Exception {
         super.setUp();
         serviceManager = new ServiceManager(kernel,
+                0,
                 serviceName,
                 serviceFactory,
                 classLoader,
                 serviceMonitor,
-                new Executor() {
-                    public void execute(Runnable command) {
-                        command.run();
-                    }
-                },
                 10,
                 TimeUnit.SECONDS);
     }
@@ -1341,7 +1438,7 @@
         private boolean isSatisfiedCalled = false;
         private boolean destroyCalled = false;
         private ServiceConditionContext context;
-        private CountDownLatch initializedSignal;
+        private CountDownLatch isSatisfiedSignal;
 
         private void reset() {
             initializeCalled = false;
@@ -1353,13 +1450,13 @@
             assertValidServiceConditionContext(context);
             initializeCalled = true;
             this.context = context;
-            if (initializedSignal != null) {
-                initializedSignal.countDown();
-            }
         }
 
         public boolean isSatisfied() {
             isSatisfiedCalled = true;
+            if (isSatisfiedSignal != null) {
+                isSatisfiedSignal.countDown();
+            }
             return satisfied;
         }
 
@@ -1374,7 +1471,7 @@
         private boolean isSatisfiedCalled = false;
         private boolean destroyCalled = false;
         private ServiceConditionContext context;
-        private CountDownLatch initializedSignal;
+        private CountDownLatch isStatisfiedSignal;
 
         private void reset() {
             initializeCalled = false;
@@ -1386,13 +1483,13 @@
             assertValidServiceConditionContext(context);
             initializeCalled = true;
             this.context = context;
-            if (initializedSignal != null) {
-                initializedSignal.countDown();
-            }
         }
 
         public boolean isSatisfied() {
             isSatisfiedCalled = true;
+            if (isStatisfiedSignal != null) {
+                isStatisfiedSignal.countDown();
+            }
             return satisfied;
         }
 
@@ -1575,10 +1672,26 @@
             throw new UnsupportedOperationException();
         }
 
+        public Object getService(Class type) {
+            throw new UnsupportedOperationException();
+        }
+
+        public List getServices(Class type) {
+            throw new UnsupportedOperationException();
+        }
+
         public ServiceFactory getServiceFactory(ServiceName serviceName) throws ServiceNotFoundException {
             throw new UnsupportedOperationException();
         }
 
+        public ServiceFactory getServiceFactory(Class type) {
+            throw new UnsupportedOperationException();
+        }
+
+        public List getServiceFactories(Class type) {
+            throw new UnsupportedOperationException();
+        }
+
         public ClassLoader getClassLoaderFor(ServiceName serviceName) throws ServiceNotFoundException {
             throw new UnsupportedOperationException();
         }
diff --git a/kernel/src/test/org/gbean/kernel/ServiceMonitorBroadcasterTest.java b/kernel/src/test/org/gbean/kernel/standard/ServiceMonitorBroadcasterTest.java
similarity index 96%
rename from kernel/src/test/org/gbean/kernel/ServiceMonitorBroadcasterTest.java
rename to kernel/src/test/org/gbean/kernel/standard/ServiceMonitorBroadcasterTest.java
index 14e0edf..c3e51b2 100644
--- a/kernel/src/test/org/gbean/kernel/ServiceMonitorBroadcasterTest.java
+++ b/kernel/src/test/org/gbean/kernel/standard/ServiceMonitorBroadcasterTest.java
@@ -14,7 +14,7 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package org.gbean.kernel;
+package org.gbean.kernel.standard;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -23,7 +23,14 @@
 import java.util.Set;
 
 import junit.framework.TestCase;
-import org.gbean.kernel.standard.StandardKernel;
+import org.gbean.kernel.KernelErrorsError;
+import org.gbean.kernel.KernelMonitor;
+import org.gbean.kernel.NullServiceMonitor;
+import org.gbean.kernel.ServiceEvent;
+import org.gbean.kernel.ServiceMonitor;
+import org.gbean.kernel.ServiceName;
+import org.gbean.kernel.StaticServiceFactory;
+import org.gbean.kernel.StringServiceName;
 
 /**
  * Tests ServiceMonitorBroadcaster.
@@ -263,11 +270,11 @@
 
     private void assertNotificationCorrect(int serviceId, int notificationType) {
         for (Iterator iterator = expectedMonitors[serviceId].iterator(); iterator.hasNext();) {
-            MockServiceMonitor mockServiceMonitor = ((MockServiceMonitor) iterator.next());
+            MockServiceMonitor mockServiceMonitor = (MockServiceMonitor) iterator.next();
             assertEquals(notificationType, mockServiceMonitor.getCalled());
         }
         for (Iterator iterator = unexpectedMonitors[serviceId].iterator(); iterator.hasNext();) {
-            MockServiceMonitor mockServiceMonitor = ((MockServiceMonitor) iterator.next());
+            MockServiceMonitor mockServiceMonitor = (MockServiceMonitor) iterator.next();
             assertEquals(0, mockServiceMonitor.getCalled());
         }
         assertTrue("Unfired service errors " + kernelMonitor.getExpectedServiceMonitors(), kernelMonitor.getExpectedServiceMonitors().isEmpty());
@@ -361,6 +368,8 @@
                 serviceName,
                 SERVICE_FACTORY,
                 SYSTEM_CLASS_LOADER,
+                null,
+                null,
                 null);
     }
 
diff --git a/kernel/src/test/org/gbean/kernel/standard/StandardKernelTest.java b/kernel/src/test/org/gbean/kernel/standard/StandardKernelTest.java
index c46a08e..15d5b20 100644
--- a/kernel/src/test/org/gbean/kernel/standard/StandardKernelTest.java
+++ b/kernel/src/test/org/gbean/kernel/standard/StandardKernelTest.java
@@ -29,6 +29,7 @@
 import org.gbean.kernel.StringServiceName;
 
 /**
+ * Tests the StandardKernel.
  * @author Dain Sundstrom
  * @version $Id$
  * @since 1.0
@@ -42,6 +43,10 @@
     private final MockServiceFactory serviceFactory = new MockServiceFactory();
     private final ClassLoader classLoader = new URLClassLoader(new URL[0]);
 
+    /**
+     * Tests the initial state of the kernel is as expected.
+     * @throws Exception if a problem occurs
+     */
     public void testInitialState() throws Exception {
         assertEquals(KERNEL_NAME, kernel.getKernelName());
         assertTrue(kernel.isRunning());
@@ -223,6 +228,10 @@
         }
     }
 
+    /**
+     * Test the life cycle of a simple service.
+     * @throws Exception if a problem occurs
+     */ 
     public void testSimpleLifecycle() throws Exception {
         kernel.registerService(serviceName, serviceFactory, classLoader);
         assertTrue(kernel.isRegistered(serviceName));
diff --git a/server/maven.xml b/server/maven.xml
new file mode 100644
index 0000000..0872ff3
--- /dev/null
+++ b/server/maven.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+* 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.
+-->
+<project default="default"
+    xmlns:j="jelly:core"
+    xmlns:ant="jelly:ant"
+    >
+
+    <goal name="default">
+        <attainGoal name="jar:install"/>
+    </goal>
+
+    <goal name="build">
+        <attainGoal name="default"/>
+    </goal>
+
+    <goal name="rebuild">
+        <attainGoal name="clean"/>
+        <attainGoal name="build"/>
+    </goal>
+</project>
diff --git a/server/project.properties b/server/project.properties
new file mode 100644
index 0000000..9fff46b
--- /dev/null
+++ b/server/project.properties
@@ -0,0 +1,34 @@
+###
+# 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.
+###
+maven.repo.remote=http://www.gbean.org/maven,http://www.openejb.org/maven,http://www.ibiblio.org/maven
+
+maven.compile.source=1.4
+maven.compile.target=1.4
+maven.compile.deprecation=true
+maven.compile.debug=true
+maven.compile.optimize=true
+
+maven.remote.group=gbean
+maven.username=${user.name}
+maven.repo.central=beaver.codehaus.org
+maven.repo.central.directory=/dist
+
+maven.javadoc.source=1.4
+maven.javadoc.links=http://java.sun.com/j2se/1.4.1/docs/api/
+maven.javadoc.additionalparam=-linksource
+
+maven.site.deploy.method=rsync
+maven.site.deploy.clean=true
diff --git a/server/project.xml b/server/project.xml
new file mode 100644
index 0000000..fd776d9
--- /dev/null
+++ b/server/project.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+* 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.
+-->
+<project>
+    <!-- the version of maven's project object model -->
+    <pomVersion>3</pomVersion>
+
+    <name>GBean :: Server</name>
+    <id>gbean-server</id>
+    <groupId>gbean</groupId>
+    <currentVersion>1.0-SNAPSHOT</currentVersion>
+    <organization>
+        <name>GBean.org</name>
+        <url>http://gbean.org</url>
+    </organization>
+
+    <package>org.gbean</package>
+
+    <shortDescription>GBean: generic server components</shortDescription>
+
+    <description>
+        The GBean server module contains generic services useful in a server environment.
+    </description>
+
+    <url>http://www.gbean.org/</url>
+
+    <siteAddress>www.gbean.org</siteAddress>
+    <siteDirectory>/home/projects/gbean/public_html/maven</siteDirectory>
+
+    <mailingLists>
+        <mailingList>
+            <name>gbean developers</name>
+            <subscribe>mailto:dev-subscribe@gbean.org</subscribe>
+            <unsubscribe>mailto:dev-unsubscribe@gbean.org</unsubscribe>
+        </mailingList>
+        <mailingList>
+            <name>gbean users</name>
+            <subscribe>mailto:user-subscribe@gbean.org</subscribe>
+            <unsubscribe>mailto:user-unsubscribe@gbean.org</unsubscribe>
+        </mailingList>
+        <mailingList>
+            <name>gbean source control messages</name>
+            <subscribe>mailto:scm-subscribe@gbean.org</subscribe>
+            <unsubscribe>mailto:scm-unsubscribe@gbean.org</unsubscribe>
+        </mailingList>
+    </mailingLists>
+
+    <dependencies>
+        <dependency>
+            <groupId>backport-util-concurrent</groupId>
+            <artifactId>backport-util-concurrent</artifactId>
+            <version>2.0_01_pd</version>
+        </dependency>
+        <dependency>
+            <groupId>cglib</groupId>
+            <artifactId>cglib-nodep</artifactId>
+            <version>2.1_2</version>
+            <type>test</type>
+        </dependency>
+        <dependency>
+            <groupId>gbean</groupId>
+            <artifactId>gbean-kernel</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>mx4j</groupId>
+            <artifactId>mx4j</artifactId>
+            <version>3.0.1</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <sourceDirectory>src/java</sourceDirectory>
+        <unitTestSourceDirectory>src/test</unitTestSourceDirectory>
+        <unitTest>
+            <includes>
+                <include>**/*Test.java</include>
+            </includes>
+        </unitTest>
+    </build>
+
+    <reports>
+        <!-- <report>maven-license-plugin</report> -->
+        <!-- <report>maven-checkstyle-plugin</report>-->
+        <!-- <report>maven-pmd-plugin</report> -->
+        <!-- <report>maven-simian-plugin</report> -->
+        <!-- <report>maven-jdepend-plugin</report> -->
+        <!-- <report>maven-changelog-plugin</report> -->
+        <!-- <report>maven-statcvs-plugin</report> -->
+        <!-- <report>maven-file-activity-plugin</report> -->
+        <!-- <report>maven-developer-activity-plugin</report> -->
+        <report>maven-jxr-plugin</report>
+        <report>maven-javadoc-plugin</report>
+        <report>maven-junit-report-plugin</report>
+        <!-- <report>maven-faq-plugin</report> -->
+        <!-- <report>maven-clover-plugin</report>-->
+        <!-- <report>maven-changes-plugin</report>-->
+    </reports>
+
+</project>
+
diff --git a/server/src/java/org/gbean/server/classloader/ClassLoaderUtil.java b/server/src/java/org/gbean/server/classloader/ClassLoaderUtil.java
new file mode 100644
index 0000000..8e4b56a
--- /dev/null
+++ b/server/src/java/org/gbean/server/classloader/ClassLoaderUtil.java
@@ -0,0 +1,90 @@
+/**
+ *
+ * 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.server.classloader;
+
+import java.util.Map;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+
+/**
+ * Utility methods for class loader manipulation in a server environment.
+ *
+ * @author Dain Sundstrom
+ * @version $Id$
+ * @since 1.0
+ */
+public final class ClassLoaderUtil {
+    private ClassLoaderUtil() {
+    }
+
+    /**
+     * Cleans well known class loader leaks in VMs and libraries.  There is a lot of bad code out there and this method
+     * will clear up the know problems.  This method should only be called when the class loader will no longer be used.
+     * It this method is called two often it can have a serious impact on preformance.
+     * @param classLoader the class loader to destroy
+     */
+    public static void destroy(ClassLoader classLoader) {
+        releaseCommonsLoggingCache(classLoader);
+        clearSunSoftCache(ObjectInputStream.class, "subclassAudits");
+        clearSunSoftCache(ObjectOutputStream.class, "subclassAudits");
+        clearSunSoftCache(ObjectStreamClass.class, "localDescs");
+        clearSunSoftCache(ObjectStreamClass.class, "reflectors");
+    }
+
+    /**
+     * Clears the caches maintained by the SunVM object stream implementation.  This method uses reflection and
+     * setAccessable to obtain access to the Sun cache.  The cache is locked with a synchronize monitor and cleared.
+     * This method completely clears the class loader cache which will impact preformance of object serialization.
+     *
+     * @param clazz the name of the class containing the cache field
+     * @param fieldName the name of the cache field
+     */
+    public static void clearSunSoftCache(Class clazz, String fieldName) {
+        Map cache = null;
+        try {
+            Field field = clazz.getDeclaredField(fieldName);
+            field.setAccessible(true);
+            cache = (Map) field.get(null);
+        } catch (Throwable ignored) {
+            // there is nothing a user could do about this anyway
+        }
+
+        if (cache != null) {
+            synchronized (cache) {
+                cache.clear();
+            }
+        }
+    }
+
+    /**
+     * Releases the specified classloader from the Apache Jakarta Commons Logging class loader cache using reflection.
+     * @param classLoader the class loader to release
+     */
+    public static void releaseCommonsLoggingCache(ClassLoader classLoader) {
+        try {
+            Class logFactory = classLoader.loadClass("org.apache.commons.logging.LogFactory");
+            Method release = logFactory.getMethod("release", new Class[] {ClassLoader.class});
+            release.invoke(null, new Object[] {classLoader});
+        } catch (Throwable ignored) {
+            // there is nothing a user could do about this anyway
+        }
+    }
+
+}
diff --git a/server/src/java/org/gbean/server/classloader/DestroyableClassLoader.java b/server/src/java/org/gbean/server/classloader/DestroyableClassLoader.java
new file mode 100644
index 0000000..ff6c2ff
--- /dev/null
+++ b/server/src/java/org/gbean/server/classloader/DestroyableClassLoader.java
@@ -0,0 +1,33 @@
+/**
+ *
+ * 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.server.classloader;
+
+/**
+ * DestroyableClassLoader is a mixin interface for a ClassLoader that add a destroy method to propertly cleanup a
+ * classloader then dereferenced by the server.
+ *
+ * @author Dain Sundstrom
+ * @version $Id$
+ * @since 1.0
+ */
+public interface DestroyableClassLoader {
+    /**
+     * Destroys the clasloader releasing all resources.  After this mehtod is called, the class loader will no longer
+     * load any classes or resources.
+     */
+    void destroy();
+}
diff --git a/server/src/java/org/gbean/server/classloader/JarFileClassLoader.java b/server/src/java/org/gbean/server/classloader/JarFileClassLoader.java
new file mode 100644
index 0000000..71bbf4e
--- /dev/null
+++ b/server/src/java/org/gbean/server/classloader/JarFileClassLoader.java
@@ -0,0 +1,398 @@
+/**
+ *
+ * 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.server.classloader;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLStreamHandlerFactory;
+import java.security.CodeSource;
+import java.security.cert.Certificate;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+/**
+ * The JarFileClassLoader that loads classes and resources from a list of JarFiles.  This method is simmilar to URLClassLoader
+ * except it properly closes JarFiles when the classloader is destroyed so that the file read lock will be released, and
+ * the jar file can be modified and deleted.
+ * <p>
+ * Note: This implementation currently does not work reliably on windows, since the jar URL handler included with the Sun JavaVM
+ * holds a read lock on the JarFile, and this lock is not released when the jar url is dereferenced.  To fix this a
+ * replacement for the jar url handler must be written.
+ *
+ * @author Dain Sundstrom
+ * @version $Id$
+ * @since 1.0
+ */
+public class JarFileClassLoader extends MultiParentClassLoader {
+    private static final URL[] EMPTY_URLS = new URL[0];
+    private final Object lock = new Object();
+    private final LinkedHashMap classPath = new LinkedHashMap();
+    private boolean destroyed = false;
+
+    /**
+     * Creates a JarFileClassLoader that is a child of the system class loader.
+     * @param name the name of this class loader
+     * @param urls a list of URLs from which classes and resources should be loaded
+     */
+    public JarFileClassLoader(String name, URL[] urls) {
+        super(name, EMPTY_URLS);
+        addURLs(urls);
+    }
+
+    /**
+     * Creates a JarFileClassLoader that is a child of the specified class loader.
+     * @param name the name of this class loader
+     * @param urls a list of URLs from which classes and resources should be loaded
+     * @param parent the parent of this class loader
+     */
+    public JarFileClassLoader(String name, URL[] urls, ClassLoader parent) {
+        this(name, urls, new ClassLoader[] {parent});
+    }
+
+    /**
+     * Creates a named class loader as a child of the specified parent and using the specified URLStreamHandlerFactory
+     * for accessing the urls..
+     * @param name the name of this class loader
+     * @param urls the urls from which this class loader will classes and resources
+     * @param parent the parent of this class loader
+     * @param factory the URLStreamHandlerFactory used to access the urls
+     */
+    public JarFileClassLoader(String name, URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
+        this(name, urls, new ClassLoader[] {parent}, factory);
+    }
+
+    /**
+     * Creates a named class loader as a child of the specified parents.
+     * @param name the name of this class loader
+     * @param urls the urls from which this class loader will classes and resources
+     * @param parents the parents of this class loader
+     */
+    public JarFileClassLoader(String name, URL[] urls, ClassLoader[] parents) {
+        super(name, EMPTY_URLS, parents);
+        addURLs(urls);
+    }
+
+    /**
+     * Creates a named class loader as a child of the specified parents and using the specified URLStreamHandlerFactory
+     * for accessing the urls..
+     * @param name the name of this class loader
+     * @param urls the urls from which this class loader will classes and resources
+     * @param parents the parents of this class loader
+     * @param factory the URLStreamHandlerFactory used to access the urls
+     */
+    public JarFileClassLoader(String name, URL[] urls, ClassLoader[] parents, URLStreamHandlerFactory factory) {
+        super(name, EMPTY_URLS, parents, factory);
+        addURLs(urls);
+    }
+
+    public URL[] getURLs() {
+        return (URL[]) classPath.keySet().toArray(new URL[classPath.keySet().size()]);
+    }
+
+    protected void addURL(URL url) {
+        addURLs(Collections.singletonList(url));
+    }
+
+    /**
+     * Adds an array of urls to the end of this class loader.
+     * @param urls the URLs to add
+     */
+    protected void addURLs(URL[] urls) {
+        addURLs(Arrays.asList(urls));
+    }
+
+    /**
+     * Adds a list of urls to the end of this class loader.
+     * @param urls the URLs to add
+     */
+    protected void addURLs(List urls) {
+        LinkedList locationStack = new LinkedList(urls);
+        try {
+            while (!locationStack.isEmpty()) {
+                URL url = (URL) locationStack.removeFirst();
+
+                if (!"file".equals(url.getProtocol())) {
+                    // download the jar
+                    throw new Error("Only local file jars are supported " + url);
+                }
+
+                String path = url.getPath();
+                if (classPath.containsKey(path)) {
+                    continue;
+                }
+
+                File file = new File(path);
+                if (!file.canRead()) {
+                    // can't read file...
+                    continue;
+                }
+
+                // open the jar file
+                JarFile jarFile = null;
+                try {
+                    jarFile = new JarFile(file);
+                } catch (IOException e) {
+                    // can't seem to open the file
+                    continue;
+                }
+                classPath.put(url, jarFile);
+
+                // push the manifest classpath on the stack (make sure to maintain the order)
+                Manifest manifest = null;
+                try {
+                    manifest = jarFile.getManifest();
+                } catch (IOException ignored) {
+                }
+
+                if (manifest != null) {
+                    Attributes mainAttributes = manifest.getMainAttributes();
+                    String manifestClassPath = mainAttributes.getValue(Attributes.Name.CLASS_PATH);
+                    if (manifestClassPath != null) {
+                        LinkedList classPathUrls = new LinkedList();
+                        for (StringTokenizer tokenizer = new StringTokenizer(manifestClassPath, " "); tokenizer.hasMoreTokens();) {
+                            String entry = tokenizer.nextToken();
+                            File parentDir = file.getParentFile();
+                            File entryFile = new File(parentDir, entry);
+                            // manifest entries are optional... if they aren't there it is ok
+                            if (entryFile.canRead()) {
+                                classPathUrls.addLast(entryFile.getAbsolutePath());
+                            }
+                        }
+                        locationStack.addAll(0, classPathUrls);
+                    }
+                }
+            }
+        } catch (Error e) {
+            destroy();
+            throw e;
+        }
+    }
+
+    public void destroy() {
+        synchronized (lock) {
+            if (destroyed) {
+                return;
+            }
+            destroyed = true;
+            for (Iterator iterator = classPath.values().iterator(); iterator.hasNext();) {
+                JarFile jarFile = (JarFile) iterator.next();
+                try {
+                    jarFile.close();
+                } catch (IOException ignored) {
+                }
+            }
+            classPath.clear();
+        }
+        super.destroy();
+    }
+
+    public URL findResource(String resourceName) {
+        URL jarUrl = null;
+        synchronized (lock) {
+            if (destroyed) {
+                return null;
+            }
+            for (Iterator iterator = classPath.entrySet().iterator(); iterator.hasNext() && jarUrl == null;) {
+                Map.Entry entry = (Map.Entry) iterator.next();
+                JarFile jarFile = (JarFile) entry.getValue();
+                JarEntry jarEntry = jarFile.getJarEntry(resourceName);
+                if (jarEntry != null && !jarEntry.isDirectory()) {
+                    jarUrl = (URL) entry.getKey();
+                }
+            }
+        }
+
+
+        try {
+            String urlString = "jar:" + jarUrl + "!/" + resourceName;
+            return new URL(jarUrl, urlString);
+        } catch (MalformedURLException e) {
+            return null;
+        }
+    }
+
+    public Enumeration findResources(String resourceName) throws IOException {
+        List resources = new ArrayList();
+        List superResources = Collections.list(super.findResources(resourceName));
+        resources.addAll(superResources);
+
+        synchronized (lock) {
+            if (destroyed) {
+                return Collections.enumeration(Collections.EMPTY_LIST);
+            }
+            for (Iterator iterator = classPath.entrySet().iterator(); iterator.hasNext();) {
+                Map.Entry entry = (Map.Entry) iterator.next();
+                JarFile jarFile = (JarFile) entry.getValue();
+                JarEntry jarEntry = jarFile.getJarEntry(resourceName);
+                if (jarEntry != null && !jarEntry.isDirectory()) {
+                    try {
+                        URL url = (URL) entry.getKey();
+                        String urlString = "jar:" + url + "!/" + resourceName;
+                        resources.add(new URL(url, urlString));
+                    } catch (MalformedURLException e) {
+                    }
+                }
+            }
+        }
+
+        return Collections.enumeration(resources);
+    }
+
+    protected Class findClass(String className) throws ClassNotFoundException {
+        SecurityManager securityManager = System.getSecurityManager();
+        if (securityManager != null) {
+            String packageName = null;
+            int packageEnd = className.lastIndexOf('.');
+            if (packageEnd >= 0) {
+                packageName = className.substring(0, packageEnd);
+                securityManager.checkPackageDefinition(packageName);
+            }
+        }
+
+        Certificate[] certificates = null;
+        URL jarUrl = null;
+        Manifest manifest = null;
+        byte[] bytes;
+        synchronized (lock) {
+            if (destroyed) {
+                throw new ClassNotFoundException("Class loader has been destroyed: " + className);
+            }
+
+            try {
+                String entryName = className.replace('.', '/') + ".class";
+                InputStream inputStream = null;
+                for (Iterator iterator = classPath.entrySet().iterator(); iterator.hasNext() && inputStream == null;) {
+                    Map.Entry entry = (Map.Entry) iterator.next();
+                    jarUrl = (URL) entry.getKey();
+                    JarFile jarFile = (JarFile) entry.getValue();
+                    JarEntry jarEntry = jarFile.getJarEntry(entryName);
+                    if (jarEntry != null && !jarEntry.isDirectory()) {
+                        inputStream = jarFile.getInputStream(jarEntry);
+                        certificates = jarEntry.getCertificates();
+                        manifest = jarFile.getManifest();
+                    }
+                }
+                if (inputStream == null) {
+                    throw new ClassNotFoundException(className);
+                }
+
+                try {
+                    byte[] buffer = new byte[4096];
+                    ByteArrayOutputStream out = new ByteArrayOutputStream();
+                    for (int count = inputStream.read(buffer); count >= 0; count = inputStream.read(buffer)) {
+                        out.write(buffer, 0, count);
+                    }
+                    bytes = out.toByteArray();
+                } finally {
+                    if (inputStream != null) {
+                        inputStream.close();
+                    }
+                }
+            } catch (IOException e) {
+                throw new ClassNotFoundException(className, e);
+            }
+        }
+
+        definePackage(className, jarUrl, manifest);
+        CodeSource codeSource = new CodeSource(jarUrl, certificates);
+        Class clazz = defineClass(className, bytes, 0, bytes.length, codeSource);
+        return clazz;
+    }
+
+    private void definePackage(String className, URL jarUrl, Manifest manifest) {
+        int packageEnd = className.lastIndexOf('.');
+        if (packageEnd < 0) {
+            return;
+        }
+
+        String packageName = className.substring(0, packageEnd);
+        String packagePath = packageName.replace('.', '/') + "/";
+
+        Attributes packageAttributes = null;
+        Attributes mainAttributes = null;
+        if (manifest != null) {
+            packageAttributes = manifest.getAttributes(packagePath);
+            mainAttributes = manifest.getMainAttributes();
+        }
+        Package pkg = getPackage(packageName);
+        if (pkg != null) {
+            if (pkg.isSealed()) {
+                if (!pkg.isSealed(jarUrl)) {
+                    throw new SecurityException("Package was already sealed with another URL: package=" + packageName + ", url=" + jarUrl);
+                }
+            } else {
+                if (isSealed(packageAttributes, mainAttributes)) {
+                    throw new SecurityException("Package was already been loaded and not sealed: package=" + packageName + ", url=" + jarUrl);
+                }
+            }
+        } else {
+            String specTitle = getAttribute(Attributes.Name.SPECIFICATION_TITLE, packageAttributes, mainAttributes);
+            String specVendor = getAttribute(Attributes.Name.SPECIFICATION_VENDOR, packageAttributes, mainAttributes);
+            String specVersion = getAttribute(Attributes.Name.SPECIFICATION_VERSION, packageAttributes, mainAttributes);
+            String implTitle = getAttribute(Attributes.Name.IMPLEMENTATION_TITLE, packageAttributes, mainAttributes);
+            String implVendor = getAttribute(Attributes.Name.IMPLEMENTATION_VENDOR, packageAttributes, mainAttributes);
+            String implVersion = getAttribute(Attributes.Name.IMPLEMENTATION_VERSION, packageAttributes, mainAttributes);
+
+            URL sealBase = null;
+            if (isSealed(packageAttributes, mainAttributes)) {
+                sealBase = jarUrl;
+            }
+
+            definePackage(packageName, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase);
+        }
+    }
+
+    private String getAttribute(Attributes.Name name, Attributes packageAttributes, Attributes mainAttributes) {
+        if (packageAttributes != null) {
+            String value = packageAttributes.getValue(name);
+            if (value != null) {
+                return value;
+            }
+        }
+        if (mainAttributes != null) {
+            return mainAttributes.getValue(name);
+        }
+        return null;
+    }
+
+    private boolean isSealed(Attributes packageAttributes, Attributes mainAttributes) {
+        String sealed = getAttribute(Attributes.Name.SEALED, packageAttributes, mainAttributes);
+        if (sealed == null) {
+            return false;
+        }
+        if (sealed == null) {
+            return false;
+        }
+        return "true".equalsIgnoreCase(sealed);
+    }
+}
\ No newline at end of file
diff --git a/kernel/src/java/org/gbean/kernel/MultiParentClassLoader.java b/server/src/java/org/gbean/server/classloader/MultiParentClassLoader.java
similarity index 91%
rename from kernel/src/java/org/gbean/kernel/MultiParentClassLoader.java
rename to server/src/java/org/gbean/server/classloader/MultiParentClassLoader.java
index f03be59..03337f3 100644
--- a/kernel/src/java/org/gbean/kernel/MultiParentClassLoader.java
+++ b/server/src/java/org/gbean/server/classloader/MultiParentClassLoader.java
@@ -14,16 +14,16 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package org.gbean.kernel;
+package org.gbean.server.classloader;
 
-import java.net.URLClassLoader;
+import java.io.IOException;
 import java.net.URL;
 import java.net.URLStreamHandlerFactory;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.io.IOException;
 
 /**
  * A MultiParentClassLoader is a simple extension of the URLClassLoader that simply changes the single parent class
@@ -36,8 +36,7 @@
  * @version $Id$
  * @since 1.0
  */
-public class MultiParentClassLoader extends URLClassLoader {
-    private final String name;
+public class MultiParentClassLoader extends NamedClassLoader {
     private final ClassLoader[] parents;
 
     /**
@@ -46,8 +45,7 @@
      * @param urls the urls from which this class loader will classes and resources
      */
     public MultiParentClassLoader(String name, URL[] urls) {
-        super(urls);
-        this.name = name;
+        super(name, urls);
         parents = new ClassLoader[0];
     }
 
@@ -80,8 +78,7 @@
      * @param parents the parents of this class loader
      */
     public MultiParentClassLoader(String name, URL[] urls, ClassLoader[] parents) {
-        super(urls);
-        this.name = name;
+        super(name, urls);
         this.parents = copyParents(parents);
     }
 
@@ -94,8 +91,7 @@
      * @param factory the URLStreamHandlerFactory used to access the urls
      */
     public MultiParentClassLoader(String name, URL[] urls, ClassLoader[] parents, URLStreamHandlerFactory factory) {
-        super(urls, null, factory);
-        this.name = name;
+        super(name, urls, null, factory);
         this.parents = copyParents(parents);
     }
 
@@ -112,14 +108,6 @@
     }
 
     /**
-     * Gets the name of this class loader.
-     * @return the name of this class loader
-     */
-    public String getName() {
-        return name;
-    }
-
-    /**
      * Gets the parents of this class loader.
      * @return the parents of this class loader
      */
@@ -184,6 +172,10 @@
     }
 
     public String toString() {
-        return "[" + getClass().getName() + " name=" + name + "]";
+        return "[" + getClass().getName() + ":" +
+                " name=" + getName() +
+                " urls=" + Arrays.asList(getURLs()) +
+                " parents=" + Arrays.asList(parents) +
+                "]";
     }
 }
diff --git a/server/src/java/org/gbean/server/classloader/NamedClassLoader.java b/server/src/java/org/gbean/server/classloader/NamedClassLoader.java
new file mode 100644
index 0000000..2208750
--- /dev/null
+++ b/server/src/java/org/gbean/server/classloader/NamedClassLoader.java
@@ -0,0 +1,88 @@
+/**
+ *
+ * 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.server.classloader;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.net.URLStreamHandlerFactory;
+import java.util.Arrays;
+
+
+/**
+ * The NamedClassLoader is a simple extension to URLClassLoader that adds a name and a destroy method that cleans up
+ * the commons logging and JavaVM caches of the classloader.
+ *
+ * @author Dain Sundstrom
+ * @version $Id$
+ * @since 1.0
+ */
+public class NamedClassLoader extends URLClassLoader implements DestroyableClassLoader {
+    private final String name;
+
+    /**
+     * Creates a named class loader with no parents.
+     * @param name the name of this class loader
+     * @param urls the urls from which this class loader will classes and resources
+     */
+    public NamedClassLoader(String name, URL[] urls) {
+        super(urls);
+        this.name = name;
+    }
+
+    /**
+     * Creates a named class loader as a child of the specified parent.
+     * @param name the name of this class loader
+     * @param urls the urls from which this class loader will classes and resources
+     * @param parent the parent of this class loader
+     */
+    public NamedClassLoader(String name, URL[] urls, ClassLoader parent) {
+        super(urls, parent);
+        this.name = name;
+    }
+
+    /**
+     * Creates a named class loader as a child of the specified parent and using the specified URLStreamHandlerFactory
+     * for accessing the urls..
+     * @param name the name of this class loader
+     * @param urls the urls from which this class loader will classes and resources
+     * @param parent the parent of this class loader
+     * @param factory the URLStreamHandlerFactory used to access the urls
+     */
+    public NamedClassLoader(String name, URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
+        super(urls, parent, factory);
+        this.name = name;
+    }
+
+    public void destroy() {
+        ClassLoaderUtil.destroy(this);
+    }
+
+    /**
+     * Gets the name of this class loader.
+     * @return the name of this class loader
+     */
+    public String getName() {
+        return name;
+    }
+
+    public String toString() {
+        return "[" + getClass().getName() + ":" +
+                " name=" + getName() +
+                " urls=" + Arrays.asList(getURLs()) +
+                "]";
+    }
+}
diff --git a/server/src/java/org/gbean/server/propertyeditor/InetAddressEditor.java b/server/src/java/org/gbean/server/propertyeditor/InetAddressEditor.java
new file mode 100644
index 0000000..4c289fb
--- /dev/null
+++ b/server/src/java/org/gbean/server/propertyeditor/InetAddressEditor.java
@@ -0,0 +1,44 @@
+/**
+ *
+ * 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.server.propertyeditor;
+
+import java.beans.PropertyEditorSupport;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * InetAddressEditor is a java beans property editor that can convert an InetAddreass to and from a String.
+ *
+ * @author Dain Sundstrom
+ * @version $Id$
+ * @since 1.0
+ */
+public class InetAddressEditor extends PropertyEditorSupport {
+    public void setAsText(String value) throws IllegalArgumentException {
+        try {
+            setValue(InetAddress.getByName(value));
+        } catch (UnknownHostException e) {
+            throw (IllegalArgumentException) new IllegalArgumentException().initCause(e);
+        }
+    }
+
+    public String getAsText() {
+        InetAddress inetAddress = (InetAddress) getValue();
+        String text = inetAddress.toString();
+        return text;
+    }
+}
\ No newline at end of file
diff --git a/server/src/java/org/gbean/server/propertyeditor/ObjectNameEditor.java b/server/src/java/org/gbean/server/propertyeditor/ObjectNameEditor.java
new file mode 100644
index 0000000..0290c7c
--- /dev/null
+++ b/server/src/java/org/gbean/server/propertyeditor/ObjectNameEditor.java
@@ -0,0 +1,44 @@
+/**
+ *
+ * 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.server.propertyeditor;
+
+import java.beans.PropertyEditorSupport;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+/**
+ * InetAddressEditor is a java beans property editor that can convert an ObjectName to and from a String.
+ *
+ * @author Dain Sundstrom
+ * @version $Id$
+ * @since 1.0
+ */
+public class ObjectNameEditor extends PropertyEditorSupport {
+    public void setAsText(String value) throws IllegalArgumentException {
+        try {
+            setValue(new ObjectName(value));
+        } catch (MalformedObjectNameException e) {
+            throw (IllegalArgumentException) new IllegalArgumentException().initCause(e);
+        }
+    }
+
+    public String getAsText() {
+        ObjectName objectName = (ObjectName) getValue();
+        String text = objectName.getCanonicalName();
+        return text;
+    }
+}
\ No newline at end of file
diff --git a/server/src/java/org/gbean/server/propertyeditor/URIEditor.java b/server/src/java/org/gbean/server/propertyeditor/URIEditor.java
new file mode 100644
index 0000000..6346730
--- /dev/null
+++ b/server/src/java/org/gbean/server/propertyeditor/URIEditor.java
@@ -0,0 +1,44 @@
+/**
+ *
+ * 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.server.propertyeditor;
+
+import java.beans.PropertyEditorSupport;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * InetAddressEditor is a java beans property editor that can convert an URI to and from a String.
+ *
+ * @author Dain Sundstrom
+ * @version $Id$
+ * @since 1.0
+ */
+public class URIEditor extends PropertyEditorSupport {
+    public void setAsText(String value) throws IllegalArgumentException {
+        try {
+            setValue(new URI(value));
+        } catch (URISyntaxException e) {
+            throw (IllegalArgumentException) new IllegalArgumentException().initCause(e);
+        }
+    }
+
+    public String getAsText() {
+        URI uri = (URI) getValue();
+        String text = uri.toString();
+        return text;
+    }
+}
\ No newline at end of file
diff --git a/server/src/test/org/gbean/server/classloader/JarFileClassLoaderTest.java b/server/src/test/org/gbean/server/classloader/JarFileClassLoaderTest.java
new file mode 100644
index 0000000..c4d7b53
--- /dev/null
+++ b/server/src/test/org/gbean/server/classloader/JarFileClassLoaderTest.java
@@ -0,0 +1,32 @@
+/**
+ *
+ * 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.server.classloader;
+
+import java.net.URL;
+
+/**
+ * Test the JarFileClassLoader.
+ *
+ * @author Dain Sundstrom
+ * @version $Id$
+ * @since 1.0
+ */
+public class JarFileClassLoaderTest extends MultiParentClassLoaderTest {
+    protected MultiParentClassLoader createClassLoader(String name, URL[] urls, ClassLoader[] parents) {
+        return new JarFileClassLoader(name, urls, parents);
+    }
+}
diff --git a/kernel/src/test/org/gbean/kernel/MultiParentClassLoaderTest.java b/server/src/test/org/gbean/server/classloader/MultiParentClassLoaderTest.java
similarity index 89%
rename from kernel/src/test/org/gbean/kernel/MultiParentClassLoaderTest.java
rename to server/src/test/org/gbean/server/classloader/MultiParentClassLoaderTest.java
index 5d420a6..06d022c 100644
--- a/kernel/src/test/org/gbean/kernel/MultiParentClassLoaderTest.java
+++ b/server/src/test/org/gbean/server/classloader/MultiParentClassLoaderTest.java
@@ -14,16 +14,16 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package org.gbean.kernel;
+package org.gbean.server.classloader;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.Serializable;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.Enumeration;
+import java.util.SortedSet;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 import java.util.jar.JarOutputStream;
@@ -36,6 +36,7 @@
 import net.sf.cglib.proxy.NoOp;
 
 /**
+ * Tests the MultiParentClassLoader including classloading and resource loading.
  * @author Dain Sundstrom
  * @version $Id$
  * @since 1.0
@@ -53,8 +54,8 @@
     private static final String NAME = "my test class loader";
 
     /**
-     * Verify that the test jars are valid
-     * @throws Exception
+     * Verify that the test jars are valid.
+     * @throws Exception if a problem occurs
      */
     public void testTestJars() throws Exception {
         for (int i = 0; i < files.length; i++) {
@@ -69,12 +70,12 @@
             // clazz shared by all
             Class clazz = urlClassLoader.loadClass(CLASS_NAME);
             assertNotNull(clazz);
-            assertTrue(clazz instanceof Serializable);
+            assertTrue(SortedSet.class.isAssignableFrom(clazz));
 
             // clazz specific to this jar
             clazz = urlClassLoader.loadClass(CLASS_NAME + i);
             assertNotNull(clazz);
-            assertTrue(clazz instanceof Serializable);
+            assertTrue(SortedSet.class.isAssignableFrom(clazz));
 
             // resource shared by all jars
             InputStream in = urlClassLoader.getResourceAsStream(ENTRY_NAME );
@@ -114,7 +115,7 @@
         // load class specific to my class loader
         Class clazz = classLoader.loadClass(CLASS_NAME + 33);
         assertNotNull(clazz);
-        assertTrue(clazz instanceof Serializable);
+        assertTrue(SortedSet.class.isAssignableFrom(clazz));
         assertEquals(classLoader, clazz.getClassLoader());
 
         // load class specific to each parent class loader
@@ -122,14 +123,14 @@
             URLClassLoader parent = parents[i];
             clazz = classLoader.loadClass(CLASS_NAME + i);
             assertNotNull(clazz);
-            assertTrue(clazz instanceof Serializable);
+            assertTrue(SortedSet.class.isAssignableFrom(clazz));
             assertEquals(parent, clazz.getClassLoader());
         }
 
         // class shared by all class loaders
         clazz = classLoader.loadClass(CLASS_NAME);
         assertNotNull(clazz);
-        assertTrue(clazz instanceof Serializable);
+        assertTrue(SortedSet.class.isAssignableFrom(clazz));
         assertEquals(parents[0], clazz.getClassLoader());
     }
 
@@ -164,6 +165,7 @@
 
     /**
      * Test getResourceAsStream returns null when attempt is made to loade a non-existant resource.
+     * @throws Exception if a problem occurs
      */
     public void testGetNonExistantResourceAsStream() throws Exception {
         InputStream in = classLoader.getResourceAsStream(NON_EXISTANT_RESOURCE);
@@ -189,6 +191,7 @@
 
     /**
      * Test getResource returns null when attempt is made to loade a non-existant resource.
+     * @throws Exception if a problem occurs
      */
     public void testGetNonExistantResource() throws Exception {
         URL resource = classLoader.getResource(NON_EXISTANT_RESOURCE);
@@ -218,6 +221,7 @@
 
     /**
      * Test getResources returns an empty enumeration when attempt is made to loade a non-existant resource. 
+     * @throws Exception if a problem occurs
      */
     public void testGetNonExistantResources() throws Exception {
         Enumeration resources = classLoader.getResources(NON_EXISTANT_RESOURCE);
@@ -260,6 +264,7 @@
     }
 
     protected void setUp() throws Exception {
+        super.setUp();
         files = new File[3];
         for (int i = 0; i < files.length; i++) {
             files[i] = createJarFile(i);
@@ -271,7 +276,18 @@
         }
 
         myFile = createJarFile(33);
-        classLoader = new MultiParentClassLoader(NAME, new URL[]{myFile.toURL()}, parents);
+        classLoader = createClassLoader(NAME, new URL[]{myFile.toURL()}, parents);
+    }
+
+    /**
+     * Creates the class loader to test.
+     * @param name the name of the classloader
+     * @param urls the urls to load classes and resources from
+     * @param parents the parents of the class loader
+     * @return the class loader to test
+     */
+    protected MultiParentClassLoader createClassLoader(String name, URL[] urls, ClassLoader[] parents) {
+        return new MultiParentClassLoader(name, urls, parents);
     }
 
     private static File createJarFile(int i) throws IOException {
@@ -312,7 +328,7 @@
         });
         enhancer.setClassLoader(new URLClassLoader(new URL[0]));
         enhancer.setSuperclass(Object.class);
-        enhancer.setInterfaces(new Class[]{Serializable.class});
+        enhancer.setInterfaces(new Class[]{SortedSet.class});
         enhancer.setCallbackTypes(new Class[]{NoOp.class});
         enhancer.setUseFactory(false);
         ByteCode byteCode = new ByteCode();
@@ -323,6 +339,7 @@
     }
 
     protected void tearDown() throws Exception {
+        super.tearDown();
         for (int i = 0; i < files.length; i++) {
             files[i].delete();
             assertFileNotExists(files[i]);
@@ -341,5 +358,4 @@
             return byteCode;
         }
     }
-
 }