Implemented optinality of interfaces.
[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  * Composite system consisting of multiple subsystems.
29  * 
30  * @author Erik Brakkee
31  */
32 public class Container extends AbstractComponent {
33
34         private static final Log LOG = LogFactory.getLog(Container.class);
35
36         private Component[] _systems;
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 composite system.
63          * 
64          * @param aName
65          *            Name of the system.
66          * @param aRegistry
67          *            Service registry.
68          * @param aSystems
69          *            Subsystems.
70          * @param aProvided
71          *            Provided services of the system.
72          * @param aRequired
73          *            Required services by the system.
74          */
75         public Container(String aName, Component[] aSystems,
76                         ProvidedInterface[] aProvided, RequiredInterface[] aRequired) {
77                 super(aName, aProvided, aRequired);
78                 _systems = aSystems;
79                 for (Component component : aSystems) {
80                         component.addContext(getQualifiedName());
81                 }
82                 validate();
83         }
84
85         /**
86          * Validates the subsystems together to check that there are no required
87          * services not in the required list and no services in the provided list
88          * that cannot be provided. Also logs a warning in case of superfluous
89          * requirements.
90          */
91         private void validate() {
92                 List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
93                 for (Component system : _systems) {
94                         provided.addAll(Arrays.asList(system.getProvidedInterfaces()));
95                 }
96
97                 List<RequiredInterface> required = new ArrayList<RequiredInterface>();
98                 for (Component system : _systems) {
99                         required.addAll(Arrays.asList(system.getRequiredInterfaces()));
100                 }
101
102                 validateProvidedInterfaces(provided);
103
104                 validateRequiredInterfaces(required);
105
106                 List<RequiredInterface> reallyRequired = validateRequiredProvidedMatch(
107                                 provided, required);
108                 
109                 String missingRequired = "";
110                 for (RequiredInterface service : reallyRequired) {
111                         missingRequired += service + "\n";
112                 }
113                 if (missingRequired.length() > 0) {
114                         throw new SystemAssemblyException(getName()
115                                         + ": missing required services\n" + missingRequired);
116                 }
117         }
118
119         private List<RequiredInterface> validateRequiredProvidedMatch(
120                         List<ProvidedInterface> provided, List<RequiredInterface> required) {
121                 List<RequiredInterface> reallyRequired = new ArrayList<RequiredInterface>(
122                                 required);
123                 // Compute all required interfaces that are not provided
124
125                 for (ProvidedInterface service : provided) {
126                         List<RequiredInterface> fulfilled = Arrays
127                                         .asList(filterRequiredServices(service, reallyRequired));
128                         reallyRequired.removeAll(fulfilled);
129                 }
130                 // Now remove all optional interfaces from the list.
131                 for (Iterator<RequiredInterface> i = 
132                         reallyRequired.iterator(); i.hasNext(); ) { 
133                         RequiredInterface req = i.next();
134                         if ( req.isOptional() ) {
135                                 i.remove(); 
136                         }
137                 }
138                 // Now the remaining interfaces should be covered by the required
139                 // list.
140                 reallyRequired.removeAll(Arrays.asList(getRequiredInterfaces()));
141                 return reallyRequired;
142         }
143
144         private void validateRequiredInterfaces(List<RequiredInterface> required) {
145                 for (RequiredInterface service : getRequiredInterfaces()) {
146                         // TODO required services by the subsystem could be 
147                         //      subclasses or implementations of the requirements
148                         //      of the contained systems. The code below assumes
149                         //      an exact match. 
150                         if (!(required.contains(service))) {
151                                 info("Service '"
152                                                 + service
153                                                 + "' indicated as required is not actually required by any of the subsystems");
154                         }
155                         // Check for the case that the externally required service
156                         // is optional whereas the internally required service is 
157                         // mandatory. 
158                         if ( service.isOptional()) { 
159                                 for (RequiredInterface intf: required) { 
160                                         if ( intf.equals(service) && !intf.isOptional()) { 
161                                                 // TODO indicate which subsystem this is. 
162                                                 warn("Required service '" + service + "' indicated as optional is mandatory by one of its subsystems (" + getClients(intf) + ", " + intf + "), this can lead to problems when the system is started and the service is not there.");
163                                         }
164                                 }
165                         }
166                 }
167         }
168
169         private void validateProvidedInterfaces(List<ProvidedInterface> provided) {
170                 for (ProvidedInterface service : getProvidedInterfaces()) {
171                         // TODO provided interfaces by subsystems could be 
172                         //      provide subclasses or implementations of the 
173                         //      provided interfaces of the container.
174                         //      The code below assumes an exact match. 
175                         if (!(provided.contains(service))) {
176                                 throw new SystemAssemblyException(getName() + ": Service '"
177                                                 + service
178                                                 + "' is not provided by any of the subsystems");
179                         }
180                 }
181         }
182
183         @Override
184         protected void doStart() {
185                 LOG.info("Starting '" + getQualifiedName() + "'");
186                 List<ProvidedInterface> allProvided = new ArrayList<ProvidedInterface>();
187
188                 // all interfaces from the required list of this container are
189                 // provided to the components inside it.
190                 RequiredInterface[] required = getRequiredInterfaces();
191                 for (RequiredInterface intf : required) {
192                         ProvidedInterface provider = intf.getProvider();
193                         if (provider != null ) { 
194                                 allProvided.add(provider);
195                         } else { 
196                                 if ( !intf.isOptional()) { 
197                                         throw new SystemAssemblyException(getQualifiedName()
198                                                         + ": required interface '" + intf + "' is not provided");               
199                                 }
200                         }
201                 }
202
203                 List<Component> started = new ArrayList<Component>();
204                 for (Component system : _systems) {
205                         try {
206                                 // Check if all required services are already provided by
207                                 // earlier
208                                 // systems.
209
210                                 for (RequiredInterface descriptor : system.getRequiredInterfaces()) {
211                                         ProvidedInterface[] filtered = filterProvidedServices(
212                                                         descriptor, allProvided);
213                                         if ( filtered.length == 1 ) { 
214                                                 descriptor.setProvider(filtered[0]);
215                                         } else if ( filtered.length > 1 ) { 
216                                                 throw new SystemAssemblyException(
217                                                                 "Service '"
218                                                                                 + descriptor
219                                                                                 + "' required by system '"
220                                                                                 + system
221                                                                                 + "' matches multiple services provided by other systems: "
222                                                                                 + Arrays.asList(filtered));
223                                         } else { 
224                                                 // filtered.length == 0
225                                                 if ( !descriptor.isOptional()) { 
226                                                         throw new SystemAssemblyException(
227                                                                         "Service '"
228                                                                                         + descriptor
229                                                                                         + "' required by system '"
230                                                                                         + system
231                                                                                         + "' is not provided by systems that are started earlier");     
232                                                 }
233                                         }
234                                 }
235
236                                 // Start the service.
237                                 system.start();
238                                 started.add(system);
239
240                                 // add all provided services
241                                 ProvidedInterface[] provided = system.getProvidedInterfaces();
242                                 allProvided.addAll(Arrays.asList(provided));
243                         } catch (SystemAssemblyException e) { 
244                                 throw e; 
245                         } catch (RuntimeException e) {
246                                 LOG.error(getQualifiedName() + ": could not start '"
247                                                 + system.getQualifiedName() + "'", e);
248                                 // an exception occurred, stop the successfully started
249                                 // systems
250                                 for (int i = started.size() - 1; i >= 0; i--) {
251                                         try {
252                                                 started.get(i).stop();
253                                         } catch (Throwable t) {
254                                                 LOG.error(getQualifiedName() + ": error stopping "
255                                                                 + started.get(i).getQualifiedName());
256                                         }
257                                 }
258                                 throw e; 
259                         }
260                 }
261
262         }
263
264         @Override
265         protected void doStop() {
266                 for (int i = _systems.length - 1; i >= 0; i--) {
267                         _systems[i].stop();
268                 }
269         }
270
271         private void info(String aMsg) {
272                 LOG.info(getQualifiedName() + ": " + aMsg);
273         }
274         
275         private void warn(String aMsg) {
276                 LOG.warn(getQualifiedName() + ": " + aMsg);
277         }
278
279         private List<Component> getClients(RequiredInterface aRequirement) {
280                 List<Component> clients = new ArrayList<Component>();
281                 for (Component component: _systems) { 
282                         for (RequiredInterface required: component.getRequiredInterfaces()) { 
283                                 if ( required.equals(aRequirement) && required.isOptional() == aRequirement.isOptional()) { 
284                                         clients.add(component);
285                                 }
286                         }
287                 }
288                 return clients; 
289         }
290 }