-/*
- * Copyright 2007 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.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;
-import org.wamblee.collections.CollectionFilter;
-import org.wamblee.conditions.Condition;
-import org.wamblee.general.Pair;
-
-/**
- * Container consisting of multiple components.
- *
- * @author Erik Brakkee
- */
-public class Container extends AbstractComponent<Scope> {
-
- 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");
- }
- }
-}