/* * 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 { 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(getName() + ": Service '" + service + "' is not provided by any of its components"); } } } /** * 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. */ 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) { return doStartOptionalDryRun(aExternalScope, false); } private Scope doStartOptionalDryRun(Scope aExternalScope, boolean aDryRun) { LOG.info("Starting '" + getQualifiedName() + "'"); Scope scope = new DefaultScope(getProvidedInterfaces(), aExternalScope); 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(scope); scope.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, scope); throw e; } } return scope; } 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"); } } }