Author: jleroux
Date: Wed Sep  5 16:23:04 2018
New Revision: 1840139

URL: http://svn.apache.org/viewvc?rev=1840139&view=rev
Log:
Implemented: Implement Depends on support for Component Loading.
(OFBIZ-10368)

Components are loaded in the order they are found (i.e. alphabetic or creation 
date). 

Here is a feature which supports in ofbiz-component to create a series of 
dependency for component loading, before loading a particular component.
 
Here we can define dependency for a component like Component ebaystore is 
dependent on Component ebay (even multiple dependencies for a single component 
is also supported). We can declare it in this fashion in ofbiz-component.xml 
file.
 

<ofbiz-component name="ebaystore" enabled="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
xsi:noNamespaceSchemaLocation="http://ofbiz.apache.org/dtds/ofbiz-component.xsd";>
<depends-on component-name="ebay"/>

 
Now we can declare dependency and if any anomaly is found loading of components 
will stop.  

Thanks:

Modified:
    ofbiz/ofbiz-framework/trunk/framework/base/dtd/ofbiz-component.xsd
    
ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/component/ComponentConfig.java
    
ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/container/ComponentContainer.java

Modified: ofbiz/ofbiz-framework/trunk/framework/base/dtd/ofbiz-component.xsd
URL: 
http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/base/dtd/ofbiz-component.xsd?rev=1840139&r1=1840138&r2=1840139&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/base/dtd/ofbiz-component.xsd 
(original)
+++ ofbiz/ofbiz-framework/trunk/framework/base/dtd/ofbiz-component.xsd Wed Sep  
5 16:23:04 2018
@@ -43,21 +43,26 @@ under the License.
             <xs:attributeGroup ref="attlist.depends-on">
                 <xs:annotation>
                     <xs:documentation>
-                        This is yet unimplemented. It's not a trivial 
implementation because at the moment components are loaded
-                        in the order they are found (i.e. alphabetic or 
creation date).
-                        While this feature would require them all to be 
located first and then reordered and loaded.
-                        So, for now, simply follow below guidelines.
+                        Alter the way components are loaded. 
                         
+                        By default components are loaded in the order they are 
found (i.e. alphabetic or creation date).
                         For the plugins, all components will be loaded after 
the OFBiz components, including, in this order, those in:
-                        framework
-                        themes
-                        applications
+                            framework
+                            themes
+                            applications
+                        The plugins Auto-Loading feature also loads all 
components in the order they are found (i.e. alphabetic or creation date).
+                         
+                        Using depends-on allows to create a series of 
dependencies for component loading, before loading a particular component.
+                        For instance we can define dependency for a component 
like component ebaystore is dependent on component ebay 
+                        (even multiple dependencies for a single component is 
also supported). 
                         
-                        The plugins Auto-Loading feature loads all components 
in the order they are found (i.e. alphabetic or creation date).
-                        
-                        If you need a specific loading order of these 
components then you need to disable the Auto-Loading feature 
-                        by creating a component-load.xml file in the plugins  
directory and use the load-component tag to load 
-                        your components in the order you want (just use the 
component-load.xml file in the application folder as a template).
+                        We can declare it in this fashion in 
ofbiz-component.xml file (beware ebaystore is disabled by default)
+                        <![CDATA[
+                        <ofbiz-component name="ebaystore" enabled="true"
+                        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                        
xsi:noNamespaceSchemaLocation="http://ofbiz.apache.org/dtds/ofbiz-component.xsd";>
+                        <depends-on component-name="ebay"/>
+                        ]]>
                     </xs:documentation>
                 </xs:annotation>
             </xs:attributeGroup>

Modified: 
ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/component/ComponentConfig.java
URL: 
http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/component/ComponentConfig.java?rev=1840139&r1=1840138&r2=1840139&view=diff
==============================================================================
--- 
ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/component/ComponentConfig.java
 (original)
+++ 
ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/component/ComponentConfig.java
 Wed Sep  5 16:23:04 2018
