Moving around a lot of files to work towards production components.
[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(getName() + ": Service '"
199                         + service
200                         + "' is not provided by any of its components");
201             }
202         }
203     }
204
205     /**
206      * Starts the container. After the container is started, the container
207      * becomes sealed meaning that no further components, required or provided
208      * interfaces may be added.
209      * 
210      * @return Scope.
211      */
212     public Scope start() {
213         checkSealed();
214         validate();
215         Scope scope = super.start(new DefaultScope(new ProvidedInterface[0]));
216         seal();
217         return scope;
218     }
219
220     /**
221      * Seal the container, meaning that no further components or interfaces may
222      * be added.
223      */
224     public void seal() {
225         _sealed = true;
226     }
227
228     /**
229      * Checks if the container is sealed.
230      * 
231      * @return True iff the container is sealed.
232      */
233     public boolean isSealed() {
234         return _sealed;
235     }
236
237     @Override
238     protected Scope doStart(Scope aExternalScope) {
239         return doStartOptionalDryRun(aExternalScope, false);
240     }
241
242     private Scope doStartOptionalDryRun(Scope aExternalScope, boolean aDryRun) {
243         LOG.info("Starting '" + getQualifiedName() + "'");
244
245         Scope scope = new DefaultScope(getProvidedInterfaces(), aExternalScope);
246
247         List<Pair<ProvidedInterface,Component>> allProvided = new ArrayList<Pair<ProvidedInterface,Component>>();
248         
249         addProvidersOfRequiredInterfaces(allProvided);
250
251         List<Component> started = new ArrayList<Component>();
252         for (Component component : _components) {
253             try {
254                 initializeProvidersForRequiredInterfaces(allProvided,
255                         component, aDryRun);
256
257                 // Start the service.
258                 if (!aDryRun) {
259                     Object runtime = component.start(scope);
260                     scope.addRuntime(component, runtime);
261                     started.add(component);
262                 }
263
264                 // add all provided services
265                 ProvidedInterface[] provided = component
266                         .getProvidedInterfaces();
267                 for (ProvidedInterface prov: provided) { 
268                     allProvided.add(new Pair<ProvidedInterface,Component>(prov, component));
269                 }
270             } catch (SystemAssemblyException e) {
271                 throw e;
272             } catch (RuntimeException e) {
273                 LOG.error(getQualifiedName() + ": could not start '"
274                         + component.getQualifiedName() + "'", e);
275                 stopAlreadyStartedComponents(started, scope);
276                 throw e;
277             }
278         }
279         return scope;
280     }
281
282     private void addProvidersOfRequiredInterfaces(
283             List<Pair<ProvidedInterface,Component>> allProvided) {
284         // all interfaces from the required list of this container are
285         // provided to the components inside it.
286         RequiredInterface[] required = getRequiredInterfaces();
287         for (RequiredInterface intf : required) {
288             ProvidedInterface provider = intf.getProvider();
289             if (provider != null) {
290                 allProvided.add(new Pair<ProvidedInterface,Component>(provider, null));
291             } else {
292                 if (!intf.isOptional()) {
293                     throw new SystemAssemblyException(getQualifiedName()
294                             + ": required interface '" + intf
295                             + "' is not provided");
296                 }
297             }
298         }
299     }
300
301     private void stopAlreadyStartedComponents(List<Component> aStarted,
302             Scope aScope) {
303         // an exception occurred, stop the successfully started
304         // components
305         for (int i = aStarted.size() - 1; i >= 0; i--) {
306             try {
307                 Component component = aStarted.get(i);
308                 aStarted.get(i).stop(aScope.getRuntime(component));
309             } catch (Throwable t) {
310                 LOG.error(getQualifiedName() + ": error stopping "
311                         + aStarted.get(i).getQualifiedName());
312             }
313         }
314     }
315
316     /**
317      * Sets the provided interface or a component.
318      * 
319      * @param aAllProvided
320      *            All available provided interfaces.
321      * @param aComponent
322      *            Component whose required interfaces we are looking at.
323      * @param aValidateOnly
324      *            If true then the provider will not be set for required
325      *            interfaces.
326      */
327     private void initializeProvidersForRequiredInterfaces(
328             List<Pair<ProvidedInterface,Component>> aAllProvided, Component aComponent,
329             boolean aValidateOnly) {
330         // Check if all required services are already provided by
331         // earlier
332         // systems.
333
334         for (RequiredInterface descriptor : aComponent.getRequiredInterfaces()) {
335             ProvidedInterface[] filtered = filterProvidedServices(aComponent, descriptor,
336                     aAllProvided, _restriction);
337             if (filtered.length == 1) {
338                 if (!aValidateOnly) {
339                     descriptor.setProvider(filtered[0]);
340                 }
341             } else if (filtered.length > 1) {
342                 throw new SystemAssemblyException(
343                         "Service '"
344                                 + descriptor
345                                 + "' required by system '"
346                                 + aComponent
347                                 + "' matches multiple services provided by other systems: "
348                                 + getServers(filtered));
349             } else {
350                 // filtered.length == 0
351                 if (!descriptor.isOptional()) {
352                     throw new SystemAssemblyException(
353                             "Service '"
354                                     + descriptor
355                                     + "' required by system '"
356                                     + aComponent
357                                     + "' is not provided by systems that are started earlier");
358                 }
359             }
360         }
361     }
362
363     @Override
364     protected void doStop(Scope aScope) {
365         for (int i = _components.size() - 1; i >= 0; i--) {
366             Component component = _components.get(i);
367             Object runtime = aScope.getRuntime(component);
368             component.stop(runtime);
369         }
370     }
371
372     private void info(String aMsg) {
373         LOG.info(getQualifiedName() + ": " + aMsg);
374     }
375
376     private void warn(String aMsg) {
377         LOG.warn(getQualifiedName() + ": " + aMsg);
378     }
379
380     private String getServers(ProvidedInterface[] aProvidedList) {
381         String result = "";
382         for (ProvidedInterface provided : aProvidedList) {
383             result += "(components ";
384             for (Component component : _components) {
385                 for (ProvidedInterface provided2 : component
386                         .getProvidedInterfaces()) {
387                     if (provided.equals(provided2)) {
388                         result += component + " ";
389                     }
390                 }
391             }
392             result += ", interface " + provided + ")";
393         }
394         return result;
395     }
396
397     private List<Component> getClients(RequiredInterface aRequirement) {
398         List<Component> clients = new ArrayList<Component>();
399         for (Component component : _components) {
400             for (RequiredInterface required : component.getRequiredInterfaces()) {
401                 if (required.equals(aRequirement)
402                         && required.isOptional() == aRequirement.isOptional()) {
403                     clients.add(component);
404                 }
405             }
406         }
407         return clients;
408     }
409
410     private void checkSealed() {
411         if (_sealed) {
412             throw new SystemAssemblyException("Container is sealed");
413         }
414     }
415 }