Implemented optinality of interfaces.
authorerik <erik@77661180-640e-0410-b3a8-9f9b13e6d0e0>
Fri, 11 Apr 2008 20:22:46 +0000 (20:22 +0000)
committererik <erik@77661180-640e-0410-b3a8-9f9b13e6d0e0>
Fri, 11 Apr 2008 20:22:46 +0000 (20:22 +0000)
system/general/src/main/java/org/wamblee/system/core/AbstractComponent.java
system/general/src/main/java/org/wamblee/system/core/Container.java
system/general/src/main/java/org/wamblee/system/core/DefaultRequiredInterface.java
system/general/src/main/java/org/wamblee/system/core/RequiredInterface.java
system/general/src/test/java/org/wamblee/system/core/Application.java
system/general/src/test/java/org/wamblee/system/core/ContainerTest.java

index 9bad650d23cb47d42da9cb29dec850b166aafcff..b202f285412040a8a42b13bc9aba4a3684f26636 100644 (file)
@@ -164,7 +164,7 @@ public abstract class AbstractComponent implements Component {
 
        @Override
        public String toString() {
-               return _name;
+               return getQualifiedName();
        }
 
 }
index 8436ebca4174ba7090966756a5b6b2e5b8923ea5..81dcb6a2c95bc106ce2195e384eb7c521d07b5c6 100644 (file)
@@ -18,6 +18,7 @@ package org.wamblee.system.core;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 
 import org.apache.commons.logging.Log;
@@ -78,7 +79,7 @@ public class Container extends AbstractComponent {
                for (Component component : aSystems) {
                        component.addContext(getQualifiedName());
                }
-               validate(aRequired);
+               validate();
        }
 
        /**
@@ -87,7 +88,7 @@ public class Container extends AbstractComponent {
         * that cannot be provided. Also logs a warning in case of superfluous
         * requirements.
         */
