Removed DOCUMENT ME comments that were generated and applied source code
[utils] / system / general / src / main / java / org / wamblee / system / container / Container.java
1 /*
2  * Copyright 2007 the original author or authors.
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package org.wamblee.system.container;
17
18 import org.apache.commons.logging.Log;
19 import org.apache.commons.logging.LogFactory;
20
21 import org.wamblee.general.Pair;
22
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;
36
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.List;
40
41 /**
42  * Container consisting of multiple components.
43  * 
44  * @author Erik Brakkee
45  */
46 public class Container extends AbstractComponent<Scope> {
47     private static final Log LOG = LogFactory.getLog(Container.class);
48
49     private List<Component> components;
50
51     private CompositeEdgeFilter edgeFilter;
52
53     private boolean sealed;
54
55     /**
56      * Constructs the container
57      * 
58      * @param aName
59      *            Name of the container
60      * @param aComponents
61      *            Components.
62      * @param aProvided
63      *            Provided services of the container
64      * @param aRequired
65      *            Required services by the container.
66      */
67     public Container(String aName, Component[] aComponents,
68         List<ProvidedInterface> aProvided, List<RequiredInterface> aRequired) {
69         super(aName, aProvided, aRequired);
70         components = new ArrayList<Component>();
71
72         edgeFilter = new CompositeEdgeFilter();
73         sealed = false;
74
75         for (Component component : aComponents) {
76             addComponent(component);
77         }
78     }
79
80     /**
81      * Constructs the container
82      * 
83      * @param aName
84      *            Name of the container
85      * @param aComponents
86      *            Components.
87      * @param aProvided
88      *            Provided services of the container
89      * @param aRequired
90      *            Required services by the container.
91      */
92     public Container(String aName, Component[] aComponents,
93         ProvidedInterface[] aProvided, RequiredInterface[] aRequired) {
94         this(aName, aComponents, Arrays.asList(aProvided), Arrays
95             .asList(aRequired));
96     }
97
98     /**
99      * Creates a new Container object.
100      * 
101      */
102     public Container(String aName) {
103         this(aName, new Component[0], new ProvidedInterface[0],
104             new RequiredInterface[0]);
105     }
106
107     public Container addComponent(Component aComponent) {
108         checkSealed();
109
110         if (aComponent.getContext() != null) {
111             throw new SystemAssemblyException(
112                 "Inconsistent hierarchy, component '" + aComponent.getName() +
113                     "' is already part of another hierarchy");
114         }
115
116         if (findComponent(aComponent.getName()) != null) {
117             throw new SystemAssemblyException("Duplicate component '" +
118                 aComponent.getName() + "'");
119         }
120
121         components.add(aComponent);
122         aComponent.addContext(getQualifiedName());
123
124         return this;
125     }
126
127     /**
128      * Explictly connects required and provided interfaces.
129      * 
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.
142      * 
143      */
144     public void connectRequiredProvided(String aClientComponent,
145         String aRequiredInterface, String aServerComponent,
146         String aProvidedInterface) {
147         checkSealed();
148
149         Component client = findComponent(aClientComponent);
150         Component server = findComponent(aServerComponent);
151
152         if (client == null) {
153             throw new SystemAssemblyException(getQualifiedName() +
154                 ": No component '" + aClientComponent + "' in the container");
155         }
156
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 + "'");
164             }
165         }
166
167         if (server == null) {
168             throw new SystemAssemblyException("No component '" +
169                 aClientComponent + "' in the container");
170         }
171
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 + "'");
179             }
180         }
181
182         edgeFilter.add(new ConnectRequiredProvidedEdgeFilter(aClientComponent,
183             aRequiredInterface, aServerComponent, aProvidedInterface));
184     }
185
186     /**
187      * Explicitly connects a externally required interface to an internally
188      * required interface.
189      * 
190      * @param aComponent
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).
196      * 
197      */
198     public void connectExternalRequired(String aComponent,
199         String aRequiredInterface, String aExternalRequiredInterface) {
200         checkSealed();
201
202         Component client = findComponent(aComponent);
203
204         if (client == null) {
205             throw new SystemAssemblyException(getQualifiedName() +
206                 ": No component '" + aComponent + "' in the container");
207         }
208
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 + "'");
216             }
217         }
218
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 + "'");
225             }
226         }
227
228         edgeFilter.add(new ConnectRequiredExternallyRequiredEdgeFilter(
229             aComponent, aRequiredInterface, aExternalRequiredInterface));
230     }
231
232     public void connectExternalProvided(String aExternalProvided,
233         String aComponent, String aProvidedInterface) {
234         checkSealed();
235
236         Component server = findComponent(aComponent);
237
238         if (server == null) {
239             throw new SystemAssemblyException("No component '" + aComponent +
240                 "' in the container");
241         }
242
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 + "'");
250             }
251         }
252
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 + "'");
258             }
259         }
260
261         edgeFilter.add(new ConnectExternalProvidedProvidedFilter(
262             aExternalProvided, aComponent, aProvidedInterface));
263     }
264
265     @Override
266     public Container addProvidedInterface(ProvidedInterface aProvided) {
267         checkSealed();
268         super.addProvidedInterface(aProvided);
269
270         return this;
271     }
272
273     @Override
274     public Container addRequiredInterface(RequiredInterface aRequired) {
275         checkSealed();
276         super.addRequiredInterface(aRequired);
277
278         return this;
279     }
280
281     @Override
282     public void addContext(String aContext) {
283         super.addContext(aContext);
284
285         for (Component component : components) {
286             component.addContext(aContext);
287         }
288     }
289
290     /**
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
294      * requirements.
295      */
296     public void validate() {
297         doStartOptionalDryRun(null, true);
298     }
299
300     /**
301      * Seal the container, meaning that no further components or interfaces may
302      * be added.
303      */
304     public void seal() {
305         sealed = true;
306     }
307
308     /**
309      * Checks if the container is sealed.
310      * 
311      * @return True iff the container is sealed.
312      */
313     public boolean isSealed() {
314         return sealed;
315     }
316
317     /**
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.
320      * 
321      * @return Scope.
322      */
323     public Scope start() {
324         Scope scope = new DefaultScope(getProvidedInterfaces());
325
326         return super.start(scope);
327     }
328
329     @Override
330     protected Scope doStart(Scope aExternalScope) {
331         validate();
332
333         Scope scope = new DefaultScope(getProvidedInterfaces().toArray(
334             new ProvidedInterface[0]), aExternalScope);
335         ComponentGraph graph = doStartOptionalDryRun(scope, false);
336         exposeProvidedInterfaces(graph, aExternalScope, scope);
337         seal();
338
339         return scope;
340     }
341
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);
349         }
350     }
351
352     private ComponentGraph doStartOptionalDryRun(Scope aScope, boolean aDryRun) {
353         ComponentGraph graph = createComponentGraph();
354         graph.validate();
355         graph.link();
356
357         LOG.info("Starting '" + getQualifiedName() + "'");
358
359         List<Component> started = new ArrayList<Component>();
360
361         for (Component component : components) {
362             try {
363                 // Start the service.
364                 if (!aDryRun) {
365                     Object runtime = component.start(aScope);
366                     aScope.addRuntime(component, runtime);
367                     started.add(component);
368                 }
369             } catch (SystemAssemblyException e) {
370                 throw e;
371             } catch (RuntimeException e) {
372                 LOG.error(getQualifiedName() + ": could not start '" +
373                     component.getQualifiedName() + "'", e);
374                 stopAlreadyStartedComponents(started, aScope);
375                 throw e;
376             }
377         }
378
379         return graph;
380     }
381
382     private ComponentGraph createComponentGraph() {
383         ComponentGraph graph = new ComponentGraph();
384
385         for (RequiredInterface req : getRequiredInterfaces()) {
386             graph.addRequiredInterface(this, req);
387         }
388
389         for (Component comp : components) {
390             graph.addComponent(comp);
391         }
392
393         for (ProvidedInterface prov : getProvidedInterfaces()) {
394             graph.addProvidedInterface(this, prov);
395         }
396
397         graph.addEdgeFilter(edgeFilter);
398
399         return graph;
400     }
401
402     private void stopAlreadyStartedComponents(List<Component> aStarted,
403         Scope aScope) {
404         // an exception occurred, stop the successfully started
405         // components
406         for (int i = aStarted.size() - 1; i >= 0; i--) {
407             try {
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());
413             }
414         }
415     }
416
417     @Override
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);
423         }
424     }
425
426     private void checkSealed() {
427         if (sealed) {
428             throw new SystemAssemblyException("Container is sealed");
429         }
430     }
431
432     /**
433      * Finds a component based on the non-qualified name of the component.
434      * 
435      * @param aName
436      *            Component name.
437      * 
438      * @return Component or null if not found.
439      */
440     public Component findComponent(String aName) {
441         for (Component<?> component : components) {
442             if (component.getName().equals(aName)) {
443                 return component;
444             }
445         }
446
447         return null;
448     }
449
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)) {
454                 return intf;
455             }
456         }
457
458         return null;
459     }
460 }