2 * Copyright 2007 the original author or authors.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package org.wamblee.system.container;
18 import org.apache.commons.logging.Log;
19 import org.apache.commons.logging.LogFactory;
21 import org.wamblee.general.Pair;
23 import org.wamblee.system.core.AbstractComponent;
24 import org.wamblee.system.core.Component;
25 import org.wamblee.system.core.DefaultScope;
26 import org.wamblee.system.core.NamedInterface;
27 import org.wamblee.system.core.ProvidedInterface;
28 import org.wamblee.system.core.RequiredInterface;
29 import org.wamblee.system.core.Scope;
30 import org.wamblee.system.core.SystemAssemblyException;
31 import org.wamblee.system.graph.CompositeEdgeFilter;
32 import org.wamblee.system.graph.component.ComponentGraph;
33 import org.wamblee.system.graph.component.ConnectExternalProvidedProvidedFilter;
34 import org.wamblee.system.graph.component.ConnectRequiredExternallyRequiredEdgeFilter;
35 import org.wamblee.system.graph.component.ConnectRequiredProvidedEdgeFilter;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.List;
43 * Container consisting of multiple components.
45 * @author Erik Brakkee
47 public class Container extends AbstractComponent<Scope> {
51 private static final Log LOG = LogFactory.getLog(Container.class);
56 private List<Component> components;
61 private CompositeEdgeFilter edgeFilter;
66 private boolean sealed;
69 * Constructs the container
72 * Name of the container
76 * Provided services of the container
78 * Required services by the container.
80 public Container(String aName, Component[] aComponents,
81 List<ProvidedInterface> aProvided, List<RequiredInterface> aRequired) {
82 super(aName, aProvided, aRequired);
83 components = new ArrayList<Component>();
85 edgeFilter = new CompositeEdgeFilter();
88 for (Component component : aComponents) {
89 addComponent(component);
94 * Constructs the container
97 * Name of the container
101 * Provided services of the container
103 * Required services by the container.
105 public Container(String aName, Component[] aComponents,
106 ProvidedInterface[] aProvided, RequiredInterface[] aRequired) {
107 this(aName, aComponents, Arrays.asList(aProvided),
108 Arrays.asList(aRequired));
112 * Creates a new Container object.
114 * @param aName DOCUMENT ME!
116 public Container(String aName) {
117 this(aName, new Component[0], new ProvidedInterface[0],
118 new RequiredInterface[0]);
124 * @param aComponent DOCUMENT ME!
126 * @return DOCUMENT ME!
128 public Container addComponent(Component aComponent) {
131 if (aComponent.getContext() != null) {
132 throw new SystemAssemblyException(
133 "Inconsistent hierarchy, component '" + aComponent.getName()
134 + "' is already part of another hierarchy");
137 if (findComponent(aComponent.getName()) != null) {
138 throw new SystemAssemblyException("Duplicate component '"
139 + aComponent.getName() + "'");
142 components.add(aComponent);
143 aComponent.addContext(getQualifiedName());
149 * Explictly connects required and provided interfaces.
151 * @param aClientComponent Client component, may not be null.
152 * @param aRequiredInterface Required interface. If null it means all
153 * required interfaces.
154 * @param aServerComponent Server component to connect to. If null, it
155 * means that no server components may be connected to and the
156 * provider of the required interface will be null.
157 * @param aProvidedInterface Provided interface. If null, it means that
158 * there is no restriction on the name of the provided interface
159 * and that it is automatically selected.
161 * @throws SystemAssemblyException DOCUMENT ME!
163 public void connectRequiredProvided(String aClientComponent,
164 String aRequiredInterface, String aServerComponent,
165 String aProvidedInterface) {
168 Component client = findComponent(aClientComponent);
169 Component server = findComponent(aServerComponent);
171 if (client == null) {
172 throw new SystemAssemblyException(getQualifiedName()
173 + ": No component '" + aClientComponent + "' in the container");
176 if (aRequiredInterface != null) {
177 if (findInterface(client.getRequiredInterfaces(), aRequiredInterface) == null) {
178 throw new SystemAssemblyException(getQualifiedName()
179 + ": Component '" + aClientComponent
180 + "' does not have a required interface named '"
181 + aRequiredInterface + "'");
185 if (server == null) {
186 throw new SystemAssemblyException("No component '"
187 + aClientComponent + "' in the container");
190 if (aProvidedInterface != null) {
191 if (findInterface(server.getProvidedInterfaces(), aProvidedInterface) == null) {
192 throw new SystemAssemblyException(getQualifiedName()
193 + ": Component '" + aServerComponent
194 + "' does not have a provided interface named '"
195 + aProvidedInterface + "'");
199 edgeFilter.add(new ConnectRequiredProvidedEdgeFilter(aClientComponent,
200 aRequiredInterface, aServerComponent, aProvidedInterface));
204 * Explicitly connects a externally required interface to an
205 * internally required interface.
207 * @param aComponent Component requiring the interface (must be non-null).
208 * @param aRequiredInterface Required interface of the component (must be
210 * @param aExternalRequiredInterface Externally required interface (must be
213 * @throws SystemAssemblyException DOCUMENT ME!
215 public void connectExternalRequired(String aComponent,
216 String aRequiredInterface, String aExternalRequiredInterface) {
219 Component client = findComponent(aComponent);
221 if (client == null) {
222 throw new SystemAssemblyException(getQualifiedName()
223 + ": No component '" + aComponent + "' in the container");
226 if (aRequiredInterface != null) {
227 if (findInterface(client.getRequiredInterfaces(), aRequiredInterface) == null) {
228 throw new SystemAssemblyException(getQualifiedName()
229 + ": Component '" + aComponent
230 + "' does not have a required interface named '"
231 + aRequiredInterface + "'");
235 if (aExternalRequiredInterface != null) {
236 if (findInterface(getRequiredInterfaces(),
237 aExternalRequiredInterface) == null) {
238 throw new SystemAssemblyException(getQualifiedName()
239 + ": container does not have a required interface named '"
240 + aExternalRequiredInterface + "'");
244 edgeFilter.add(new ConnectRequiredExternallyRequiredEdgeFilter(
245 aComponent, aRequiredInterface, aExternalRequiredInterface));
251 * @param aExternalProvided DOCUMENT ME!
252 * @param aComponent DOCUMENT ME!
253 * @param aProvidedInterface DOCUMENT ME!
255 public void connectExternalProvided(String aExternalProvided,
256 String aComponent, String aProvidedInterface) {
259 Component server = findComponent(aComponent);
261 if (server == null) {
262 throw new SystemAssemblyException("No component '" + aComponent
263 + "' in the container");
266 if (aProvidedInterface != null) {
267 if (findInterface(server.getProvidedInterfaces(), aProvidedInterface) == null) {
268 throw new SystemAssemblyException(getQualifiedName()
269 + ": Component '" + aComponent
270 + "' does not have a provided interface named '"
271 + aProvidedInterface + "'");
275 if (aExternalProvided != null) {
276 if (findInterface(getProvidedInterfaces(), aExternalProvided) == null) {
277 throw new SystemAssemblyException(getQualifiedName()
278 + ": Container does not have a provided interface named '"
279 + aExternalProvided + "'");
283 edgeFilter.add(new ConnectExternalProvidedProvidedFilter(
284 aExternalProvided, aComponent, aProvidedInterface));
290 * @param aProvided DOCUMENT ME!
292 * @return DOCUMENT ME!
295 public Container addProvidedInterface(ProvidedInterface aProvided) {
297 super.addProvidedInterface(aProvided);
305 * @param aRequired DOCUMENT ME!
307 * @return DOCUMENT ME!
310 public Container addRequiredInterface(RequiredInterface aRequired) {
312 super.addRequiredInterface(aRequired);
320 * @param aContext DOCUMENT ME!
323 public void addContext(String aContext) {
324 super.addContext(aContext);
326 for (Component component : components) {
327 component.addContext(aContext);
332 * Validates the components together to check that there are no
333 * required services not in the required list and no services in the
334 * provided list that cannot be provided. Also logs a warning in case of
335 * superfluous requirements.
337 public void validate() {
338 doStartOptionalDryRun(null, true);
342 * Seal the container, meaning that no further components or
343 * interfaces may be added.
350 * Checks if the container is sealed.
352 * @return True iff the container is sealed.
354 public boolean isSealed() {
359 * Utility method to start with an empty external scope. This is
360 * useful for top-level containers which are not part of another
365 public Scope start() {
366 Scope scope = new DefaultScope(getProvidedInterfaces());
368 return super.start(scope);
374 * @param aExternalScope DOCUMENT ME!
376 * @return DOCUMENT ME!
379 protected Scope doStart(Scope aExternalScope) {
382 Scope scope = new DefaultScope(getProvidedInterfaces()
383 .toArray(new ProvidedInterface[0]), aExternalScope);
384 ComponentGraph graph = doStartOptionalDryRun(scope, false);
385 exposeProvidedInterfaces(graph, aExternalScope, scope);
394 * @param aGraph DOCUMENT ME!
395 * @param aExternalScope DOCUMENT ME!
396 * @param aInternalScope DOCUMENT ME!
398 private void exposeProvidedInterfaces(ComponentGraph aGraph,
399 Scope aExternalScope, Scope aInternalScope) {
400 for (Pair<ProvidedInterface, ProvidedInterface> mapping : aGraph
401 .findExternalProvidedInterfaceMapping()) {
402 Object svc = aInternalScope.getInterfaceImplementation(mapping
403 .getSecond(), Object.class);
404 addInterface(mapping.getFirst(), svc, aExternalScope);
411 * @param aScope DOCUMENT ME!
412 * @param aDryRun DOCUMENT ME!
414 * @return DOCUMENT ME!
416 private ComponentGraph doStartOptionalDryRun(Scope aScope, boolean aDryRun) {
417 ComponentGraph graph = createComponentGraph();
421 LOG.info("Starting '" + getQualifiedName() + "'");
423 List<Component> started = new ArrayList<Component>();
425 for (Component component : components) {
427 // Start the service.
429 Object runtime = component.start(aScope);
430 aScope.addRuntime(component, runtime);
431 started.add(component);
433 } catch (SystemAssemblyException e) {
435 } catch (RuntimeException e) {
436 LOG.error(getQualifiedName() + ": could not start '"
437 + component.getQualifiedName() + "'", e);
438 stopAlreadyStartedComponents(started, aScope);
449 * @return DOCUMENT ME!
451 private ComponentGraph createComponentGraph() {
452 ComponentGraph graph = new ComponentGraph();
454 for (RequiredInterface req : getRequiredInterfaces()) {
455 graph.addRequiredInterface(this, req);
458 for (Component comp : components) {
459 graph.addComponent(comp);
462 for (ProvidedInterface prov : getProvidedInterfaces()) {
463 graph.addProvidedInterface(this, prov);
466 graph.addEdgeFilter(edgeFilter);
474 * @param aStarted DOCUMENT ME!
475 * @param aScope DOCUMENT ME!
477 private void stopAlreadyStartedComponents(List<Component> aStarted,
479 // an exception occurred, stop the successfully started
481 for (int i = aStarted.size() - 1; i >= 0; i--) {
483 Component component = aStarted.get(i);
484 aStarted.get(i).stop(aScope.getRuntime(component));
485 } catch (Throwable t) {
486 LOG.error(getQualifiedName() + ": error stopping "
487 + aStarted.get(i).getQualifiedName());
495 * @param aScope DOCUMENT ME!
498 protected void doStop(Scope aScope) {
499 for (int i = components.size() - 1; i >= 0; i--) {
500 Component component = components.get(i);
501 Object runtime = aScope.getRuntime(component);
502 component.stop(runtime);
509 private void checkSealed() {
511 throw new SystemAssemblyException("Container is sealed");
516 * Finds a component based on the non-qualified name of the
519 * @param aName Component name.
521 * @return Component or null if not found.
523 public Component findComponent(String aName) {
524 for (Component<?> component : components) {
525 if (component.getName().equals(aName)) {
536 * @param <T> DOCUMENT ME!
537 * @param aInterfaces DOCUMENT ME!
538 * @param aInterfaceName DOCUMENT ME!
540 * @return DOCUMENT ME!
542 private static <T extends NamedInterface> T findInterface(
543 List<T> aInterfaces, String aInterfaceName) {
544 for (T intf : aInterfaces) {
545 if (intf.getName().equals(aInterfaceName)) {