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"/>