(no commit message)
[utils] / system / general / src / main / java / org / wamblee / system / container / Container.java
1 /*
2  * Copyright 2007 the original author or authors.
3  * 
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
7  * 
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * 
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.
15  */
16 package org.wamblee.system.container;
17
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Set;
23
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
26 import org.wamblee.general.Pair;
27 import org.wamblee.system.core.AbstractComponent;
28 import org.wamblee.system.core.Component;
29 import org.wamblee.system.core.DefaultScope;
30 import org.wamblee.system.core.ProvidedInterface;
31 import org.wamblee.system.core.RequiredInterface;
32 import org.wamblee.system.core.Scope;
33 import org.wamblee.system.core.SystemAssemblyException;
34 import org.wamblee.system.graph.CompositeEdgeFilter;
35 import org.wamblee.system.graph.component.ComponentGraph;
36 import org.wamblee.system.graph.component.ConnectExternalProvidedProvidedFilter;
37 import org.wamblee.system.graph.component.ConnectRequiredExternallyRequiredEdgeFilter;
38 import org.wamblee.system.graph.component.ConnectRequiredProvidedEdgeFilter;
39
40 /**
41  * Container consisting of multiple components.
42  * 
43  * @author Erik Brakkee
44  */
45 public class Container extends AbstractComponent<Scope> {
46
47     private static final Log LOG = LogFactory.getLog(Container.class);
48
49     private List<Component> _components;
50     private Set<String> _componentNames;
51     private CompositeEdgeFilter _edgeFilter; 
52     private boolean _sealed;
53
54     /**
55      * Constructs the container
56      * 
57      * @param aName
58      *            Name of the container
59      * @param aComponents
60      *            Components.
61      * @param aProvided
62      *            Provided services of the container
63      * @param aRequired
64      *            Required services by the container.
65      */
66     public Container(String aName, Component[] aComponents,
67             List<ProvidedInterface> aProvided, List<RequiredInterface> aRequired) {
68         super(aName, aProvided, aRequired);
69         _components = new ArrayList<Component>();
70
71         _componentNames = new HashSet<String>();
72         _edgeFilter = new CompositeEdgeFilter();
73         _sealed = false;
74         for (Component component : aComponents) {
75             addComponent(component);
76         }
77     }
78     
79     /**
80      * Constructs the container
81      * 
82      * @param aName
83      *            Name of the container
84      * @param aComponents
85      *            Components.
86      * @param aProvided
87      *            Provided services of the container
88      * @param aRequired
89      *            Required services by the container.
90      */
91     public Container(String aName, Component[] aComponents,
92             ProvidedInterface[] aProvided, RequiredInterface[] aRequired) {
93         this(aName, aComponents, Arrays.asList(aProvided), Arrays.asList(aRequired));
94     }
95
96     public Container(String aName) {
97         this(aName, new Component[0], new ProvidedInterface[0],
98                 new RequiredInterface[0]);
99     }
100
101     public Container addComponent(Component aComponent) {
102         checkSealed();
103         if (aComponent.getContext() != null) {
104             throw new SystemAssemblyException(
105                     "Inconsistent hierarchy, component '"
106                             + aComponent.getName()
107                             + "' is already part of another hierarchy");
108         }
109         if (_componentNames.contains(aComponent.getName())) {
110             throw new SystemAssemblyException("Duplicate component '"
111                     + aComponent.getName() + "'");
112         }
113         _components.add(aComponent);
114         _componentNames.add(aComponent.getName());
115         aComponent.addContext(getQualifiedName());
116         return this;
117     }
118     
119     /**
120      * Explictly connects required and provided interfaces. 
121      * @param aClientComponent Client component, may not be null. 
122      * @param aRequiredInterface Required interface. If null it means all required interfaces. 
123      * @param aServerComponent Server component to connect to. If null, it means that no server components
124      *         may be connected to and the provider of the required interface will be null. 
125      * @param aProvidedInterface Provided interface. If null, it means that there is no restriction on the 
126      *   name of the provided interface and that it is automatically selected. 
127      */
128     public void connectRequiredProvided(String aClientComponent, String aRequiredInterface, 
129             String aServerComponent, String aProvidedInterface) {
130         checkSealed();
131         // TODO validate 
132         _edgeFilter.add(new ConnectRequiredProvidedEdgeFilter(aClientComponent, aRequiredInterface, aServerComponent, aProvidedInterface));
133     }
134     
135     /**
136      * Explicitly connects a externally required interface to an internally required interface. 
137      * @param aComponent Component requiring the interface (must be non-null). 
138      * @param aRequiredInterface Required interface of the component (must be non-null).
139      * @param aExternalRequiredInterface Externally required interface (must be non-null).
140      */
141     public void connectExternalRequired(String aComponent, String aRequiredInterface, 
142             String aExternalRequiredInterface) {
143         checkSealed();
144         // TODO validate
145         _edgeFilter.add(new ConnectRequiredExternallyRequiredEdgeFilter(
146                 aComponent, aRequiredInterface, aExternalRequiredInterface));
147     }
148     
149     public void connectExternalProvided(String aExternalProvided, String aComponent, String aProvidedInterface) {
150         checkSealed();
151         // TODO validate
152         _edgeFilter.add(new ConnectExternalProvidedProvidedFilter(aExternalProvided, aComponent, aProvidedInterface));
153     }
154
155
156     @Override
157     public Container addProvidedInterface(ProvidedInterface aProvided) {
158         checkSealed();
159         super.addProvidedInterface(aProvided);
160         return this;
161     }
162
163     @Override
164     public Container addRequiredInterface(RequiredInterface aRequired) {
165         checkSealed();
166         super.addRequiredInterface(aRequired);
167         return this;
168     }
169
170     @Override
171     public void addContext(String aContext) {
172         super.addContext(aContext);
173         for (Component component : _components) {
174             component.addContext(aContext);
175         }
176     }
177
178     /**
179      * Validates the components together to check that there are no required
180      * services not in the required list and no services in the provided list
181      * that cannot be provided. Also logs a warning in case of superfluous
182      * requirements.
183      * 
184      * @throws SystemAssemblyException
185      *             in case of any validation problems.
186      */
187     public void validate() {
188         doStartOptionalDryRun(null, true);
189     }
190
191     /**
192      * Seal the container, meaning that no further components or interfaces may
193      * be added.
194      */
195     public void seal() {
196         _sealed = true;
197     }
198
199     /**
200      * Checks if the container is sealed.
201      * 
202      * @return True iff the container is sealed.
203      */
204     public boolean isSealed() {
205         return _sealed;
206     }
207
208     /**
209      * Utility method to start with an empty external scope. This is useful for
210      * top-level containers which are not part of another container.
211      * 
212      * @return Scope.
213      */
214     public Scope start() {
215         Scope scope = new DefaultScope(getProvidedInterfaces());
216         return super.start(scope);
217     }
218
219     @Override
220     protected Scope doStart(Scope aExternalScope) {
221         checkSealed();
222         validate();
223         Scope scope = new DefaultScope(getProvidedInterfaces().toArray(new ProvidedInterface[0]), aExternalScope);
224         ComponentGraph graph = doStartOptionalDryRun(scope, false);
225         exposeProvidedInterfaces(graph, aExternalScope, scope);
226         seal();
227         return scope;
228     }
229
230     private void exposeProvidedInterfaces(ComponentGraph aGraph, Scope aExternalScope,
231             Scope aInternalScope) {
232         for (Pair<ProvidedInterface,ProvidedInterface> mapping: 
233             aGraph.findExternalProvidedInterfaceMapping()) { 
234             Object svc = aInternalScope.getInterfaceImplementation(mapping.getSecond(), Object.class);
235             addInterface(mapping.getFirst(), svc, aExternalScope);
236         }
237     }
238
239     private ComponentGraph doStartOptionalDryRun(Scope aScope, boolean aDryRun) {
240         ComponentGraph graph = createComponentGraph();
241         graph.validate();
242         if (!aDryRun) {
243             graph.link();
244         }
245
246         LOG.info("Starting '" + getQualifiedName() + "'");
247
248         List<Component> started = new ArrayList<Component>();
249         for (Component component : _components) {
250             try {
251                 // Start the service.
252                 if (!aDryRun) {
253                     Object runtime = component.start(aScope);
254                     aScope.addRuntime(component, runtime);
255                     started.add(component);
256                 }
257             } catch (SystemAssemblyException e) {
258                 throw e;
259             } catch (RuntimeException e) {
260                 LOG.error(getQualifiedName() + ": could not start '"
261                         + component.getQualifiedName() + "'", e);
262                 stopAlreadyStartedComponents(started, aScope);
263                 throw e;
264             }
265         }
266         return graph;
267     }
268
269     private ComponentGraph createComponentGraph() {
270         ComponentGraph graph = new ComponentGraph();
271         for (RequiredInterface req : getRequiredInterfaces()) {
272             graph.addRequiredInterface(this, req);
273         }
274         for (Component comp : _components) {
275             graph.addComponent(comp);
276         }
277         for (ProvidedInterface prov: getProvidedInterfaces()) { 
278             graph.addProvidedInterface(this, prov);
279         }
280
281         graph.addEdgeFilter(_edgeFilter);
282         return graph;
283     }
284
285     private void stopAlreadyStartedComponents(List<Component> aStarted,
286             Scope aScope) {
287         // an exception occurred, stop the successfully started
288         // components
289         for (int i = aStarted.size() - 1; i >= 0; i--) {
290             try {
291                 Component component = aStarted.get(i);
292                 aStarted.get(i).stop(aScope.getRuntime(component));
293             } catch (Throwable t) {
294                 LOG.error(getQualifiedName() + ": error stopping "
295                         + aStarted.get(i).getQualifiedName());
296             }
297         }
298     }
299
300     @Override
301     protected void doStop(Scope aScope) {
302         for (int i = _components.size() - 1; i >= 0; i--) {
303             Component component = _components.get(i);
304             Object runtime = aScope.getRuntime(component);
305             component.stop(runtime);
306         }
307     }
308
309     private void checkSealed() {
310         if (_sealed) {
311             throw new SystemAssemblyException("Container is sealed");
312         }
313     }
314 }