2 * Copyright 2007 the original author or authors.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.wamblee.system.core;
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;
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
30 * Container consisting of multiple components.
32 * @author Erik Brakkee
34 public class Container extends AbstractComponent<Scope> {
36 private static final Log LOG = LogFactory.getLog(Container.class);
38 private List<Component> _components;
39 private Set<String> _componentNames;
40 private boolean _sealed;
42 public static RequiredInterface[] filterRequiredServices(
43 ProvidedInterface aProvided,
44 Collection<RequiredInterface> aDescriptors) {
45 List<RequiredInterface> required = new ArrayList<RequiredInterface>();
46 for (RequiredInterface descriptor : aDescriptors) {
47 if (descriptor.implementedBy(aProvided)) {
48 required.add(descriptor);
51 return required.toArray(new RequiredInterface[0]);
54 public static ProvidedInterface[] filterProvidedServices(
55 RequiredInterface aRequired, Collection<ProvidedInterface> aProvided) {
56 List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
57 for (ProvidedInterface descriptor : aProvided) {
58 if (aRequired.implementedBy(descriptor)) {
59 provided.add(descriptor);
62 return provided.toArray(new ProvidedInterface[0]);
66 * Constructs the container
69 * Name of the container
73 * Provided services of the container
75 * Required services by the container.
77 public Container(String aName, Component[] aComponents,
78 ProvidedInterface[] aProvided, RequiredInterface[] aRequired) {
79 super(aName, aProvided, aRequired);
80 _components = new ArrayList<Component>();
82 _componentNames = new HashSet<String>();
84 for (Component component : aComponents) {
85 addComponent(component);
90 public Container(String aName) {
91 this(aName, new Component[0], new ProvidedInterface[0],
92 new RequiredInterface[0]);
95 public Container addComponent(Component aComponent) {
97 if (aComponent.getContext() != null) {
98 throw new SystemAssemblyException(
99 "Inconsistent hierarchy, component '"
100 + aComponent.getName()
101 + "' is already part of another hierarchy");
103 if (_componentNames.contains(aComponent.getName())) {
104 throw new SystemAssemblyException("Duplicate component '"
105 + aComponent.getName() + "'");
107 _components.add(aComponent);
108 _componentNames.add(aComponent.getName());
109 aComponent.addContext(getQualifiedName());
114 public Container addProvidedInterface(ProvidedInterface aProvided) {
116 super.addProvidedInterface(aProvided);
121 public Container addRequiredInterface(RequiredInterface aRequired) {
123 super.addRequiredInterface(aRequired);
128 * Validates the components together to check that there are no required
129 * services not in the required list and no services in the provided list
130 * that cannot be provided. Also logs a warning in case of superfluous
133 * @throws SystemAssemblyException
134 * in case of any validation problems.
136 public void validate() {
137 List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
138 for (Component component : _components) {
139 provided.addAll(Arrays.asList(component.getProvidedInterfaces()));
142 List<RequiredInterface> required = new ArrayList<RequiredInterface>();
143 for (Component component : _components) {
144 required.addAll(Arrays.asList(component.getRequiredInterfaces()));
147 validateProvidedInterfaces(provided);
149 validateRequiredInterfaces(required);
151 List<RequiredInterface> reallyRequired = validateRequiredProvidedMatch(
154 String missingRequired = "";
155 for (RequiredInterface service : reallyRequired) {
156 missingRequired += service + "\n";
158 if (missingRequired.length() > 0) {
159 throw new SystemAssemblyException(getName()
160 + ": missing required services\n" + missingRequired);
164 private List<RequiredInterface> validateRequiredProvidedMatch(
165 List<ProvidedInterface> aProvided, List<RequiredInterface> aRequired) {
166 List<RequiredInterface> reallyRequired = new ArrayList<RequiredInterface>(
168 // Compute all required interfaces that are not provided
170 for (ProvidedInterface service : aProvided) {
171 List<RequiredInterface> fulfilled = Arrays
172 .asList(filterRequiredServices(service, reallyRequired));
173 reallyRequired.removeAll(fulfilled);
175 // Now remove all optional interfaces from the list.
176 for (Iterator<RequiredInterface> i = reallyRequired.iterator(); i
178 RequiredInterface req = i.next();
179 if (req.isOptional()) {
183 // Now the remaining interfaces should be covered by the required
185 reallyRequired.removeAll(Arrays.asList(getRequiredInterfaces()));
186 return reallyRequired;
189 private void validateRequiredInterfaces(List<RequiredInterface> aRequired) {
190 for (RequiredInterface service : getRequiredInterfaces()) {
191 // TODO required interfaces by the component could be
192 // subclasses or implementations of the requirements
193 // of the contained components. The code below assumes
195 if (!(aRequired.contains(service))) {
198 + "' indicated as required is not actually required by any of the components");
200 // Check for the case that the externally required service
201 // is optional whereas the internally required service is
203 if (service.isOptional()) {
204 for (RequiredInterface intf : aRequired) {
205 if (intf.equals(service) && !intf.isOptional()) {
206 warn("Required service '"
208 + "' indicated as optional is mandatory by one of its components ("
212 + "), this can lead to problems when the container is started and the interface is not provided to the container.");
219 private void validateProvidedInterfaces(List<ProvidedInterface> aProvided) {
220 for (ProvidedInterface service : getProvidedInterfaces()) {
221 // TODO provided interfaces by components could be
222 // provide subclasses or implementations of the
223 // provided interfaces of the container.
224 // The code below assumes an exact match.
225 if (!(aProvided.contains(service))) {
226 throw new SystemAssemblyException(getName() + ": Service '"
228 + "' is not provided by any of its components");
234 * Starts the container. After the container is started, the container
235 * becomes sealed meaning that no further components, required or provided
236 * interfaces may be added.
240 public Scope start() {
243 Scope scope = super.start(new DefaultScope(new ProvidedInterface[0]));
249 * Seal the container, meaning that no further components or interfaces may
257 * Checks if the container is sealed.
259 * @return True iff the container is sealed.
261 public boolean isSealed() {
266 protected Scope doStart(Scope aExternalScope) {
267 LOG.info("Starting '" + getQualifiedName() + "'");
269 Scope scope = new DefaultScope(getProvidedInterfaces(), aExternalScope);
271 List<ProvidedInterface> allProvided = new ArrayList<ProvidedInterface>();
273 // all interfaces from the required list of this container are
274 // provided to the components inside it.
275 RequiredInterface[] required = getRequiredInterfaces();
276 for (RequiredInterface intf : required) {
277 ProvidedInterface provider = intf.getProvider();
278 if (provider != null) {
279 allProvided.add(provider);
281 if (!intf.isOptional()) {
282 throw new SystemAssemblyException(getQualifiedName()
283 + ": required interface '" + intf
284 + "' is not provided");
289 List<Component> started = new ArrayList<Component>();
290 for (Component component : _components) {
292 checkAllRequiredServicesAlreadyProvided(allProvided, component);
294 // Start the service.
295 Object runtime = component.start(scope);
296 scope.addRuntime(component, runtime);
297 started.add(component);
299 // add all provided services
300 ProvidedInterface[] provided = component
301 .getProvidedInterfaces();
302 allProvided.addAll(Arrays.asList(provided));
303 } catch (SystemAssemblyException e) {
305 } catch (RuntimeException e) {
306 LOG.error(getQualifiedName() + ": could not start '"
307 + component.getQualifiedName() + "'", e);
308 stopAlreadyStartedComponents(started, scope);
315 private void stopAlreadyStartedComponents(List<Component> aStarted,
317 // an exception occurred, stop the successfully started
319 for (int i = aStarted.size() - 1; i >= 0; i--) {
321 aStarted.get(i).stop(aScope);
322 } catch (Throwable t) {
323 LOG.error(getQualifiedName() + ": error stopping "
324 + aStarted.get(i).getQualifiedName());
329 private void checkAllRequiredServicesAlreadyProvided(
330 List<ProvidedInterface> aAllProvided, Component aComponent) {
331 // Check if all required services are already provided by
335 for (RequiredInterface descriptor : aComponent.getRequiredInterfaces()) {
336 ProvidedInterface[] filtered = filterProvidedServices(descriptor,
338 if (filtered.length == 1) {
339 descriptor.setProvider(filtered[0]);
340 } else if (filtered.length > 1) {
341 throw new SystemAssemblyException(
344 + "' required by system '"
346 + "' matches multiple services provided by other systems: "
347 + getServers(filtered));
349 // filtered.length == 0
350 if (!descriptor.isOptional()) {
351 throw new SystemAssemblyException(
354 + "' required by system '"
356 + "' is not provided by systems that are started earlier");
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);
371 private void info(String aMsg) {
372 LOG.info(getQualifiedName() + ": " + aMsg);
375 private void warn(String aMsg) {
376 LOG.warn(getQualifiedName() + ": " + aMsg);
379 private String getServers(ProvidedInterface[] aProvidedList) {
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 + " ";
391 result += ", interface " + provided + ")";
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);
409 private void checkSealed() {
411 throw new SystemAssemblyException("Container is sealed");