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