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;
28 import org.wamblee.collections.CollectionFilter;
29 import org.wamblee.conditions.Condition;
30 import org.wamblee.general.Pair;
33 * Container consisting of multiple components.
35 * @author Erik Brakkee
37 public class Container extends AbstractComponent<Scope> {
39 private static final Log LOG = LogFactory.getLog(Container.class);
41 private List<Component> _components;
42 private Set<String> _componentNames;
43 private CompositeInterfaceRestriction _restriction;
44 private boolean _sealed;
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)) {
58 return result.toArray(new ProvidedInterface[0]);
62 * Constructs the container
65 * Name of the container
69 * Provided services of the container
71 * Required services by the container.
73 public Container(String aName, Component[] aComponents,
74 ProvidedInterface[] aProvided, RequiredInterface[] aRequired) {
75 super(aName, aProvided, aRequired);
76 _components = new ArrayList<Component>();
78 _componentNames = new HashSet<String>();
79 _restriction = new CompositeInterfaceRestriction();
81 for (Component component : aComponents) {
82 addComponent(component);
86 public Container(String aName) {
87 this(aName, new Component[0], new ProvidedInterface[0],
88 new RequiredInterface[0]);
91 public Container addComponent(Component aComponent) {
93 if (aComponent.getContext() != null) {
94 throw new SystemAssemblyException(
95 "Inconsistent hierarchy, component '"
96 + aComponent.getName()
97 + "' is already part of another hierarchy");
99 if (_componentNames.contains(aComponent.getName())) {
100 throw new SystemAssemblyException("Duplicate component '"
101 + aComponent.getName() + "'");
103 _components.add(aComponent);
104 _componentNames.add(aComponent.getName());
105 aComponent.addContext(getQualifiedName());
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.
115 public Container addRestriction(InterfaceRestriction aRestriction) {
117 _restriction.add(aRestriction);
122 public Container addProvidedInterface(ProvidedInterface aProvided) {
124 super.addProvidedInterface(aProvided);
129 public Container addRequiredInterface(RequiredInterface aRequired) {
131 super.addRequiredInterface(aRequired);
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
141 * @throws SystemAssemblyException
142 * in case of any validation problems.
144 public void validate() {
145 validateProvidedInterfacesArePresent();
147 validateRequiredInterfaces();
149 doStartOptionalDryRun(null, true);
152 private void validateRequiredInterfaces() {
153 List<RequiredInterface> required = new ArrayList<RequiredInterface>();
154 for (Component component : _components) {
155 required.addAll(Arrays.asList(component.getRequiredInterfaces()));
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
163 if (!(required.contains(service))) {
166 + "' indicated as required is not actually required by any of the components");
168 // Check for the case that the externally required service
169 // is optional whereas the internally required service is
171 if (service.isOptional()) {
172 for (RequiredInterface intf : required) {
173 if (intf.equals(service) && !intf.isOptional()) {
174 warn("Required service '"
176 + "' indicated as optional is mandatory by one of its components ("
180 + "), this can lead to problems when the container is started and the interface is not provided to the container.");
187 private void validateProvidedInterfacesArePresent() {
188 for (ProvidedInterface service : getProvidedInterfaces()) {
189 findProvidedInterface(service);
194 * Finds the component and provided interface that matches a provided interface of this
196 * @param aProvided Interface to provide externally.
197 * @return Pair of component and provided interface
198 * @throws SystemAssemblyException In case there are multiple matches or no match at all.
200 private Pair<Component,ProvidedInterface> findProvidedInterface(ProvidedInterface aProvided) {
201 List<Pair<Component,ProvidedInterface>> result =
202 new ArrayList<Pair<Component,ProvidedInterface>>();
203 for (Component component: _components) {
204 for (ProvidedInterface provided: component.getProvidedInterfaces()) {
205 if ( aProvided.equals(provided) ) {
206 result.add(new Pair<Component,ProvidedInterface>(component, provided));
210 if ( result.size() == 0) {
211 throw new SystemAssemblyException(getQualifiedName() + ": Service '"
213 + "' is not provided by any of its components");
215 if ( result.size() > 1) {
216 throw new SystemAssemblyException(getQualifiedName() + ": Service '"
218 + "' is provided by multiple components: " + result);
220 return result.get(0);
225 * Seal the container, meaning that no further components or interfaces may
233 * Checks if the container is sealed.
235 * @return True iff the container is sealed.
237 public boolean isSealed() {
242 * Utility method to start with an empty external scope. This is useful for
243 * top-level containers which are not part of another container.
246 public Scope start() {
247 Scope scope = new DefaultScope(getProvidedInterfaces());
248 return super.start(scope);
252 protected Scope doStart(Scope aExternalScope) {
255 Scope scope = new DefaultScope(getProvidedInterfaces(), aExternalScope);
256 doStartOptionalDryRun(scope, false);
257 exposeProvidedInterfaces(aExternalScope, scope);
262 private void exposeProvidedInterfaces(Scope aExternalScope, Scope aInternalScope) {
263 for (ProvidedInterface intf: getProvidedInterfaces()) {
264 Pair<Component, ProvidedInterface> found = findProvidedInterface(intf);
265 Object svc = aInternalScope.getInterfaceImplementation(found.getSecond(), Object.class);
266 addInterface(intf, svc, aExternalScope);
270 private void doStartOptionalDryRun(Scope aScope, boolean aDryRun) {
271 LOG.info("Starting '" + getQualifiedName() + "'");
273 List<Pair<ProvidedInterface,Component>> allProvided = new ArrayList<Pair<ProvidedInterface,Component>>();
275 addProvidersOfRequiredInterfaces(allProvided);
277 List<Component> started = new ArrayList<Component>();
278 for (Component component : _components) {
280 initializeProvidersForRequiredInterfaces(allProvided,
283 // Start the service.
285 Object runtime = component.start(aScope);
286 aScope.addRuntime(component, runtime);
287 started.add(component);
290 // add all provided services
291 ProvidedInterface[] provided = component
292 .getProvidedInterfaces();
293 for (ProvidedInterface prov: provided) {
294 allProvided.add(new Pair<ProvidedInterface,Component>(prov, component));
296 } catch (SystemAssemblyException e) {
298 } catch (RuntimeException e) {
299 LOG.error(getQualifiedName() + ": could not start '"
300 + component.getQualifiedName() + "'", e);
301 stopAlreadyStartedComponents(started, aScope);
307 private void addProvidersOfRequiredInterfaces(
308 List<Pair<ProvidedInterface,Component>> allProvided) {
309 // all interfaces from the required list of this container are
310 // provided to the components inside it.
311 RequiredInterface[] required = getRequiredInterfaces();
312 for (RequiredInterface intf : required) {
313 ProvidedInterface provider = intf.getProvider();
314 if (provider != null) {
315 allProvided.add(new Pair<ProvidedInterface,Component>(provider, null));
317 if (!intf.isOptional()) {
318 throw new SystemAssemblyException(getQualifiedName()
319 + ": required interface '" + intf
320 + "' is not provided");
326 private void stopAlreadyStartedComponents(List<Component> aStarted,
328 // an exception occurred, stop the successfully started
330 for (int i = aStarted.size() - 1; i >= 0; i--) {
332 Component component = aStarted.get(i);
333 aStarted.get(i).stop(aScope.getRuntime(component));
334 } catch (Throwable t) {
335 LOG.error(getQualifiedName() + ": error stopping "
336 + aStarted.get(i).getQualifiedName());
342 * Sets the provided interface or a component.
344 * @param aAllProvided
345 * All available provided interfaces.
347 * Component whose required interfaces we are looking at.
348 * @param aValidateOnly
349 * If true then the provider will not be set for required
352 private void initializeProvidersForRequiredInterfaces(
353 List<Pair<ProvidedInterface,Component>> aAllProvided, Component aComponent,
354 boolean aValidateOnly) {
355 // Check if all required services are already provided by
359 for (RequiredInterface descriptor : aComponent.getRequiredInterfaces()) {
360 ProvidedInterface[] filtered = filterProvidedServices(aComponent, descriptor,
361 aAllProvided, _restriction);
362 if (filtered.length == 1) {
363 if (!aValidateOnly) {
364 descriptor.setProvider(filtered[0]);
366 } else if (filtered.length > 1) {
367 throw new SystemAssemblyException(
370 + "' required by system '"
372 + "' matches multiple services provided by other systems: "
373 + getServers(filtered));
375 // filtered.length == 0
376 if (!descriptor.isOptional()) {
377 throw new SystemAssemblyException(
380 + "' required by system '"
382 + "' is not provided by systems that are started earlier");
389 protected void doStop(Scope aScope) {
390 for (int i = _components.size() - 1; i >= 0; i--) {
391 Component component = _components.get(i);
392 Object runtime = aScope.getRuntime(component);
393 component.stop(runtime);
397 private void info(String aMsg) {
398 LOG.info(getQualifiedName() + ": " + aMsg);
401 private void warn(String aMsg) {
402 LOG.warn(getQualifiedName() + ": " + aMsg);
405 private String getServers(ProvidedInterface[] aProvidedList) {
407 for (ProvidedInterface provided : aProvidedList) {
408 result += "(components ";
409 for (Component component : _components) {
410 for (ProvidedInterface provided2 : component
411 .getProvidedInterfaces()) {
412 if (provided.equals(provided2)) {
413 result += component + " ";
417 result += ", interface " + provided + ")";
422 private List<Component> getClients(RequiredInterface aRequirement) {
423 List<Component> clients = new ArrayList<Component>();
424 for (Component component : _components) {
425 for (RequiredInterface required : component.getRequiredInterfaces()) {
426 if (required.equals(aRequirement)
427 && required.isOptional() == aRequirement.isOptional()) {
428 clients.add(component);
435 private void checkSealed() {
437 throw new SystemAssemblyException("Container is sealed");