[ofbiz-framework] branch trunk updated: Fixed: Remove dependency management from ‘ComponentContainer’ (OFBIZ-11275)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[ofbiz-framework] branch trunk updated: Fixed: Remove dependency management from ‘ComponentContainer’ (OFBIZ-11275)

mthl
This is an automated email from the ASF dual-hosted git repository.

mthl pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 0003252  Fixed: Remove dependency management from ‘ComponentContainer’ (OFBIZ-11275)
0003252 is described below

commit 000325208a43d9364a97f827492559fccac15a34
Author: Samuel Trégouët <[hidden email]>
AuthorDate: Mon Nov 25 14:49:06 2019 +0100

    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.
---
 build.gradle                                       |   2 +-
 .../base/component/AlreadyLoadedException.java     |  44 --------
 .../ofbiz/base/component/ComponentConfig.java      |  62 ++++++++++-
 .../ofbiz/base/container/ComponentContainer.java   | 120 ++-------------------
 .../base/container/ComponentContainerTest.java     |   2 -
 5 files changed, 70 insertions(+), 160 deletions(-)

diff --git a/build.gradle b/build.gradle
index e8d0f9d..b84153a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -310,7 +310,7 @@ checkstyle {
     // the sum of errors that were present before introducing the
     // ‘checkstyle’ tool present in the framework and in the official
     // plugins.
-    tasks.checkstyleMain.maxErrors = 37779
+    tasks.checkstyleMain.maxErrors = 37776
     // Currently there are a lot of errors so we need to temporarily
     // hide them to avoid polluting the terminal output.
     showViolations = false
diff --git a/framework/base/src/main/java/org/apache/ofbiz/base/component/AlreadyLoadedException.java b/framework/base/src/main/java/org/apache/ofbiz/base/component/AlreadyLoadedException.java
deleted file mode 100644
index 4485ccf..0000000
--- a/framework/base/src/main/java/org/apache/ofbiz/base/component/AlreadyLoadedException.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.base.component;
-
-/**
- * Component Already Loaded Exception
- *
- */
-@SuppressWarnings("serial")
-public class AlreadyLoadedException extends ComponentException {
-
-    public AlreadyLoadedException() {
-        super();
-    }
-
-    public AlreadyLoadedException(String str) {
-        super(str);
-    }
-
-    public AlreadyLoadedException(Throwable nested) {
-        super(nested);
-    }
-
-    public AlreadyLoadedException(String str, Throwable nested) {
-        super(str, nested);
-    }
-}
-
diff --git a/framework/base/src/main/java/org/apache/ofbiz/base/component/ComponentConfig.java b/framework/base/src/main/java/org/apache/ofbiz/base/component/ComponentConfig.java
index 4a39885..9ddffbb 100644
--- a/framework/base/src/main/java/org/apache/ofbiz/base/component/ComponentConfig.java
+++ b/framework/base/src/main/java/org/apache/ofbiz/base/component/ComponentConfig.java
@@ -40,6 +40,7 @@ import org.apache.ofbiz.base.container.ContainerConfig;
 import org.apache.ofbiz.base.location.FlexibleLocation;
 import org.apache.ofbiz.base.util.Assert;
 import org.apache.ofbiz.base.util.Debug;
+import org.apache.ofbiz.base.util.Digraph;
 import org.apache.ofbiz.base.util.KeyStoreUtil;
 import org.apache.ofbiz.base.util.UtilURL;
 import org.apache.ofbiz.base.util.UtilValidate;
@@ -91,6 +92,16 @@ public final class ComponentConfig {
         return componentConfigCache.values();
     }
 
+    /**
+     * Sorts the cached component configurations.
+     *
+     * @throws ComponentException when a component configuration contains an invalid dependency
+     *         or if the dependency graph contains a cycle.
+     */
+    public static void sortDependencies() throws ComponentException {
+        componentConfigCache.sort();
+    }
+
     public static Stream<ComponentConfig> components() {
         return componentConfigCache.values().stream();
     }
@@ -681,6 +692,8 @@ public final class ComponentConfig {
         private final Map<String, ComponentConfig> componentConfigs = new LinkedHashMap<>();
         // Root location mapped to global name.
         private final Map<String, String> componentLocations = new HashMap<>();
+        /** Sorted component configurations (based on depends-on declaration if ofbiz-component.xml). */
+        private List<ComponentConfig> componentConfigsSorted;
 
         private synchronized ComponentConfig fromGlobalName(String globalName) {
             return componentConfigs.get(globalName);
@@ -701,8 +714,53 @@ public final class ComponentConfig {
             return componentConfigs.put(globalName, config);
         }
 
-        synchronized Collection<ComponentConfig> values() {
-            return Collections.unmodifiableList(new ArrayList<>(componentConfigs.values()));
+        /**
+         * Provides a sequence of cached component configurations.
+         *
+         * <p>the order of the sequence is arbitrary unless the {@link #sort())} has been invoked beforehand.
+         *
+         * @return the list of cached component configurations
+         */
+        synchronized List<ComponentConfig> values() {
+            // When the configurations have not been sorted use arbitrary order.
+            List<ComponentConfig> res = (componentConfigsSorted == null)
+                    ? new ArrayList<>(componentConfigs.values())
+                    : componentConfigsSorted;
+            return Collections.unmodifiableList(res);
+        }
+
+        /**
+         * Provides the stored dependencies as component configurations.
+         *
+         * <p>Handle invalid dependencies by creating dummy component configurations.
+         *
+         * @param the component configuration containing dependencies
+         * @return a collection of component configurations dependencies.
+         */
+        private Collection<ComponentConfig> componentConfigDeps(ComponentConfig cc) {
+            return cc.getDependsOn().stream()
+                    .map(dep -> {
+                        ComponentConfig storedCc = componentConfigs.get(dep);
+                        return (storedCc != null) ? storedCc : new Builder().globalName(dep).create();
+                    })
+                    .collect(Collectors.toList());
+        }
+
+        /**
+         * Sorts the component configurations based on their “depends-on” XML elements.
+         */
+        synchronized void sort() throws ComponentException {
+            /* Create the dependency graph specification with a LinkedHashMap to preserve
+               implicit dependencies defined by `component-load.xml` files. */
+            Map<ComponentConfig, Collection<ComponentConfig>> graphSpec = new LinkedHashMap<>();
+            componentConfigs.values().forEach(cc -> {
+                graphSpec.put(cc, componentConfigDeps(cc));
+            });
+            try {
+                componentConfigsSorted = new Digraph<>(graphSpec).sort();
+            } catch (IllegalArgumentException | IllegalStateException e) {
+                throw new ComponentException("failed to initialize components", e);
+            }
         }
     }
 
diff --git a/framework/base/src/main/java/org/apache/ofbiz/base/container/ComponentContainer.java b/framework/base/src/main/java/org/apache/ofbiz/base/container/ComponentContainer.java
index 3f14707..31f6e60 100644
--- a/framework/base/src/main/java/org/apache/ofbiz/base/container/ComponentContainer.java
+++ b/framework/base/src/main/java/org/apache/ofbiz/base/container/ComponentContainer.java
@@ -26,13 +26,7 @@ import java.net.URLClassLoader;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -44,7 +38,6 @@ import org.apache.ofbiz.base.component.ComponentLoaderConfig.ComponentDef;
 import org.apache.ofbiz.base.start.Start;
 import org.apache.ofbiz.base.start.StartupCommand;
 import org.apache.ofbiz.base.util.Debug;
-import org.apache.ofbiz.base.util.UtilValidate;
 
 /**
  * ComponentContainer - StartupContainer implementation for Components
@@ -62,9 +55,6 @@ public class ComponentContainer implements Container {
 
     private String name;
     private final AtomicBoolean loaded = new AtomicBoolean(false);
-    /** The set of ready components in their inverse dependency order. */
-    private final LinkedHashSet<ComponentConfig> readyComponents = new LinkedHashSet<>();
-    private static Map<String, List<String>> toBeLoadedComponents = new ConcurrentHashMap<>();
 
     @Override
     public void init(List<StartupCommand> ofbizCommands, String name, String configFile) throws ContainerException {
@@ -89,11 +79,12 @@ public class ComponentContainer implements Container {
             for (ComponentDef def: ComponentLoaderConfig.getRootComponents()) {
                 loadComponent(ofbizHome, def);
             }
+            ComponentConfig.sortDependencies();
         } catch (IOException | ComponentException e) {
             throw new ContainerException(e);
         }
         String fmt = "Added class path for component : [%s]";
-        List<Classpath> componentsClassPath = readyComponents.stream()
+        List<Classpath> componentsClassPath = ComponentConfig.components()
                 .peek(cmpnt -> Debug.logInfo(String.format(fmt, cmpnt.getComponentName()), module))
                 .map(cmpnt -> buildClasspathFromComponentConfig(cmpnt))
                 .collect(Collectors.toList());
@@ -134,19 +125,15 @@ public class ComponentContainer implements Container {
      * @param dir  the location where the component should be loaded
      * @param component  a single component or a component directory definition
      * @throws IOException when component directory loading fails.
-     * @throws ComponentException when retrieving component configuration files fails.
      */
-    private void loadComponent(Path dir, ComponentDef component) throws IOException, ComponentException {
+    private void loadComponent(Path dir, ComponentDef component) throws IOException {
         Path location = component.location.isAbsolute() ? component.location : dir.resolve(component.location);
         switch (component.type) {
         case COMPONENT_DIRECTORY:
             loadComponentDirectory(location);
             break;
         case SINGLE_COMPONENT:
-            ComponentConfig config = retrieveComponentConfig(null, location);
-            if (config != null) {
-                loadSingleComponent(config);
-            }
+            retrieveComponentConfig(null, location);
             break;
         }
     }
@@ -157,9 +144,8 @@ public class ComponentContainer implements Container {
      *
      * @param directoryName the name of component directory to load
      * @throws IOException
-     * @throws ComponentException
      */
-    private void loadComponentDirectory(Path directoryName) throws IOException, ComponentException {
+    private void loadComponentDirectory(Path directoryName) throws IOException {
         Debug.logInfo("Auto-Loading component directory : [" + directoryName + "]", module);
         if (Files.exists(directoryName) && Files.isDirectory(directoryName)) {
             Path componentLoad = directoryName.resolve(ComponentLoaderConfig.COMPONENT_LOAD_XML_FILENAME);
@@ -205,56 +191,15 @@ public class ComponentContainer implements Container {
      * for loading purposes
      *
      * @param directoryPath a valid absolute path of a component directory
-     * @throws IOException
-     * @throws ComponentException
+     * @throws IOException if an I/O error occurs when opening the directory
      */
-    private void loadComponentsInDirectory(Path directoryPath) throws IOException, ComponentException {
+    private static void loadComponentsInDirectory(Path directoryPath) throws IOException {
         try (Stream<Path> paths = Files.list(directoryPath)) {
-            List<ComponentConfig> componentConfigs = paths.sorted()
+            paths.sorted()
                     .map(cmpnt -> directoryPath.resolve(cmpnt).toAbsolutePath().normalize())
                     .filter(Files::isDirectory)
                     .filter(dir -> Files.exists(dir.resolve(ComponentConfig.OFBIZ_COMPONENT_XML_FILENAME)))
-                    .map(componentDir -> retrieveComponentConfig(null, componentDir))
-                    .filter(Objects::nonNull)
-                    .collect(Collectors.toList());
-
-            for (ComponentConfig cmpnt : componentConfigs) {
-                loadSingleComponent(cmpnt);
-            }
-        }
-        loadComponentWithDependency();
-    }
-
-    /**
-     * Checks dependency for unloaded components and add them into
-     * componentsClassPath
-     *
-     * @throws IOException
-     * @throws ComponentException
-     */
-    private void loadComponentWithDependency() throws IOException, ComponentException {
-        while (true) {
-            if (UtilValidate.isEmpty(toBeLoadedComponents)) {
-                return;
-            } else {
-                for (Map.Entry<String, List<String>> entries : toBeLoadedComponents.entrySet()) {
-                    ComponentConfig config = retrieveComponentConfig(entries.getKey(), null);
-                    if (config.enabled()) {
-                        List<String> dependencyList = checkDependencyForComponent(config);
-                        if (UtilValidate.isNotEmpty(dependencyList)) {
-                            toBeLoadedComponents.replace(config.getComponentName(), dependencyList);
-                            String msg = "Not loading component [" + config.getComponentName() + "] because it's dependent Component is not loaded [ " + dependencyList + "]";
-                            Debug.logInfo(msg, module);
-                        }
-                        if (UtilValidate.isEmpty(dependencyList)) {
-                            readyComponents.add(config);
-                            toBeLoadedComponents.replace(config.getComponentName(), dependencyList);
-                        }
-                    } else {
-                        Debug.logInfo("Not loading component [" + config.getComponentName() + "] because it's disabled", module);
-                    }
-                }
-            }
+                    .forEach(componentDir -> retrieveComponentConfig(null, componentDir));
         }
     }
 
@@ -279,53 +224,6 @@ public class ComponentContainer implements Container {
     }
 
     /**
-     * Load a single component by adding all its classpath entries to
-     * the list of classpaths to be loaded
-     *
-     * @param config the component configuration
-     * @throws ComponentException
-     */
-    private void loadSingleComponent(ComponentConfig config) throws ComponentException {
-        if (config.enabled()) {
-            List<String> dependencyList = checkDependencyForComponent(config);
-            if (UtilValidate.isEmpty(dependencyList)) {
-                readyComponents.add(config);
-            }
-        } else {
-            Debug.logInfo("Not loading component [" + config.getComponentName() + "] because it's disabled", module);
-        }
-    }
-
-    /**
-     * Check for components loaded and Removes loaded components dependency
-     * from list of unloaded components
-     *
-     * @param config the component configuration
-     * @throws ComponentException
-     */
-    private List<String> checkDependencyForComponent(ComponentConfig config) throws ComponentException {
-        List<String> dependencyList = new ArrayList<>(config.getDependsOn());
-        if (UtilValidate.isNotEmpty(dependencyList)) {
-            Set<String> resolvedDependencyList = new HashSet<>();
-            for (String dependency : dependencyList) {
-                Debug.logInfo("Component : " + config.getComponentName() + " is Dependent on  " + dependency, module);
-                ComponentConfig componentConfig = ComponentConfig.getComponentConfig(String.valueOf(dependency));
-                if (readyComponents.contains(componentConfig)) {
-                    resolvedDependencyList.add(dependency);
-                }
-            }
-            resolvedDependencyList.forEach(resolvedDependency -> Debug.logInfo("Resolved : " + resolvedDependency + " Dependency for Component " + config.getComponentName(), module));
-            dependencyList.removeAll(resolvedDependencyList);
-            if (UtilValidate.isEmpty(dependencyList)) {
-                toBeLoadedComponents.remove(config.getComponentName());
-            } else {
-                toBeLoadedComponents.put(config.getComponentName(), dependencyList);
-            }
-        }
-        return dependencyList;
-    }
-
-    /**
      * Constructs a {@code Classpath} object for a specific component definition.
      *
      * @param config  the component configuration
diff --git a/framework/base/src/test/java/org/apache/ofbiz/base/container/ComponentContainerTest.java b/framework/base/src/test/java/org/apache/ofbiz/base/container/ComponentContainerTest.java
index 358f0bc..57be05a 100644
--- a/framework/base/src/test/java/org/apache/ofbiz/base/container/ComponentContainerTest.java
+++ b/framework/base/src/test/java/org/apache/ofbiz/base/container/ComponentContainerTest.java
@@ -35,7 +35,6 @@ import org.apache.ofbiz.base.component.ComponentConfig;
 import org.apache.ofbiz.base.start.StartupException;
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 
 
@@ -61,7 +60,6 @@ public class ComponentContainerTest {
         }
     }
 
-    @Ignore("Dependency declarations do not impact the components order")
     @Test
     public void testCheckDependencyForComponent() throws ContainerException, StartupException, MalformedURLException {
         ComponentContainer containerObj = new ComponentContainer();