Author: lukaszlenart
Date: Mon Sep  3 05:45:21 2012
New Revision: 1380129

URL: http://svn.apache.org/viewvc?rev=1380129&view=rev
Log:
WW-3870 Adds package inheritance in plugins

Added:
    
struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/CycleDetector.java
    
struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/DirectedGraph.java
Modified:
    
struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationUtil.java
    
struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java
    
struts/struts2/trunk/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderPackagesTest.java
    
struts/struts2/trunk/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-inheritance.xml

Modified: 
struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationUtil.java
URL: 
http://svn.apache.org/viewvc/struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationUtil.java?rev=1380129&r1=1380128&r2=1380129&view=diff
==============================================================================
--- 
struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationUtil.java
 (original)
+++ 
struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/ConfigurationUtil.java
 Mon Sep  3 05:45:21 2012
@@ -24,39 +24,56 @@ import java.util.Collections;
 import java.util.List;
 import java.util.StringTokenizer;
 
-
 /**
  * ConfigurationUtil
- *
- * @author Jason Carreira
- *         Created May 23, 2003 11:22:49 PM
+ * 
+ * @author Jason Carreira Created May 23, 2003 11:22:49 PM
  */
 public class ConfigurationUtil {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(ConfigurationUtil.class);
 
-
     private ConfigurationUtil() {
     }
 
-
+    /**
+     * Get the {@link PackageConfig} elements with the specified names.
+     * @param configuration Configuration from which to find the package 
elements
+     * @param parent Comma separated list of parent package names
+     * @return The package elements that correspond to the names in the {@code 
parent} parameter.
+     */
     public static List<PackageConfig> buildParentsFromString(Configuration 
configuration, String parent) {
+        List<String> parentPackageNames = buildParentListFromString(parent);
+        List<PackageConfig> parentPackageConfigs = new 
ArrayList<PackageConfig>();
+        for (String parentPackageName : parentPackageNames) {
+            PackageConfig parentPackageContext = 
configuration.getPackageConfig(parentPackageName);
+
+            if (parentPackageContext != null) {
+                parentPackageConfigs.add(parentPackageContext);
+            }
+        }
+
+        return parentPackageConfigs;
+    }
+
+    /**
+     * Splits the string into a list using a comma as the token separator.
+     * @param parent The comma separated string.
+     * @return A list of tokens from the specified string.
+     */
+    public static List<String> buildParentListFromString(String parent) {
         if ((parent == null) || ("".equals(parent))) {
             return Collections.emptyList();
         }
 
-        StringTokenizer tokenizer = new StringTokenizer(parent, ", ");
-        List<PackageConfig> parents = new ArrayList<PackageConfig>();
+        StringTokenizer tokenizer = new StringTokenizer(parent, ",");
+        List<String> parents = new ArrayList<String>();
 
         while (tokenizer.hasMoreTokens()) {
             String parentName = tokenizer.nextToken().trim();
 
             if (!"".equals(parentName)) {
-                PackageConfig parentPackageContext = 
configuration.getPackageConfig(parentName);
-
-                if (parentPackageContext != null) {
-                    parents.add(parentPackageContext);
-                }
+                parents.add(parentName);
             }
         }
 

Added: 
struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/CycleDetector.java
URL: 
http://svn.apache.org/viewvc/struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/CycleDetector.java?rev=1380129&view=auto
==============================================================================
--- 
struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/CycleDetector.java
 (added)
+++ 
struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/CycleDetector.java
 Mon Sep  3 05:45:21 2012
@@ -0,0 +1,59 @@
+package com.opensymphony.xwork2.config.providers;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class CycleDetector<T> {
+    private static final String marked = "marked";
+    private static final String complete = "complete";
+    private DirectedGraph<T> graph;
+    private Map<T, String> marks;
+    private List<T> verticesInCycles;
+
+    public CycleDetector(DirectedGraph<T> graph) {
+        this.graph = graph;
+        marks = new HashMap<T, String>();
+        verticesInCycles = new ArrayList<T>();
+    }
+
+    public boolean containsCycle() {
+        for (T v : graph) {
+            if (!marks.containsKey(v)) {
+                if (mark(v)) {
+                    // return true;
+                }
+            }
+        }
+        // return false;
+        return !verticesInCycles.isEmpty();
+    }
+
+    private boolean mark(T vertex) {
+        /*
+         * return statements commented out for fail slow behavior detect all 
nodes in cycles instead of just the first one
+         */
+        List<T> localCycles = new ArrayList<T>();
+        marks.put(vertex, marked);
+        for (T u : graph.edgesFrom(vertex)) {
+            if (marks.containsKey(u) && marks.get(u).equals(marked)) {
+                localCycles.add(vertex);
+                // return true;
+            } else if (!marks.containsKey(u)) {
+                if (mark(u)) {
+                    localCycles.add(vertex);
+                    // return true;
+                }
+            }
+        }
+        marks.put(vertex, complete);
+        // return false;
+        verticesInCycles.addAll(localCycles);
+        return !localCycles.isEmpty();
+    }
+
+    public List<T> getVerticesInCycles() {
+        return verticesInCycles;
+    }
+}

Added: 
struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/DirectedGraph.java
URL: 
http://svn.apache.org/viewvc/struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/DirectedGraph.java?rev=1380129&view=auto
==============================================================================
--- 
struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/DirectedGraph.java
 (added)
+++ 
struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/DirectedGraph.java
 Mon Sep  3 05:45:21 2012
@@ -0,0 +1,143 @@
+package com.opensymphony.xwork2.config.providers;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+public final class DirectedGraph<T> implements Iterable<T> {
+    private final Map<T, Set<T>> mGraph = new HashMap<T, Set<T>>();
+
+    /**
+     * Adds a new node to the graph. If the node already exists, this function 
is a no-op.
+     * 
+     * @param node
+     *            The node to add.
+     * @return Whether or not the node was added.
+     */
+    public boolean addNode(T node) {
+        /* If the node already exists, don't do anything. */
+        if (mGraph.containsKey(node))
+            return false;
+
+        /* Otherwise, add the node with an empty set of outgoing edges. */
+        mGraph.put(node, new HashSet<T>());
+        return true;
+    }
+
+    /**
+     * Given a start node, and a destination, adds an arc from the start node 
to the destination. If an arc already exists, this operation is a no-op.
+     * If either endpoint does not exist in the graph, throws a 
NoSuchElementException.
+     * 
+     * @param start
+     *            The start node.
+     * @param dest
+     *            The destination node.
+     * @throws NoSuchElementException
+     *             If either the start or destination nodes do not exist.
+     */
+    public void addEdge(T start, T dest) {
+        /* Confirm both endpoints exist. */
+        if (!mGraph.containsKey(start)) {
+            throw new NoSuchElementException("The start node does not exist in 
the graph.");
+        } else if (!mGraph.containsKey(dest)) {
+            throw new NoSuchElementException("The destination node does not 
exist in the graph.");
+        }
+
+        /* Add the edge. */
+        mGraph.get(start).add(dest);
+    }
+
+    /**
+     * Removes the edge from start to dest from the graph. If the edge does 
not exist, this operation is a no-op. If either endpoint does not exist,
+     * this throws a NoSuchElementException.
+     * 
+     * @param start
+     *            The start node.
+     * @param dest
+     *            The destination node.
+     * @throws NoSuchElementException
+     *             If either node is not in the graph.
+     */
+    public void removeEdge(T start, T dest) {
+        /* Confirm both endpoints exist. */
+        if (!mGraph.containsKey(start)) {
+            throw new NoSuchElementException("The start node does not exist in 
the graph.");
+        } else if (!mGraph.containsKey(dest)) {
+            throw new NoSuchElementException("The destination node does not 
exist in the graph.");
+        }
+
+        mGraph.get(start).remove(dest);
+    }
+
+    /**
+     * Given two nodes in the graph, returns whether there is an edge from the 
first node to the second node. If either node does not exist in the
+     * graph, throws a NoSuchElementException.
+     * 
+     * @param start
+     *            The start node.
+     * @param end
+     *            The destination node.
+     * @return Whether there is an edge from start to end.
+     * @throws NoSuchElementException
+     *             If either endpoint does not exist.
+     */
+    public boolean edgeExists(T start, T end) {
+        /* Confirm both endpoints exist. */
+        if (!mGraph.containsKey(start)) {
+            throw new NoSuchElementException("The start node does not exist in 
the graph.");
+        } else if (!mGraph.containsKey(end)) {
+            throw new NoSuchElementException("The end node does not exist in 
the graph.");
+        }
+
+        return mGraph.get(start).contains(end);
+    }
+
+    /**
+     * Given a node in the graph, returns an immutable view of the edges 
leaving that node as a set of endpoints.
+     * 
+     * @param node
+     *            The node whose edges should be queried.
+     * @return An immutable view of the edges leaving that node.
+     * @throws NoSuchElementException
+     *             If the node does not exist.
+     */
+    public Set<T> edgesFrom(T node) {
+        /* Check that the node exists. */
+        Set<T> arcs = mGraph.get(node);
+        if (arcs == null)
+            throw new NoSuchElementException("Source node does not exist.");
+
+        return Collections.unmodifiableSet(arcs);
+    }
+
+    /**
+     * Returns an iterator that can traverse the nodes in the graph.
+     * 
+     * @return An iterator that traverses the nodes in the graph.
+     */
+    public Iterator<T> iterator() {
+        return mGraph.keySet().iterator();
+    }
+
+    /**
+     * Returns the number of nodes in the graph.
+     * 
+     * @return The number of nodes in the graph.
+     */
+    public int size() {
+        return mGraph.size();
+    }
+
+    /**
+     * Returns whether the graph is empty.
+     * 
+     * @return Whether the graph is empty.
+     */
+    public boolean isEmpty() {
+        return mGraph.isEmpty();
+    }
+}

Modified: 
struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java
URL: 
http://svn.apache.org/viewvc/struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java?rev=1380129&r1=1380128&r2=1380129&view=diff
==============================================================================
--- 
struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java
 (original)
+++ 
struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java
 Mon Sep  3 05:45:21 2012
@@ -74,6 +74,7 @@ public class XmlConfigurationProvider im
     private Map<String, String> dtdMappings;
     private Configuration configuration;
     private boolean throwExceptionOnDuplicateBeans = true;
+    private Map<String, Element> declaredPackages = new HashMap<String, 
Element>();
 
     private FileManager fileManager;
 
@@ -270,6 +271,8 @@ public class XmlConfigurationProvider im
 
     public void loadPackages() throws ConfigurationException {
         List<Element> reloads = new ArrayList<Element>();
+        verifyPackageStructure();
+
         for (Document doc : documents) {
             Element rootElement = doc.getDocumentElement();
             NodeList children = rootElement.getChildNodes();
@@ -303,9 +306,51 @@ public class XmlConfigurationProvider im
         }
 
         documents.clear();
+        declaredPackages.clear();
         configuration = null;
     }
 
+    private void verifyPackageStructure() {
+        DirectedGraph<String> graph = new DirectedGraph<String>();
+
+        for (Document doc : documents) {
+            Element rootElement = doc.getDocumentElement();
+            NodeList children = rootElement.getChildNodes();
+            int childSize = children.getLength();
+            for (int i = 0; i < childSize; i++) {
+                Node childNode = children.item(i);
+                if (childNode instanceof Element) {
+                    Element child = (Element) childNode;
+
+                    final String nodeName = child.getNodeName();
+
+                    if ("package".equals(nodeName)) {
+                        String packageName = child.getAttribute("name");
+                        declaredPackages.put(packageName, child);
+                        graph.addNode(packageName);
+
+                        String extendsAttribute = 
child.getAttribute("extends");
+                        List<String> parents = 
ConfigurationUtil.buildParentListFromString(extendsAttribute);
+                        for (String parent : parents) {
+                            graph.addNode(parent);
+                            graph.addEdge(packageName, parent);
+                        }
+                    }
+                }
+            }
+        }
+
+        CycleDetector<String> detector = new CycleDetector<String>(graph);
+        if (detector.containsCycle()) {
+            StringBuilder builder = new StringBuilder("The following packages 
participate in cycles:");
+            for (String packageName : detector.getVerticesInCycles()) {
+                builder.append(" ");
+                builder.append(packageName);
+            }
+            throw new ConfigurationException(builder.toString());
+        }
+    }
+
     private void reloadRequiredPackages(List<Element> reloads) {
         if (reloads.size() > 0) {
             List<Element> result = new ArrayList<Element>();
@@ -602,8 +647,20 @@ public class XmlConfigurationProvider im
                 .location(DomHelper.getLocationObject(packageElement));
 
         if (StringUtils.isNotEmpty(StringUtils.defaultString(parent))) { // 
has parents, let's look it up
+            List<PackageConfig> parents = new ArrayList<PackageConfig>();
+            for (String parentPackageName : 
ConfigurationUtil.buildParentListFromString(parent)) {
+                if 
(configuration.getPackageConfigNames().contains(parentPackageName)) {
+                    
parents.add(configuration.getPackageConfig(parentPackageName));
+                } else if (declaredPackages.containsKey(parentPackageName)) {
+                    if (configuration.getPackageConfig(parentPackageName) == 
null) {
+                        addPackage(declaredPackages.get(parentPackageName));
+                    }
+                    
parents.add(configuration.getPackageConfig(parentPackageName));
+                } else {
+                    throw new ConfigurationException("Parent package is not 
defined: " + parentPackageName);
+                }
 
-            List<PackageConfig> parents = 
ConfigurationUtil.buildParentsFromString(configuration, parent);
+            }
 
             if (parents.size() <= 0) {
                 cfg.needsRefresh(true);

Modified: 
struts/struts2/trunk/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderPackagesTest.java
URL: 
http://svn.apache.org/viewvc/struts/struts2/trunk/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderPackagesTest.java?rev=1380129&r1=1380128&r2=1380129&view=diff
==============================================================================
--- 
struts/struts2/trunk/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderPackagesTest.java
 (original)
+++ 
struts/struts2/trunk/xwork-core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderPackagesTest.java
 Mon Sep  3 05:45:21 2012
@@ -34,9 +34,15 @@ public class XmlConfigurationProviderPac
 
     public void testBadInheritance() throws ConfigurationException {
         final String filename = 
"com/opensymphony/xwork2/config/providers/xwork-test-bad-inheritance.xml";
-        ConfigurationProvider provider = buildConfigurationProvider(filename);
-        provider.init(configuration);
-        provider.loadPackages();
+        ConfigurationProvider provider = null;
+        try {
+               provider = buildConfigurationProvider(filename);
+               fail("Should have thrown a ConfigurationException");
+               provider.init(configuration);
+               provider.loadPackages();
+        } catch (ConfigurationException e) {
+               // Expected
+        }
     }
 
     public void testBasicPackages() throws ConfigurationException {
@@ -82,7 +88,7 @@ public class XmlConfigurationProviderPac
         provider.loadPackages();
 
         // test expectations
-        assertEquals(4, configuration.getPackageConfigs().size());
+        assertEquals(5, configuration.getPackageConfigs().size());
         PackageConfig defaultPackage = 
configuration.getPackageConfig("default");
         assertNotNull(defaultPackage);
         assertEquals("default", defaultPackage.getName());
@@ -98,10 +104,15 @@ public class XmlConfigurationProviderPac
         assertNotNull(multiplePackage);
         assertEquals("multipleInheritance", multiplePackage.getName());
         assertEquals(3, multiplePackage.getParents().size());
-        List multipleParents = multiplePackage.getParents();
+        List<PackageConfig> multipleParents = multiplePackage.getParents();
         assertTrue(multipleParents.contains(defaultPackage));
         assertTrue(multipleParents.contains(abstractPackage));
         assertTrue(multipleParents.contains(singlePackage));
+        
+        PackageConfig parentBelow = 
configuration.getPackageConfig("testParentBelow");
+        assertEquals(1, parentBelow.getParents().size());
+        List<PackageConfig> parentBelowParents = parentBelow.getParents();
+        assertTrue(parentBelowParents.contains(multiplePackage));
 
         configurationManager.addContainerProvider(provider);
         configurationManager.reload();
@@ -115,6 +126,12 @@ public class XmlConfigurationProviderPac
         assertNull(runtimeConfiguration.getActionConfig("/single", 
"abstract"));
         assertNotNull(runtimeConfiguration.getActionConfig("/single", 
"single"));
         assertNull(runtimeConfiguration.getActionConfig("/single", 
"multiple"));
+        
+        assertNotNull(runtimeConfiguration.getActionConfig("/parentBelow", 
"default"));
+        assertNotNull(runtimeConfiguration.getActionConfig("/parentBelow", 
"abstract"));
+        assertNotNull(runtimeConfiguration.getActionConfig("/parentBelow", 
"single"));
+        assertNotNull(runtimeConfiguration.getActionConfig("/parentBelow", 
"multiple"));
+        assertNotNull(runtimeConfiguration.getActionConfig("/parentBelow", 
"testParentBelowAction"));
 
     }
 

Modified: 
struts/struts2/trunk/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-inheritance.xml
URL: 
http://svn.apache.org/viewvc/struts/struts2/trunk/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-inheritance.xml?rev=1380129&r1=1380128&r2=1380129&view=diff
==============================================================================
--- 
struts/struts2/trunk/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-inheritance.xml
 (original)
+++ 
struts/struts2/trunk/xwork-core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-inheritance.xml
 Mon Sep  3 05:45:21 2012
@@ -16,6 +16,10 @@
     <package name="singleInheritance" namespace="/single" extends="default">
         <action name="single" class="com.opensymphony.xwork2.ActionSupport"/>
     </package>
+    
+    <package name="testParentBelow" namespace="/parentBelow" 
extends="multipleInheritance">
+       <action name="testParentBelowAction" 
class="com.opensymphony.xwork2.ActionSupport"/>
+    </package>
 
     <package name="multipleInheritance" namespace="/multiple" 
extends="default,abstractPackage,singleInheritance">
         <action name="multiple" class="com.opensymphony.xwork2.ActionSupport"/>


Reply via email to