Skip to content

Commit 3d3533c

Browse files
Samuel Trégouëtmthl
Samuel Trégouët
authored andcommitted
Fixed: Remove dependency management from ‘ComponentContainer’
(OFBIZ-11275) This fixes a bug identified by ‘testCheckDependencyForComponent’ where the dependencies declared inside component configurations were not impacting the component retrieval order. Component configurations stored in the cache can now be properly sorted based on their <depends-on> declaration. The new ‘ComponentConfig#sortDependencies’ method returns a collection of component configuration following a topological ordering. In case of dependency cycle we throw an error.
1 parent 7044af8 commit 3d3533c

File tree

4 files changed

+68
-159
lines changed

4 files changed

+68
-159
lines changed

framework/base/src/main/java/org/apache/ofbiz/base/component/AlreadyLoadedException.java

-44
This file was deleted.

framework/base/src/main/java/org/apache/ofbiz/base/component/ComponentConfig.java

+59-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
*******************************************************************************/
1919
package org.apache.ofbiz.base.component;
2020

21+
import static java.util.function.Function.identity;
22+
import static java.util.stream.Collectors.toMap;
23+
2124
import java.io.InputStream;
2225
import java.net.URL;
2326
import java.nio.file.Files;
@@ -40,6 +43,7 @@
4043
import org.apache.ofbiz.base.location.FlexibleLocation;
4144
import org.apache.ofbiz.base.util.Assert;
4245
import org.apache.ofbiz.base.util.Debug;
46+
import org.apache.ofbiz.base.util.Digraph;
4347
import org.apache.ofbiz.base.util.KeyStoreUtil;
4448
import org.apache.ofbiz.base.util.UtilURL;
4549
import org.apache.ofbiz.base.util.UtilValidate;
@@ -91,6 +95,16 @@ public static Collection<ComponentConfig> getAllComponents() {
9195
return componentConfigCache.values();
9296
}
9397

