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.Iterator;
22 import java.util.List;
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
28 * Container consisting of multiple components.
30 * @author Erik Brakkee
32 public class Container extends AbstractComponent<Scope> {
34 private static final Log LOG = LogFactory.getLog(Container.class);
36 private List<Component> _components;
37 private boolean _sealed;
39 public static RequiredInterface[] filterRequiredServices(
40 ProvidedInterface aProvided,
41 Collection<RequiredInterface> aDescriptors) {
42 List<RequiredInterface> required = new ArrayList<RequiredInterface>();
43 for (RequiredInterface descriptor : aDescriptors) {
44 if (descriptor.implementedBy(aProvided)) {
45 required.add(descriptor);
48 return required.toArray(new RequiredInterface[0]);
51 public static ProvidedInterface[] filterProvidedServices(
52 RequiredInterface aRequired, Collection<ProvidedInterface> aProvided) {
53 List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
54 for (ProvidedInterface descriptor : aProvided) {
55 if (aRequired.implementedBy(descriptor)) {
56 provided.add(descriptor);
59 return provided.toArray(new ProvidedInterface[0]);
63 * Constructs the container
66 * Name of the container
70 * Provided services of the container
72 * Required services by the container.
74 public Container(String aName, Component[] aComponents,
75 ProvidedInterface[] aProvided, RequiredInterface[] aRequired) {
76 super(aName, aProvided, aRequired);
77 _components = new ArrayList<Component>(Arrays.asList(aComponents));
78 for (Component component : aComponents) {
79 component.addContext(getQualifiedName());
85 public Container(String aName) {
86 this(aName, new Component[0], new ProvidedInterface[0], new RequiredInterface[0]);
89 public Container addComponent(Component aComponent) {
91 _components.add(aComponent);
96 protected Container addProvidedInterface(ProvidedInterface aProvided) {
98 super.addProvidedInterface(aProvided);
103 protected Container addRequiredInterface(RequiredInterface aRequired) {
105 super.addRequiredInterface(aRequired);
110 * Validates the components together to check that there are no required
111 * services not in the required list and no services in the provided list
112 * that cannot be provided. Also logs a warning in case of superfluous
114 * @throws SystemAssemblyException in case of any validation problems.
116 public void validate() {
117 List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
118 for (Component component : _components) {
119 provided.addAll(Arrays.asList(component.getProvidedInterfaces()));
122 List<RequiredInterface> required = new ArrayList<RequiredInterface>();
123 for (Component component : _components) {
124 required.addAll(Arrays.asList(component.getRequiredInterfaces()));
127 validateProvidedInterfaces(provided);
129 validateRequiredInterfaces(required);
131 List<RequiredInterface> reallyRequired = validateRequiredProvidedMatch(
134 String missingRequired = "";
135 for (RequiredInterface service : reallyRequired) {
136 missingRequired += service + "\n";
138 if (missingRequired.length() > 0) {
139 throw new SystemAssemblyException(getName()
140 + ": missing required services\n" + missingRequired);
144 private List<RequiredInterface> validateRequiredProvidedMatch(
145 List<ProvidedInterface> aProvided, List<RequiredInterface> aRequired) {
146 List<RequiredInterface> reallyRequired = new ArrayList<RequiredInterface>(
148 // Compute all required interfaces that are not provided
150 for (ProvidedInterface service : aProvided) {
151 List<RequiredInterface> fulfilled = Arrays
152 .asList(filterRequiredServices(service, reallyRequired));
153 reallyRequired.removeAll(fulfilled);
155 // Now remove all optional interfaces from the list.
156 for (Iterator<RequiredInterface> i =
157 reallyRequired.iterator(); i.hasNext(); ) {
158 RequiredInterface req = i.next();
159 if ( req.isOptional() ) {
163 // Now the remaining interfaces should be covered by the required
165 reallyRequired.removeAll(Arrays.asList(getRequiredInterfaces()));
166 return reallyRequired;
169 private void validateRequiredInterfaces(List<RequiredInterface> aRequired) {
170 for (RequiredInterface service : getRequiredInterfaces()) {
171 // TODO required interfaces by the component could be
172 // subclasses or implementations of the requirements
173 // of the contained components. The code below assumes
175 if (!(aRequired.contains(service))) {
178 + "' indicated as required is not actually required by any of the components");
180 // Check for the case that the externally required service
181 // is optional whereas the internally required service is
183 if ( service.isOptional()) {
184 for (RequiredInterface intf: aRequired) {
185 if ( intf.equals(service) && !intf.isOptional()) {
186 warn("Required service '" + service + "' indicated as optional is mandatory by one of its components (" + getClients(intf) + ", " + intf + "), this can lead to problems when the container is started and the interface is not provided to the container.");
193 private void validateProvidedInterfaces(List<ProvidedInterface> aProvided) {
194 for (ProvidedInterface service : getProvidedInterfaces()) {
195 // TODO provided interfaces by components could be
196 // provide subclasses or implementations of the
197 // provided interfaces of the container.
198 // The code below assumes an exact match.
199 if (!(aProvided.contains(service))) {
200 throw new SystemAssemblyException(getName() + ": Service '"
202 + "' is not provided by any of its components");
208 * Starts the container. After the container is started, the container becomes sealed
209 * meaning that no further components, required or provided 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 be added.
228 * Checks if the container is sealed.
229 * @return True iff the container is sealed.
231 public boolean isSealed() {
236 protected Scope doStart(Scope aExternalScope) {
237 LOG.info("Starting '" + getQualifiedName() + "'");
239 Scope scope = new DefaultScope(getProvidedInterfaces(),
242 List<ProvidedInterface> allProvided = new ArrayList<ProvidedInterface>();
244 // all interfaces from the required list of this container are
245 // provided to the components inside it.
246 RequiredInterface[] required = getRequiredInterfaces();
247 for (RequiredInterface intf : required) {
248 ProvidedInterface provider = intf.getProvider();
249 if (provider != null ) {
250 allProvided.add(provider);
252 if ( !intf.isOptional()) {
253 throw new SystemAssemblyException(getQualifiedName()
254 + ": required interface '" + intf + "' is not provided");
259 List<Component> started = new ArrayList<Component>();
260 for (Component component : _components) {
262 checkAllRequiredServicesAlreadyProvided(allProvided, component);
264 // Start the service.
265 Object runtime = component.start(scope);
266 scope.addRuntime(component, runtime);
267 started.add(component);
269 // add all provided services
270 ProvidedInterface[] provided = component.getProvidedInterfaces();
271 allProvided.addAll(Arrays.asList(provided));
272 } catch (SystemAssemblyException e) {
274 } catch (RuntimeException e) {
275 LOG.error(getQualifiedName() + ": could not start '"
276 + component.getQualifiedName() + "'", e);
277 stopAlreadyStartedComponents(started, scope);
284 private void stopAlreadyStartedComponents(List<Component> aStarted, Scope aScope) {
285 // an exception occurred, stop the successfully started
287 for (int i = aStarted.size() - 1; i >= 0; i--) {
289 aStarted.get(i).stop(aScope);
290 } catch (Throwable t) {
291 LOG.error(getQualifiedName() + ": error stopping "
292 + aStarted.get(i).getQualifiedName());
297 private void checkAllRequiredServicesAlreadyProvided(
298 List<ProvidedInterface> aAllProvided, Component aComponent) {
299 // Check if all required services are already provided by
303 for (RequiredInterface descriptor : aComponent.getRequiredInterfaces()) {
304 ProvidedInterface[] filtered = filterProvidedServices(
305 descriptor, aAllProvided);
306 if ( filtered.length == 1 ) {
307 descriptor.setProvider(filtered[0]);
308 } else if ( filtered.length > 1 ) {
309 throw new SystemAssemblyException(
312 + "' required by system '"
314 + "' matches multiple services provided by other systems: "
315 + getServers(filtered));
317 // filtered.length == 0
318 if ( !descriptor.isOptional()) {
319 throw new SystemAssemblyException(
322 + "' required by system '"
324 + "' is not provided by systems that are started earlier");
331 protected void doStop(Scope aScope) {
332 for (int i = _components.size() - 1; i >= 0; i--) {
333 Component component = _components.get(i);
334 Object runtime = aScope.getRuntime(component);
335 component.stop(runtime);
339 private void info(String aMsg) {
340 LOG.info(getQualifiedName() + ": " + aMsg);
343 private void warn(String aMsg) {
344 LOG.warn(getQualifiedName() + ": " + aMsg);
347 private String getServers(ProvidedInterface[] aProvidedList ) {
349 for (ProvidedInterface provided: aProvidedList) {
350 result += "(components ";
351 for (Component component: _components) {
352 for (ProvidedInterface provided2: component.getProvidedInterfaces()) {
353 if ( provided.equals(provided2)) {
354 result += component + " ";
358 result += ", interface " + provided + ")";
363 private List<Component> getClients(RequiredInterface aRequirement) {
364 List<Component> clients = new ArrayList<Component>();
365 for (Component component: _components) {
366 for (RequiredInterface required: component.getRequiredInterfaces()) {
367 if ( required.equals(aRequirement) && required.isOptional() == aRequirement.isOptional()) {
368 clients.add(component);
375 private void checkSealed() {
377 throw new SystemAssemblyException("Container is sealed");