/* * 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.container; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wamblee.general.Pair; import org.wamblee.system.core.AbstractComponent; import org.wamblee.system.core.Component; import org.wamblee.system.core.DefaultScope; import org.wamblee.system.core.NamedInterface; import org.wamblee.system.core.ProvidedInterface; import org.wamblee.system.core.RequiredInterface; import org.wamblee.system.core.Scope; import org.wamblee.system.core.SystemAssemblyException; import org.wamblee.system.graph.CompositeEdgeFilter; import org.wamblee.system.graph.component.ComponentGraph; import org.wamblee.system.graph.component.ConnectExternalProvidedProvidedFilter; import org.wamblee.system.graph.component.ConnectRequiredExternallyRequiredEdgeFilter; import org.wamblee.system.graph.component.ConnectRequiredProvidedEdgeFilter; /** * 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 CompositeEdgeFilter _edgeFilter; private boolean _sealed; /** * 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, List aProvided, List aRequired) { super(aName, aProvided, aRequired); _components = new ArrayList(); _edgeFilter = new CompositeEdgeFilter(); _sealed = false; for (Component component : aComponents) { addComponent(component); } } /** * 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) { this(aName, aComponents, Arrays.asList(aProvided), Arrays.asList(aRequired)); } 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 ( findComponent(aComponent.getName()) != null ) { throw new SystemAssemblyException("Duplicate component '" + aComponent.getName() + "'"); } _components.add(aComponent); aComponent.addContext(getQualifiedName()); return this; } /** * Explictly connects required and provided interfaces. * @param aClientComponent Client component, may not be null. * @param aRequiredInterface Required interface. If null it means all required interfaces. * @param aServerComponent Server component to connect to. If null, it means that no server components * may be connected to and the provider of the required interface will be null. * @param aProvidedInterface Provided interface. If null, it means that there is no restriction on the * name of the provided interface and that it is automatically selected. */ public void connectRequiredProvided(String aClientComponent, String aRequiredInterface, String aServerComponent, String aProvidedInterface) { checkSealed(); Component client = findComponent(aClientComponent); Component server = findComponent(aServerComponent); if ( client == null ) { throw new SystemAssemblyException(getQualifiedName() + ": No component '" + aClientComponent + "' in the container"); } if ( aRequiredInterface != null ) { if ( findInterface(client.getRequiredInterfaces(), aRequiredInterface) == null ) { throw new SystemAssemblyException( getQualifiedName() + ": Component '" + aClientComponent + "' does not have a required interface named '" + aRequiredInterface + "'"); } } if ( server == null ) { throw new SystemAssemblyException("No component '" + aClientComponent + "' in the container"); } if ( aProvidedInterface != null ) { if ( findInterface(server.getProvidedInterfaces(), aProvidedInterface) == null) { throw new SystemAssemblyException( getQualifiedName() + ": Component '" + aServerComponent + "' does not have a provided interface named '" + aProvidedInterface + "'"); } } _edgeFilter.add(new ConnectRequiredProvidedEdgeFilter(aClientComponent, aRequiredInterface, aServerComponent, aProvidedInterface)); } /** * Explicitly connects a externally required interface to an internally required interface. * @param aComponent Component requiring the interface (must be non-null). * @param aRequiredInterface Required interface of the component (must be non-null). * @param aExternalRequiredInterface Externally required interface (must be non-null). */ public void connectExternalRequired(String aComponent, String aRequiredInterface, String aExternalRequiredInterface) { checkSealed(); Component client = findComponent(aComponent); if ( client == null ) { throw new SystemAssemblyException(getQualifiedName() + ": No component '" + aComponent + "' in the container"); } if ( aRequiredInterface != null ) { if ( findInterface(client.getRequiredInterfaces(), aRequiredInterface) == null ) { throw new SystemAssemblyException( getQualifiedName() + ": Component '" + aComponent + "' does not have a required interface named '" + aRequiredInterface + "'"); } } if ( aExternalRequiredInterface != null) { if ( findInterface(getRequiredInterfaces(), aExternalRequiredInterface) == null ) { throw new SystemAssemblyException( getQualifiedName() + ": container does not have a required interface named '" + aExternalRequiredInterface + "'"); } } _edgeFilter.add(new ConnectRequiredExternallyRequiredEdgeFilter( aComponent, aRequiredInterface, aExternalRequiredInterface)); } public void connectExternalProvided(String aExternalProvided, String aComponent, String aProvidedInterface) { checkSealed(); Component server = findComponent(aComponent); if ( server == null ) { throw new SystemAssemblyException("No component '" + aComponent + "' in the container"); } if ( aProvidedInterface != null ) { if ( findInterface(server.getProvidedInterfaces(), aProvidedInterface) == null) { throw new SystemAssemblyException( getQualifiedName() + ": Component '" + aComponent + "' does not have a provided interface named '" + aProvidedInterface + "'"); } } if ( aExternalProvided != null ) { if ( findInterface(getProvidedInterfaces(), aExternalProvided) == null) { throw new SystemAssemblyException( getQualifiedName() + ": Container does not have a provided interface named '" + aExternalProvided + "'"); } } _edgeFilter.add(new ConnectExternalProvidedProvidedFilter(aExternalProvided, aComponent, aProvidedInterface)); } @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() { doStartOptionalDryRun(null, true); } /** * 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) { validate(); Scope scope = new DefaultScope(getProvidedInterfaces().toArray(new ProvidedInterface[0]), aExternalScope); ComponentGraph graph = doStartOptionalDryRun(scope, false); exposeProvidedInterfaces(graph, aExternalScope, scope); seal(); return scope; } private void exposeProvidedInterfaces(ComponentGraph aGraph, Scope aExternalScope, Scope aInternalScope) { for (Pair mapping: aGraph.findExternalProvidedInterfaceMapping()) { Object svc = aInternalScope.getInterfaceImplementation(mapping.getSecond(), Object.class); addInterface(mapping.getFirst(), svc, aExternalScope); } } private ComponentGraph doStartOptionalDryRun(Scope aScope, boolean aDryRun) { ComponentGraph graph = createComponentGraph(); graph.validate(); graph.link(); LOG.info("Starting '" + getQualifiedName() + "'"); List started = new ArrayList(); for (Component component : _components) { try { // Start the service. if (!aDryRun) { Object runtime = component.start(aScope); aScope.addRuntime(component, runtime); started.add(component); } } catch (SystemAssemblyException e) { throw e; } catch (RuntimeException e) { LOG.error(getQualifiedName() + ": could not start '" + component.getQualifiedName() + "'", e); stopAlreadyStartedComponents(started, aScope); throw e; } } return graph; } private ComponentGraph createComponentGraph() { ComponentGraph graph = new ComponentGraph(); for (RequiredInterface req : getRequiredInterfaces()) { graph.addRequiredInterface(this, req); } for (Component comp : _components) { graph.addComponent(comp); } for (ProvidedInterface prov: getProvidedInterfaces()) { graph.addProvidedInterface(this, prov); } graph.addEdgeFilter(_edgeFilter); return graph; } 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()); } } } @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 checkSealed() { if (_sealed) { throw new SystemAssemblyException("Container is sealed"); } } /** * Finds a component based on the non-qualified name of the component. * @param aName Component name. * @return Component or null if not found. */ public Component findComponent(String aName) { for (Component component: _components) { if ( component.getName().equals(aName)) { return component; } } return null; } private static T findInterface(List aInterfaces, String aInterfaceName) { for (T intf: aInterfaces) { if ( intf.getName().equals(aInterfaceName)) { return intf; } } return null; } }