8e98755b5e7b02b8d8c33c2243150fac045660a5
[utils] / system / general / src / main / java / org / wamblee / system / core / 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.core;
17
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Set;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28
29 /**
30  * Container consisting of multiple components.
31  * 
32  * @author Erik Brakkee
33  */
34 public class Container extends AbstractComponent<Scope> {
35
36     private static final Log LOG = LogFactory.getLog(Container.class);
37
38     private List<Component> _components;
39     private Set<String> _componentNames;
40     private boolean _sealed;
41
42     public static RequiredInterface[] filterRequiredServices(
43             ProvidedInterface aProvided,
44             Collection<RequiredInterface> aDescriptors) {
45         List<RequiredInterface> required = new ArrayList<RequiredInterface>();
46         for (RequiredInterface descriptor : aDescriptors) {
47             if (descriptor.implementedBy(aProvided)) {
48                 required.add(descriptor);
49             }
50         }
51         return required.toArray(new RequiredInterface[0]);
52     }
53
54     public static ProvidedInterface[] filterProvidedServices(
55             RequiredInterface aRequired, Collection<ProvidedInterface> aProvided) {
56         List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
57         for (ProvidedInterface descriptor : aProvided) {
58             if (aRequired.implementedBy(descriptor)) {
59                 provided.add(descriptor);
60             }
61         }
62         return provided.toArray(new ProvidedInterface[0]);
63     }
64
65     /**
66      * Constructs the container
67      * 
68      * @param aName
69      *            Name of the container
70      * @param aComponents
71      *            Components.
72      * @param aProvided
73      *            Provided services of the container
74      * @param aRequired
75      *            Required services by the container.
76      */
77     public Container(String aName, Component[] aComponents,
78             ProvidedInterface[] aProvided, RequiredInterface[] aRequired) {
79         super(aName, aProvided, aRequired);
80         _components = new ArrayList<Component>();
81
82         _componentNames = new HashSet<String>();
83         _sealed = false;
84         for (Component component : aComponents) {
85             addComponent(component);
86         }
87     }
88
89     public Container(String aName) {
90         this(aName, new Component[0], new ProvidedInterface[0],
91                 new RequiredInterface[0]);
92     }
93
94     public Container addComponent(Component aComponent) {
95         checkSealed();
96         if (aComponent.getContext() != null) {
97             throw new SystemAssemblyException(
98                     "Inconsistent hierarchy, component '"
99                             + aComponent.getName()
100                             + "' is already part of another hierarchy");
101         }
102         if (_componentNames.contains(aComponent.getName())) {
103             throw new SystemAssemblyException("Duplicate component '"
104                     + aComponent.getName() + "'");
105         }
106         _components.add(aComponent);
107         _componentNames.add(aComponent.getName());
108         aComponent.addContext(getQualifiedName());
109         return this;
110     }
111
112     @Override
113     public Container addProvidedInterface(ProvidedInterface aProvided) {
114         checkSealed();
115         super.addProvidedInterface(aProvided);
116         return this;
117     }
118
119     @Override
120     public Container addRequiredInterface(RequiredInterface aRequired) {
121         checkSealed();
122         super.addRequiredInterface(aRequired);
123         return this;
124     }
125
126     /**
127      * Validates the components together to check that there are no required
128      * services not in the required list and no services in the provided list
129      * that cannot be provided. Also logs a warning in case of superfluous
130      * requirements.
131      * 
132      * @throws SystemAssemblyException
133      *             in case of any validation problems.
134      */
135     public void validate() {
136         List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
137         for (Component component : _components) {
138             provided.addAll(Arrays.asList(component.getProvidedInterfaces()));
139         }
140
141         List<RequiredInterface> required = new ArrayList<RequiredInterface>();
142         for (Component component : _components) {
143             required.addAll(Arrays.asList(component.getRequiredInterfaces()));
144         }
145
146         validateProvidedInterfacesArePresent(provided);
147
148         validateRequiredInterfaces(required);
149
150         addProvidersOfRequiredInterfaces(provided);
151         validateRequiredProvidedMatch(provided, required);
152     }
153
154     /**
155      * 
156      * @param aProvided
157      *            All provided interfaces.
158      * @param aRequired
159      *            All required interfaces.
160      * @return
161      */
162     private void validateRequiredProvidedMatch(
163             List<ProvidedInterface> aProvided, List<RequiredInterface> aRequired) {
164
165         for (Component component : _components) {
166             initializeProvidersForRequiredInterfaces(aProvided, component, true);
167         }
168     }
169
170     private void validateRequiredInterfaces(List<RequiredInterface> aRequired) {
171         for (RequiredInterface service : getRequiredInterfaces()) {
172             // TODO required interfaces by the component could be
173             // subclasses or implementations of the requirements
174             // of the contained components. The code below assumes
175             // an exact match.
176             if (!(aRequired.contains(service))) {
177                 info("Service '"
178                         + service
179                         + "' indicated as required is not actually required by any of the components");
180             }
181             // Check for the case that the externally required service
182             // is optional whereas the internally required service is
183             // mandatory.
184             if (service.isOptional()) {
185                 for (RequiredInterface intf : aRequired) {
186                     if (intf.equals(service) && !intf.isOptional()) {
187                         warn("Required service '"
188                                 + service
189                                 + "' indicated as optional is mandatory by one of its components ("
190                                 + getClients(intf)
191                                 + ", "
192                                 + intf
193                                 + "), this can lead to problems when the container is started and the interface is not provided to the container.");
194                     }
195                 }
196             }
197         }
198     }
199
200     private void validateProvidedInterfacesArePresent(
201             List<ProvidedInterface> aProvided) {
202         for (ProvidedInterface service : getProvidedInterfaces()) {
203             // TODO provided interfaces by components could be
204             // provide subclasses or implementations of the
205             // provided interfaces of the container.
206             // The code below assumes an exact match.
207             if (!(aProvided.contains(service))) {
208                 throw new SystemAssemblyException(getName() + ": Service '"
209                         + service
210                         + "' is not provided by any of its components");
211             }
212         }
213     }
214
215     /**
216      * Starts the container. After the container is started, the container
217      * becomes sealed meaning that no further components, required or provided
218      * interfaces may be added.
219      * 
220      * @return Scope.
221      */
222     public Scope start() {
223         checkSealed();
224         validate();
225         Scope scope = super.start(new DefaultScope(new ProvidedInterface[0]));
226         seal();
227         return scope;
228     }
229
230     /**
231      * Seal the container, meaning that no further components or interfaces may
232      * be added.
233      */
234     public void seal() {
235         _sealed = true;
236     }
237
238     /**
239      * Checks if the container is sealed.
240      * 
241      * @return True iff the container is sealed.
242      */
243     public boolean isSealed() {
244         return _sealed;
245     }
246
247     @Override
248     protected Scope doStart(Scope aExternalScope) {
249         LOG.info("Starting '" + getQualifiedName() + "'");
250
251         Scope scope = new DefaultScope(getProvidedInterfaces(), aExternalScope);
252
253         List<ProvidedInterface> allProvided = new ArrayList<ProvidedInterface>();
254
255         addProvidersOfRequiredInterfaces(allProvided);
256
257         List<Component> started = new ArrayList<Component>();
258         for (Component component : _components) {
259             try {
260                 initializeProvidersForRequiredInterfaces(allProvided,
261                         component, false);
262
263                 // Start the service.
264                 Object runtime = component.start(scope);
265                 scope.addRuntime(component, runtime);
266                 started.add(component);
267
268                 // add all provided services
269                 ProvidedInterface[] provided = component
270                         .getProvidedInterfaces();
271                 allProvided.addAll(Arrays.asList(provided));
272             } catch (SystemAssemblyException e) {
273                 throw e;
274             } catch (RuntimeException e) {
275                 LOG.error(getQualifiedName() + ": could not start '"
276                         + component.getQualifiedName() + "'", e);
277                 stopAlreadyStartedComponents(started, scope);
278                 throw e;
279             }
280         }
281         return scope;
282     }
283
284     private void addProvidersOfRequiredInterfaces(
285             List<ProvidedInterface> allProvided) {
286         // all interfaces from the required list of this container are
287         // provided to the components inside it.
288         RequiredInterface[] required = getRequiredInterfaces();
289         for (RequiredInterface intf : required) {
290             ProvidedInterface provider = intf.getProvider();
291             if (provider != null) {
292                 allProvided.add(provider);
293             } else {
294                 if (!intf.isOptional()) {
295                     throw new SystemAssemblyException(getQualifiedName()
296                             + ": required interface '" + intf
297                             + "' is not provided");
298                 }
299             }
300         }
301     }
302
303     private void stopAlreadyStartedComponents(List<Component> aStarted,
304             Scope aScope) {
305         // an exception occurred, stop the successfully started
306         // components
307         for (int i = aStarted.size() - 1; i >= 0; i--) {
308             try {
309                 aStarted.get(i).stop(aScope);
310             } catch (Throwable t) {
311                 LOG.error(getQualifiedName() + ": error stopping "
312                         + aStarted.get(i).getQualifiedName());
313             }
314         }
315     }
316
317     /**
318      * Sets the provided interface or a component.
319      * 
320      * @param aAllProvided
321      *            All available provided interfaces.
322      * @param aComponent
323      *            Component whose required interfaces we are looking at.
324      * @param aValidateOnly
325      *            If true then the provider will not be set for required
326      *            interfaces.
327      */
328     private void initializeProvidersForRequiredInterfaces(
329             List<ProvidedInterface> aAllProvided, Component aComponent,
330             boolean aValidateOnly) {
331         // Check if all required services are already provided by
332         // earlier
333         // systems.
334
335         for (RequiredInterface descriptor : aComponent.getRequiredInterfaces()) {
336             ProvidedInterface[] filtered = filterProvidedServices(descriptor,
337                     aAllProvided);
338             if (filtered.length == 1) {
339                 if (!aValidateOnly) {
340                     descriptor.setProvider(filtered[0]);
341                 }
342             } else if (filtered.length > 1) {
343                 throw new SystemAssemblyException(
344                         "Service '"
345                                 + descriptor
346                                 + "' required by system '"
347                                 + aComponent
348                                 + "' matches multiple services provided by other systems: "
349                                 + getServers(filtered));
350             } else {
351                 // filtered.length == 0
352                 if (!descriptor.isOptional()) {
353                     throw new SystemAssemblyException(
354                             "Service '"
355                                     + descriptor
356                                     + "' required by system '"
357                                     + aComponent
358                                     + "' is not provided by systems that are started earlier");
359                 }
360             }
361         }
362     }
363
364     @Override
365     protected void doStop(Scope aScope) {
366         for (int i = _components.size() - 1; i >= 0; i--) {
367             Component component = _components.get(i);
368             Object runtime = aScope.getRuntime(component);
369             component.stop(runtime);
370         }
371     }
372
373     private void info(String aMsg) {
374         LOG.info(getQualifiedName() + ": " + aMsg);
375     }
376
377     private void warn(String aMsg) {
378         LOG.warn(getQualifiedName() + ": " + aMsg);
379     }
380
381     private String getServers(ProvidedInterface[] aProvidedList) {
382         String result = "";
383         for (ProvidedInterface provided : aProvidedList) {
384             result += "(components ";
385             for (Component component : _components) {
386                 for (ProvidedInterface provided2 : component
387                         .getProvidedInterfaces()) {
388                     if (provided.equals(provided2)) {
389                         result += component + " ";
390                     }
391                 }
392             }
393             result += ", interface " + provided + ")";
394         }
395         return result;
396     }
397
398     private List<Component> getClients(RequiredInterface aRequirement) {
399         List<Component> clients = new ArrayList<Component>();
400         for (Component component : _components) {
401             for (RequiredInterface required : component.getRequiredInterfaces()) {
402                 if (required.equals(aRequirement)
403                         && required.isOptional() == aRequirement.isOptional()) {
404                     clients.add(component);
405                 }
406             }
407         }
408         return clients;
409     }
410
411     private void checkSealed() {
412         if (_sealed) {
413             throw new SystemAssemblyException("Container is sealed");
414         }
415     }
416 }