a7d19e6c761be637611d08e8edfc26d6ef95dfad
[utils] / system / general / src / main / java / org / wamblee / system / container / Container.java
1 /*
2  * Copyright 2005-2010 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 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;
23
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;
38
39 /**
40  * Container consisting of multiple components.
41  * 
42  * @author Erik Brakkee
43  */
44 public class Container extends AbstractComponent<Scope> {
45     private static final Logger LOG = Logger.getLogger(Container.class.getName());
46
47     private List<Component> components;
48
49     private CompositeEdgeFilter edgeFilter;
50
51     private boolean sealed;
52
53     /**
54      * Constructs the container
55      * 
56      * @param aName
57      *            Name of the container
58      * @param aComponents
59      *            Components.
60      * @param aProvided
61      *            Provided services of the container
62      * @param aRequired
63      *            Required services by the container.
64      */
65     public Container(String aName, Component[] aComponents,
66         List<ProvidedInterface> aProvided, List<RequiredInterface> aRequired) {
67         super(aName, aProvided, aRequired);
68         components = new ArrayList<Component>();
69
70         edgeFilter = new CompositeEdgeFilter();
71         sealed = false;
72
73         for (Component component : aComponents) {
74             addComponent(component);
75         }
76     }
77
78     /**
79      * Constructs the container
80      * 
81      * @param aName
82      *            Name of the container
83      * @param aComponents
84      *            Components.
85      * @param aProvided
86      *            Provided services of the container
87      * @param aRequired
88      *            Required services by the container.
89      */
90     public Container(String aName, Component[] aComponents,
91         ProvidedInterface[] aProvided, RequiredInterface[] aRequired) {
92         this(aName, aComponents, Arrays.asList(aProvided), Arrays
93             .asList(aRequired));
94     }
95
96     /**
97      * Creates a new Container object.
98      * 
99      */
100     public Container(String aName) {
101         this(aName, new Component[0], new ProvidedInterface[0],
102             new RequiredInterface[0]);
103     }
104
105     public Container addComponent(Component aComponent) {
106         checkSealed();
107
108         if (aComponent.getContext() != null) {
109             throw new SystemAssemblyException(
110                 "Inconsistent hierarchy, component '" + aComponent.getName() +
111                     "' is already part of another hierarchy");
112         }
113
114         if (findComponent(aComponent.getName()) != null) {
115             throw new SystemAssemblyException("Duplicate component '" +
116                 aComponent.getName() + "'");
117         }
118
119         components.add(aComponent);
120         aComponent.addContext(getQualifiedName());
121
122         return this;
123     }
124
125     /**
126      * Explictly connects required and provided interfaces.
127      * 
128      * @param aClientComponent
129      *            Client component, may not be null.
130      * @param aRequiredInterface
131      *            Required interface. If null it means all required interfaces.
132      * @param aServerComponent
133      *            Server component to connect to. If null, it means that no
134      *            server components may be connected to and the provider of the
135      *            required interface will be null.
136      * @param aProvidedInterface
137      *            Provided interface. If null, it means that there is no
138      *            restriction on the name of the provided interface and that it
139      *            is automatically selected.
140      * 
141      */
142     public void connectRequiredProvided(String aClientComponent,
143         String aRequiredInterface, String aServerComponent,
144         String aProvidedInterface) {
145         checkSealed();
146
147         Component client = findComponent(aClientComponent);
148         Component server = findComponent(aServerComponent);
149
150         if (client == null) {
151             throw new SystemAssemblyException(getQualifiedName() +
152                 ": No component '" + aClientComponent + "' in the container");
153         }
154
155         if (aRequiredInterface != null) {
156             if (findInterface(client.getRequiredInterfaces(),
157                 aRequiredInterface) == null) {
158                 throw new SystemAssemblyException(getQualifiedName() +
159                     ": Component '" + aClientComponent +
160                     "' does not have a required interface named '" +
161                     aRequiredInterface + "'");
162             }
163         }
164
165         if (server == null) {
166             throw new SystemAssemblyException("No component '" +
167                 aClientComponent + "' in the container");
168         }
169
170         if (aProvidedInterface != null) {
171             if (findInterface(server.getProvidedInterfaces(),
172                 aProvidedInterface) == null) {
173                 throw new SystemAssemblyException(getQualifiedName() +
174                     ": Component '" + aServerComponent +
175                     "' does not have a provided interface named '" +
176                     aProvidedInterface + "'");
177             }
178         }
179
180         edgeFilter.add(new ConnectRequiredProvidedEdgeFilter(aClientComponent,
181             aRequiredInterface, aServerComponent, aProvidedInterface));
182     }
183
184     /**
185      * Explicitly connects a externally required interface to an internally
186      * required interface.
187      * 
188      * @param aComponent
189      *            Component requiring the interface (must be non-null).
190      * @param aRequiredInterface
191      *            Required interface of the component (must be non-null).
192      * @param aExternalRequiredInterface
193      *            Externally required interface (must be non-null).
194      * 
195      */
196     public void connectExternalRequired(String aComponent,
197         String aRequiredInterface, String aExternalRequiredInterface) {
198         checkSealed();
199
200         Component client = findComponent(aComponent);
201
202         if (client == null) {
203             throw new SystemAssemblyException(getQualifiedName() +
204                 ": No component '" + aComponent + "' in the container");
205         }
206
207         if (aRequiredInterface != null) {
208             if (findInterface(client.getRequiredInterfaces(),
209                 aRequiredInterface) == null) {
210                 throw new SystemAssemblyException(getQualifiedName() +
211                     ": Component '" + aComponent +
212                     "' does not have a required interface named '" +
213                     aRequiredInterface + "'");
214             }
215         }
216
217         if (aExternalRequiredInterface != null) {
218             if (findInterface(getRequiredInterfaces(),
219                 aExternalRequiredInterface) == null) {
220                 throw new SystemAssemblyException(getQualifiedName() +
221                     ": container does not have a required interface named '" +
222                     aExternalRequiredInterface + "'");
223             }
224         }
225
226         edgeFilter.add(new ConnectRequiredExternallyRequiredEdgeFilter(
227             aComponent, aRequiredInterface, aExternalRequiredInterface));
228     }
229
230     public void connectExternalProvided(String aExternalProvided,
231         String aComponent, String aProvidedInterface) {
232         checkSealed();
233
234         Component server = findComponent(aComponent);
235
236         if (server == null) {
237             throw new SystemAssemblyException("No component '" + aComponent +
238                 "' in the container");
239         }
240
241         if (aProvidedInterface != null) {
242             if (findInterface(server.getProvidedInterfaces(),
243                 aProvidedInterface) == null) {
244                 throw new SystemAssemblyException(getQualifiedName() +
245                     ": Component '" + aComponent +
246                     "' does not have a provided interface named '" +
247                     aProvidedInterface + "'");
248             }
249         }
250
251         if (aExternalProvided != null) {
252             if (findInterface(getProvidedInterfaces(), aExternalProvided) == null) {
253                 throw new SystemAssemblyException(getQualifiedName() +
254                     ": Container does not have a provided interface named '" +
255                     aExternalProvided + "'");
256             }
257         }
258
259         edgeFilter.add(new ConnectExternalProvidedProvidedFilter(
260             aExternalProvided, aComponent, aProvidedInterface));
261     }
262
263     @Override
264     public Container addProvidedInterface(ProvidedInterface aProvided) {
265         checkSealed();
266         super.addProvidedInterface(aProvided);
267
268         return this;
269     }
270
271     @Override
272     public Container addRequiredInterface(RequiredInterface aRequired) {
273         checkSealed();
274         super.addRequiredInterface(aRequired);
275
276         return this;
277     }
278
279     @Override
280     public void addContext(String aContext) {
281         super.addContext(aContext);
282
283         for (Component component : components) {
284             component.addContext(aContext);
285         }
286     }
287
288     /**
289      * Validates the components together to check that there are no required
290      * services not in the required list and no services in the provided list
291      * that cannot be provided. Also logs a warning in case of superfluous
292      * requirements.
293      */
294     public void validate() {
295         doStartOptionalDryRun(null, true);
296     }
297
298     /**
299      * Seal the container, meaning that no further components or interfaces may
300      * be added.
301      */
302     public void seal() {
303         sealed = true;
304     }
305
306     /**
307      * Checks if the container is sealed.
308      * 
309      * @return True iff the container is sealed.
310      */
311     public boolean isSealed() {
312         return sealed;
313     }
314
315     /**
316      * Utility method to start with an empty external scope. This is useful for
317      * top-level containers which are not part of another container.
318      * 
319      * @return Scope.
320      */
321     public Scope start() {
322         Scope scope = new DefaultScope(getProvidedInterfaces());
323
324         return super.start(scope);
325     }
326
327     @Override
328     protected Scope doStart(Scope aExternalScope) {
329         validate();
330
331         Scope scope = new DefaultScope(getProvidedInterfaces().toArray(
332             new ProvidedInterface[0]), aExternalScope);
333         ComponentGraph graph = doStartOptionalDryRun(scope, false);
334         exposeProvidedInterfaces(graph, aExternalScope, scope);
335         seal();
336
337         return scope;
338     }
339
340     private void exposeProvidedInterfaces(ComponentGraph aGraph,
341         Scope aExternalScope, Scope aInternalScope) {
342         for (Pair<ProvidedInterface, ProvidedInterface> mapping : aGraph
343             .findExternalProvidedInterfaceMapping()) {
344             Object svc = aInternalScope.getInterfaceImplementation(mapping
345                 .getSecond(), Object.class);
346             addInterface(mapping.getFirst(), svc, aExternalScope);
347         }
348     }
349
350     private ComponentGraph doStartOptionalDryRun(Scope aScope, boolean aDryRun) {
351         ComponentGraph graph = createComponentGraph();
352         graph.validate();
353         graph.link();
354
355         LOG.info("Starting '" + getQualifiedName() + "'");
356
357         List<Component> started = new ArrayList<Component>();
358
359         for (Component component : components) {
360             try {
361                 // Start the service.
362                 if (!aDryRun) {
363                     Object runtime = component.start(aScope);
364                     aScope.addRuntime(component, runtime);
365                     started.add(component);
366                 }
367             } catch (SystemAssemblyException e) {
368                 throw e;
369             } catch (RuntimeException e) {
370                 LOG.log(Level.WARNING, getQualifiedName() + ": could not start '" +
371                     component.getQualifiedName() + "'", e);
372                 stopAlreadyStartedComponents(started, aScope);
373                 throw e;
374             }
375         }
376
377         return graph;
378     }
379
380     private ComponentGraph createComponentGraph() {
381         ComponentGraph graph = new ComponentGraph();
382
383         for (RequiredInterface req : getRequiredInterfaces()) {
384             graph.addRequiredInterface(this, req);
385         }
386
387         for (Component comp : components) {
388             graph.addComponent(comp);
389         }
390
391         for (ProvidedInterface prov : getProvidedInterfaces()) {
392             graph.addProvidedInterface(this, prov);
393         }
394
395         graph.addEdgeFilter(edgeFilter);
396
397         return graph;
398     }
399
400     private void stopAlreadyStartedComponents(List<Component> aStarted,
401         Scope aScope) {
402         // an exception occurred, stop the successfully started
403         // components
404         for (int i = aStarted.size() - 1; i >= 0; i--) {
405             try {
406                 Component component = aStarted.get(i);
407                 aStarted.get(i).stop(aScope.getRuntime(component));
408             } catch (Throwable t) {
409                 LOG.log(Level.WARNING, getQualifiedName() + ": error stopping " +
410                     aStarted.get(i).getQualifiedName(), t);
411             }
412         }
413     }
414
415     @Override
416     protected void doStop(Scope aScope) {
417         for (int i = components.size() - 1; i >= 0; i--) {
418             Component component = components.get(i);
419             Object runtime = aScope.getRuntime(component);
420             component.stop(runtime);
421         }
422     }
423
424     private void checkSealed() {
425         if (sealed) {
426             throw new SystemAssemblyException("Container is sealed");
427         }
428     }
429
430     /**
431      * Finds a component based on the non-qualified name of the component.
432      * 
433      * @param aName
434      *            Component name.
435      * 
436      * @return Component or null if not found.
437      */
438     public Component findComponent(String aName) {
439         for (Component<?> component : components) {
440             if (component.getName().equals(aName)) {
441                 return component;
442             }
443         }
444
445         return null;
446     }
447
448     private static <T extends NamedInterface> T findInterface(
449         List<T> aInterfaces, String aInterfaceName) {
450         for (T intf : aInterfaces) {
451             if (intf.getName().equals(aInterfaceName)) {
452                 return intf;
453             }
454         }
455
456         return null;
457     }
458 }