From fc10e15e031cf409146b04cbb5e98b649c6ccddc Mon Sep 17 00:00:00 2001 From: erik Date: Sat, 12 Apr 2008 20:19:09 +0000 Subject: [PATCH] Huge refactoring. Separation of the description/metadata part (Component/Container) and the runtime part (Scope). --- .../org/wamblee/test/EasyMockMatchers.java | 54 ++++++++ .../system/core/AbstractComponent.java | 115 +++++++++--------- .../org/wamblee/system/core/Component.java | 36 +++--- .../org/wamblee/system/core/Container.java | 29 +++-- .../system/core/DefaultProvidedInterface.java | 42 ++----- .../system/core/DefaultRequiredInterface.java | 8 -- .../system/core/ProvidedInterface.java | 25 ++-- .../system/core/RequiredInterface.java | 8 -- .../system/core/AbstractComponentTest.java | 10 +- .../org/wamblee/system/core/Application.java | 21 ++-- .../wamblee/system/core/ContainerTest.java | 45 +++---- .../core/DefaultRequiredInterfaceTest.java | 5 - .../org/wamblee/system/core/Environment.java | 63 +++++----- .../system/spring/RequiredServiceBean.java | 3 +- .../system/spring/SpringComponent.java | 69 ++++++----- .../system/spring/SpringComponentTest.java | 106 ++++++++-------- 16 files changed, 346 insertions(+), 293 deletions(-) create mode 100644 support/general/src/test/java/org/wamblee/test/EasyMockMatchers.java diff --git a/support/general/src/test/java/org/wamblee/test/EasyMockMatchers.java b/support/general/src/test/java/org/wamblee/test/EasyMockMatchers.java new file mode 100644 index 00000000..ef9c5d0f --- /dev/null +++ b/support/general/src/test/java/org/wamblee/test/EasyMockMatchers.java @@ -0,0 +1,54 @@ +/* + * Copyright 2008 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.wamblee.test; + +import org.easymock.IArgumentMatcher; +import org.easymock.classextension.EasyMock; + +/** + * Some general matchers for easy mock. + * + * @author Erik Brakkee + */ +public class EasyMockMatchers { + + public static class Any implements IArgumentMatcher { + public Any() { + // Empty + } + + @Override + public boolean matches(Object aArg0) { + return true; + } + + @Override + public void appendTo(StringBuffer aBuf) { + aBuf.append("anyObject()"); + } + } + + /** + * Type-safe matcher to match any object of a given type. + * @param + * @param aType Type. + * @return Returns null. + */ + public static T anyObject(Class aType) { + EasyMock.reportMatcher(new Any()); + return null; + } +} diff --git a/system/general/src/main/java/org/wamblee/system/core/AbstractComponent.java b/system/general/src/main/java/org/wamblee/system/core/AbstractComponent.java index 01eff741..b8e4e6be 100644 --- a/system/general/src/main/java/org/wamblee/system/core/AbstractComponent.java +++ b/system/general/src/main/java/org/wamblee/system/core/AbstractComponent.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; @@ -27,19 +27,20 @@ import org.apache.commons.logging.LogFactory; /** * Abstract subsystem class making it easy to implement new subsystems. */ -public abstract class AbstractComponent implements Component { +public abstract class AbstractComponent implements Component { private static final Log LOG = LogFactory.getLog(AbstractComponent.class); - private String _context; - private String _name; + private ThreadLocal> _remaining; + + private String _context; + private String _name; private List _provided; private List _required; - private Set _running; - + /** * Constructs the subsystem. - * + * * @param aName * Name of the system. * @param aProvided @@ -49,36 +50,35 @@ public abstract class AbstractComponent implements Component { */ protected AbstractComponent(String aName, ProvidedInterface[] aProvided, RequiredInterface[] aRequired) { - _context = null; + _remaining = new ThreadLocal>(); + _context = null; _name = aName; _provided = new ArrayList(); _provided.addAll(Arrays.asList(aProvided)); _required = new ArrayList(); _required.addAll(Arrays.asList(aRequired)); - _running = new HashSet(); } @Override public final String getName() { return _name; } - + @Override public void addContext(String aContext) { - if (_context == null ) { - _context = aContext; - } - else { + if (_context == null) { + _context = aContext; + } else { _context = aContext + "." + _context; } } - + @Override public String getQualifiedName() { - if ( _context == null ) { - return getName(); + if (_context == null) { + return getName(); } - return _context + "." + getName(); + return _context + "." + getName(); } @Override @@ -92,67 +92,64 @@ public abstract class AbstractComponent implements Component { } @Override - public final void start() { + public final Type start(Scope aScope) { LOG.info("Initializing '" + getQualifiedName() + "'"); - doStart(); - if ( _running.size() != _provided.size()) { - List remaining = - new ArrayList(_provided); - remaining.removeAll(_running); - throw new SystemAssemblyException(getQualifiedName() + ": not all services were started, missing " + remaining); + List oldRemaining = _remaining.get(); + _remaining.set(new ArrayList(Arrays.asList(getProvidedInterfaces()))); + try { + Type runtime = doStart(aScope); + checkNotStartedInterfaces(); + return runtime; + } finally { + _remaining.set(oldRemaining); + } + } + + private void checkNotStartedInterfaces() { + if (_remaining.get().size() > 0) { + String notProvided = ""; + for (ProvidedInterface provided : _remaining.get()) { + notProvided += "\nComponent " + getQualifiedName() + + " did not start interface " + provided; + } + throw new SystemAssemblyException(notProvided); } } /** * Must be implemented for initializing the subsystem. The implementation * must call {@link #addService(Service)} for each service that is started. + * + * @return Returns the runtime of the component. */ - protected abstract void doStart(); + protected abstract Type doStart(Scope aScope); /** * Implementations must call this method to indicate that a new service has * been started. * + * @param aDescriptor + * Provided interface. * @param aService - * Service. + * Implementation of the interface. + * @param aScope + * scope in which to publish the implementation. */ - protected final void addInterface( - ProvidedInterface aDescriptor, Object aService) { - LOG.info("Interface '" + getQualifiedName() + "." + aDescriptor.getName() + "' started."); - _running.add(aDescriptor); - aDescriptor.publish(aService); + protected final void addInterface(ProvidedInterface aDescriptor, + Object aService, Scope aScope) { + LOG.info("Interface '" + getQualifiedName() + "." + + aDescriptor.getName() + "' started."); + _remaining.get().remove(aDescriptor); + aScope.publishInterface(aDescriptor, aService); } @Override - public ProvidedInterface[] getRunningInterfaces() { - return _running.toArray(new ProvidedInterface[0]); - } - - @Override - public void stop() { - doStop(); - if ( _running.size() > 0 ) { - // programming error. - throw new RuntimeException(getQualifiedName() + ": still services running after the stop call."); - } - } - - 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); + public void stop(Type aRuntime) { + doStop(aRuntime); } + protected abstract void doStop(Type aRuntime); + @Override public String toString() { return getQualifiedName(); diff --git a/system/general/src/main/java/org/wamblee/system/core/Component.java b/system/general/src/main/java/org/wamblee/system/core/Component.java index ff80ee69..d14eb08a 100644 --- a/system/general/src/main/java/org/wamblee/system/core/Component.java +++ b/system/general/src/main/java/org/wamblee/system/core/Component.java @@ -17,11 +17,19 @@ package org.wamblee.system.core; /** * A component represents a part of a system that requires a - * number of interfaces and provides a number of interfaces. + * number of interfaces and provides a number of interfaces. + * + * The component interface provides the meta-data for a component. + * After calling {@link #start(Scope)}, an actual runtime representation of the + * component can be created which is independent of this component. + * As a special case, the runtime representation may be identical to the + * component instance but in general it is not. This allows a component to be + * used as a factory for creating objects. + * * * @author Erik Brakkee */ -public interface Component { +public interface Component { /** * Gets the name of the subsystem. @@ -56,22 +64,18 @@ public interface Component { /** - * Initialises the subsytem by starting all the services that - * it described as provided. - */ - void start(); - - /** - * Gets the list of running services in the subsystem. - * - * This method may only be called after the - * {@link #initialize(String, Service[])} has been called. - * @return + * Initialises the subsystem by starting all the services that + * it described as provided. + * @param aScope Scope with external interface implementations that are available. The component + * implementation can either oublish itself in this scope or it can decide to + * create a new scope with the scope passed in as a parent. + * @return Gets an object representing the runtime of the component. */ - ProvidedInterface[] getRunningInterfaces(); + Type start(Scope aScope); /** - * Stops a subsystem. + * Stops a component. + * @param aRuntime THe runtime part of the component. */ - void stop(); + void stop(Type aRuntime); } diff --git a/system/general/src/main/java/org/wamblee/system/core/Container.java b/system/general/src/main/java/org/wamblee/system/core/Container.java index df92c0d0..f1b8b1ec 100644 --- a/system/general/src/main/java/org/wamblee/system/core/Container.java +++ b/system/general/src/main/java/org/wamblee/system/core/Container.java @@ -29,7 +29,7 @@ import org.apache.commons.logging.LogFactory; * * @author Erik Brakkee */ -public class Container extends AbstractComponent { +public class Container extends AbstractComponent { private static final Log LOG = LogFactory.getLog(Container.class); @@ -176,10 +176,18 @@ public class Container extends AbstractComponent { } } } + + public Scope start() { + return super.start(new DefaultScope(new ProvidedInterface[0])); + } @Override - protected void doStart() { + protected Scope doStart(Scope aExternalScope) { LOG.info("Starting '" + getQualifiedName() + "'"); + + Scope scope = new DefaultScope(getProvidedInterfaces(), + aExternalScope); + List allProvided = new ArrayList(); // all interfaces from the required list of this container are @@ -203,7 +211,8 @@ public class Container extends AbstractComponent { checkAllRequiredServicesAlreadyProvided(allProvided, component); // Start the service. - component.start(); + Object runtime = component.start(scope); + scope.addRuntime(component, runtime); started.add(component); // add all provided services @@ -214,19 +223,19 @@ public class Container extends AbstractComponent { } catch (RuntimeException e) { LOG.error(getQualifiedName() + ": could not start '" + component.getQualifiedName() + "'", e); - stopAlreadyStartedComponents(started); + stopAlreadyStartedComponents(started, scope); throw e; } } - + return scope; } - private void stopAlreadyStartedComponents(List aStarted) { + private void stopAlreadyStartedComponents(List aStarted, Scope aScope) { // an exception occurred, stop the successfully started // components for (int i = aStarted.size() - 1; i >= 0; i--) { try { - aStarted.get(i).stop(); + aStarted.get(i).stop(aScope); } catch (Throwable t) { LOG.error(getQualifiedName() + ": error stopping " + aStarted.get(i).getQualifiedName()); @@ -268,9 +277,11 @@ public class Container extends AbstractComponent { } @Override - protected void doStop() { + protected void doStop(Scope aScope) { for (int i = _components.length - 1; i >= 0; i--) { - _components[i].stop(); + Component component = _components[i]; + Object runtime = aScope.getRuntime(component); + component.stop(runtime); } } diff --git a/system/general/src/main/java/org/wamblee/system/core/DefaultProvidedInterface.java b/system/general/src/main/java/org/wamblee/system/core/DefaultProvidedInterface.java index 60c111ed..90c230dc 100644 --- a/system/general/src/main/java/org/wamblee/system/core/DefaultProvidedInterface.java +++ b/system/general/src/main/java/org/wamblee/system/core/DefaultProvidedInterface.java @@ -26,20 +26,20 @@ public class DefaultProvidedInterface implements ProvidedInterface { private String _name; private Class[] _interfaces; - private Object _implementation; + private String _uniqueId; /** * Constructs the descriptor. * @param aInterface Type of service. */ public DefaultProvidedInterface(String aName, Class aInterface) { - _name = aName; - _interfaces = new Class[] { aInterface }; + this(aName, new Class[] { aInterface }); } public DefaultProvidedInterface(String aName, Class[] aInterfaces) { _name = aName; - _interfaces = Arrays.copyOf(aInterfaces, aInterfaces.length); + _interfaces = Arrays.copyOf(aInterfaces, aInterfaces.length); + _uniqueId = null; } @Override @@ -51,40 +51,20 @@ public class DefaultProvidedInterface implements ProvidedInterface { public Class[] getInterfaceTypes() { return _interfaces; } - + @Override - public void publish(Object aImplementation) { - _implementation = aImplementation; + public void setUniqueId(String aId) { + _uniqueId = aId; } @Override - public Object getImplementation() { - return _implementation; + public String getUniqueId() { + return _uniqueId; } @Override - public boolean equals(Object obj) { - if ( !(obj instanceof DefaultProvidedInterface)) { - return false; - } - DefaultProvidedInterface descr = (DefaultProvidedInterface)obj; - if ( _interfaces.length != descr._interfaces.length ) { - return false; - } - String[] interfaces1 = new String[_interfaces.length]; - String[] interfaces2 = new String[_interfaces.length]; - for (int i = 0; i < _interfaces.length; i++) { - interfaces1[i] = _interfaces[i].getName(); - interfaces2[i] = descr._interfaces[i].getName(); - } - Arrays.sort(interfaces1); - Arrays.sort(interfaces2); - return Arrays.equals(interfaces1, interfaces2); - } - - @Override - public int hashCode() { - return _interfaces.hashCode(); + public void publish(Object aImplementation, Scope aScope) { + aScope.publishInterface(this, aImplementation); } @Override diff --git a/system/general/src/main/java/org/wamblee/system/core/DefaultRequiredInterface.java b/system/general/src/main/java/org/wamblee/system/core/DefaultRequiredInterface.java index e028342e..9927b517 100644 --- a/system/general/src/main/java/org/wamblee/system/core/DefaultRequiredInterface.java +++ b/system/general/src/main/java/org/wamblee/system/core/DefaultRequiredInterface.java @@ -92,14 +92,6 @@ public class DefaultRequiredInterface implements RequiredInterface { public void setProvider(ProvidedInterface aProvider) { _provider = aProvider; } - - @Override - public T getImplementation(Class aClass) { - if ( _provider == null ) { - return null; - } - return (T)_provider.getImplementation(); - } @Override public boolean equals(Object obj) { diff --git a/system/general/src/main/java/org/wamblee/system/core/ProvidedInterface.java b/system/general/src/main/java/org/wamblee/system/core/ProvidedInterface.java index 349197d0..fe7f7d26 100644 --- a/system/general/src/main/java/org/wamblee/system/core/ProvidedInterface.java +++ b/system/general/src/main/java/org/wamblee/system/core/ProvidedInterface.java @@ -19,6 +19,7 @@ import java.util.Collection; /** * Represents an interface provided by a component. + * Different component objects should never share ProvidedInterface instances! * * @author Erik Brakkee */ @@ -37,14 +38,24 @@ public interface ProvidedInterface { Class[] getInterfaceTypes(); /** - * Publish an implementation of the interface. - * @param aImplementation + * Sets a unique id of the provided interface to identify it within a given scope. + * Will be called by the container as part of calling {@link #publish(Object, Scope)}. + * @param aId Unique id. */ - void publish(Object aImplementation); - + void setUniqueId(String aId); + + /** + * Gets the unique if of the provided interface. This is set by the container + * using {@link #setUniqueId(String)}. + */ + String getUniqueId(); + /** - * Gets the implementation. - * @return Implementation or null if not started. + * Publishes an implementation of the interface. The implementation must + * call {@link Scope#publishInterface(ProvidedInterface, Object)} to publish the + * interface implementation in a given scope. + * @param aImplementation Implementation to publish. + * @param aScope Scope in which to publish the implementation. */ - Object getImplementation(); + void publish(Object aImplementation, Scope aScope); } diff --git a/system/general/src/main/java/org/wamblee/system/core/RequiredInterface.java b/system/general/src/main/java/org/wamblee/system/core/RequiredInterface.java index bcd2635d..d3a4e522 100644 --- a/system/general/src/main/java/org/wamblee/system/core/RequiredInterface.java +++ b/system/general/src/main/java/org/wamblee/system/core/RequiredInterface.java @@ -46,12 +46,4 @@ public interface RequiredInterface { * @return Provider or null if not set. */ ProvidedInterface getProvider(); - - /** - * Gets the implementation of the required interface. - * @param - * @param aClass Interface type. - * @return Interface implementation or null if not known yet. - */ - T getImplementation(Class aClass); } diff --git a/system/general/src/test/java/org/wamblee/system/core/AbstractComponentTest.java b/system/general/src/test/java/org/wamblee/system/core/AbstractComponentTest.java index e65433be..ebda473d 100644 --- a/system/general/src/test/java/org/wamblee/system/core/AbstractComponentTest.java +++ b/system/general/src/test/java/org/wamblee/system/core/AbstractComponentTest.java @@ -19,23 +19,25 @@ import junit.framework.TestCase; public class AbstractComponentTest extends TestCase { - public void testNotAllComponentsStarted() { + public void testNotAllInterfacesStarted() { try { Component component = new AbstractComponent("xx", new ProvidedInterface[] { new DefaultProvidedInterface( "xxx", String.class) }, new RequiredInterface[0]) { @Override - protected void doStart() { + protected Object doStart(Scope aScope) { // Empty, not starting service. + return null; } @Override - protected void doStop() { + protected void doStop(Object aRuntime) { // Empty. } }; - component.start(); + component.start(new DefaultScope(component.getProvidedInterfaces())); } catch (SystemAssemblyException e) { + //e.printStackTrace(); return; } fail(); diff --git a/system/general/src/test/java/org/wamblee/system/core/Application.java b/system/general/src/test/java/org/wamblee/system/core/Application.java index ca1467cc..07857aee 100644 --- a/system/general/src/test/java/org/wamblee/system/core/Application.java +++ b/system/general/src/test/java/org/wamblee/system/core/Application.java @@ -34,10 +34,12 @@ public class Application extends AbstractComponent { private EventTracker _tracker; private String _string; - private Integer _integer; + private Integer _integer; + private double _random; public Application() { - super("application", new ProvidedInterface[0], required(false)); + super("application", new ProvidedInterface[0], required(false)); + _random = Math.random(); } public Application(boolean aIsOptinal) { @@ -50,10 +52,11 @@ public class Application extends AbstractComponent { } @Override - protected void doStart() { + protected Object doStart(Scope aScope) { track("start." + getName()); - _string = getRequiredInterfaces()[0].getImplementation(String.class); - _integer = getRequiredInterfaces()[1].getImplementation(Integer.class); + _string = aScope.retrieveInterfaceImplementation(getRequiredInterfaces()[0].getProvider(), String.class); + _integer = aScope.retrieveInterfaceImplementation(getRequiredInterfaces()[1].getProvider(), Integer.class); + return _random; } public String getString() { @@ -65,8 +68,12 @@ public class Application extends AbstractComponent { } @Override - protected void doStop() { - track("stop." + getName()); + protected void doStop(Object aRuntime) { + track("stop." + getName()); + if ( _random != (Double)aRuntime) { + throw new IllegalArgumentException("Wrong runtime: expected " + _random + " but got " + + aRuntime); + } } private void track(String aString) { diff --git a/system/general/src/test/java/org/wamblee/system/core/ContainerTest.java b/system/general/src/test/java/org/wamblee/system/core/ContainerTest.java index bcca8cc1..74eb4310 100644 --- a/system/general/src/test/java/org/wamblee/system/core/ContainerTest.java +++ b/system/general/src/test/java/org/wamblee/system/core/ContainerTest.java @@ -24,6 +24,7 @@ import org.easymock.classextension.ConstructorArgs; import org.easymock.classextension.EasyMock; import org.easymock.classextension.IMocksControl; import org.wamblee.test.AssertionUtils; +import org.wamblee.test.EasyMockMatchers; import org.wamblee.test.EventTracker; public class ContainerTest extends TestCase { @@ -83,14 +84,11 @@ public class ContainerTest extends TestCase { environment, application }, new ProvidedInterface[0], new RequiredInterface[0]); - container.start(); + Scope scope = container.start(); AssertionUtils.assertEquals(new String[] { "start.environment", "start.application" }, _tracker.getEvents( Thread.currentThread()).toArray(new String[0])); - ProvidedInterface[] envServices = environment.getRunningInterfaces(); - assertEquals(2, envServices.length); - ProvidedInterface[] appServices = application.getRunningInterfaces(); - assertEquals(0, appServices.length); + assertEquals(0, scope.getProvidedInterfaces().length); assertEquals(environment.getString(), application.getString()); assertEquals(environment.getInteger(), application.getInteger()); @@ -120,7 +118,7 @@ public class ContainerTest extends TestCase { Container system = new Container("all", new Component[] { environment, application }, new ProvidedInterface[0], new RequiredInterface[0]); - system.start(); + Scope runtime = system.start(); RequiredInterface[] required = system.getRequiredInterfaces(); assertEquals(0, required.length); ProvidedInterface[] provided = system.getProvidedInterfaces(); @@ -131,7 +129,7 @@ public class ContainerTest extends TestCase { Thread.currentThread()).toArray(new String[0])); _tracker.clear(); - system.stop(); + system.stop(runtime); AssertionUtils.assertEquals(new String[] { "stop.application", "stop.environment" }, _tracker .getEvents(Thread.currentThread()).toArray(new String[0])); @@ -206,7 +204,7 @@ public class ContainerTest extends TestCase { Container system = new Container("all", new Component[] { application }, new ProvidedInterface[0], application.getRequiredInterfaces()); - environment.start(); + environment.start(new DefaultScope(new ProvidedInterface[0])); system.getRequiredInterfaces()[0].setProvider(environment .getProvidedInterfaces()[0]); system.getRequiredInterfaces()[1].setProvider(environment @@ -256,9 +254,9 @@ public class ContainerTest extends TestCase { Environment environment = new Environment(_tracker); Application application = control.createMock(Application.class, new ConstructorArgs(Application.class.getConstructor()), - Application.class.getDeclaredMethod("doStart")); + Application.class.getDeclaredMethod("doStart", Scope.class)); - application.doStart(); + application.doStart(EasyMockMatchers.anyObject(Scope.class)); EasyMock.expectLastCall().andThrow(new RuntimeException()); control.replay(); @@ -272,10 +270,6 @@ public class ContainerTest extends TestCase { AssertionUtils.assertEquals(new String[] { "start.environment", "stop.environment" }, _tracker.getEvents( Thread.currentThread()).toArray(new String[0])); - ProvidedInterface[] envServices = environment.getRunningInterfaces(); - assertEquals(0, envServices.length); - assertNull(environment.getProvidedInterfaces()[0].getImplementation()); - assertNull(environment.getProvidedInterfaces()[0].getImplementation()); return; } fail(); @@ -289,17 +283,17 @@ public class ContainerTest extends TestCase { // Application 1 will throw an exception while stopping. Application application1 = control.createMock(Application.class, new ConstructorArgs(Application.class.getConstructor()), - Application.class.getDeclaredMethod("doStop")); + Application.class.getDeclaredMethod("doStop", Object.class)); - application1.doStop(); + application1.doStop(EasyMock.anyObject()); 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")); + Application.class.getDeclaredMethod("doStart", Scope.class)); - application2.doStart(); + application2.doStart(EasyMockMatchers.anyObject(Scope.class)); EasyMock.expectLastCall().andThrow(new RuntimeException()); control.replay(); @@ -314,10 +308,6 @@ public class ContainerTest extends TestCase { AssertionUtils.assertEquals(new String[] { "start.environment", "stop.environment" }, _tracker.getEvents( Thread.currentThread()).toArray(new String[0])); - ProvidedInterface[] envServices = environment.getRunningInterfaces(); - assertEquals(0, envServices.length); - assertNull(environment.getProvidedInterfaces()[0].getImplementation()); - assertNull(environment.getProvidedInterfaces()[0].getImplementation()); return; } fail(); @@ -332,7 +322,10 @@ public class ContainerTest extends TestCase { env.getProvidedInterfaces()[0]); container.getRequiredInterfaces()[1].setProvider( env.getProvidedInterfaces()[1]); - container.start(); + Scope external = new DefaultScope(env.getProvidedInterfaces()); + env.start(external); + + container.start(external); assertSame(env.getProvidedInterfaces()[0], container.getRequiredInterfaces()[0].getProvider()); assertSame(env.getProvidedInterfaces()[1], container.getRequiredInterfaces()[1].getProvider()); assertSame(env.getProvidedInterfaces()[0], application.getRequiredInterfaces()[0].getProvider()); @@ -345,8 +338,10 @@ public class ContainerTest extends TestCase { new ProvidedInterface[0], Application.required(true)); Environment env = new Environment(); container.getRequiredInterfaces()[0].setProvider( - env.getProvidedInterfaces()[0]); - container.start(); + env.getProvidedInterfaces()[0]); + Scope external = new DefaultScope(new ProvidedInterface[0]); + external.publishInterface(env.getProvidedInterfaces()[0], env.getString()); + container.start(external); assertSame(env.getProvidedInterfaces()[0], container.getRequiredInterfaces()[0].getProvider()); assertNull(container.getRequiredInterfaces()[1].getProvider()); assertSame(env.getProvidedInterfaces()[0], application.getRequiredInterfaces()[0].getProvider()); diff --git a/system/general/src/test/java/org/wamblee/system/core/DefaultRequiredInterfaceTest.java b/system/general/src/test/java/org/wamblee/system/core/DefaultRequiredInterfaceTest.java index a7abee5d..1c56070c 100644 --- a/system/general/src/test/java/org/wamblee/system/core/DefaultRequiredInterfaceTest.java +++ b/system/general/src/test/java/org/wamblee/system/core/DefaultRequiredInterfaceTest.java @@ -38,9 +38,4 @@ public class DefaultRequiredInterfaceTest extends TestCase { new DefaultRequiredInterface("a", new Class[]{ String.class, Integer.class}))); } - - public void testGetImplementation() { - RequiredInterface required = new DefaultRequiredInterface("hello", String.class); - assertNull(required.getImplementation(String.class)); - } } diff --git a/system/general/src/test/java/org/wamblee/system/core/Environment.java b/system/general/src/test/java/org/wamblee/system/core/Environment.java index 0f164b62..00d947b7 100644 --- a/system/general/src/test/java/org/wamblee/system/core/Environment.java +++ b/system/general/src/test/java/org/wamblee/system/core/Environment.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 javax.sql.DataSource; @@ -23,52 +23,55 @@ import org.wamblee.system.core.ProvidedInterface; import org.wamblee.system.core.RequiredInterface; import org.wamblee.test.EventTracker; - public class Environment extends AbstractComponent { - - private static final ProvidedInterface[] provided() { - return new ProvidedInterface[] { - new DefaultProvidedInterface("datasource", String.class), - new DefaultProvidedInterface("integer", Integer.class) - }; + + private static final ProvidedInterface[] provided() { + return new ProvidedInterface[] { + new DefaultProvidedInterface("datasource", String.class), + new DefaultProvidedInterface("integer", Integer.class) }; } - - private EventTracker _tracker; - - public Environment() { + + private EventTracker _tracker; + private double _random; + + public Environment() { super("environment", provided(), new RequiredInterface[0]); + _random = Math.random(); } - - public Environment(EventTracker aTracker) { + + public Environment(EventTracker aTracker) { this(); - _tracker = aTracker; + _tracker = aTracker; } - - public Integer getInteger() { + + public Integer getInteger() { return 2; } - - public String getString() { + + public String getString() { return "Hello"; } - + @Override - protected void doStart() { - addInterface(getProvidedInterfaces()[0], getString()); - addInterface(getProvidedInterfaces()[1], getInteger()); - track("start." + getName()); + protected Object doStart(Scope aScope) { + addInterface(getProvidedInterfaces()[0], getString(), aScope); + addInterface(getProvidedInterfaces()[1], getInteger(), aScope); + track("start." + getName()); + return _random; } @Override - protected void doStop() { + protected void doStop(Object aRuntime) { track("stop." + getName()); - removeInterface(getProvidedInterfaces()[0]); - removeInterface(getProvidedInterfaces()[1]); + if (_random != (Double) aRuntime) { + throw new IllegalArgumentException("Wrong runtime: expected " + + _random + " but got " + aRuntime); + } } - + private void track(String aString) { - if ( _tracker == null ) { - return; + if (_tracker == null) { + return; } _tracker.eventOccurred(aString); } diff --git a/system/spring/src/main/java/org/wamblee/system/spring/RequiredServiceBean.java b/system/spring/src/main/java/org/wamblee/system/spring/RequiredServiceBean.java index 6c18cc8c..55433a9c 100644 --- a/system/spring/src/main/java/org/wamblee/system/spring/RequiredServiceBean.java +++ b/system/spring/src/main/java/org/wamblee/system/spring/RequiredServiceBean.java @@ -47,7 +47,8 @@ class RequiredServiceBean implements FactoryBean { @Override public Object getObject() throws Exception { - return _required.getImplementation(Object.class); + return SpringComponent.SCOPE.get().retrieveInterfaceImplementation( + _required.getProvider(), Object.class); } @Override diff --git a/system/spring/src/main/java/org/wamblee/system/spring/SpringComponent.java b/system/spring/src/main/java/org/wamblee/system/spring/SpringComponent.java index 8d50bf28..aea9b15a 100644 --- a/system/spring/src/main/java/org/wamblee/system/spring/SpringComponent.java +++ b/system/spring/src/main/java/org/wamblee/system/spring/SpringComponent.java @@ -26,8 +26,10 @@ import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.wamblee.system.core.AbstractComponent; +import org.wamblee.system.core.DefaultScope; import org.wamblee.system.core.ProvidedInterface; import org.wamblee.system.core.RequiredInterface; +import org.wamblee.system.core.Scope; import org.wamblee.system.core.SystemAssemblyException; /** @@ -36,23 +38,17 @@ import org.wamblee.system.core.SystemAssemblyException; * * @author Erik Brakkee */ -public class SpringComponent extends AbstractComponent { +public class SpringComponent extends AbstractComponent { + + private static final String CONTEXT_KEY = "context"; static final ThreadLocal THIS = new ThreadLocal(); + static final ThreadLocal SCOPE = new ThreadLocal(); private Properties _properties; private String[] _configFiles; private Map _provided; private Map _required; - /** - * Parent application context containing required services. - */ - private GenericApplicationContext _parentContext; - - /** - * Application context containing parsed objects. - */ - private AbstractApplicationContext _context; /** * Constructs a spring system. @@ -98,55 +94,68 @@ public class SpringComponent extends AbstractComponent { setProperty((String) key, aProperties.getProperty((String) key)); } } + + public Scope start() { + return super.start(new DefaultScope(new ProvidedInterface[0])); + } @Override - protected void doStart() { + protected Scope doStart(Scope aExternalScope) { SpringComponent old = THIS.get(); + Scope oldScope = SCOPE.get(); THIS.set(this); + Scope scope = new DefaultScope(getProvidedInterfaces(), aExternalScope); + SCOPE.set(scope); try { - _parentContext = new GenericApplicationContext(); + GenericApplicationContext parentContext = new GenericApplicationContext(); - registerRequiredServices(); + registerRequiredServices(parentContext); - _parentContext.refresh(); + parentContext.refresh(); - parseConfigFiles(); + AbstractApplicationContext context = parseConfigFiles(parentContext); - _context + context .addBeanFactoryPostProcessor(new PropertySetter(_properties)); - _context.refresh(); + context.refresh(); - exposeProvidedServices(); + exposeProvidedServices(context, scope); + + scope.put(CONTEXT_KEY, context); + return scope; } catch (Exception e) { throw new SystemAssemblyException( "Failed to assemble spring system", e); } finally { THIS.set(old); + SCOPE.set(oldScope); } } - private void exposeProvidedServices() { + private void exposeProvidedServices(AbstractApplicationContext aContext, Scope aScope) { // Call addService for each provided service. for (String name : _provided.keySet()) { - Object svc = _context.getBean(name); + Object svc = aContext.getBean(name); if (svc == null) { throw new IllegalArgumentException(getQualifiedName() + ": service '" + name + "' is null"); } - addInterface(_provided.get(name), svc); + addInterface(_provided.get(name), svc, aScope); + System.out.println("addService " + _provided.get(name) + " " + svc); + aScope.publishInterface(_provided.get(name), svc); } } - private void parseConfigFiles() { + private AbstractApplicationContext parseConfigFiles(GenericApplicationContext aParentContext) { // Parse spring config files - _context = new ClassPathXmlApplicationContext((String[]) _configFiles, - _parentContext); + return new ClassPathXmlApplicationContext((String[]) _configFiles, + aParentContext); } - private void registerRequiredServices() { + private void registerRequiredServices(GenericApplicationContext aParentContext) { // Register required services in a parent context for (RequiredInterface required: getRequiredInterfaces()) { String beanName = _required.get(required); @@ -155,15 +164,13 @@ public class SpringComponent extends AbstractComponent { BeanDefinition definition = new RootBeanDefinition( RequiredServiceBean.class, cargs, new MutablePropertyValues()); - _parentContext.registerBeanDefinition(beanName, definition); + aParentContext.registerBeanDefinition(beanName, definition); } } @Override - protected void doStop() { - _context.close(); - for (ProvidedInterface provided: getProvidedInterfaces()) { - removeInterface(provided); - } + protected void doStop(Scope aRuntime) { + AbstractApplicationContext context = (AbstractApplicationContext)aRuntime.get(CONTEXT_KEY); + context.close(); } } diff --git a/system/spring/src/test/java/org/wamblee/system/spring/SpringComponentTest.java b/system/spring/src/test/java/org/wamblee/system/spring/SpringComponentTest.java index 43902d43..0914c9aa 100644 --- a/system/spring/src/test/java/org/wamblee/system/spring/SpringComponentTest.java +++ b/system/spring/src/test/java/org/wamblee/system/spring/SpringComponentTest.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.spring; import java.io.IOException; @@ -25,8 +25,10 @@ import junit.framework.TestCase; import org.wamblee.io.ClassPathResource; import org.wamblee.system.core.DefaultProvidedInterface; import org.wamblee.system.core.DefaultRequiredInterface; +import org.wamblee.system.core.DefaultScope; import org.wamblee.system.core.ProvidedInterface; import org.wamblee.system.core.RequiredInterface; +import org.wamblee.system.core.Scope; import org.wamblee.system.core.SystemAssemblyException; public class SpringComponentTest extends TestCase { @@ -34,7 +36,7 @@ public class SpringComponentTest extends TestCase { private static final String HELLO_SERVICE_SPRING_XML = "test.org.wamblee.system.spring.xml"; private static final String HELLO_SERVICE_SPRING_WITH_REQS_XML = "test.org.wamblee.system.springWithRequirements.xml"; private static final String HELLO_SERVICE_SPRING_WITH_PROPERTIES_XML = "test.org.wamblee.system.springWithProperties.xml"; - private static final String PROPERTY_FILE = "test.org.wamblee.system.spring.properties"; + private static final String PROPERTY_FILE = "test.org.wamblee.system.spring.properties"; @Override protected void setUp() throws Exception { @@ -46,46 +48,43 @@ public class SpringComponentTest extends TestCase { new String[] { HELLO_SERVICE_SPRING_XML }, new HashMap(), new HashMap()); - system.start(); - ProvidedInterface[] services = system.getRunningInterfaces(); - assertEquals(0, services.length); - - system.stop(); + Scope runtime = system.start(); + assertEquals(0, runtime.getProvidedInterfaces().length); + + system.stop(runtime); } public void testOneProvidedService() { Map provided = new HashMap(); - provided.put("helloService", new DefaultProvidedInterface( - "hello", HelloService.class)); + provided.put("helloService", new DefaultProvidedInterface("hello", + HelloService.class)); - SpringComponent system = new SpringComponent("system", + SpringComponent system = new SpringComponent("system", new String[] { HELLO_SERVICE_SPRING_XML }, provided, new HashMap()); - system.start(); - ProvidedInterface[] services = system.getRunningInterfaces(); + Scope runtime = system.start(); + ProvidedInterface[] services = runtime.getProvidedInterfaces(); assertEquals(1, services.length); - assertTrue(services[0].getImplementation() instanceof HelloService); - assertEquals("Hello world!", ((HelloService)services[0].getImplementation()) - .say()); - system.stop(); + Object service = runtime.retrieveInterfaceImplementation(services[0], Object.class); + assertTrue(service instanceof HelloService); + assertEquals("Hello world!", ((HelloService) service).say()); + system.stop(runtime); } - + public void testWithProperties() throws IOException { Map provided = new HashMap(); - provided.put("helloService", new DefaultProvidedInterface( - "hello", HelloService.class)); - SpringComponent system = new SpringComponent("system", + provided.put("helloService", new DefaultProvidedInterface("hello", + HelloService.class)); + SpringComponent system = new SpringComponent("system", new String[] { HELLO_SERVICE_SPRING_WITH_PROPERTIES_XML }, - provided, - new HashMap()); + provided, new HashMap()); Properties props = new Properties(); props.load(new ClassPathResource(PROPERTY_FILE).getInputStream()); system.addProperties(props); - - system.start(); - ProvidedInterface[] services = system.getRunningInterfaces(); - assertEquals("Property Value", - ((HelloService)services[0].getImplementation()).say()); + + Scope scope = system.start(); + ProvidedInterface[] services = scope.getProvidedInterfaces(); + assertEquals("Property Value", scope.retrieveInterfaceImplementation(services[0], HelloService.class).say()); } public void testWithMissingRequirement() { @@ -96,7 +95,7 @@ public class SpringComponentTest extends TestCase { new HashMap()); system.start(); } catch (SystemAssemblyException e) { - //e.printStackTrace(); + // e.printStackTrace(); return; } fail(); @@ -109,41 +108,44 @@ public class SpringComponentTest extends TestCase { SpringComponent system = new SpringComponent("system", new String[] { HELLO_SERVICE_SPRING_WITH_REQS_XML }, new HashMap(), required); - - HelloService helloObject = new HelloService("ladida"); - ProvidedInterface helloService = new DefaultProvidedInterface("hello", HelloService.class); - helloService.publish(helloObject); + + HelloService helloObject = new HelloService("ladida"); + ProvidedInterface helloService = new DefaultProvidedInterface("hello", + HelloService.class); + Scope scope = new DefaultScope(new ProvidedInterface[]{ helloService }); + scope.publishInterface(helloService, helloObject); system.getRequiredInterfaces()[0].setProvider(helloService); - - system.start(); - system.stop(); + + Scope runtime = system.start(scope); + system.stop(runtime); } - + public void testWithRequirementAndProvidedService() { Map required = new HashMap(); required.put(new DefaultRequiredInterface("hello", HelloService.class), "helloService"); - Map provided = new HashMap(); + Map provided = new HashMap(); provided.put("blaService", new DefaultProvidedInterface("bla", BlaService.class)); SpringComponent system = new SpringComponent("system", - new String[] { HELLO_SERVICE_SPRING_WITH_REQS_XML }, - provided, required); - - HelloService helloObject = new HelloService("ladida"); - ProvidedInterface helloService = - new DefaultProvidedInterface("hello", HelloService.class); - helloService.publish(helloObject); + new String[] { HELLO_SERVICE_SPRING_WITH_REQS_XML }, provided, + required); + + HelloService helloObject = new HelloService("ladida"); + ProvidedInterface helloService = new DefaultProvidedInterface("hello", + HelloService.class); + Scope scope = new DefaultScope(new ProvidedInterface[] { helloService }); + scope.publishInterface(helloService, helloObject); system.getRequiredInterfaces()[0].setProvider(helloService); - system.start(); - ProvidedInterface started = system.getProvidedInterfaces()[0]; - - assertNotNull(started.getImplementation()); - assertTrue(started.getImplementation() instanceof BlaService); - assertEquals("ladida", - ((BlaService)started.getImplementation()).execute()); - system.stop(); + Scope runtime = system.start(scope); + ProvidedInterface started = runtime.getProvidedInterfaces()[0]; + + Object impl = runtime.retrieveInterfaceImplementation(started, BlaService.class); + assertNotNull(impl); + assertTrue(impl instanceof BlaService); + assertEquals("ladida", ((BlaService)impl).execute()); + system.stop(runtime); } } -- 2.31.1