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 java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.List;
21 import java.util.logging.Level;
22 import java.util.logging.Logger;
24 import org.wamblee.general.Pair;
25 import org.wamblee.system.core.AbstractComponent;
26 import org.wamblee.system.core.Component;
27 import org.wamblee.system.core.DefaultScope;
28 import org.wamblee.system.core.NamedInterface;
29 import org.wamblee.system.core.ProvidedInterface;
30 import org.wamblee.system.core.RequiredInterface;
31 import org.wamblee.system.core.Scope;
32 import org.wamblee.system.core.SystemAssemblyException;
33 import org.wamblee.system.graph.CompositeEdgeFilter;
34 import org.wamblee.system.graph.component.ComponentGraph;
35 import org.wamblee.system.graph.component.ConnectExternalProvidedProvidedFilter;
36 import org.wamblee.system.graph.component.ConnectRequiredExternallyRequiredEdgeFilter;
37 import org.wamblee.system.graph.component.ConnectRequiredProvidedEdgeFilter;
40 * Container consisting of multiple components.
42 * @author Erik Brakkee
44 public class Container extends AbstractComponent<Scope> {
45 private static final Logger LOG = Logger.getLogger(Container.class
48 private List<Component> components;
50 private CompositeEdgeFilter edgeFilter;
52 private boolean sealed;
55 * Constructs the container
58 * Name of the container
62 * Provided services of the container
64 * Required services by the container.
66 public Container(String aName, Component[] aComponents,
67 List<ProvidedInterface> aProvided, List<RequiredInterface> aRequired) {
68 super(aName, aProvided, aRequired);
69 components = new ArrayList<Component>();
71 edgeFilter = new CompositeEdgeFilter();
74 for (Component component : aComponents) {
75 addComponent(component);
80 * Constructs the container
83 * Name of the container
87 * Provided services of the container
89 * Required services by the container.
91 public Container(String aName, Component[] aComponents,
92 ProvidedInterface[] aProvided, RequiredInterface[] aRequired) {
93 this(aName, aComponents, Arrays.asList(aProvided), Arrays
98 * Creates a new Container object.
101 public Container(String aName) {
102 this(aName, new Component[0], new ProvidedInterface[0],
103 new RequiredInterface[0]);
106 public Container addComponent(Component aComponent) {
109 if (aComponent.getContext() != null) {
110 throw new SystemAssemblyException(
111 "Inconsistent hierarchy, component '" + aComponent.getName() +
112 "' is already part of another hierarchy");
115 if (findComponent(aComponent.getName()) != null) {
116 throw new SystemAssemblyException("Duplicate component '" +
117 aComponent.getName() + "'");
120 components.add(aComponent);
121 aComponent.addContext(getQualifiedName());
127 * Explictly connects required and provided interfaces.
129 * @param aClientComponent
130 * Client component, may not be null.
131 * @param aRequiredInterface
132 * Required interface. If null it means all required interfaces.
133 * @param aServerComponent
134 * Server component to connect to. If null, it means that no
135 * server components may be connected to and the provider of the
136 * required interface will be null.
137 * @param aProvidedInterface
138 * Provided interface. If null, it means that there is no
139 * restriction on the name of the provided interface and that it
140 * is automatically selected.
143 public void connectRequiredProvided(String aClientComponent,
144 String aRequiredInterface, String aServerComponent,
145 String aProvidedInterface) {
148 Component client = findComponent(aClientComponent);
149 Component server = findComponent(aServerComponent);
151 if (client == null) {
152 throw new SystemAssemblyException(getQualifiedName() +
153 ": No component '" + aClientComponent + "' in the container");
156 if (aRequiredInterface != null) {
157 if (findInterface(client.getRequiredInterfaces(),
158 aRequiredInterface) == null) {
159 throw new SystemAssemblyException(getQualifiedName() +
160 ": Component '" + aClientComponent +
161 "' does not have a required interface named '" +
162 aRequiredInterface + "'");
166 if (server == null) {
167 throw new SystemAssemblyException("No component '" +
168 aClientComponent + "' in the container");
171 if (aProvidedInterface != null) {
172 if (findInterface(server.getProvidedInterfaces(),
173 aProvidedInterface) == null) {
174 throw new SystemAssemblyException(getQualifiedName() +
175 ": Component '" + aServerComponent +
176 "' does not have a provided interface named '" +
177 aProvidedInterface + "'");
181 edgeFilter.add(new ConnectRequiredProvidedEdgeFilter(aClientComponent,
182 aRequiredInterface, aServerComponent, aProvidedInterface));
186 * Explicitly connects a externally required interface to an internally
187 * required interface.
190 * Component requiring the interface (must be non-null).
191 * @param aRequiredInterface
192 * Required interface of the component (must be non-null).
193 * @param aExternalRequiredInterface
194 * Externally required interface (must be non-null).
197 public void connectExternalRequired(String aComponent,
198 String aRequiredInterface, String aExternalRequiredInterface) {
201 Component client = findComponent(aComponent);
203 if (client == null) {
204 throw new SystemAssemblyException(getQualifiedName() +
205 ": No component '" + aComponent + "' in the container");
208 if (aRequiredInterface != null) {
209 if (findInterface(client.getRequiredInterfaces(),
210 aRequiredInterface) == null) {
211 throw new SystemAssemblyException(getQualifiedName() +
212 ": Component '" + aComponent +
213 "' does not have a required interface named '" +
214 aRequiredInterface + "'");
218 if (aExternalRequiredInterface != null) {
219 if (findInterface(getRequiredInterfaces(),
220 aExternalRequiredInterface) == null) {
221 throw new SystemAssemblyException(getQualifiedName() +
222 ": container does not have a required interface named '" +
223 aExternalRequiredInterface + "'");
227 edgeFilter.add(new ConnectRequiredExternallyRequiredEdgeFilter(
228 aComponent, aRequiredInterface, aExternalRequiredInterface));
231 public void connectExternalProvided(String aExternalProvided,
232 String aComponent, String aProvidedInterface) {
235 Component server = findComponent(aComponent);
237 if (server == null) {
238 throw new SystemAssemblyException("No component '" + aComponent +
239 "' in the container");
242 if (aProvidedInterface != null) {
243 if (findInterface(server.getProvidedInterfaces(),
244 aProvidedInterface) == null) {
245 throw new SystemAssemblyException(getQualifiedName() +
246 ": Component '" + aComponent +
247 "' does not have a provided interface named '" +
248 aProvidedInterface + "'");
252 if (aExternalProvided != null) {
253 if (findInterface(getProvidedInterfaces(), aExternalProvided) == null) {
254 throw new SystemAssemblyException(getQualifiedName() +
255 ": Container does not have a provided interface named '" +
256 aExternalProvided + "'");
260 edgeFilter.add(new ConnectExternalProvidedProvidedFilter(
261 aExternalProvided, aComponent, aProvidedInterface));
265 public Container addProvidedInterface(ProvidedInterface aProvided) {
267 super.addProvidedInterface(aProvided);
273 public Container addRequiredInterface(RequiredInterface aRequired) {
275 super.addRequiredInterface(aRequired);
281 public void addContext(String aContext) {
282 super.addContext(aContext);
284 for (Component component : components) {
285 component.addContext(aContext);
290 * Validates the components together to check that there are no required
291 * services not in the required list and no services in the provided list
292 * that cannot be provided. Also logs a warning in case of superfluous
295 public void validate() {
296 doStartOptionalDryRun(null, true);
300 * Seal the container, meaning that no further components or interfaces may
308 * Checks if the container is sealed.
310 * @return True iff the container is sealed.
312 public boolean isSealed() {
317 * Utility method to start with an empty external scope. This is useful for
318 * top-level containers which are not part of another container.
322 public Scope start() {
323 Scope scope = new DefaultScope(getProvidedInterfaces());
325 return super.start(scope);
329 protected Scope doStart(Scope aExternalScope) {
332 Scope scope = new DefaultScope(getProvidedInterfaces().toArray(
333 new ProvidedInterface[0]), aExternalScope);
334 ComponentGraph graph = doStartOptionalDryRun(scope, false);
335 exposeProvidedInterfaces(graph, aExternalScope, scope);
341 private void exposeProvidedInterfaces(ComponentGraph aGraph,
342 Scope aExternalScope, Scope aInternalScope) {
343 for (Pair<ProvidedInterface, ProvidedInterface> mapping : aGraph
344 .findExternalProvidedInterfaceMapping()) {
345 Object svc = aInternalScope.getInterfaceImplementation(mapping
346 .getSecond(), Object.class);
347 addInterface(mapping.getFirst(), svc, aExternalScope);
351 private ComponentGraph doStartOptionalDryRun(Scope aScope, boolean aDryRun) {
352 ComponentGraph graph = createComponentGraph();
356 LOG.info("Starting '" + getQualifiedName() + "'");
358 List<Component> started = new ArrayList<Component>();
360 for (Component component : components) {
362 // Start the service.
364 Object runtime = component.start(aScope);
365 aScope.addRuntime(component, runtime);
366 started.add(component);
368 } catch (SystemAssemblyException e) {
370 } catch (RuntimeException e) {
371 LOG.log(Level.WARNING, getQualifiedName() +
372 ": could not start '" + component.getQualifiedName() + "'",
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) {
412 .log(Level.WARNING, getQualifiedName() +
413 ": error stopping " +
414 aStarted.get(i).getQualifiedName(), t);
420 protected void doStop(Scope aScope) {
421 for (int i = components.size() - 1; i >= 0; i--) {
422 Component component = components.get(i);
423 Object runtime = aScope.getRuntime(component);
424 component.stop(runtime);
428 private void checkSealed() {
430 throw new SystemAssemblyException("Container is sealed");
435 * Finds a component based on the non-qualified name of the component.
440 * @return Component or null if not found.
442 public Component findComponent(String aName) {
443 for (Component<?> component : components) {
444 if (component.getName().equals(aName)) {
452 private static <T extends NamedInterface> T findInterface(
453 List<T> aInterfaces, String aInterfaceName) {
454 for (T intf : aInterfaces) {
455 if (intf.getName().equals(aInterfaceName)) {