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