X-Git-Url: http://wamblee.org/gitweb/?a=blobdiff_plain;f=system%2Fgeneral%2Fsrc%2Fmain%2Fjava%2Forg%2Fwamblee%2Fsystem%2Fcore%2FContainer.java;h=1c19be341d874e10675fb465ee702b5a662845ea;hb=001d69a3cfa9c4d949963d222c05a3134b594ddb;hp=b7ee4e60c6ddcb61f0fed6c52b911cfb37fb7998;hpb=8a6ee427cf3de42d9dd9d8fea09ee9fd059ee53e;p=utils 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 b7ee4e60..1c19be34 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 @@ -19,231 +19,394 @@ 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; +import org.wamblee.collections.CollectionFilter; +import org.wamblee.conditions.Condition; +import org.wamblee.general.Pair; /** - * Composite system consisting of multiple subsystems. + * Container consisting of multiple components. * * @author Erik Brakkee */ -public class Container extends AbstractComponent { - - private static final Log LOG = LogFactory.getLog(Container.class); - - private Component[] _systems; - - public static RequiredInterface[] filterRequiredServices( - ProvidedInterface aProvided, - Collection aDescriptors) { - List required = new ArrayList(); - for (RequiredInterface descriptor : aDescriptors) { - if (descriptor.implementedBy(aProvided)) { - required.add(descriptor); - } - } - return required.toArray(new RequiredInterface[0]); - } - - public static ProvidedInterface[] filterProvidedServices( - RequiredInterface aRequired, Collection aProvided) { - List provided = new ArrayList(); - for (ProvidedInterface descriptor : aProvided) { - if (aRequired.implementedBy(descriptor)) { - provided.add(descriptor); - } - } - 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. - */ - public Container(String aName, Component[] aSystems, - ProvidedInterface[] aProvided, RequiredInterface[] aRequired) { - super(aName, aProvided, aRequired); - _systems = 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. - */ - private void validate(RequiredInterface[] aRequired) { - List provided = new ArrayList(); - for (Component system : _systems) { - provided.addAll(Arrays.asList(system.getProvidedInterfaces())); - } - - List required = new ArrayList(); - for (Component system : _systems) { - 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"); - } - } - - for (RequiredInterface service : getRequiredInterfaces()) { - if (!(required.contains(service))) { - info("Service '" - + service - + "' indicated as required is not actually required by any of the subsystems"); - } - } - - List reallyRequired = new ArrayList( - required); - // Compute all required interfaces that are not provided - for (ProvidedInterface service : provided) { - List 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"; - } - 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"); - } - 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 allProvided = new ArrayList(); - - // Add the provides of all externally required interfaces to the list of - // available - // interfaces - for (RequiredInterface required : getRequiredInterfaces()) { - allProvided.add(required.getProvider()); - } - - List started = new ArrayList(); - for (Component system : _systems) { - 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]); - } - - // 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()); - } - } - throw e; - } - } - - } - - @Override - protected void doStop() { - for (int i = _systems.length - 1; i >= 0; i--) { - _systems[i].stop(); - } - } - - private void info(String aMsg) { - LOG.info(getName() + ": " + aMsg); - } +public class Container extends AbstractComponent { + private static final Log LOG = LogFactory.getLog(Container.class); + + private List _components; + private Set _componentNames; + private CompositeInterfaceRestriction _restriction; + private boolean _sealed; + + static ProvidedInterface[] filterProvidedServices( + Component aClient, RequiredInterface aRequired, Collection> aProvided, + InterfaceRestriction aRestriction) { + List result = new ArrayList(); + for (Pair descriptor : aProvided) { + ProvidedInterface provided = descriptor.getFirst(); + Component server = descriptor.getSecond(); + if (aRequired.implementedBy(provided) && + !aRestriction.isViolated(aClient, aRequired, server, provided)) { + result.add(provided); + } + } + return result.toArray(new ProvidedInterface[0]); + } + + /** + * 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[] aComponents, + ProvidedInterface[] aProvided, RequiredInterface[] aRequired) { + super(aName, aProvided, aRequired); + _components = new ArrayList(); + + _componentNames = new HashSet(); + _restriction = new CompositeInterfaceRestriction(); + _sealed = false; + for (Component component : aComponents) { + addComponent(component); + } + } + + public Container(String aName) { + this(aName, new Component[0], new ProvidedInterface[0], + new RequiredInterface[0]); + } + + public Container addComponent(Component aComponent) { + checkSealed(); + if (aComponent.getContext() != null) { + throw new SystemAssemblyException( + "Inconsistent hierarchy, component '" + + aComponent.getName() + + "' is already part of another hierarchy"); + } + if (_componentNames.contains(aComponent.getName())) { + throw new SystemAssemblyException("Duplicate component '" + + aComponent.getName() + "'"); + } + _components.add(aComponent); + _componentNames.add(aComponent.getName()); + aComponent.addContext(getQualifiedName()); + return this; + } + + /** + * Adds an interface restriction for explicitly configuring the + * relations between components. + * @param aRestriction Restriction to add. + * @return Reference to this to allow call chaining. + */ + public Container addRestriction(InterfaceRestriction aRestriction) { + checkSealed(); + _restriction.add(aRestriction); + return this; + } + + @Override + public Container addProvidedInterface(ProvidedInterface aProvided) { + checkSealed(); + super.addProvidedInterface(aProvided); + return this; + } + + @Override + public Container addRequiredInterface(RequiredInterface aRequired) { + checkSealed(); + super.addRequiredInterface(aRequired); + return this; + } + + /** + * 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. + */ + public void validate() { + validateProvidedInterfacesArePresent(); + + validateRequiredInterfaces(); + + doStartOptionalDryRun(null, true); + } + + private void validateRequiredInterfaces() { + List required = new ArrayList(); + for (Component component : _components) { + required.addAll(Arrays.asList(component.getRequiredInterfaces())); + } + + 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 (!(required.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 : required) { + 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."); + } + } + } + } + } + + private void validateProvidedInterfacesArePresent() { + List provided = new ArrayList(); + for (Component component : _components) { + provided.addAll(Arrays.asList(component.getProvidedInterfaces())); + } + 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 (!(provided.contains(service))) { + throw new SystemAssemblyException(getQualifiedName() + ": Service '" + + service + + "' is not provided by any of its components"); + } + } + } + + /** + * Seal the container, meaning that no further components or interfaces may + * be added. + */ + public void seal() { + _sealed = true; + } + + /** + * Checks if the container is sealed. + * + * @return True iff the container is sealed. + */ + public boolean isSealed() { + return _sealed; + } + + /** + * Utility method to start with an empty external scope. This is useful for + * top-level containers which are not part of another container. + * @return Scope. + */ + public Scope start() { + Scope scope = new DefaultScope(getProvidedInterfaces()); + return super.start(scope); + } + + @Override + protected Scope doStart(Scope aExternalScope) { + checkSealed(); + validate(); + Scope scope = new DefaultScope(getProvidedInterfaces(), aExternalScope); + doStartOptionalDryRun(scope, false); + seal(); + return scope; + } + + private void doStartOptionalDryRun(Scope aScope, boolean aDryRun) { + LOG.info("Starting '" + getQualifiedName() + "'"); + + List> allProvided = new ArrayList>(); + + addProvidersOfRequiredInterfaces(allProvided); + + List started = new ArrayList(); + for (Component component : _components) { + try { + initializeProvidersForRequiredInterfaces(allProvided, + component, aDryRun); + + // Start the service. + if (!aDryRun) { + Object runtime = component.start(aScope); + aScope.addRuntime(component, runtime); + started.add(component); + } + + // add all provided services + ProvidedInterface[] provided = component + .getProvidedInterfaces(); + for (ProvidedInterface prov: provided) { + allProvided.add(new Pair(prov, component)); + } + } catch (SystemAssemblyException e) { + throw e; + } catch (RuntimeException e) { + LOG.error(getQualifiedName() + ": could not start '" + + component.getQualifiedName() + "'", e); + stopAlreadyStartedComponents(started, aScope); + throw e; + } + } + } + + private void addProvidersOfRequiredInterfaces( + List> allProvided) { + // 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(new Pair(provider, null)); + } else { + if (!intf.isOptional()) { + throw new SystemAssemblyException(getQualifiedName() + + ": required interface '" + intf + + "' is not provided"); + } + } + } + } + + 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 { + Component component = aStarted.get(i); + aStarted.get(i).stop(aScope.getRuntime(component)); + } catch (Throwable t) { + LOG.error(getQualifiedName() + ": error stopping " + + aStarted.get(i).getQualifiedName()); + } + } + } + + /** + * Sets the provided interface or a component. + * + * @param aAllProvided + * All available provided interfaces. + * @param aComponent + * Component whose required interfaces we are looking at. + * @param aValidateOnly + * If true then the provider will not be set for required + * interfaces. + */ + private void initializeProvidersForRequiredInterfaces( + List> aAllProvided, Component aComponent, + boolean aValidateOnly) { + // Check if all required services are already provided by + // earlier + // systems. + + for (RequiredInterface descriptor : aComponent.getRequiredInterfaces()) { + ProvidedInterface[] filtered = filterProvidedServices(aComponent, descriptor, + aAllProvided, _restriction); + if (filtered.length == 1) { + if (!aValidateOnly) { + 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 '" + + aComponent + + "' is not provided by systems that are started earlier"); + } + } + } + } + + @Override + 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(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 getClients(RequiredInterface aRequirement) { + List clients = new ArrayList(); + 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"); + } + } }