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 List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
189 for (Component component : _components) {
190 provided.addAll(Arrays.asList(component.getProvidedInterfaces()));
192 for (ProvidedInterface service : getProvidedInterfaces()) {
193 // TODO provided interfaces by components could be
194 // provide subclasses or implementations of the
195 // provided interfaces of the container.
196 // The code below assumes an exact match.
197 if (!(provided.contains(service))) {
198 throw new SystemAssemblyException(getQualifiedName() + ": Service '"
200 + "' is not provided by any of its components");
206 * Seal the container, meaning that no further components or interfaces may
214 * Checks if the container is sealed.
216 * @return True iff the container is sealed.
218 public boolean isSealed() {
223 * Utility method to start with an empty external scope. This is useful for
224 * top-level containers which are not part of another container.
227 public Scope start() {
228 Scope scope = new DefaultScope(getProvidedInterfaces());
229 return super.start(scope);
233 protected Scope doStart(Scope aExternalScope) {
236 Scope scope = new DefaultScope(getProvidedInterfaces(), aExternalScope);
237 doStartOptionalDryRun(scope, false);
242 private void doStartOptionalDryRun(Scope aScope, boolean aDryRun) {
243 LOG.info("Starting '" + getQualifiedName() + "'");
245 List<Pair<ProvidedInterface,Component>> allProvided = new ArrayList<Pair<ProvidedInterface,Component>>();
247 addProvidersOfRequiredInterfaces(allProvided);
249 List<Component> started = new ArrayList<Component>();
250 for (Component component : _components) {
252 initializeProvidersForRequiredInterfaces(allProvided,
255 // Start the service.
257 Object runtime = component.start(aScope);
258 aScope.addRuntime(component, runtime);
259 started.add(component);
262 // add all provided services
263 ProvidedInterface[] provided = component
264 .getProvidedInterfaces();
265 for (ProvidedInterface prov: provided) {
266 allProvided.add(new Pair<ProvidedInterface,Component>(prov, component));
268 } catch (SystemAssemblyException e) {
270 } catch (RuntimeException e) {
271 LOG.error(getQualifiedName() + ": could not start '"
272 + component.getQualifiedName() + "'", e);
273 stopAlreadyStartedComponents(started, aScope);
279 private void addProvidersOfRequiredInterfaces(
280 List<Pair<ProvidedInterface,Component>> allProvided) {
281 // all interfaces from the required list of this container are
282 // provided to the components inside it.
283 RequiredInterface[] required = getRequiredInterfaces();
284 for (RequiredInterface intf : required) {
285 ProvidedInterface provider = intf.getProvider();
286 if (provider != null) {
287 allProvided.add(new Pair<ProvidedInterface,Component>(provider, null));
289 if (!intf.isOptional()) {
290 throw new SystemAssemblyException(getQualifiedName()
291 + ": required interface '" + intf
292 + "' is not provided");
298 private void stopAlreadyStartedComponents(List<Component> aStarted,
300 // an exception occurred, stop the successfully started
302 for (int i = aStarted.size() - 1; i >= 0; i--) {
304 Component component = aStarted.get(i);
305 aStarted.get(i).stop(aScope.getRuntime(component));
306 } catch (Throwable t) {
307 LOG.error(getQualifiedName() + ": error stopping "
308 + aStarted.get(i).getQualifiedName());
314 * Sets the provided interface or a component.
316 * @param aAllProvided
317 * All available provided interfaces.
319 * Component whose required interfaces we are looking at.
320 * @param aValidateOnly
321 * If true then the provider will not be set for required
324 private void initializeProvidersForRequiredInterfaces(
325 List<Pair<ProvidedInterface,Component>> aAllProvided, Component aComponent,
326 boolean aValidateOnly) {
327 // Check if all required services are already provided by
331 for (RequiredInterface descriptor : aComponent.getRequiredInterfaces()) {
332 ProvidedInterface[] filtered = filterProvidedServices(aComponent, descriptor,
333 aAllProvided, _restriction);
334 if (filtered.length == 1) {
335 if (!aValidateOnly) {
336 descriptor.setProvider(filtered[0]);
338 } else if (filtered.length > 1) {
339 throw new SystemAssemblyException(
342 + "' required by system '"
344 + "' matches multiple services provided by other systems: "
345 + getServers(filtered));
347 // filtered.length == 0
348 if (!descriptor.isOptional()) {
349 throw new SystemAssemblyException(
352 + "' required by system '"
354 + "' is not provided by systems that are started earlier");
361 protected void doStop(Scope aScope) {
362 for (int i = _components.size() - 1; i >= 0; i--) {
363 Component component = _components.get(i);
364 Object runtime = aScope.getRuntime(component);
365 component.stop(runtime);
369 private void info(String aMsg) {
370 LOG.info(getQualifiedName() + ": " + aMsg);
373 private void warn(String aMsg) {
374 LOG.warn(getQualifiedName() + ": " + aMsg);
377 private String getServers(ProvidedInterface[] aProvidedList) {
379 for (ProvidedInterface provided : aProvidedList) {
380 result += "(components ";
381 for (Component component : _components) {
382 for (ProvidedInterface provided2 : component
383 .getProvidedInterfaces()) {
384 if (provided.equals(provided2)) {
385 result += component + " ";
389 result += ", interface " + provided + ")";
394 private List<Component> getClients(RequiredInterface aRequirement) {
395 List<Component> clients = new ArrayList<Component>();
396 for (Component component : _components) {
397 for (RequiredInterface required : component.getRequiredInterfaces()) {
398 if (required.equals(aRequirement)
399 && required.isOptional() == aRequirement.isOptional()) {
400 clients.add(component);
407 private void checkSealed() {
409 throw new SystemAssemblyException("Container is sealed");