@@ -340,6 +340,7 @@ public final class ComponentConfig {
     private final boolean enabled;
     private final Map<String, ResourceLoaderInfo> resourceLoaderInfos;
     private final List<ClasspathInfo> classpathInfos;
+    private final List<DependsOnInfo> dependsOnInfos;
     private final List<EntityResourceInfo> entityResourceInfos;
     private final List<ServiceResourceInfo> serviceResourceInfos;
     private final List<TestSuiteInfo> testSuiteInfos;
@@ -390,6 +391,19 @@ public final class ComponentConfig {
         } else {
             this.resourceLoaderInfos = Collections.emptyMap();
         }
+
+        childElements = UtilXml.childElementList(ofbizComponentElement, 
"depends-on");
+        if (!childElements.isEmpty()) {
+            List<DependsOnInfo> dependsOnList = new 
ArrayList<>(childElements.size());
+            for (Element curElement : childElements) {
+                DependsOnInfo dependsOnInfo = new DependsOnInfo(this, 
curElement);
+                dependsOnList.add(dependsOnInfo);
+            }
+            this.dependsOnInfos = Collections.unmodifiableList(dependsOnList);
+        } else {
+            this.dependsOnInfos = Collections.emptyList();
+        }
+
         // classpath - classpathInfos
         childElements = UtilXml.childElementList(ofbizComponentElement, 
"classpath");
         if (!childElements.isEmpty()) {
@@ -529,6 +543,10 @@ public final class ComponentConfig {
         return this.globalName;
     }
 
+    public List<DependsOnInfo> getDependsOn() {
+        return this.dependsOnInfos;
+    }
+
     public List<KeystoreInfo> getKeystoreInfos() {
         return this.keystoreInfos;
     }
@@ -677,6 +695,15 @@ public final class ComponentConfig {
         }
     }
 
+    public static final class DependsOnInfo extends ResourceInfo {
+        public final String componentName;
+
+        private DependsOnInfo(ComponentConfig componentConfig, Element 
element) {
+            super(componentConfig, element);
+            this.componentName = element.getAttribute("component-name");
+        }
+    }
+
     /**
      * An object that models the <code>&lt;keystore&gt;</code> element.
      *

Modified: 
ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/container/ComponentContainer.java
URL: 
http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/container/ComponentContainer.java?rev=1840139&r1=1840138&r2=1840139&view=diff
==============================================================================
--- 
ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/container/ComponentContainer.java
 (original)
+++ 
ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/container/ComponentContainer.java
 Wed Sep  5 16:23:04 2018
@@ -25,9 +25,14 @@ import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.ArrayList;
 import java.util.Arrays;
+
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
-
+import java.util.stream.Collectors;
 import org.apache.ofbiz.base.component.ComponentConfig;
 import org.apache.ofbiz.base.component.ComponentException;
 import org.apache.ofbiz.base.component.ComponentLoaderConfig;
@@ -36,6 +41,7 @@ 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.FileUtil;
+import org.apache.ofbiz.base.util.UtilValidate;
 
 /**
  * ComponentContainer - StartupContainer implementation for Components
@@ -54,6 +60,7 @@ public class ComponentContainer implemen
     private String name;
     private final AtomicBoolean loaded = new AtomicBoolean(false);
     private final List<Classpath> componentsClassPath = new ArrayList<>();
+    private static Map<String, List<ComponentConfig.DependsOnInfo>> 
toBeLoadedComponents = new HashMap<>();
 
     @Override
     public void init(List<StartupCommand> ofbizCommands, String name, String 
configFile) throws ContainerException {
@@ -110,8 +117,10 @@ public class ComponentContainer implemen
      * @param parentPath the parent path of what is being loaded
      * @param def the component or directory loader definition
      * @throws IOException
+     * @throws ContainerException
+     * @throws ComponentException
      */
-    private void loadComponentFromConfig(String parentPath, 
ComponentLoaderConfig.ComponentDef def) throws IOException {
+    private void loadComponentFromConfig(String parentPath, 
ComponentLoaderConfig.ComponentDef def) throws IOException, ContainerException, 
ComponentException {
         String location = def.location.startsWith("/") ? def.location : 
parentPath + "/" + def.location;
 
         if 
(def.type.equals(ComponentLoaderConfig.ComponentType.COMPONENT_DIRECTORY)) {
@@ -130,8 +139,10 @@ public class ComponentContainer implemen
      *
      * @param directoryName the name of component directory to load
      * @throws IOException
+     * @throws ContainerException
+     * @throws ComponentException
      */
-    private void loadComponentDirectory(String directoryName) throws 
IOException {
+    private void loadComponentDirectory(String directoryName) throws 
IOException, ContainerException, ComponentException {
         Debug.logInfo("Auto-Loading component directory : [" + directoryName + 
"]", module);
 
         File directoryPath = FileUtil.getFile(directoryName);
@@ -156,8 +167,9 @@ public class ComponentContainer implemen
      * @param directoryPath the absolute path of the directory
      * @param componentLoadFile the name of the load file (i.e. 
component-load.xml)
      * @throws IOException
+     * @throws ContainerException
      */
-    private void loadComponentsInDirectoryUsingLoadFile(File directoryPath, 
File componentLoadFile) throws IOException {
+    private void loadComponentsInDirectoryUsingLoadFile(File directoryPath, 
File componentLoadFile) throws IOException, ContainerException {
         URL configUrl = null;
         try {
             configUrl = componentLoadFile.toURI().toURL();
@@ -179,9 +191,11 @@ public class ComponentContainer implemen
      *
      * @param directoryPath the absolute path of the directory
      * @throws IOException
+     * @throws ComponentException
      */
-    private void loadComponentsInDirectory(File directoryPath) throws 
IOException {
+    private void loadComponentsInDirectory(File directoryPath) throws 
IOException, ComponentException {
         String[] sortedComponentNames = directoryPath.list();
+        List<ComponentConfig> componentConfigs = new ArrayList<>();
         if (sortedComponentNames == null) {
             throw new IllegalArgumentException("sortedComponentNames is null, 
directory path is invalid " + directoryPath.getPath());
         }
@@ -194,8 +208,46 @@ public class ComponentContainer implemen
 
             if (componentPath.isDirectory() && !componentName.startsWith(".") 
&& configFile.exists()) {
                 ComponentConfig config = retrieveComponentConfig(null, 
componentLocation);
-                if (config != null) {
-                    loadComponent(config);
+                componentConfigs.add(config);
+            }
+        }
+        for (ComponentConfig componentConfig : componentConfigs) {
+            if (componentConfig != null) {
+                loadComponent(componentConfig);
+            }
+        }
+        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<ComponentConfig.DependsOnInfo>> 
entries : toBeLoadedComponents.entrySet()) {
+                    ComponentConfig config = 
retrieveComponentConfig(entries.getKey(), null);
+                    if (config.enabled()) {
+                        List<ComponentConfig.DependsOnInfo> 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)) {
+                            
componentsClassPath.add(buildClasspathFromComponentConfig(config));
+                            
toBeLoadedComponents.replace(config.getComponentName(), dependencyList);
+                            Debug.logInfo("Added class path for component : [" 
+ config.getComponentName() + "]", module);
+                        }
+                    } else {
+                        Debug.logInfo("Not loading component [" + 
config.getComponentName() + "] because it's disabled", module);
+                    }
                 }
             }
         }
@@ -227,15 +279,52 @@ public class ComponentContainer implemen
      *
      * @param config the component configuration
      * @throws IOException
+     * @throws ComponentException
      */
-    private void loadComponent(ComponentConfig config) throws IOException {
+    private void loadComponent(ComponentConfig config) throws IOException, 
ComponentException {
         if (config.enabled()) {
-            Classpath classpath = buildClasspathFromComponentConfig(config);
-            componentsClassPath.add(classpath);
-            Debug.logInfo("Added class path for component : [" + 
config.getComponentName() + "]", module);
+            List<ComponentConfig.DependsOnInfo> dependencyList = 
checkDependencyForComponent(config);
+            if (UtilValidate.isEmpty(dependencyList)) {
+                
componentsClassPath.add(buildClasspathFromComponentConfig(config));
+                Debug.logInfo("Added class path for component : [" + 
config.getComponentName() + "]", module);
+            }
         } else {
-            Debug.logInfo("Not loading component [" + 
config.getComponentName() + "] because it is disabled", module);
+            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 IOException
+     * @throws ComponentException
+     *
+     */
+    private List<ComponentConfig.DependsOnInfo> 
checkDependencyForComponent(ComponentConfig config) throws IOException, 
ComponentException {
+        List<ComponentConfig.DependsOnInfo> dependencyList = new 
ArrayList<>(config.getDependsOn());
+        if (UtilValidate.isNotEmpty(dependencyList)) {
+            Set<ComponentConfig.DependsOnInfo> resolvedDependencyList = new 
HashSet<>();
+            for (ComponentConfig.DependsOnInfo dependency : dependencyList) {
+                Debug.logInfo("Component : " + config.getComponentName() + " 
is Dependent on  " + dependency.componentName, module);
+                ComponentConfig componentConfig = 
ComponentConfig.getComponentConfig(String.valueOf(dependency.componentName));
+                Classpath dependentComponentClasspath = 
buildClasspathFromComponentConfig(componentConfig);
+                componentsClassPath.forEach(componentClassPath -> {
+                    if 
(Arrays.equals(componentClassPath.toString().split(":"), 
dependentComponentClasspath.toString().split(":"))) {
+                        resolvedDependencyList.add(dependency);
+                    }
+                });
+            }
+            resolvedDependencyList.forEach(resolvedDependency -> 
Debug.logInfo("Resolved : " + resolvedDependency.componentName + " 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;
     }
 
     /**


Reply via email to