From f5160588d34e7bbeb9c27414c44c9108fa4cea78 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 10 Apr 2008 21:48:27 +0000 Subject: [PATCH] --- .../system/core/AbstractComponent.java | 26 ++- .../org/wamblee/system/core/Component.java | 2 +- .../org/wamblee/system/core/Container.java | 173 ++++++++++-------- .../wamblee/system/core/ContainerTest.java | 125 ++++++++++--- .../org/wamblee/system/core/Environment.java | 6 +- .../system/spring/SpringComponent.java | 5 +- .../system/spring/SpringComponentTest.java | 6 +- 7 files changed, 232 insertions(+), 111 deletions(-) diff --git a/trunk/system/general/src/main/java/org/wamblee/system/core/AbstractComponent.java b/trunk/system/general/src/main/java/org/wamblee/system/core/AbstractComponent.java index d86277d8..9bad650d 100644 --- a/trunk/system/general/src/main/java/org/wamblee/system/core/AbstractComponent.java +++ b/trunk/system/general/src/main/java/org/wamblee/system/core/AbstractComponent.java @@ -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() { diff --git a/trunk/system/general/src/main/java/org/wamblee/system/core/Component.java b/trunk/system/general/src/main/java/org/wamblee/system/core/Component.java index 9409e44f..b36fe56b 100644 --- a/trunk/system/general/src/main/java/org/wamblee/system/core/Component.java +++ b/trunk/system/general/src/main/java/org/wamblee/system/core/Component.java @@ -78,7 +78,7 @@ public interface Component { * {@link #initialize(String, Service[])} has been called. * @return */ - ProvidedInterface[] getRunningServices(); + ProvidedInterface[] getRunningInterfaces(); /** * Stops a subsystem. diff --git a/trunk/system/general/src/main/java/org/wamblee/system/core/Container.java b/trunk/system/general/src/main/java/org/wamblee/system/core/Container.java index ec45283e..b7ee4e60 100644 --- a/trunk/system/general/src/main/java/org/wamblee/system/core/Container.java +++ b/trunk/system/general/src/main/java/org/wamblee/system/core/Container.java @@ -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 aProvided) { + RequiredInterface aRequired, Collection aProvided) { List provided = new ArrayList(); 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 provided = new ArrayList(); @@ -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 fulfilled = - Arrays.asList(filterRequiredServices(service, - reallyRequired)); - reallyRequired.removeAll(fulfilled); + List 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 provided = new ArrayList(); - + // 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 allProvided = new ArrayList(); - - // 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 started = new ArrayList(); 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(); } } diff --git a/trunk/system/general/src/test/java/org/wamblee/system/core/ContainerTest.java b/trunk/system/general/src/test/java/org/wamblee/system/core/ContainerTest.java index 0c0a9935..dfd8ed58 100644 --- a/trunk/system/general/src/test/java/org/wamblee/system/core/ContainerTest.java +++ b/trunk/system/general/src/test/java/org/wamblee/system/core/ContainerTest.java @@ -12,13 +12,16 @@ * 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(); + } } diff --git a/trunk/system/general/src/test/java/org/wamblee/system/core/Environment.java b/trunk/system/general/src/test/java/org/wamblee/system/core/Environment.java index 4406e443..9ee40e39 100644 --- a/trunk/system/general/src/test/java/org/wamblee/system/core/Environment.java +++ b/trunk/system/general/src/test/java/org/wamblee/system/core/Environment.java @@ -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) { diff --git a/trunk/system/spring/src/main/java/org/wamblee/system/spring/SpringComponent.java b/trunk/system/spring/src/main/java/org/wamblee/system/spring/SpringComponent.java index 09ef3411..8d50bf28 100644 --- a/trunk/system/spring/src/main/java/org/wamblee/system/spring/SpringComponent.java +++ b/trunk/system/spring/src/main/java/org/wamblee/system/spring/SpringComponent.java @@ -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); + } } } diff --git a/trunk/system/spring/src/test/java/org/wamblee/system/spring/SpringComponentTest.java b/trunk/system/spring/src/test/java/org/wamblee/system/spring/SpringComponentTest.java index c8f8798a..43902d43 100644 --- a/trunk/system/spring/src/test/java/org/wamblee/system/spring/SpringComponentTest.java +++ b/trunk/system/spring/src/test/java/org/wamblee/system/spring/SpringComponentTest.java @@ -47,7 +47,7 @@ public class SpringComponentTest extends TestCase { new HashMap(), new HashMap()); 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()); 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()); } -- 2.31.1