+ private static final Log LOG = LogFactory.getLog(Container.class);
+
+ private List<Component> _components;
+ private Set<String> _componentNames;
+ private CompositeInterfaceRestriction _restriction;
+ private boolean _sealed;
+
+ static ProvidedInterface[] filterProvidedServices(
+ Component aClient, RequiredInterface aRequired, Collection<Pair<ProvidedInterface,Component>> aProvided,
+ InterfaceRestriction aRestriction) {
+ List<ProvidedInterface> result = new ArrayList<ProvidedInterface>();
+ for (Pair<ProvidedInterface,Component> 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<Component>();
+
+ _componentNames = new HashSet<String>();
+ _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;
+ }
+
+ @Override
+ public void addContext(String aContext) {
+ super.addContext(aContext);
+ for (Component component: _components) {
+ component.addContext(aContext);
+ }
+ }
+
+ /**
+ * 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<RequiredInterface> required = new ArrayList<RequiredInterface>();
+ 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() {
+ for (ProvidedInterface service : getProvidedInterfaces()) {
+ findProvidedInterface(service);
+ }
+ }
+
+ /**
+ * Finds the component and provided interface that matches a provided interface of this
+ * container.
+ * @param aProvided Interface to provide externally.
+ * @return Pair of component and provided interface
+ * @throws SystemAssemblyException In case there are multiple matches or no match at all.
+ */
+ private Pair<Component,ProvidedInterface> findProvidedInterface(ProvidedInterface aProvided) {
+ List<Pair<Component,ProvidedInterface>> result =
+ new ArrayList<Pair<Component,ProvidedInterface>>();
+ for (Component component: _components) {
+ for (ProvidedInterface provided: component.getProvidedInterfaces()) {
+ if ( aProvided.equals(provided) ) {
+ result.add(new Pair<Component,ProvidedInterface>(component, provided));
+ }
+ }
+ }
+ if ( result.size() == 0) {
+ throw new SystemAssemblyException(getQualifiedName() + ": Service '"
+ + aProvided
+ + "' is not provided by any of its components");
+ }
+ if ( result.size() > 1) {
+ throw new SystemAssemblyException(getQualifiedName() + ": Service '"
+ + aProvided
+ + "' is provided by multiple components: " + result);
+ }
+ return result.get(0);
+ }
+
+
+ /**
+ * 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);
+ exposeProvidedInterfaces(aExternalScope, scope);
+ seal();
+ return scope;
+ }
+
+ private void exposeProvidedInterfaces(Scope aExternalScope, Scope aInternalScope) {
+ for (ProvidedInterface intf: getProvidedInterfaces()) {
+ Pair<Component, ProvidedInterface> found = findProvidedInterface(intf);
+ Object svc = aInternalScope.getInterfaceImplementation(found.getSecond(), Object.class);
+ addInterface(intf, svc, aExternalScope);
+ }
+ }
+
+ private void doStartOptionalDryRun(Scope aScope, boolean aDryRun) {
+ LOG.info("Starting '" + getQualifiedName() + "'");
+
+ List<Pair<ProvidedInterface,Component>> allProvided = new ArrayList<Pair<ProvidedInterface,Component>>();
+
+ addProvidersOfRequiredInterfaces(allProvided);
+
+ List<Component> started = new ArrayList<Component>();
+ 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<ProvidedInterface,Component>(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<Pair<ProvidedInterface,Component>> 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<ProvidedInterface,Component>(provider, null));
+ } else {
+ if (!intf.isOptional()) {
+ throw new SystemAssemblyException(getQualifiedName()
+ + ": required interface '" + intf
+ + "' is not provided");
+ }
+ }
+ }
+ }
+
+ 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 {
+ 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<Pair<ProvidedInterface,Component>> 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<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");
+ }
+ }