added default container to support classes directly.
[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         validate();
88     }
89
90     public Container(String aName) {
91         this(aName, new Component[0], new ProvidedInterface[0],
92                 new RequiredInterface[0]);
93     }
94
95     public Container addComponent(Component aComponent) {
96         checkSealed();
97         if (aComponent.getContext() != null) {
98             throw new SystemAssemblyException(
99                     "Inconsistent hierarchy, component '"
100                             + aComponent.getName()
101                             + "' is already part of another hierarchy");
102         }
103         if (_componentNames.contains(aComponent.getName())) {
104             throw new SystemAssemblyException("Duplicate component '"
105                     + aComponent.getName() + "'");
106         }
107         _components.add(aComponent);
108         _componentNames.add(aComponent.getName());
109         aComponent.addContext(getQualifiedName());
110         return this;
111     }
112
113     @Override
114     public Container addProvidedInterface(ProvidedInterface aProvided) {
115         checkSealed();
116         super.addProvidedInterface(aProvided);
117         return this;
118     }
119
120     @Override
121     public Container addRequiredInterface(RequiredInterface aRequired) {
122         checkSealed();
123         super.addRequiredInterface(aRequired);
124         return this;
125     }
126
127     /**
128      * Validates the components together to check that there are no required
129      * services not in the required list and no services in the provided list
130      * that cannot be provided. Also logs a warning in case of superfluous
131      * requirements.
132      * 
133      * @throws SystemAssemblyException
134      *             in case of any validation problems.
135      */
136     public void validate() {
137         List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
138         for (Component component : _components) {
139             provided.addAll(Arrays.asList(component.getProvidedInterfaces()));
140         }
141
142         List<RequiredInterface> required = new ArrayList<RequiredInterface>();
143         for (Component component : _components) {
144             required.addAll(Arrays.asList(component.getRequiredInterfaces()));
145         }
146
147         validateProvidedInterfaces(provided);
148
149         validateRequiredInterfaces(required);
150
151         List<RequiredInterface> reallyRequired = validateRequiredProvidedMatch(
152                 provided, required);
153
154         String missingRequired = "";
155         for (RequiredInterface service : reallyRequired) {
156             missingRequired += service + "\n";
157         }
158         if (missingRequired.length() > 0) {
159             throw new SystemAssemblyException(getName()
160                     + ": missing required services\n" + missingRequired);
161         }
162     }
163
164     private List<RequiredInterface> validateRequiredProvidedMatch(
165             List<ProvidedInterface> aProvided, List<RequiredInterface> aRequired) {
166         List<RequiredInterface> reallyRequired = new ArrayList<RequiredInterface>(
167                 aRequired);
168         // Compute all required interfaces that are not provided
169
170         for (ProvidedInterface service : aProvided) {
171             List<RequiredInterface> fulfilled = Arrays
172                     .asList(filterRequiredServices(service, reallyRequired));
173             reallyRequired.removeAll(fulfilled);
174         }
175         // Now remove all optional interfaces from the list.
176         for (Iterator<RequiredInterface> i = reallyRequired.iterator(); i
177                 .hasNext();) {
178             RequiredInterface req = i.next();
179             if (req.isOptional()) {
180                 i.remove();
181             }
182         }
183         // Now the remaining interfaces should be covered by the required
184         // list.
185         reallyRequired.removeAll(Arrays.asList(getRequiredInterfaces()));
186         return reallyRequired;
187     }
188
189     private void validateRequiredInterfaces(List<RequiredInterface> aRequired) {
190         for (RequiredInterface service : getRequiredInterfaces()) {
191             // TODO required interfaces by the component could be
192             // subclasses or implementations of the requirements
193             // of the contained components. The code below assumes
194             // an exact match.
195             if (!(aRequired.contains(service))) {
196                 info("Service '"
197                         + service
198                         + "' indicated as required is not actually required by any of the components");
199             }
200             // Check for the case that the externally required service
201             // is optional whereas the internally required service is
202             // mandatory.
203             if (service.isOptional()) {
204                 for (RequiredInterface intf : aRequired) {
205                     if (intf.equals(service) && !intf.isOptional()) {
206                         warn("Required service '"
207                                 + service
208                                 + "' indicated as optional is mandatory by one of its components ("
209                                 + getClients(intf)
210                                 + ", "
211                                 + intf
212                                 + "), this can lead to problems when the container is started and the interface is not provided to the container.");
213                     }
214                 }
215             }
216         }
217     }
218
219     private void validateProvidedInterfaces(List<ProvidedInterface> aProvided) {
220         for (ProvidedInterface service : getProvidedInterfaces()) {
221             // TODO provided interfaces by components could be
222             // provide subclasses or implementations of the
223             // provided interfaces of the container.
224             // The code below assumes an exact match.
225             if (!(aProvided.contains(service))) {
226                 throw new SystemAssemblyException(getName() + ": Service '"
227                         + service
228                         + "' is not provided by any of its components");
229             }
230         }
231     }
232
233     /**
234      * Starts the container. After the container is started, the container
235      * becomes sealed meaning that no further components, required or provided
236      * interfaces may be added.
237      * 
238      * @return Scope.
239      */
240     public Scope start() {
241         checkSealed();
242         validate();
243         Scope scope = super.start(new DefaultScope(new ProvidedInterface[0]));
244         seal();
245         return scope;
246     }
247
248     /**
249      * Seal the container, meaning that no further components or interfaces may
250      * be added.
251      */
252     public void seal() {
253         _sealed = true;
254     }
255
256     /**
257      * Checks if the container is sealed.
258      * 
259      * @return True iff the container is sealed.
260      */
261     public boolean isSealed() {
262         return _sealed;
263     }
264
265     @Override
266     protected Scope doStart(Scope aExternalScope) {
267         LOG.info("Starting '" + getQualifiedName() + "'");
268
269         Scope scope = new DefaultScope(getProvidedInterfaces(), aExternalScope);
270
271         List<ProvidedInterface> allProvided = new ArrayList<ProvidedInterface>();
272
273         // all interfaces from the required list of this container are
274         // provided to the components inside it.
275         RequiredInterface[] required = getRequiredInterfaces();
276         for (RequiredInterface intf : required) {
277             ProvidedInterface provider = intf.getProvider();
278             if (provider != null) {
279                 allProvided.add(provider);
280             } else {
281                 if (!intf.isOptional()) {
282                     throw new SystemAssemblyException(getQualifiedName()
283                             + ": required interface '" + intf
284                             + "' is not provided");
285                 }
286             }
287         }
288
289         List<Component> started = new ArrayList<Component>();
290         for (Component component : _components) {
291             try {
292                 checkAllRequiredServicesAlreadyProvided(allProvided, component);
293
294                 // Start the service.
295                 Object runtime = component.start(scope);
296                 scope.addRuntime(component, runtime);
297                 started.add(component);
298
299                 // add all provided services
300                 ProvidedInterface[] provided = component
301                         .getProvidedInterfaces();
302                 allProvided.addAll(Arrays.asList(provided));
303             } catch (SystemAssemblyException e) {
304                 throw e;
305             } catch (RuntimeException e) {
306                 LOG.error(getQualifiedName() + ": could not start '"
307                         + component.getQualifiedName() + "'", e);
308                 stopAlreadyStartedComponents(started, scope);
309                 throw e;
310             }
311         }
312         return scope;
313     }
314
315     private void stopAlreadyStartedComponents(List<Component> aStarted,
316             Scope aScope) {
317         // an exception occurred, stop the successfully started
318         // components
319         for (int i = aStarted.size() - 1; i >= 0; i--) {
320             try {
321                 aStarted.get(i).stop(aScope);
322             } catch (Throwable t) {
323                 LOG.error(getQualifiedName() + ": error stopping "
324                         + aStarted.get(i).getQualifiedName());
325             }
326         }
327     }
328
329     private void checkAllRequiredServicesAlreadyProvided(
330             List<ProvidedInterface> aAllProvided, Component aComponent) {
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                 descriptor.setProvider(filtered[0]);
340             } else if (filtered.length > 1) {
341                 throw new SystemAssemblyException(
342                         "Service '"
343                                 + descriptor
344                                 + "' required by system '"
345                                 + aComponent
346                                 + "' matches multiple services provided by other systems: "
347                                 + getServers(filtered));
348             } else {
349                 // filtered.length == 0
350                 if (!descriptor.isOptional()) {
351                     throw new SystemAssemblyException(
352                             "Service '"
353                                     + descriptor
354                                     + "' required by system '"
355                                     + aComponent
356                                     + "' is not provided by systems that are started earlier");
357                 }
358             }
359         }
360     }
361
362     @Override
363     protected void doStop(Scope aScope) {
364         for (int i = _components.size() - 1; i >= 0; i--) {
365             Component component = _components.get(i);
366             Object runtime = aScope.getRuntime(component);
367             component.stop(runtime);
368         }
369     }
370
371     private void info(String aMsg) {
372         LOG.info(getQualifiedName() + ": " + aMsg);
373     }
374
375     private void warn(String aMsg) {
376         LOG.warn(getQualifiedName() + ": " + aMsg);
377     }
378
379     private String getServers(ProvidedInterface[] aProvidedList) {
380         String result = "";
381         for (ProvidedInterface provided : aProvidedList) {
382             result += "(components ";
383             for (Component component : _components) {
384                 for (ProvidedInterface provided2 : component
385                         .getProvidedInterfaces()) {
386                     if (provided.equals(provided2)) {
387                         result += component + " ";
388                     }
389                 }
390             }
391             result += ", interface " + provided + ")";
392         }
393         return result;
394     }
395
396     private List<Component> getClients(RequiredInterface aRequirement) {
397         List<Component> clients = new ArrayList<Component>();
398         for (Component component : _components) {
399             for (RequiredInterface required : component.getRequiredInterfaces()) {
400                 if (required.equals(aRequirement)
401                         && required.isOptional() == aRequirement.isOptional()) {
402                     clients.add(component);
403                 }
404             }
405         }
406         return clients;
407     }
408
409     private void checkSealed() {
410         if (_sealed) {
411             throw new SystemAssemblyException("Container is sealed");
412         }
413     }
414 }