-       private void validate(RequiredInterface[] aRequired) {
+       private void validate() {
                List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
                for (Component system : _systems) {
                        provided.addAll(Arrays.asList(system.getProvidedInterfaces()));
@@ -98,79 +99,105 @@ public class Container extends AbstractComponent {
                        required.addAll(Arrays.asList(system.getRequiredInterfaces()));
                }
 
-               for (ProvidedInterface service : getProvidedInterfaces()) {
-                       if (!(provided.contains(service))) {
-                               throw new SystemAssemblyException(getName() + ": Service '"
-                                               + service
-                                               + "' is not provided by any of the subsystems");
-                       }
-               }
+               validateProvidedInterfaces(provided);
 
-               for (RequiredInterface service : getRequiredInterfaces()) {
-                       if (!(required.contains(service))) {
-                               info("Service '"
-                                               + service
-                                               + "' indicated as required is not actually required by any of the subsystems");
-                       }
+               validateRequiredInterfaces(required);
+
+               List<RequiredInterface> reallyRequired = validateRequiredProvidedMatch(
+                               provided, required);
+               
+               String missingRequired = "";
+               for (RequiredInterface service : reallyRequired) {
+                       missingRequired += service + "\n";
+               }
+               if (missingRequired.length() > 0) {
+                       throw new SystemAssemblyException(getName()
+                                       + ": missing required services\n" + missingRequired);
                }
+       }
 
+       private List<RequiredInterface> validateRequiredProvidedMatch(
+                       List<ProvidedInterface> provided, List<RequiredInterface> required) {
                List<RequiredInterface> reallyRequired = new ArrayList<RequiredInterface>(
                                required);
                // Compute all required interfaces that are not provided
+
                for (ProvidedInterface service : provided) {
                        List<RequiredInterface> fulfilled = Arrays
                                        .asList(filterRequiredServices(service, reallyRequired));
                        reallyRequired.removeAll(fulfilled);
                }
+               // Now remove all optional interfaces from the list.
+               for (Iterator<RequiredInterface> i = 
+                       reallyRequired.iterator(); i.hasNext(); ) { 
+                       RequiredInterface req = i.next();
+                       if ( req.isOptional() ) {
+                               i.remove(); 
+                       }
+               }
                // Now the remaining interfaces should be covered by the required
                // list.
-               reallyRequired.removeAll(Arrays.asList(aRequired));
+               reallyRequired.removeAll(Arrays.asList(getRequiredInterfaces()));
+               return reallyRequired;
+       }
 
-               String missingRequired = "";
-               for (RequiredInterface service : reallyRequired) {
-                       missingRequired += service + "\n";
+       private void validateRequiredInterfaces(List<RequiredInterface> required) {
+               for (RequiredInterface service : getRequiredInterfaces()) {
+                       // TODO required services by the subsystem could be 
+                       //      subclasses or implementations of the requirements
+                       //      of the contained systems. The code below assumes
+                       //      an exact match. 
+                       if (!(required.contains(service))) {
+                               info("Service '"
+                                               + service
+                                               + "' indicated as required is not actually required by any of the subsystems");
+                       }
+                       // Check for the case that the externally required service
+                       // is optional whereas the internally required service is 
+                       // mandatory. 
+                       if ( service.isOptional()) { 
+                               for (RequiredInterface intf: required) { 
+                                       if ( intf.equals(service) && !intf.isOptional()) { 
+                                               // TODO indicate which subsystem this is. 
+                                               warn("Required service '" + service + "' indicated as optional is mandatory by one of its subsystems (" + getClients(intf) + ", " + intf + "), this can lead to problems when the system is started and the service is not there.");
+                                       }
+                               }
+                       }
                }
-               if (missingRequired.length() > 0) {
-                       throw new SystemAssemblyException(getName()
-                                       + ": missing required services\n" + missingRequired);
+       }
+
+       private void validateProvidedInterfaces(List<ProvidedInterface> provided) {
+               for (ProvidedInterface service : getProvidedInterfaces()) {
+                       // TODO provided interfaces by subsystems could be 
+                       //      provide subclasses or implementations of the 
+                       //      provided interfaces of the container.
+                       //      The code below assumes an exact match. 
+                       if (!(provided.contains(service))) {
+                               throw new SystemAssemblyException(getName() + ": Service '"
+                                               + service
+                                               + "' is not provided by any of the subsystems");
+                       }
                }
        }
 
        @Override
        protected void doStart() {
-               List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
+               LOG.info("Starting '" + getQualifiedName() + "'");
+               List<ProvidedInterface> allProvided = 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");
+                       if (provider != null ) { 
+                               allProvided.add(provider);
+                       } else { 
+                               if ( !intf.isOptional()) { 
+                                       throw new SystemAssemblyException(getQualifiedName()
+                                                       + ": required interface '" + intf + "' is not provided");               
+                               }
                        }
-                       provided.add(intf.getProvider());
-               }
-
-               startImpl();
-       }
-
-       /**
-        * Starts the subsystems.
-        * 
-        * @param aRequiredServices
-        *            Services that are available from other systems that have been
-        *            started before.
-        */
-       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
-               // interfaces
-               for (RequiredInterface required : getRequiredInterfaces()) {
-                       allProvided.add(required.getProvider());
                }
 
                List<Component> started = new ArrayList<Component>();
@@ -179,21 +206,13 @@ public class Container extends AbstractComponent {
                                // Check if all required services are already provided by
                                // earlier
                                // systems.
-                               RequiredInterface[] required = system.getRequiredInterfaces();
 
-                               for (RequiredInterface descriptor : required) {
+                               for (RequiredInterface descriptor : system.getRequiredInterfaces()) {
                                        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) {
+                                       if ( filtered.length == 1 ) { 
+                                               descriptor.setProvider(filtered[0]);
+                                       } else if ( filtered.length > 1 ) { 
                                                throw new SystemAssemblyException(
                                                                "Service '"
                                                                                + descriptor
@@ -201,8 +220,17 @@ public class Container extends AbstractComponent {
                                                                                + system
                                                                                + "' matches multiple services provided by other systems: "
                                                                                + Arrays.asList(filtered));
+                                       } else { 
+                                               // filtered.length == 0
+                                               if ( !descriptor.isOptional()) { 
+                                                       throw new SystemAssemblyException(
+                                                                       "Service '"
+                                                                                       + descriptor
+                                                                                       + "' required by system '"
+                                                                                       + system
+                                                                                       + "' is not provided by systems that are started earlier");     
+                                               }
                                        }
-                                       descriptor.setProvider(filtered[0]);
                                }
 
                                // Start the service.
@@ -241,7 +269,22 @@ public class Container extends AbstractComponent {
        }
 
        private void info(String aMsg) {
-               LOG.info(getName() + ": " + aMsg);
+               LOG.info(getQualifiedName() + ": " + aMsg);
+       }
+       
+       private void warn(String aMsg) {
+               LOG.warn(getQualifiedName() + ": " + aMsg);
        }
 
+       private List<Component> getClients(RequiredInterface aRequirement) {
+               List<Component> clients = new ArrayList<Component>();
+               for (Component component: _systems) { 
+                       for (RequiredInterface required: component.getRequiredInterfaces()) { 
+                               if ( required.equals(aRequirement) && required.isOptional() == aRequirement.isOptional()) { 
+                                       clients.add(component);
+                               }
+                       }
+               }
+               return clients; 
+       }
 }
index 32276a25cc26d7237e479f379b5b94309773e3ae..e028342e9d2fa98528ec04e7ccc2dfd90ce59e39 100644 (file)
@@ -20,6 +20,7 @@ import java.util.Arrays;
 public class DefaultRequiredInterface implements RequiredInterface {
 
        private String _name;
+       private boolean _optional; 
        private Class[] _required;
        private ProvidedInterface _provider; 
        
@@ -28,7 +29,17 @@ public class DefaultRequiredInterface implements RequiredInterface {
        }
 
        public DefaultRequiredInterface(String aName, Class[] aInterfaces) {
+               this(aName, aInterfaces, false); 
+       }
+       
+       public DefaultRequiredInterface(String aName, Class aInterface, boolean aIsOptional) {
+           this(aName, new Class[] { aInterface }, aIsOptional );
+       }
+
+       
+       public DefaultRequiredInterface(String aName, Class[] aInterfaces, boolean aIsOptional) {
                _name = aName; 
+               _optional = aIsOptional; 
                _required = aInterfaces; 
        }
 
@@ -37,6 +48,11 @@ public class DefaultRequiredInterface implements RequiredInterface {
                return _name;
        }
        
+       @Override
+       public boolean isOptional() {
+               return _optional; 
+       }
+       
        @Override
        public boolean implementedBy(ProvidedInterface aDescriptor) {
                Class[] provided = aDescriptor.getInterfaceTypes();
index ce8d084cdbaa2b11fcd25c65c7c194a871f64b1a..bcd2635d6cdc6e19742edd2789d9a0c6dcbd0d7b 100644 (file)
@@ -20,7 +20,13 @@ public interface RequiredInterface {
        /**
         * Name for the interface. 
         */
-       public String getName(); 
+       String getName();
+       
+       /**
+        * @return True iff the required interface is optional. 
+        */
+       boolean isOptional(); 
+       
        
        /**
         * Checks if the service is provided by a given provided interface. 
index 938f2bd1d50e7e969d46e71d97b778f81a3573ee..16d1f36f0187b273810ae010dfaa8f77fb994b1c 100644 (file)
@@ -24,18 +24,22 @@ import org.wamblee.system.core.RequiredInterface;
 import org.wamblee.test.EventTracker;
 
 public class Application extends AbstractComponent {
-       private static RequiredInterface[] required() {
+       public static RequiredInterface[] required(boolean aOptional) {
                return
                new RequiredInterface[] { 
-                       new DefaultRequiredInterface("datasource", DataSource.class), 
-                       new DefaultRequiredInterface("integer", Integer.class)
+                       new DefaultRequiredInterface("datasource", DataSource.class, aOptional), 
+                       new DefaultRequiredInterface("integer", Integer.class, aOptional)
        };
        }
 
        private EventTracker<String> _tracker;
        
        public Application() {
-               super("application", new ProvidedInterface[0], required()); 
+               super("application", new ProvidedInterface[0], required(false)); 
+       }
+       
+       public Application(boolean aIsOptinal) { 
+               super("application", new ProvidedInterface[0], required(true)); 
        }
        
        public Application(EventTracker<String> aTracker) { 
index 5a7fa90bf1c4137af659cae507d726b70e6eb858..27a1937ff59f4f7e845022f1bb056397d3c3aa20 100644 (file)
@@ -338,4 +338,50 @@ public class ContainerTest extends TestCase {
                }
                fail();
        }
+       
+       public void testOptionalRequiredInterfaceProvidedOptionalInternal() {
+               Application application = new Application(true);
+               Container container = new Container("top", new Component[] { application }, 
+                               new ProvidedInterface[0], Application.required(true));
+               Environment env = new Environment();
+               container.getRequiredInterfaces()[0].setProvider(
+                               env.getProvidedInterfaces()[0]); 
+               container.getRequiredInterfaces()[1].setProvider(
+                               env.getProvidedInterfaces()[1]);
+           container.start();
+           assertSame(env.getProvidedInterfaces()[0], container.getRequiredInterfaces()[0].getProvider());
+           assertSame(env.getProvidedInterfaces()[1], container.getRequiredInterfaces()[1].getProvider());
+           assertSame(env.getProvidedInterfaces()[0], application.getRequiredInterfaces()[0].getProvider());
+           assertSame(env.getProvidedInterfaces()[1], application.getRequiredInterfaces()[1].getProvider());
+       }
+       
+       public void testOptionalRequiredInterfaceNotProvidedOptionalInternal() {
+               Application application = new Application(true);
+               Container container = new Container("top", new Component[] { application }, 
+                               new ProvidedInterface[0], Application.required(true));
+               Environment env = new Environment();
+               container.getRequiredInterfaces()[0].setProvider(
+                               env.getProvidedInterfaces()[0]); 
+           container.start();
+           assertSame(env.getProvidedInterfaces()[0], container.getRequiredInterfaces()[0].getProvider());
+           assertNull(container.getRequiredInterfaces()[1].getProvider());
+           assertSame(env.getProvidedInterfaces()[0], application.getRequiredInterfaces()[0].getProvider());
+           assertNull(application.getRequiredInterfaces()[1].getProvider());
+       }
+       
+       public void testOptionalRequiredInterfaceProvidedMandatoryInternal() { 
+               Application application = new Application();
+               Container container = new Container("top", new Component[] { application }, 
+                               new ProvidedInterface[0], Application.required(true));
+               Environment env = new Environment();
+               container.getRequiredInterfaces()[0].setProvider(
+                               env.getProvidedInterfaces()[0]); 
+               container.getRequiredInterfaces()[1].setProvider(
+                               env.getProvidedInterfaces()[1]);
+           container.start();
+           assertSame(env.getProvidedInterfaces()[0], container.getRequiredInterfaces()[0].getProvider());
+           assertSame(env.getProvidedInterfaces()[1], container.getRequiredInterfaces()[1].getProvider());
+           assertSame(env.getProvidedInterfaces()[0], application.getRequiredInterfaces()[0].getProvider());
+           assertSame(env.getProvidedInterfaces()[1], application.getRequiredInterfaces()[1].getProvider());   
+       }
 }