98+
/**
99+
* Sorts the cached component configurations.
100+
*
101+
* @throws ComponentException when a component configuration contains an invalid dependency
102+
* or if the dependency graph contains a cycle.
103+
*/
104+
public static void sortDependencies() throws ComponentException {
105+
componentConfigCache.sort();
106+
}
107+
94108
public static Stream<ComponentConfig> components() {
95109
return componentConfigCache.values().stream();
96110
}
@@ -681,6 +695,8 @@ static final class ComponentConfigCache {
681695
private final Map<String, ComponentConfig> componentConfigs = new LinkedHashMap<>();
682696
// Root location mapped to global name.
683697
private final Map<String, String> componentLocations = new HashMap<>();
698+
/** Sorted component configurations (based on depends-on declaration if ofbiz-component.xml). */
699+
private List<ComponentConfig> componentConfigsSorted;
684700

685701
private synchronized ComponentConfig fromGlobalName(String globalName) {
686702
return componentConfigs.get(globalName);
@@ -701,8 +717,49 @@ synchronized ComponentConfig put(ComponentConfig config) {
701717
return componentConfigs.put(globalName, config);
702718
}
703719

704-
synchronized Collection<ComponentConfig> values() {
705-
return Collections.unmodifiableList(new ArrayList<>(componentConfigs.values()));
720+
/**
721+
* Provides a sequence of cached component configurations.
722+
*
723+
* <p>the order of the sequence is arbitrary unless the {@link #sort())} has been invoked beforehand.
724+
*
725+
* @return the list of cached component configurations
726+
*/
727+
synchronized List<ComponentConfig> values() {
728+
// When the configurations have not been sorted use arbitrary order.
729+
List<ComponentConfig> res = (componentConfigsSorted == null)
730+
? new ArrayList<>(componentConfigs.values())
731+
: componentConfigsSorted;
732+
return Collections.unmodifiableList(res);
733+
}
734+
735+
/**
736+
* Provides the stored dependencies as component configurations.
737+
*
738+
* <p>Handle invalid dependencies by creating dummy component configurations.
739+
*
740+
* @param the component configuration containing dependencies
741+
* @return a collection of component configurations dependencies.
742+
*/
743+
private Collection<ComponentConfig> componentConfigDeps(ComponentConfig cc) {
744+
return cc.getDependsOn().stream()
745+
.map(dep -> {
746+
ComponentConfig storedCc = componentConfigs.get(dep);
747+
return (storedCc != null) ? storedCc : new Builder().globalName(dep).create();
748+
})
749+
.collect(Collectors.toList());
750+
}
751+
752+
/**
753+
* Sorts the component configurations based on their “depends-on” XML elements.
754+
*/
755+
synchronized void sort() throws ComponentException {
756+
Map<ComponentConfig, Collection<ComponentConfig>> graphSpec = componentConfigs.values().stream()
757+
.collect(toMap(identity(), this::componentConfigDeps));
758+
try {
759+
componentConfigsSorted = new Digraph<>(graphSpec).sort();
760+
} catch (IllegalArgumentException | IllegalStateException e) {
761+
throw new ComponentException("failed to initialize components", e);
762+
}
706763
}
707764
}
708765

framework/base/src/main/java/org/apache/ofbiz/base/container/ComponentContainer.java

+9-111
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,7 @@
2626
import java.nio.file.Files;
2727
import java.nio.file.Path;
2828
import java.util.ArrayList;
29-
import java.util.HashSet;
30-
import java.util.LinkedHashSet;
3129
import java.util.List;
32-
import java.util.Map;
33-
import java.util.Objects;
34-
import java.util.Set;
35-
import java.util.concurrent.ConcurrentHashMap;
3630
import java.util.concurrent.atomic.AtomicBoolean;
3731
import java.util.stream.Collectors;
3832
import java.util.stream.Stream;
@@ -44,7 +38,6 @@
4438
import org.apache.ofbiz.base.start.Start;
4539
import org.apache.ofbiz.base.start.StartupCommand;
4640
import org.apache.ofbiz.base.util.Debug;
47-
import org.apache.ofbiz.base.util.UtilValidate;
4841

4942
/**
5043
* ComponentContainer - StartupContainer implementation for Components
@@ -62,9 +55,6 @@ public class ComponentContainer implements Container {
6255

6356
private String name;
6457
private final AtomicBoolean loaded = new AtomicBoolean(false);
65-
/** The set of ready components in their inverse dependency order. */
66-
private final LinkedHashSet<ComponentConfig> readyComponents = new LinkedHashSet<>();
67-
private static Map<String, List<String>> toBeLoadedComponents = new ConcurrentHashMap<>();
6858

6959
@Override
7060
public void init(List<StartupCommand> ofbizCommands, String name, String configFile) throws ContainerException {
@@ -89,11 +79,12 @@ void init(String name, Path ofbizHome) throws ContainerException {
8979
for (ComponentDef def: ComponentLoaderConfig.getRootComponents()) {
9080
loadComponent(ofbizHome, def);
9181
}
82+
ComponentConfig.sortDependencies();
9283
} catch (IOException | ComponentException e) {
9384
throw new ContainerException(e);
9485
}
9586
String fmt = "Added class path for component : [%s]";
96-
List<Classpath> componentsClassPath = readyComponents.stream()
87+
List<Classpath> componentsClassPath = ComponentConfig.components()
9788
.peek(cmpnt -> Debug.logInfo(String.format(fmt, cmpnt.getComponentName()), module))
9889
.map(cmpnt -> buildClasspathFromComponentConfig(cmpnt))
9990
.collect(Collectors.toList());
@@ -134,19 +125,15 @@ private static void loadClassPathForAllComponents(List<Classpath> componentsClas
134125
* @param dir the location where the component should be loaded
135126
* @param component a single component or a component directory definition
136127
* @throws IOException when component directory loading fails.
137-
* @throws ComponentException when retrieving component configuration files fails.
138128
*/
139-
private void loadComponent(Path dir, ComponentDef component) throws IOException, ComponentException {
129+
private void loadComponent(Path dir, ComponentDef component) throws IOException {
140130
Path location = component.location.isAbsolute() ? component.location : dir.resolve(component.location);
141131
switch (component.type) {
142132
case COMPONENT_DIRECTORY:
143133
loadComponentDirectory(location);
144134
break;
145135
case SINGLE_COMPONENT:
146-
ComponentConfig config = retrieveComponentConfig(null, location);
147-
if (config != null) {
148-
loadSingleComponent(config);
149-
}
136+
retrieveComponentConfig(null, location);
150137
break;
151138
}
152139
}
@@ -157,9 +144,8 @@ private void loadComponent(Path dir, ComponentDef component) throws IOException,
157144
*
158145
* @param directoryName the name of component directory to load
159146
* @throws IOException
160-
* @throws ComponentException
161147
*/
162-
private void loadComponentDirectory(Path directoryName) throws IOException, ComponentException {
148+
private void loadComponentDirectory(Path directoryName) throws IOException {
163149
Debug.logInfo("Auto-Loading component directory : [" + directoryName + "]", module);
164150
if (Files.exists(directoryName) && Files.isDirectory(directoryName)) {
165151
Path componentLoad = directoryName.resolve(ComponentLoaderConfig.COMPONENT_LOAD_XML_FILENAME);
@@ -205,56 +191,15 @@ private void loadComponentsInDirectoryUsingLoadFile(Path directoryPath, Path com
205191
* for loading purposes
206192
*
207193
* @param directoryPath a valid absolute path of a component directory
208-
* @throws IOException
209-
* @throws ComponentException
194+
* @throws IOException if an I/O error occurs when opening the directory
210195
*/
211-
private void loadComponentsInDirectory(Path directoryPath) throws IOException, ComponentException {
196+
private static void loadComponentsInDirectory(Path directoryPath) throws IOException {
212197
try (Stream<Path> paths = Files.list(directoryPath)) {
213-
List<ComponentConfig> componentConfigs = paths.sorted()
198+
paths.sorted()
214199
.map(cmpnt -> directoryPath.resolve(cmpnt).toAbsolutePath().normalize())
215200
.filter(Files::isDirectory)
216201
.filter(dir -> Files.exists(dir.resolve(ComponentConfig.OFBIZ_COMPONENT_XML_FILENAME)))
217-
.map(componentDir -> retrieveComponentConfig(null, componentDir))
218-
.filter(Objects::nonNull)
219-
.collect(Collectors.toList());
220-
221-
for (ComponentConfig cmpnt : componentConfigs) {
222-
loadSingleComponent(cmpnt);
223-
}
224-
}
225-
loadComponentWithDependency();
226-
}
227-
228-
/**
229-
* Checks dependency for unloaded components and add them into
230-
* componentsClassPath
231-
*
232-
* @throws IOException
233-
* @throws ComponentException
234-
*/
235-
private void loadComponentWithDependency() throws IOException, ComponentException {
236-
while (true) {
237-
if (UtilValidate.isEmpty(toBeLoadedComponents)) {
238-
return;
239-
} else {
240-
for (Map.Entry<String, List<String>> entries : toBeLoadedComponents.entrySet()) {
241-
ComponentConfig config = retrieveComponentConfig(entries.getKey(), null);
242-
if (config.enabled()) {
243-
List<String> dependencyList = checkDependencyForComponent(config);
244-
if (UtilValidate.isNotEmpty(dependencyList)) {
245-
toBeLoadedComponents.replace(config.getComponentName(), dependencyList);
246-
String msg = "Not loading component [" + config.getComponentName() + "] because it's dependent Component is not loaded [ " + dependencyList + "]";
247-
Debug.logInfo(msg, module);
248-
}
249-
if (UtilValidate.isEmpty(dependencyList)) {
250-
readyComponents.add(config);
251-
toBeLoadedComponents.replace(config.getComponentName(), dependencyList);
252-
}
253-
} else {
254-
Debug.logInfo("Not loading component [" + config.getComponentName() + "] because it's disabled", module);
255-
}
256-
}
257-
}
202+
.forEach(componentDir -> retrieveComponentConfig(null, componentDir));
258203
}
259204
}
260205

@@ -278,53 +223,6 @@ private static ComponentConfig retrieveComponentConfig(String name, Path locatio
278223
return config;
279224
}
280225

281-
/**
282-
* Load a single component by adding all its classpath entries to
283-
* the list of classpaths to be loaded
284-
*
285-
* @param config the component configuration
286-
* @throws ComponentException
287-
*/
288-
private void loadSingleComponent(ComponentConfig config) throws ComponentException {
289-
if (config.enabled()) {
290-
List<String> dependencyList = checkDependencyForComponent(config);
291-
if (UtilValidate.isEmpty(dependencyList)) {
292-
readyComponents.add(config);
293-
}
294-
} else {
295-
Debug.logInfo("Not loading component [" + config.getComponentName() + "] because it's disabled", module);
296-
}
297-
}
298-
299-
/**
300-
* Check for components loaded and Removes loaded components dependency
301-
* from list of unloaded components
302-
*
303-
* @param config the component configuration
304-
* @throws ComponentException
305-
*/
306-
private List<String> checkDependencyForComponent(ComponentConfig config) throws ComponentException {
307-
List<String> dependencyList = new ArrayList<>(config.getDependsOn());
308-
if (UtilValidate.isNotEmpty(dependencyList)) {
309-
Set<String> resolvedDependencyList = new HashSet<>();
310-
for (String dependency : dependencyList) {
311-
Debug.logInfo("Component : " + config.getComponentName() + " is Dependent on " + dependency, module);
312-
ComponentConfig componentConfig = ComponentConfig.getComponentConfig(String.valueOf(dependency));
313-
if (readyComponents.contains(componentConfig)) {
314-
resolvedDependencyList.add(dependency);
315-
}
316-
}
317-
resolvedDependencyList.forEach(resolvedDependency -> Debug.logInfo("Resolved : " + resolvedDependency + " Dependency for Component " + config.getComponentName(), module));
318-
dependencyList.removeAll(resolvedDependencyList);
319-
if (UtilValidate.isEmpty(dependencyList)) {
320-
toBeLoadedComponents.remove(config.getComponentName());
321-
} else {
322-
toBeLoadedComponents.put(config.getComponentName(), dependencyList);
323-
}
324-
}
325-
return dependencyList;
326-
}
327-
328226
/**
329227
* Constructs a {@code Classpath} object for a specific component definition.
330228
*

framework/base/src/test/java/org/apache/ofbiz/base/container/ComponentContainerTest.java

-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import org.apache.ofbiz.base.start.StartupException;
3636
import org.junit.After;
3737
import org.junit.Before;
38-
import org.junit.Ignore;
3938
import org.junit.Test;
4039

4140

@@ -61,7 +60,6 @@ public void cleanUp() throws IOException {
6160
}
6261
}
6362

64-
@Ignore("Dependency declarations do not impact the components order")
6563
@Test
6664
public void testCheckDependencyForComponent() throws ContainerException, StartupException, MalformedURLException {
6765
ComponentContainer containerObj = new ComponentContainer();

0 commit comments

Comments
 (0)