Huge refactoring.
[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.Iterator;
22 import java.util.List;
23
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
26
27 /**
28  * Container consisting of multiple components. 
29  * 
30  * @author Erik Brakkee
31  */
32 public class Container extends AbstractComponent<Scope> {
33
34         private static final Log LOG = LogFactory.getLog(Container.class);
35
36         private Component[] _components;
37
38         public static RequiredInterface[] filterRequiredServices(
39                         ProvidedInterface aProvided,
40                         Collection<RequiredInterface> aDescriptors) {
41                 List<RequiredInterface> required = new ArrayList<RequiredInterface>();
42                 for (RequiredInterface descriptor : aDescriptors) {
43                         if (descriptor.implementedBy(aProvided)) {
44                                 required.add(descriptor);
45                         }
46                 }
47                 return required.toArray(new RequiredInterface[0]);
48         }
49
50         public static ProvidedInterface[] filterProvidedServices(
51                         RequiredInterface aRequired, Collection<ProvidedInterface> aProvided) {
52                 List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
53                 for (ProvidedInterface descriptor : aProvided) {
54                         if (aRequired.implementedBy(descriptor)) {
55                                 provided.add(descriptor);
56                         }
57                 }
58                 return provided.toArray(new ProvidedInterface[0]);
59         }
60
61         /**
62          * Construcst 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 = aComponents;
77                 for (Component component : aComponents) {
78                         component.addContext(getQualifiedName());
79                 }
80                 validate();
81         }
82
83         /**
84          * Validates the components together to check that there are no required
85          * services not in the required list and no services in the provided list
86          * that cannot be provided. Also logs a warning in case of superfluous
87          * requirements.
88          */
89         private void validate() {
90                 List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
91                 for (Component component : _components) {
92                         provided.addAll(Arrays.asList(component.getProvidedInterfaces()));
93                 }
94
95                 List<RequiredInterface> required = new ArrayList<RequiredInterface>();
96                 for (Component component : _components) {
97                         required.addAll(Arrays.asList(component.getRequiredInterfaces()));
98                 }
99
100                 validateProvidedInterfaces(provided);
101
102                 validateRequiredInterfaces(required);
103
104                 List<RequiredInterface> reallyRequired = validateRequiredProvidedMatch(
105                                 provided, required);
106                 
107                 String missingRequired = "";
108                 for (RequiredInterface service : reallyRequired) {
109                         missingRequired += service + "\n";
110                 }
111                 if (missingRequired.length() > 0) {
112                         throw new SystemAssemblyException(getName()
113                                         + ": missing required services\n" + missingRequired);
114                 }
115         }
116
117         private List<RequiredInterface> validateRequiredProvidedMatch(
118                         List<ProvidedInterface> aProvided, List<RequiredInterface> aRequired) {
119                 List<RequiredInterface> reallyRequired = new ArrayList<RequiredInterface>(
120                                 aRequired);
121                 // Compute all required interfaces that are not provided
122
123                 for (ProvidedInterface service : aProvided) {
124                         List<RequiredInterface> fulfilled = Arrays
125                                         .asList(filterRequiredServices(service, reallyRequired));
126                         reallyRequired.removeAll(fulfilled);
127                 }
128                 // Now remove all optional interfaces from the list.
129                 for (Iterator<RequiredInterface> i = 
130                         reallyRequired.iterator(); i.hasNext(); ) { 
131                         RequiredInterface req = i.next();
132                         if ( req.isOptional() ) {
133                                 i.remove(); 
134                         }
135                 }
136                 // Now the remaining interfaces should be covered by the required
137                 // list.
138                 reallyRequired.removeAll(Arrays.asList(getRequiredInterfaces()));
139                 return reallyRequired;
140         }
141
142         private void validateRequiredInterfaces(List<RequiredInterface> aRequired) {
143                 for (RequiredInterface service : getRequiredInterfaces()) {
144                         // TODO required interfaces by the component could be 
145                         //      subclasses or implementations of the requirements
146                         //      of the contained components. The code below assumes
147                         //      an exact match. 
148                         if (!(aRequired.contains(service))) {
149                                 info("Service '"
150                                                 + service
151                                                 + "' indicated as required is not actually required by any of the components");
152                         }
153                         // Check for the case that the externally required service
154                         // is optional whereas the internally required service is 
155                         // mandatory. 
156                         if ( service.isOptional()) { 
157                                 for (RequiredInterface intf: aRequired) { 
158                                         if ( intf.equals(service) && !intf.isOptional()) {  
159                                                 warn("Required service '" + service + "' indicated as optional is mandatory by one of its components (" + getClients(intf) + ", " + intf + "), this can lead to problems when the container is started and the interface is not provided to the container.");
160                                         }
161                                 }
162                         }
163                 }
164         }
165
166         private void validateProvidedInterfaces(List<ProvidedInterface> aProvided) {
167                 for (ProvidedInterface service : getProvidedInterfaces()) {
168                         // TODO provided interfaces by components could be 
169                         //      provide subclasses or implementations of the 
170                         //      provided interfaces of the container.
171                         //      The code below assumes an exact match. 
172                         if (!(aProvided.contains(service))) {
173                                 throw new SystemAssemblyException(getName() + ": Service '"
174                                                 + service
175                                                 + "' is not provided by any of its components");
176                         }
177                 }
178         }
179         
180         public Scope start() { 
181                 return super.start(new DefaultScope(new ProvidedInterface[0]));
182         }
183
184         @Override
185         protected Scope doStart(Scope aExternalScope) {
186                 LOG.info("Starting '" + getQualifiedName() + "'");
187                 
188                 Scope scope = new DefaultScope(getProvidedInterfaces(), 
189                                 aExternalScope);
190                 
191                 List<ProvidedInterface> allProvided = new ArrayList<ProvidedInterface>();
192
193                 // all interfaces from the required list of this container are
194                 // provided to the components inside it.
195                 RequiredInterface[] required = getRequiredInterfaces();
196                 for (RequiredInterface intf : required) {
197                         ProvidedInterface provider = intf.getProvider();
198                         if (provider != null ) { 
199                                 allProvided.add(provider);
200                         } else { 
201                                 if ( !intf.isOptional()) { 
202                                         throw new SystemAssemblyException(getQualifiedName()
203                                                         + ": required interface '" + intf + "' is not provided");               
204                                 }
205                         }
206                 }
207
208                 List<Component> started = new ArrayList<Component>();
209                 for (Component component : _components) {
210                         try {
211                                 checkAllRequiredServicesAlreadyProvided(allProvided, component);
212
213                                 // Start the service.
214                                 Object runtime = component.start(scope);
215                                 scope.addRuntime(component, runtime); 
216                                 started.add(component);
217
218                                 // add all provided services
219                                 ProvidedInterface[] provided = component.getProvidedInterfaces();
220                                 allProvided.addAll(Arrays.asList(provided));
221                         } catch (SystemAssemblyException e) { 
222                                 throw e; 
223                         } catch (RuntimeException e) {
224                                 LOG.error(getQualifiedName() + ": could not start '"
225                                                 + component.getQualifiedName() + "'", e);
226                                 stopAlreadyStartedComponents(started, scope);
227                                 throw e; 
228                         }
229                 }
230                 return scope; 
231         }
232
233         private void stopAlreadyStartedComponents(List<Component> aStarted, Scope aScope) {
234                 // an exception occurred, stop the successfully started
235                 // components
236                 for (int i = aStarted.size() - 1; i >= 0; i--) {
237                         try {
238                                 aStarted.get(i).stop(aScope);
239                         } catch (Throwable t) {
240                                 LOG.error(getQualifiedName() + ": error stopping "
241                                                 + aStarted.get(i).getQualifiedName());
242                         }
243                 }
244         }
245
246         private void checkAllRequiredServicesAlreadyProvided(
247                         List<ProvidedInterface> aAllProvided, Component aComponent) {
248                 // Check if all required services are already provided by
249                 // earlier
250                 // systems.
251
252                 for (RequiredInterface descriptor : aComponent.getRequiredInterfaces()) {
253                         ProvidedInterface[] filtered = filterProvidedServices(
254                                         descriptor, aAllProvided);
255                         if ( filtered.length == 1 ) { 
256                                 descriptor.setProvider(filtered[0]);
257                         } else if ( filtered.length > 1 ) { 
258                                 throw new SystemAssemblyException(
259                                                 "Service '"
260                                                                 + descriptor
261                                                                 + "' required by system '"
262                                                                 + aComponent
263                                                                 + "' matches multiple services provided by other systems: "
264                                                                 + getServers(filtered));
265                         } else { 
266                                 // filtered.length == 0
267                                 if ( !descriptor.isOptional()) { 
268                                         throw new SystemAssemblyException(
269                                                         "Service '"
270                                                                         + descriptor
271                                                                         + "' required by system '"
272                                                                         + aComponent
273                                                                         + "' is not provided by systems that are started earlier");     
274                                 }
275                         }
276                 }
277         }
278
279         @Override
280         protected void doStop(Scope aScope) {
281                 for (int i = _components.length - 1; i >= 0; i--) {
282                         Component component = _components[i];
283                         Object runtime = aScope.getRuntime(component);
284                         component.stop(runtime);
285                 }
286         }
287
288         private void info(String aMsg) {
289                 LOG.info(getQualifiedName() + ": " + aMsg);
290         }
291         
292         private void warn(String aMsg) {
293                 LOG.warn(getQualifiedName() + ": " + aMsg);
294         }
295         
296         private String getServers(ProvidedInterface[] aProvidedList ) {
297                 String result = "";
298                 for (ProvidedInterface provided: aProvidedList) {
299                         result += "(components ";
300                         for (Component component: _components) { 
301                                 for (ProvidedInterface provided2: component.getProvidedInterfaces()) { 
302                                         if ( provided.equals(provided2)) { 
303                                                 result += component + " ";
304                                         }
305                                 }
306                         }
307                         result += ", interface " + provided + ")";
308                 }
309                 return result;
310         }
311
312         private List<Component> getClients(RequiredInterface aRequirement) {
313                 List<Component> clients = new ArrayList<Component>();
314                 for (Component component: _components) { 
315                         for (RequiredInterface required: component.getRequiredInterfaces()) { 
316                                 if ( required.equals(aRequirement) && required.isOptional() == aRequirement.isOptional()) { 
317                                         clients.add(component);
318                                 }
319                         }
320                 }
321                 return clients; 
322         }
323 }