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(getName() + ": Service '"
200 + "' is not provided by any of its components");
206 * Starts the container. After the container is started, the container
207 * becomes sealed meaning that no further components, required or provided
208 * interfaces may be added.
212 public Scope start() {
215 Scope scope = super.start(new DefaultScope(new ProvidedInterface[0]));
221 * Seal the container, meaning that no further components or interfaces may
229 * Checks if the container is sealed.
231 * @return True iff the container is sealed.
233 public boolean isSealed() {
238 protected Scope doStart(Scope aExternalScope) {
239 return doStartOptionalDryRun(aExternalScope, false);
242 private Scope doStartOptionalDryRun(Scope aExternalScope, boolean aDryRun) {
243 LOG.info("Starting '" + getQualifiedName() + "'");
245 Scope scope = new DefaultScope(getProvidedInterfaces(), aExternalScope);
247 List<Pair<ProvidedInterface,Component>> allProvided = new ArrayList<Pair<ProvidedInterface,Component>>();
249 addProvidersOfRequiredInterfaces(allProvided);
251 List<Component> started = new ArrayList<Component>();
252 for (Component component : _components) {
254 initializeProvidersForRequiredInterfaces(allProvided,
257 // Start the service.
259 Object runtime = component.start(scope);
260 scope.addRuntime(component, runtime);
261 started.add(component);
264 // add all provided services
265 ProvidedInterface[] provided = component
266 .getProvidedInterfaces();
267 for (ProvidedInterface prov: provided) {
268 allProvided.add(new Pair<ProvidedInterface,Component>(prov, component));
270 } catch (SystemAssemblyException e) {
272 } catch (RuntimeException e) {
273 LOG.error(getQualifiedName() + ": could not start '"
274 + component.getQualifiedName() + "'", e);
275 stopAlreadyStartedComponents(started, scope);
282 private void addProvidersOfRequiredInterfaces(
283 List<Pair<ProvidedInterface,Component>> allProvided) {
284 // all interfaces from the required list of this container are
285 // provided to the components inside it.
286 RequiredInterface[] required = getRequiredInterfaces();
287 for (RequiredInterface intf : required) {
288 ProvidedInterface provider = intf.getProvider();
289 if (provider != null) {
290 allProvided.add(new Pair<ProvidedInterface,Component>(provider, null));
292 if (!intf.isOptional()) {
293 throw new SystemAssemblyException(getQualifiedName()
294 + ": required interface '" + intf
295 + "' is not provided");
301 private void stopAlreadyStartedComponents(List<Component> aStarted,
303 // an exception occurred, stop the successfully started
305 for (int i = aStarted.size() - 1; i >= 0; i--) {
307 Component component = aStarted.get(i);
308 aStarted.get(i).stop(aScope.getRuntime(component));
309 } catch (Throwable t) {
310 LOG.error(getQualifiedName() + ": error stopping "
311 + aStarted.get(i).getQualifiedName());
317 * Sets the provided interface or a component.
319 * @param aAllProvided
320 * All available provided interfaces.
322 * Component whose required interfaces we are looking at.
323 * @param aValidateOnly
324 * If true then the provider will not be set for required
327 private void initializeProvidersForRequiredInterfaces(
328 List<Pair<ProvidedInterface,Component>> aAllProvided, Component aComponent,
329 boolean aValidateOnly) {
330 // Check if all required services are already provided by
334 for (RequiredInterface descriptor : aComponent.getRequiredInterfaces()) {
335 ProvidedInterface[] filtered = filterProvidedServices(aComponent, descriptor,
336 aAllProvided, _restriction);
337 if (filtered.length == 1) {
338 if (!aValidateOnly) {
339 descriptor.setProvider(filtered[0]);
341 } else if (filtered.length > 1) {
342 throw new SystemAssemblyException(
345 + "' required by system '"
347 + "' matches multiple services provided by other systems: "
348 + getServers(filtered));
350 // filtered.length == 0
351 if (!descriptor.isOptional()) {
352 throw new SystemAssemblyException(
355 + "' required by system '"
357 + "' is not provided by systems that are started earlier");
364 protected void doStop(Scope aScope) {
365 for (int i = _components.size() - 1; i >= 0; i--) {
366 Component component = _components.get(i);
367 Object runtime = aScope.getRuntime(component);
368 component.stop(runtime);
372 private void info(String aMsg) {
373 LOG.info(getQualifiedName() + ": " + aMsg);
376 private void warn(String aMsg) {
377 LOG.warn(getQualifiedName() + ": " + aMsg);
380 private String getServers(ProvidedInterface[] aProvidedList) {
382 for (ProvidedInterface provided : aProvidedList) {
383 result += "(components ";
384 for (Component component : _components) {
385 for (ProvidedInterface provided2 : component
386 .getProvidedInterfaces()) {
387 if (provided.equals(provided2)) {
388 result += component + " ";
392 result += ", interface " + provided + ")";
397 private List<Component> getClients(RequiredInterface aRequirement) {
398 List<Component> clients = new ArrayList<Component>();
399 for (Component component : _components) {
400 for (RequiredInterface required : component.getRequiredInterfaces()) {
401 if (required.equals(aRequirement)
402 && required.isOptional() == aRequirement.isOptional()) {
403 clients.add(component);
410 private void checkSealed() {
412 throw new SystemAssemblyException("Container is sealed");