(no commit message)
authorErik Brakkee <erik@brakkee.org>
Thu, 10 Apr 2008 21:48:27 +0000 (21:48 +0000)
committerErik Brakkee <erik@brakkee.org>
Thu, 10 Apr 2008 21:48:27 +0000 (21:48 +0000)
system/general/src/main/java/org/wamblee/system/core/AbstractComponent.java
system/general/src/main/java/org/wamblee/system/core/Component.java
system/general/src/main/java/org/wamblee/system/core/Container.java
system/general/src/test/java/org/wamblee/system/core/ContainerTest.java
system/general/src/test/java/org/wamblee/system/core/Environment.java
system/spring/src/main/java/org/wamblee/system/spring/SpringComponent.java
system/spring/src/test/java/org/wamblee/system/spring/SpringComponentTest.java

index d86277d880a16068c33dfa0d00f73a2b7487eae4..9bad650d23cb47d42da9cb29dec850b166aafcff 100644 (file)
@@ -124,7 +124,7 @@ public abstract class AbstractComponent implements Component {
         * @param aService
         *            Service.
         */
-       protected final void addService(
+       protected final void addInterface(
                        ProvidedInterface aDescriptor, Object aService) {
                LOG.info("Interface '" + getQualifiedName() + "." + aDescriptor.getName() + "' started.");
                _running.add(aDescriptor);
@@ -132,17 +132,35 @@ public abstract class AbstractComponent implements Component {
        }
 
        @Override
-       public ProvidedInterface[] getRunningServices() {
+       public ProvidedInterface[] getRunningInterfaces() {
                return _running.toArray(new ProvidedInterface[0]);
        }
        
        @Override
        public void stop() {
-               doStop();       
+               doStop();
+               if ( _running.size() > 0 ) { 
+                       // programming error.
+                       throw new RuntimeException(getQualifiedName() + ": still services running after the stop call.");
+               }
                _status = Status.STOPPED;
        }
        
-       protected abstract void doStop(); 
+       protected abstract void doStop();
+       
+       /**
+        * Implementations must call this method to indicate that a running service has
+        * been stopped.
+        * 
+        * @param aService
+        *            Service.
+        */
+       protected final void removeInterface(
+                       ProvidedInterface aDescriptor) {
+               LOG.info("Interface '" + getQualifiedName() + "." + aDescriptor.getName() + "' stopped.");
+               _running.remove(aDescriptor);
+               aDescriptor.publish(null);
+       }
 
        @Override
        public String toString() {
index 9409e44f6b0cb9a1c2eef2d95cc6ce9ebd3c5e3b..b36fe56b221163c91d31f22ccf715bf71d9fd27c 100644 (file)
@@ -78,7 +78,7 @@ public interface Component {
         * {@link #initialize(String, Service[])} has been called. 
         * @return
         */
-       ProvidedInterface[] getRunningServices();
+       ProvidedInterface[] getRunningInterfaces();
        
        /**
         * Stops a subsystem. 
index ec45283e2d0fff00b1bc711658d80f5310414994..b7ee4e60c6ddcb61f0fed6c52b911cfb37fb7998 100644 (file)
@@ -12,7 +12,7 @@
  * 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.wamblee.system.core;
 
 import java.util.ArrayList;
@@ -26,8 +26,8 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 /**
- * Composite system consisting of multiple subsystems. 
- *
+ * Composite system consisting of multiple subsystems.
+ * 
  * @author Erik Brakkee
  */
 public class Container extends AbstractComponent {
@@ -49,8 +49,7 @@ public class Container extends AbstractComponent {
        }
 
        public static ProvidedInterface[] filterProvidedServices(
-                       RequiredInterface aRequired,
-                       Collection<ProvidedInterface> aProvided) {
+                       RequiredInterface aRequired, Collection<ProvidedInterface> aProvided) {
                List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
                for (ProvidedInterface descriptor : aProvided) {
                        if (aRequired.implementedBy(descriptor)) {
@@ -59,30 +58,36 @@ public class Container extends AbstractComponent {
                }
                return provided.toArray(new ProvidedInterface[0]);
        }
-       
+
        /**
-        * Construcst the composite system. 
-        * @param aName Name of the system. 
-        * @param aRegistry Service registry.
-        * @param aSystems Subsystems. 
-        * @param aProvided Provided services of the system. 
-        * @param aRequired Required services by the system. 
+        * Construcst the composite system.
+        * 
+        * @param aName
+        *            Name of the system.
+        * @param aRegistry
+        *            Service registry.
+        * @param aSystems
+        *            Subsystems.
+        * @param aProvided
+        *            Provided services of the system.
+        * @param aRequired
+        *            Required services by the system.
         */
        public Container(String aName, Component[] aSystems,
                        ProvidedInterface[] aProvided, RequiredInterface[] aRequired) {
                super(aName, aProvided, aRequired);
                _systems = aSystems;
-               for (Component component: aSystems) { 
+               for (Component component : aSystems) {
                        component.addContext(getQualifiedName());
                }
                validate(aRequired);
        }
 
        /**
-        * Validates the subsystems together to check that there are
-        * no required services not in the required list and 
-        * no services in the provided list that cannot be provided. 
-        * Also logs a warning in case of superfluous requirements.  
+        * Validates the subsystems together to check that there are no required
+        * services not in the required list and no services in the provided list
+        * that cannot be provided. Also logs a warning in case of superfluous
+        * requirements.
         */
        private void validate(RequiredInterface[] aRequired) {
                List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
@@ -97,7 +102,8 @@ public class Container extends AbstractComponent {
 
                for (ProvidedInterface service : getProvidedInterfaces()) {
                        if (!(provided.contains(service))) {
-                               throw new SystemAssemblyException(getName() + ": Service '" + service
+                               throw new SystemAssemblyException(getName() + ": Service '"
+                                               + service
                                                + "' is not provided by any of the subsystems");
                        }
                }
@@ -114,42 +120,43 @@ public class Container extends AbstractComponent {
                                required);
                // Compute all required interfaces that are not provided
                for (ProvidedInterface service : provided) {
-                       List<RequiredInterface> fulfilled = 
-                               Arrays.asList(filterRequiredServices(service, 
-                                       reallyRequired));
-                       reallyRequired.removeAll(fulfilled); 
+                       List<RequiredInterface> fulfilled = Arrays
+                                       .asList(filterRequiredServices(service, reallyRequired));
+                       reallyRequired.removeAll(fulfilled);
                }
                // Now the remaining interfaces should be covered by the required
-               // list. 
+               // list.
                reallyRequired.removeAll(Arrays.asList(aRequired));
-               
+
                String missingRequired = "";
-               for (RequiredInterface service: reallyRequired) {
+               for (RequiredInterface service : reallyRequired) {
                        missingRequired += service + "\n";
                }
-               if ( missingRequired.length() > 0 ) { 
-                       throw new SystemAssemblyException(getName() + ": missing required services\n" + missingRequired);
+               if (missingRequired.length() > 0) {
+                       throw new SystemAssemblyException(getName()
+                                       + ": missing required services\n" + missingRequired);
                }
        }
 
        @Override
        protected void doStart() {
                List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
-               
+
                // all interfaces from the required list of this container are
                // provided to the components inside it.
                RequiredInterface[] required = getRequiredInterfaces();
-               for (RequiredInterface intf: required) { 
-                   ProvidedInterface provider = intf.getProvider(); 
-                   if ( provider == null ) { 
-                       throw new SystemAssemblyException(getQualifiedName() + ": required interface '" + intf +"' is not provided");
-                   }
+               for (RequiredInterface intf : required) {
+                       ProvidedInterface provider = intf.getProvider();
+                       if (provider == null) {
+                               throw new SystemAssemblyException(getQualifiedName()
+                                               + ": required interface '" + intf + "' is not provided");
+                       }
                        provided.add(intf.getProvider());
                }
-               
+
                startImpl();
        }
-       
+
        /**
         * Starts the subsystems.
         * 
@@ -160,55 +167,77 @@ public class Container extends AbstractComponent {
        private void startImpl() {
                LOG.info("Starting '" + getQualifiedName() + "'");
                List<ProvidedInterface> allProvided = new ArrayList<ProvidedInterface>();
-               
-               // Add the provides of all externally required interfaces to the list of available
+
+               // Add the provides of all externally required interfaces to the list of
+               // available
                // interfaces
-               for (RequiredInterface required: getRequiredInterfaces()) { 
+               for (RequiredInterface required : getRequiredInterfaces()) {
                        allProvided.add(required.getProvider());
                }
-               
+
+               List<Component> started = new ArrayList<Component>();
                for (Component system : _systems) {
-                       // Check if all required services are already provided by earlier
-                       // systems.
-                       RequiredInterface[] required = system.getRequiredInterfaces();
-
-                       for (RequiredInterface descriptor : required) {
-                               ProvidedInterface[] filtered = filterProvidedServices(
-                                               descriptor, allProvided);
-
-                               if (filtered.length == 0) {
-                                       throw new SystemAssemblyException(
-                                                       "Service '"
-                                                                       + descriptor
-                                                                       + "' required by system '"
-                                                                       + system
-                                                                       + "' is not provided by systems that are started earlier");
+                       try {
+                               // Check if all required services are already provided by
+                               // earlier
+                               // systems.
+                               RequiredInterface[] required = system.getRequiredInterfaces();
+
+                               for (RequiredInterface descriptor : required) {
+                                       ProvidedInterface[] filtered = filterProvidedServices(
+                                                       descriptor, allProvided);
+
+                                       if (filtered.length == 0) {
+                                               throw new SystemAssemblyException(
+                                                               "Service '"
+                                                                               + descriptor
+                                                                               + "' required by system '"
+                                                                               + system
+                                                                               + "' is not provided by systems that are started earlier");
+                                       }
+                                       if (filtered.length > 1) {
+                                               throw new SystemAssemblyException(
+                                                               "Service '"
+                                                                               + descriptor
+                                                                               + "' required by system '"
+                                                                               + system
+                                                                               + "' matches multiple services provided by other systems: "
+                                                                               + Arrays.asList(filtered));
+                                       }
+                                       descriptor.setProvider(filtered[0]);
                                }
-                               if (filtered.length > 1) {
-                                       throw new SystemAssemblyException(
-                                                       "Service '"
-                                                                       + descriptor
-                                                                       + "' required by system '"
-                                                                       + system
-                                                                       + "' matches multiple services provided by other systems: " + 
-                                                                       Arrays.asList(filtered));
+
+                               // Start the service.
+                               system.start();
+                               started.add(system);
+
+                               // add all provided services
+                               ProvidedInterface[] provided = system.getProvidedInterfaces();
+                               allProvided.addAll(Arrays.asList(provided));
+                       } catch (SystemAssemblyException e) { 
+                               throw e; 
+                       } catch (RuntimeException e) {
+                               LOG.error(getQualifiedName() + ": could not start '"
+                                               + system.getQualifiedName() + "'", e);
+                               // an exception occurred, stop the successfully started
+                               // systems
+                               for (int i = started.size() - 1; i >= 0; i--) {
+                                       try {
+                                               started.get(i).stop();
+                                       } catch (Throwable t) {
+                                               LOG.error(getQualifiedName() + ": error stopping "
+                                                               + started.get(i).getQualifiedName());
+                                       }
                                }
-                               descriptor.setProvider(filtered[0]);
+                               throw e; 
                        }
-                       
-                       // Start the service. 
-                       system.start();
-
-                       // add all provided services
-                       ProvidedInterface[] provided = system.getProvidedInterfaces();
-                       allProvided.addAll(Arrays.asList(provided));
                }
+
        }
 
-       
        @Override
        protected void doStop() {
-               for (int i = _systems.length-1; i >= 0; i--) { 
+               for (int i = _systems.length - 1; i >= 0; i--) {
                        _systems[i].stop();
                }
        }
index 0c0a9935feb8b1da4f78321235d5c905d5aecf59..dfd8ed58fc87484130ba82cb307589701a3c6d52 100644 (file)
  * 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.wamblee.system.core;
 
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Arrays;
 
+import org.easymock.classextension.ConstructorArgs;
+import org.easymock.classextension.EasyMock;
+import org.easymock.classextension.IMocksControl;
 import org.wamblee.system.core.Component;
 import org.wamblee.system.core.Container;
 import org.wamblee.system.core.DefaultProvidedInterface;
@@ -61,11 +64,11 @@ public class ContainerTest extends TestCase {
                ProvidedInterface prov3 = new DefaultProvidedInterface("name",
                                MyMultiple.class);
 
-               AssertionUtils.assertEquals(new RequiredInterface[] { req1 },
-                               Container.filterRequiredServices(prov1, Arrays
+               AssertionUtils.assertEquals(new RequiredInterface[] { req1 }, Container
+                               .filterRequiredServices(prov1, Arrays
                                                .asList(new RequiredInterface[] { req1 })));
-               AssertionUtils.assertEquals(new RequiredInterface[] { req1 },
-                               Container.filterRequiredServices(prov1, Arrays
+               AssertionUtils.assertEquals(new RequiredInterface[] { req1 }, Container
+                               .filterRequiredServices(prov1, Arrays
                                                .asList(new RequiredInterface[] { req1, req2 })));
                AssertionUtils.assertEquals(new RequiredInterface[] { req1, req2 },
                                Container.filterRequiredServices(prov3, Arrays
@@ -88,27 +91,25 @@ public class ContainerTest extends TestCase {
                Container container = new Container("root", new Component[] {
                                environment, application }, new ProvidedInterface[0],
                                new RequiredInterface[0]);
-                               
+
                container.start();
                AssertionUtils.assertEquals(new String[] { "start.environment",
                                "start.application" }, _tracker.getEvents(
                                Thread.currentThread()).toArray(new String[0]));
-               ProvidedInterface[] envServices = environment.getRunningServices();
+               ProvidedInterface[] envServices = environment.getRunningInterfaces();
                assertEquals(2, envServices.length);
-               ProvidedInterface[] appServices = environment.getRunningServices();
-               assertEquals(2, appServices.length);
-               
+               ProvidedInterface[] appServices = application.getRunningInterfaces();
+               assertEquals(0, appServices.length);
+
        }
 
        public void testApplicationEnvironment() {
                try {
                        Component environment = new Environment();
                        Component application = new Application();
-                       Container container = new Container(
-                                       "root",
-                                       new Component[] {
-                                       application, environment }, 
-                                       new ProvidedInterface[0], new RequiredInterface[0]);
+                       Container container = new Container("root", new Component[] {
+                                       application, environment }, new ProvidedInterface[0],
+                                       new RequiredInterface[0]);
                        container.start();
                } catch (SystemAssemblyException e) {
                        // e.printStackTrace();
@@ -123,7 +124,7 @@ public class ContainerTest extends TestCase {
                assertEquals(0, _tracker.getEventCount());
                assertEquals(Status.NOT_STARTED, environment.getStatus());
                assertEquals(Status.NOT_STARTED, application.getStatus());
-               
+
                Container system = new Container("all", new Component[] { environment,
                                application }, new ProvidedInterface[0],
                                new RequiredInterface[0]);
@@ -136,22 +137,21 @@ public class ContainerTest extends TestCase {
                assertEquals(Status.RUNNING, environment.getStatus());
                assertEquals(Status.RUNNING, application.getStatus());
                assertEquals(Status.RUNNING, system.getStatus());
-               
-               AssertionUtils.assertEquals(
-                               new String[] { "start.environment", "start.application" }, 
-                               _tracker.getEvents(Thread.currentThread()).toArray(new String[0]));
-        _tracker.clear();
-               
+
+               AssertionUtils.assertEquals(new String[] { "start.environment",
+                               "start.application" }, _tracker.getEvents(
+                               Thread.currentThread()).toArray(new String[0]));
+               _tracker.clear();
+
                system.stop();
                assertEquals(Status.STOPPED, environment.getStatus());
                assertEquals(Status.STOPPED, application.getStatus());
                assertEquals(Status.STOPPED, system.getStatus());
-               
-               AssertionUtils.assertEquals(
-                               new String[] { "stop.application", "stop.environment" }, 
-                               _tracker.getEvents(Thread.currentThread()).toArray(new String[0]));
-      
-               
+
+               AssertionUtils.assertEquals(new String[] { "stop.application",
+                               "stop.environment" }, _tracker
+                               .getEvents(Thread.currentThread()).toArray(new String[0]));
+
        }
 
        public void testCompositeWithWrongProvidedInfo() {
@@ -265,4 +265,73 @@ public class ContainerTest extends TestCase {
                fail();
        }
 
+       public void testEnvironmentApplicationRollbackOnException()
+                       throws Exception {
+               IMocksControl control = EasyMock.createStrictControl();
+
+               Environment environment = new Environment(_tracker);
+               Application application = control.createMock(Application.class,
+                               new ConstructorArgs(Application.class.getConstructor()),
+                               Application.class.getDeclaredMethod("doStart"));
+
+               application.doStart();
+               EasyMock.expectLastCall().andThrow(new RuntimeException());
+               control.replay();
+
+               try {
+                       Container container = new Container("root", new Component[] {
+                                       environment, application }, new ProvidedInterface[0],
+                                       new RequiredInterface[0]);
+
+                       container.start();
+               } catch (RuntimeException e) {
+                       AssertionUtils.assertEquals(new String[] { "start.environment",
+                                       "stop.environment" }, _tracker.getEvents(
+                                       Thread.currentThread()).toArray(new String[0]));
+                       ProvidedInterface[] envServices = environment.getRunningInterfaces();
+                       assertEquals(0, envServices.length);
+                       return;
+               }
+               fail();
+       }
+
+       public void testEnvironmentApplicationRollbackOnExceptionWithExceptionOnStop()
+                       throws Exception {
+               IMocksControl control = EasyMock.createControl();
+
+               Environment environment = new Environment(_tracker);
+               // Application 1 will throw an exception while stopping.
+               Application application1 = control.createMock(Application.class,
+                               new ConstructorArgs(Application.class.getConstructor()),
+                               Application.class.getDeclaredMethod("doStop"));
+
+               application1.doStop();
+               EasyMock.expectLastCall().andThrow(new RuntimeException());
+               
+               // application 2 will throw an exception while starting
+               Application application2 = control.createMock(Application.class,
+                               new ConstructorArgs(Application.class.getConstructor()),
+                               Application.class.getDeclaredMethod("doStart"));
+
+               application2.doStart();
+               EasyMock.expectLastCall().andThrow(new RuntimeException());
+               
+               control.replay();
+
+               try {
+                       Container container = new Container("root", new Component[] {
+                                       environment, application1, application2 }, new ProvidedInterface[0],
+                                       new RequiredInterface[0]);
+
+                       container.start();
+               } catch (RuntimeException e) {
+                       AssertionUtils.assertEquals(new String[] { "start.environment", 
+                                       "stop.environment" }, _tracker.getEvents(
+                                       Thread.currentThread()).toArray(new String[0]));
+                       ProvidedInterface[] envServices = environment.getRunningInterfaces();
+                       assertEquals(0, envServices.length);
+                       return;
+               }
+               fail();
+       }
 }
index 4406e4431026272bcc27cea6dee9814ef1e93faf..9ee40e3972306514a1e999172f50cef3f9dbf263 100644 (file)
@@ -46,14 +46,16 @@ public class Environment extends AbstractComponent {
        
        @Override
        protected void doStart() {
-           addService(getProvidedInterfaces()[0], new Integer(1));
-           addService(getProvidedInterfaces()[1], new Integer(2));
+           addInterface(getProvidedInterfaces()[0], new Integer(1));
+           addInterface(getProvidedInterfaces()[1], new Integer(2));
            track("start." + getName());
        }
 
        @Override
        protected void doStop() {
                track("stop." + getName());
+               removeInterface(getProvidedInterfaces()[0]);
+               removeInterface(getProvidedInterfaces()[1]);
        }
        
        private void track(String aString) {
index 09ef341175657aae0e7da781a6299c2b5cf0a95e..8d50bf280d53b6d09f562519cbecdc20c9526e11 100644 (file)
@@ -135,7 +135,7 @@ public class SpringComponent extends AbstractComponent {
                                throw new IllegalArgumentException(getQualifiedName() + ": service '"
                                                + name + "' is null");
                        }
-                       addService(_provided.get(name), svc);
+                       addInterface(_provided.get(name), svc);
                }
        }
 
@@ -162,5 +162,8 @@ public class SpringComponent extends AbstractComponent {
        @Override
        protected void doStop() {
                _context.close();
+           for (ProvidedInterface provided: getProvidedInterfaces()) { 
+               removeInterface(provided);
+           }
        }
 }
index c8f8798a8e09a46fb183fb68b4b77f1b0ac10563..43902d430c12f8130321bebf5eeb2a30d6f808fb 100644 (file)
@@ -47,7 +47,7 @@ public class SpringComponentTest extends TestCase {
                                new HashMap<String, ProvidedInterface>(),
                                new HashMap<RequiredInterface, String>());
                system.start();
-               ProvidedInterface[] services = system.getRunningServices();
+               ProvidedInterface[] services = system.getRunningInterfaces();
                assertEquals(0, services.length);
                
                system.stop();
@@ -62,7 +62,7 @@ public class SpringComponentTest extends TestCase {
                                new String[] { HELLO_SERVICE_SPRING_XML }, provided,
                                new HashMap<RequiredInterface, String>());
                system.start();
-               ProvidedInterface[] services = system.getRunningServices();
+               ProvidedInterface[] services = system.getRunningInterfaces();
                assertEquals(1, services.length);
                assertTrue(services[0].getImplementation() instanceof HelloService);
                assertEquals("Hello world!", ((HelloService)services[0].getImplementation())
@@ -83,7 +83,7 @@ public class SpringComponentTest extends TestCase {
                system.addProperties(props);
                
                system.start();
-               ProvidedInterface[] services = system.getRunningServices();
+               ProvidedInterface[] services = system.getRunningInterfaces();
                assertEquals("Property Value", 
                                ((HelloService)services[0].getImplementation()).say());
        }