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 public void addContext(String aContext) {
137 super.addContext(aContext);
138 for (Component component: _components) {
139 component.addContext(aContext);
144 * Validates the components together to check that there are no required
145 * services not in the required list and no services in the provided list
146 * that cannot be provided. Also logs a warning in case of superfluous
149 * @throws SystemAssemblyException
150 * in case of any validation problems.
152 public void validate() {
153 validateProvidedInterfacesArePresent();
155 validateRequiredInterfaces();
157 doStartOptionalDryRun(null, true);
160 private void validateRequiredInterfaces() {
161 List<RequiredInterface> required = new ArrayList<RequiredInterface>();
162 for (Component component : _components) {
163 required.addAll(Arrays.asList(component.getRequiredInterfaces()));
166 for (RequiredInterface service : getRequiredInterfaces()) {
167 // TODO required interfaces by the component could be
168 // subclasses or implementations of the requirements
169 // of the contained components. The code below assumes
171 if (!(required.contains(service))) {
174 + "' indicated as required is not actually required by any of the components");
176 // Check for the case that the externally required service
177 // is optional whereas the internally required service is
179 if (service.isOptional()) {
180 for (RequiredInterface intf : required) {
181 if (intf.equals(service) && !intf.isOptional()) {
182 warn("Required service '"
184 + "' indicated as optional is mandatory by one of its components ("
188 + "), this can lead to problems when the container is started and the interface is not provided to the container.");
195 private void validateProvidedInterfacesArePresent() {
196 for (ProvidedInterface service : getProvidedInterfaces()) {
197 findProvidedInterface(service);
202 * Finds the component and provided interface that matches a provided interface of this
204 * @param aProvided Interface to provide externally.
205 * @return Pair of component and provided interface
206 * @throws SystemAssemblyException In case there are multiple matches or no match at all.
208 private Pair<Component,ProvidedInterface> findProvidedInterface(ProvidedInterface aProvided) {
209 List<Pair<Component,ProvidedInterface>> result =
210 new ArrayList<Pair<Component,ProvidedInterface>>();
211 for (Component component: _components) {
212 for (ProvidedInterface provided: component.getProvidedInterfaces()) {
213 if ( aProvided.equals(provided) ) {
214 result.add(new Pair<Component,ProvidedInterface>(component, provided));
218 if ( result.size() == 0) {
219 throw new SystemAssemblyException(getQualifiedName() + ": Service '"
221 + "' is not provided by any of its components");
223 if ( result.size() > 1) {
224 throw new SystemAssemblyException(getQualifiedName() + ": Service '"
226 + "' is provided by multiple components: " + result);
228 return result.get(0);
233 * Seal the container, meaning that no further components or interfaces may
241 * Checks if the container is sealed.
243 * @return True iff the container is sealed.
245 public boolean isSealed() {
250 * Utility method to start with an empty external scope. This is useful for
251 * top-level containers which are not part of another container.
254 public Scope start() {
255 Scope scope = new DefaultScope(getProvidedInterfaces());
256 return super.start(scope);
260 protected Scope doStart(Scope aExternalScope) {
263 Scope scope = new DefaultScope(getProvidedInterfaces(), aExternalScope);
264 doStartOptionalDryRun(scope, false);
265 exposeProvidedInterfaces(aExternalScope, scope);
270 private void exposeProvidedInterfaces(Scope aExternalScope, Scope aInternalScope) {
271 for (ProvidedInterface intf: getProvidedInterfaces()) {
272 Pair<Component, ProvidedInterface> found = findProvidedInterface(intf);
273 Object svc = aInternalScope.getInterfaceImplementation(found.getSecond(), Object.class);
274 addInterface(intf, svc, aExternalScope);
278 private void doStartOptionalDryRun(Scope aScope, boolean aDryRun) {
279 LOG.info("Starting '" + getQualifiedName() + "'");
281 List<Pair<ProvidedInterface,Component>> allProvided = new ArrayList<Pair<ProvidedInterface,Component>>();
283 addProvidersOfRequiredInterfaces(allProvided);
285 List<Component> started = new ArrayList<Component>();
286 for (Component component : _components) {
288 initializeProvidersForRequiredInterfaces(allProvided,
291 // Start the service.
293 Object runtime = component.start(aScope);
294 aScope.addRuntime(component, runtime);
295 started.add(component);
298 // add all provided services
299 ProvidedInterface[] provided = component
300 .getProvidedInterfaces();
301 for (ProvidedInterface prov: provided) {
302 allProvided.add(new Pair<ProvidedInterface,Component>(prov, component));
304 } catch (SystemAssemblyException e) {
306 } catch (RuntimeException e) {
307 LOG.error(getQualifiedName() + ": could not start '"
308 + component.getQualifiedName() + "'", e);
309 stopAlreadyStartedComponents(started, aScope);
315 private void addProvidersOfRequiredInterfaces(
316 List<Pair<ProvidedInterface,Component>> allProvided) {
317 // all interfaces from the required list of this container are
318 // provided to the components inside it.
319 RequiredInterface[] required = getRequiredInterfaces();
320 for (RequiredInterface intf : required) {
321 ProvidedInterface provider = intf.getProvider();
322 if (provider != null) {
323 allProvided.add(new Pair<ProvidedInterface,Component>(provider, null));
325 if (!intf.isOptional()) {
326 throw new SystemAssemblyException(getQualifiedName()
327 + ": required interface '" + intf
328 + "' is not provided");
334 private void stopAlreadyStartedComponents(List<Component> aStarted,
336 // an exception occurred, stop the successfully started
338 for (int i = aStarted.size() - 1; i >= 0; i--) {
340 Component component = aStarted.get(i);
341 aStarted.get(i).stop(aScope.getRuntime(component));
342 } catch (Throwable t) {
343 LOG.error(getQualifiedName() + ": error stopping "
344 + aStarted.get(i).getQualifiedName());
350 * Sets the provided interface or a component.
352 * @param aAllProvided
353 * All available provided interfaces.
355 * Component whose required interfaces we are looking at.
356 * @param aValidateOnly
357 * If true then the provider will not be set for required
360 private void initializeProvidersForRequiredInterfaces(
361 List<Pair<ProvidedInterface,Component>> aAllProvided, Component aComponent,
362 boolean aValidateOnly) {
363 // Check if all required services are already provided by
367 for (RequiredInterface descriptor : aComponent.getRequiredInterfaces()) {
368 ProvidedInterface[] filtered = filterProvidedServices(aComponent, descriptor,
369 aAllProvided, _restriction);
370 if (filtered.length == 1) {
371 if (!aValidateOnly) {
372 descriptor.setProvider(filtered[0]);
374 } else if (filtered.length > 1) {
375 throw new SystemAssemblyException(
378 + "' required by system '"
380 + "' matches multiple services provided by other systems: "
381 + getServers(filtered));
383 // filtered.length == 0
384 if (!descriptor.isOptional()) {
385 throw new SystemAssemblyException(
388 + "' required by system '"
390 + "' is not provided by systems that are started earlier");
397 protected void doStop(Scope aScope) {
398 for (int i = _components.size() - 1; i >= 0; i--) {
399 Component component = _components.get(i);
400 Object runtime = aScope.getRuntime(component);
401 component.stop(runtime);
405 private void info(String aMsg) {
406 LOG.info(getQualifiedName() + ": " + aMsg);
409 private void warn(String aMsg) {
410 LOG.warn(getQualifiedName() + ": " + aMsg);
413 private String getServers(ProvidedInterface[] aProvidedList) {
415 for (ProvidedInterface provided : aProvidedList) {
416 result += "(components ";
417 for (Component component : _components) {
418 for (ProvidedInterface provided2 : component
419 .getProvidedInterfaces()) {
420 if (provided.equals(provided2)) {
421 result += component + " ";
425 result += ", interface " + provided + ")";
430 private List<Component> getClients(RequiredInterface aRequirement) {
431 List<Component> clients = new ArrayList<Component>();
432 for (Component component : _components) {
433 for (RequiredInterface required : component.getRequiredInterfaces()) {
434 if (required.equals(aRequirement)
435 && required.isOptional() == aRequirement.isOptional()) {
436 clients.add(component);
443 private void checkSealed() {
445 throw new SystemAssemblyException("Container is sealed");