You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2012/09/03 07:45:21 UTC
svn commit: r1380129 - in /struts/struts2/trunk/xwork-core/src:
main/java/com/opensymphony/xwork2/config/
main/java/com/opensymphony/xwork2/config/providers/
test/java/com/opensymphony/xwork2/config/providers/
test/resources/com/opensymphony/xwork2/con...
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"/>