now allowing components and interfaces to be added after construction.
[utils] / system / general / src / main / java / org / wamblee / system / core / Container.java
index b6f0a4ce318b361a9f375c6a7f94842921dbf07f..a4b0620fccc589ab5fd48796aa3ca8ba34d17768 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.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
-import java.util.Set;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 /**
- * Composite system consisting of multiple subsystems. 
- *
+ * Container consisting of multiple components. 
+ * 
  * @author Erik Brakkee
  */
-public class Container extends AbstractComponent {
+public class Container extends AbstractComponent<Scope> {
 
        private static final Log LOG = LogFactory.getLog(Container.class);
 
-       private Component[] _systems;
+       private List<Component> _components;
+       private boolean _sealed; 
 
        public static RequiredInterface[] filterRequiredServices(
                        ProvidedInterface aProvided,
@@ -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,162 +58,323 @@ 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. 
+        * Constructs the container
+        * 
+        * @param aName
+        *            Name of the container
+        * @param aComponents
+        *            Components.
+        * @param aProvided
+        *            Provided services of the container
+        * @param aRequired
+        *            Required services by the container.
         */
-       public Container(String aName, Component[] aSystems,
+       public Container(String aName, Component[] aComponents,
                        ProvidedInterface[] aProvided, RequiredInterface[] aRequired) {
                super(aName, aProvided, aRequired);
-               _systems = aSystems;
-               for (Component component: aSystems) { 
+               _components = new ArrayList<Component>(Arrays.asList(aComponents));
+               for (Component component : aComponents) {
                        component.addContext(getQualifiedName());
                }
-               validate(aRequired);
+               _sealed = false; 
+               validate();
+       }
+       
+       public Container(String aName) {
+           this(aName, new Component[0], new ProvidedInterface[0], new RequiredInterface[0]);
+       }
+       
+       public Container addComponent(Component aComponent) {
+               checkSealed(); 
+               _components.add(aComponent);
+               return this; 
        }
 
+       @Override
+       protected Container addProvidedInterface(ProvidedInterface aProvided) {
+               checkSealed();
+               super.addProvidedInterface(aProvided);
+               return this; 
+       }
+       
+       @Override
+       protected Container addRequiredInterface(RequiredInterface aRequired) {
+               checkSealed(); 
+               super.addRequiredInterface(aRequired);
+               return this; 
+       }
+       
        /**
-        * 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 components 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.
+        * @throws SystemAssemblyException in case of any validation problems. 
         */
-       private void validate(RequiredInterface[] aRequired) {
+       public void validate() {
                List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
-               for (Component system : _systems) {
-                       provided.addAll(Arrays.asList(system.getProvidedServices()));
+               for (Component component : _components) {
+                       provided.addAll(Arrays.asList(component.getProvidedInterfaces()));
                }
 
                List<RequiredInterface> required = new ArrayList<RequiredInterface>();
-               for (Component system : _systems) {
-                       required.addAll(Arrays.asList(system.getRequiredServices()));
+               for (Component component : _components) {
+                       required.addAll(Arrays.asList(component.getRequiredInterfaces()));
                }
 
-               for (ProvidedInterface service : getProvidedServices()) {
-                       if (!(provided.contains(service))) {
-                               throw new SystemAssemblyException(getName() + ": Service '" + service
-                                               + "' is not provided by any of the subsystems");
-                       }
-               }
+               validateProvidedInterfaces(provided);
 
-               for (RequiredInterface service : getRequiredServices()) {
-                       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> aProvided, List<RequiredInterface> aRequired) {
                List<RequiredInterface> reallyRequired = new ArrayList<RequiredInterface>(
-                               required);
+                               aRequired);
                // Compute all required interfaces that are not provided
-               for (ProvidedInterface service : provided) {
-                       List<RequiredInterface> fulfilled = 
-                               Arrays.asList(filterRequiredServices(service, 
-                                       reallyRequired));
-                       reallyRequired.removeAll(fulfilled); 
+
+               for (ProvidedInterface service : aProvided) {
+                       List<RequiredInterface> fulfilled = Arrays
+                                       .asList(filterRequiredServices(service, reallyRequired));
+                       reallyRequired.removeAll(fulfilled);
                }
-               // Now the remaining interfaces should be covered by the required
-               // list. 
-               reallyRequired.removeAll(Arrays.asList(aRequired));
-               
-               String missingRequired = "";
-               for (RequiredInterface service: reallyRequired) {
-                       missingRequired += service + "\n";
+               // 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(); 
+                       }
                }
-               if ( missingRequired.length() > 0 ) { 
-                       throw new SystemAssemblyException(getName() + ": missing required services\n" + missingRequired);
+               // Now the remaining interfaces should be covered by the required
+               // list.
+               reallyRequired.removeAll(Arrays.asList(getRequiredInterfaces()));
+               return reallyRequired;
+       }
+
+       private void validateRequiredInterfaces(List<RequiredInterface> aRequired) {
+               for (RequiredInterface service : getRequiredInterfaces()) {
+                       // TODO required interfaces by the component could be 
+                       //      subclasses or implementations of the requirements
+                       //      of the contained components. The code below assumes
+                       //      an exact match. 
+                       if (!(aRequired.contains(service))) {
+                               info("Service '"
+                                               + service
+                                               + "' indicated as required is not actually required by any of the components");
+                       }
+                       // Check for the case that the externally required service
+                       // is optional whereas the internally required service is 
+                       // mandatory. 
+                       if ( service.isOptional()) { 
+                               for (RequiredInterface intf: aRequired) { 
+                                       if ( intf.equals(service) && !intf.isOptional()) {  
+                                               warn("Required service '" + service + "' indicated as optional is mandatory by one of its components (" + getClients(intf) + ", " + intf + "), this can lead to problems when the container is started and the interface is not provided to the container.");
+                                       }
+                               }
+                       }
                }
        }
 
-       @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 = getRequiredServices();
-               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());
+       private void validateProvidedInterfaces(List<ProvidedInterface> aProvided) {
+               for (ProvidedInterface service : getProvidedInterfaces()) {
+                       // TODO provided interfaces by components could be 
+                       //      provide subclasses or implementations of the 
+                       //      provided interfaces of the container.
+                       //      The code below assumes an exact match. 
+                       if (!(aProvided.contains(service))) {
+                               throw new SystemAssemblyException(getName() + ": Service '"
+                                               + service
+                                               + "' is not provided by any of its components");
+                       }
                }
-               
-               startImpl();
        }
        
        /**
-        * Starts the subsystems.
-        * 
-        * @param aRequiredServices
-        *            Services that are available from other systems that have been
-        *            started before.
+        * Starts the container. After the container is started, the container becomes sealed
+        * meaning that no further components, required or provided interfaces may be added. 
+        * @return Scope. 
+        */
+       public Scope start() {
+               checkSealed();
+               validate();
+           Scope scope = super.start(new DefaultScope(new ProvidedInterface[0]));
+           seal();
+           return scope; 
+       }
+       
+       /**
+        * Seal the container, meaning that no further components or interfaces may be added. 
         */
-       private void startImpl() {
+       public void seal() { 
+               _sealed = true; 
+       }
+
+       /**
+        * Checks if the container is sealed. 
+        * @return True iff the container is sealed. 
+        */
+       public boolean isSealed() {
+               return _sealed;
+       }
+
+       @Override
+       protected Scope doStart(Scope aExternalScope) {
                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: getRequiredServices()) { 
-                       allProvided.add(required.getProvider());
-               }
+               Scope scope = new DefaultScope(getProvidedInterfaces(), 
+                               aExternalScope);
                
-               for (Component system : _systems) {
-                       // Check if all required services are already provided by earlier
-                       // systems.
-                       RequiredInterface[] required = system.getRequiredServices();
-
-                       for (RequiredInterface descriptor : required) {
-                               ProvidedInterface[] filtered = filterProvidedServices(
-                                               descriptor, allProvided);
+               List<ProvidedInterface> allProvided = new ArrayList<ProvidedInterface>();
 
-                               if (filtered.length == 0) {
-                                       throw new SystemAssemblyException(
-                                                       "Service '"
-                                                                       + descriptor
-                                                                       + "' required by system '"
-                                                                       + system
-                                                                       + "' is not provided by systems that are started earlier");
+               // 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 ) { 
+                               allProvided.add(provider);
+                       } else { 
+                               if ( !intf.isOptional()) { 
+                                       throw new SystemAssemblyException(getQualifiedName()
+                                                       + ": required interface '" + intf + "' is not provided");               
                                }
-                               if (filtered.length > 1) {
+                       }
+               }
+
+               List<Component> started = new ArrayList<Component>();
+               for (Component component : _components) {
+                       try {
+                               checkAllRequiredServicesAlreadyProvided(allProvided, component);
+
+                               // Start the service.
+                               Object runtime = component.start(scope);
+                               scope.addRuntime(component, runtime); 
+                               started.add(component);
+
+                               // add all provided services
+                               ProvidedInterface[] provided = component.getProvidedInterfaces();
+                               allProvided.addAll(Arrays.asList(provided));
+                       } catch (SystemAssemblyException e) { 
+                               throw e; 
+                       } catch (RuntimeException e) {
+                               LOG.error(getQualifiedName() + ": could not start '"
+                                               + component.getQualifiedName() + "'", e);
+                               stopAlreadyStartedComponents(started, scope);
+                               throw e; 
+                       }
+               }
+               return scope; 
+       }
+
+       private void stopAlreadyStartedComponents(List<Component> 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(aScope);
+                       } catch (Throwable t) {
+                               LOG.error(getQualifiedName() + ": error stopping "
+                                               + aStarted.get(i).getQualifiedName());
+                       }
+               }
+       }
+
+       private void checkAllRequiredServicesAlreadyProvided(
+                       List<ProvidedInterface> aAllProvided, Component aComponent) {
+               // Check if all required services are already provided by
+               // earlier
+               // systems.
+
+               for (RequiredInterface descriptor : aComponent.getRequiredInterfaces()) {
+                       ProvidedInterface[] filtered = filterProvidedServices(
+                                       descriptor, aAllProvided);
+                       if ( filtered.length == 1 ) { 
+                               descriptor.setProvider(filtered[0]);
+                       } else if ( filtered.length > 1 ) { 
+                               throw new SystemAssemblyException(
+                                               "Service '"
+                                                               + descriptor
+                                                               + "' required by system '"
+                                                               + aComponent
+                                                               + "' matches multiple services provided by other systems: "
+                                                               + getServers(filtered));
+                       } else { 
+                               // filtered.length == 0
+                               if ( !descriptor.isOptional()) { 
                                        throw new SystemAssemblyException(
                                                        "Service '"
                                                                        + descriptor
                                                                        + "' required by system '"
-                                                                       + system
-                                                                       + "' matches multiple services provided by other systems: " + 
-                                                                       Arrays.asList(filtered));
+                                                                       + aComponent
+                                                                       + "' is not provided by systems that are started earlier");     
                                }
-                               descriptor.setProvider(filtered[0]);
                        }
-                       
-                       // Start the service. 
-                       system.start();
-
-                       // add all provided services
-                       ProvidedInterface[] provided = system.getProvidedServices();
-                       allProvided.addAll(Arrays.asList(provided));
                }
        }
 
-       
        @Override
-       protected void doStop() {
-               for (int i = _systems.length-1; i >= 0; i--) { 
-                       _systems[i].stop();
+       protected void doStop(Scope aScope) {
+               for (int i = _components.size() - 1; i >= 0; i--) {
+                       Component component = _components.get(i);
+                       Object runtime = aScope.getRuntime(component);
+                       component.stop(runtime);
                }
        }
 
        private void info(String aMsg) {
-               LOG.info(getName() + ": " + aMsg);
+               LOG.info(getQualifiedName() + ": " + aMsg);
+       }
+       
+       private void warn(String aMsg) {
+               LOG.warn(getQualifiedName() + ": " + aMsg);
+       }
+       
+       private String getServers(ProvidedInterface[] aProvidedList ) {
+               String result = "";
+               for (ProvidedInterface provided: aProvidedList) {
+                       result += "(components ";
+                       for (Component component: _components) { 
+                               for (ProvidedInterface provided2: component.getProvidedInterfaces()) { 
+                                       if ( provided.equals(provided2)) { 
+                                               result += component + " ";
+                                       }
+                               }
+                       }
+                       result += ", interface " + provided + ")";
+               }
+               return result;
        }
 
+       private List<Component> getClients(RequiredInterface aRequirement) {
+               List<Component> clients = new ArrayList<Component>();
+               for (Component component: _components) { 
+                       for (RequiredInterface required: component.getRequiredInterfaces()) { 
+                               if ( required.equals(aRequirement) && required.isOptional() == aRequirement.isOptional()) { 
+                                       clients.add(component);
+                               }
+                       }
+               }
+               return clients; 
+       }
+       
+       private void checkSealed() { 
+               if ( _sealed ) { 
+                       throw new SystemAssemblyException("Container is sealed");
+               }
+       }
 }