2 * Copyright 2005-2010 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;
42 * Container consisting of multiple components.
44 * @author Erik Brakkee
46 public class Container extends AbstractComponent<Scope> {
47 private static final Log LOG = LogFactory.getLog(Container.class);
49 private List<Component> components;
51 private CompositeEdgeFilter edgeFilter;
53 private boolean sealed;
56 * Constructs the container
59 * Name of the container
63 * Provided services of the container
65 * Required services by the container.
67 public Container(String aName, Component[] aComponents,
68 List<ProvidedInterface> aProvided, List<RequiredInterface> aRequired) {
69 super(aName, aProvided, aRequired);
70 components = new ArrayList<Component>();
72 edgeFilter = new CompositeEdgeFilter();
75 for (Component component : aComponents) {
76 addComponent(component);
81 * Constructs the container
84 * Name of the container
88 * Provided services of the container
90 * Required services by the container.
92 public Container(String aName, Component[] aComponents,
93 ProvidedInterface[] aProvided, RequiredInterface[] aRequired) {
94 this(aName, aComponents, Arrays.asList(aProvided), Arrays
99 * Creates a new Container object.
102 public Container(String aName) {
103 this(aName, new Component[0], new ProvidedInterface[0],
104 new RequiredInterface[0]);
107 public Container addComponent(Component aComponent) {
110 if (aComponent.getContext() != null) {
111 throw new SystemAssemblyException(
112 "Inconsistent hierarchy, component '" + aComponent.getName() +
113 "' is already part of another hierarchy");
116 if (findComponent(aComponent.getName()) != null) {
117 throw new SystemAssemblyException("Duplicate component '" +
118 aComponent.getName() + "'");
121 components.add(aComponent);
122 aComponent.addContext(getQualifiedName());
128 * Explictly connects required and provided interfaces.
130 * @param aClientComponent
131 * Client component, may not be null.
132 * @param aRequiredInterface
133 * Required interface. If null it means all required interfaces.
134 * @param aServerComponent
135 * Server component to connect to. If null, it means that no
136 * server components may be connected to and the provider of the
137 * required interface will be null.
138 * @param aProvidedInterface
139 * Provided interface. If null, it means that there is no
140 * restriction on the name of the provided interface and that it
141 * is automatically selected.
144 public void connectRequiredProvided(String aClientComponent,
145 String aRequiredInterface, String aServerComponent,
146 String aProvidedInterface) {
149 Component client = findComponent(aClientComponent);
150 Component server = findComponent(aServerComponent);
152 if (client == null) {
153 throw new SystemAssemblyException(getQualifiedName() +
154 ": No component '" + aClientComponent + "' in the container");
157 if (aRequiredInterface != null) {
158 if (findInterface(client.getRequiredInterfaces(),
159 aRequiredInterface) == null) {
160 throw new SystemAssemblyException(getQualifiedName() +
161 ": Component '" + aClientComponent +
162 "' does not have a required interface named '" +
163 aRequiredInterface + "'");
167 if (server == null) {
168 throw new SystemAssemblyException("No component '" +
169 aClientComponent + "' in the container");
172 if (aProvidedInterface != null) {
173 if (findInterface(server.getProvidedInterfaces(),
174 aProvidedInterface) == null) {
175 throw new SystemAssemblyException(getQualifiedName() +
176 ": Component '" + aServerComponent +
177 "' does not have a provided interface named '" +
178 aProvidedInterface + "'");
182 edgeFilter.add(new ConnectRequiredProvidedEdgeFilter(aClientComponent,
183 aRequiredInterface, aServerComponent, aProvidedInterface));
187 * Explicitly connects a externally required interface to an internally
188 * required interface.
191 * Component requiring the interface (must be non-null).
192 * @param aRequiredInterface
193 * Required interface of the component (must be non-null).
194 * @param aExternalRequiredInterface
195 * Externally required interface (must be non-null).
198 public void connectExternalRequired(String aComponent,
199 String aRequiredInterface, String aExternalRequiredInterface) {
202 Component client = findComponent(aComponent);
204 if (client == null) {
205 throw new SystemAssemblyException(getQualifiedName() +
206 ": No component '" + aComponent + "' in the container");
209 if (aRequiredInterface != null) {
210 if (findInterface(client.getRequiredInterfaces(),
211 aRequiredInterface) == null) {
212 throw new SystemAssemblyException(getQualifiedName() +
213 ": Component '" + aComponent +
214 "' does not have a required interface named '" +
215 aRequiredInterface + "'");
219 if (aExternalRequiredInterface != null) {
220 if (findInterface(getRequiredInterfaces(),
221 aExternalRequiredInterface) == null) {
222 throw new SystemAssemblyException(getQualifiedName() +
223 ": container does not have a required interface named '" +
224 aExternalRequiredInterface + "'");
228 edgeFilter.add(new ConnectRequiredExternallyRequiredEdgeFilter(
229 aComponent, aRequiredInterface, aExternalRequiredInterface));
232 public void connectExternalProvided(String aExternalProvided,
233 String aComponent, String aProvidedInterface) {
236 Component server = findComponent(aComponent);
238 if (server == null) {
239 throw new SystemAssemblyException("No component '" + aComponent +
240 "' in the container");
243 if (aProvidedInterface != null) {
244 if (findInterface(server.getProvidedInterfaces(),
245 aProvidedInterface) == null) {
246 throw new SystemAssemblyException(getQualifiedName() +
247 ": Component '" + aComponent +
248 "' does not have a provided interface named '" +
249 aProvidedInterface + "'");
253 if (aExternalProvided != null) {
254 if (findInterface(getProvidedInterfaces(), aExternalProvided) == null) {
255 throw new SystemAssemblyException(getQualifiedName() +
256 ": Container does not have a provided interface named '" +
257 aExternalProvided + "'");
261 edgeFilter.add(new ConnectExternalProvidedProvidedFilter(
262 aExternalProvided, aComponent, aProvidedInterface));
266 public Container addProvidedInterface(ProvidedInterface aProvided) {
268 super.addProvidedInterface(aProvided);
274 public Container addRequiredInterface(RequiredInterface aRequired) {
276 super.addRequiredInterface(aRequired);
282 public void addContext(String aContext) {
283 super.addContext(aContext);
285 for (Component component : components) {
286 component.addContext(aContext);
291 * Validates the components together to check that there are no required
292 * services not in the required list and no services in the provided list
293 * that cannot be provided. Also logs a warning in case of superfluous
296 public void validate() {
297 doStartOptionalDryRun(null, true);
301 * Seal the container, meaning that no further components or interfaces may
309 * Checks if the container is sealed.
311 * @return True iff the container is sealed.
313 public boolean isSealed() {
318 * Utility method to start with an empty external scope. This is useful for
319 * top-level containers which are not part of another container.
323 public Scope start() {
324 Scope scope = new DefaultScope(getProvidedInterfaces());
326 return super.start(scope);
330 protected Scope doStart(Scope aExternalScope) {
333 Scope scope = new DefaultScope(getProvidedInterfaces().toArray(
334 new ProvidedInterface[0]), aExternalScope);
335 ComponentGraph graph = doStartOptionalDryRun(scope, false);
336 exposeProvidedInterfaces(graph, aExternalScope, scope);
342 private void exposeProvidedInterfaces(ComponentGraph aGraph,
343 Scope aExternalScope, Scope aInternalScope) {
344 for (Pair<ProvidedInterface, ProvidedInterface> mapping : aGraph
345 .findExternalProvidedInterfaceMapping()) {
346 Object svc = aInternalScope.getInterfaceImplementation(mapping
347 .getSecond(), Object.class);
348 addInterface(mapping.getFirst(), svc, aExternalScope);
352 private ComponentGraph doStartOptionalDryRun(Scope aScope, boolean aDryRun) {
353 ComponentGraph graph = createComponentGraph();
357 LOG.info("Starting '" + getQualifiedName() + "'");
359 List<Component> started = new ArrayList<Component>();
361 for (Component component : components) {
363 // Start the service.
365 Object runtime = component.start(aScope);
366 aScope.addRuntime(component, runtime);
367 started.add(component);
369 } catch (SystemAssemblyException e) {
371 } catch (RuntimeException e) {
372 LOG.error(getQualifiedName() + ": could not start '" +
373 component.getQualifiedName() + "'", e);
374 stopAlreadyStartedComponents(started, aScope);
382 private ComponentGraph createComponentGraph() {
383 ComponentGraph graph = new ComponentGraph();
385 for (RequiredInterface req : getRequiredInterfaces()) {
386 graph.addRequiredInterface(this, req);
389 for (Component comp : components) {
390 graph.addComponent(comp);
393 for (ProvidedInterface prov : getProvidedInterfaces()) {
394 graph.addProvidedInterface(this, prov);
397 graph.addEdgeFilter(edgeFilter);
402 private void stopAlreadyStartedComponents(List<Component> aStarted,
404 // an exception occurred, stop the successfully started
406 for (int i = aStarted.size() - 1; i >= 0; i--) {
408 Component component = aStarted.get(i);
409 aStarted.get(i).stop(aScope.getRuntime(component));
410 } catch (Throwable t) {
411 LOG.error(getQualifiedName() + ": error stopping " +
412 aStarted.get(i).getQualifiedName());
418 protected void doStop(Scope aScope) {
419 for (int i = components.size() - 1; i >= 0; i--) {
420 Component component = components.get(i);
421 Object runtime = aScope.getRuntime(component);
422 component.stop(runtime);
426 private void checkSealed() {
428 throw new SystemAssemblyException("Container is sealed");
433 * Finds a component based on the non-qualified name of the component.
438 * @return Component or null if not found.
440 public Component findComponent(String aName) {
441 for (Component<?> component : components) {
442 if (component.getName().equals(aName)) {
450 private static <T extends NamedInterface> T findInterface(
451 List<T> aInterfaces, String aInterfaceName) {
452 for (T intf : aInterfaces) {
453 if (intf.getName().equals(aInterfaceName)) {