diff --git a/core/pom.xml b/core/pom.xml
index 2ff5d05a9..3373d8ad0 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -56,7 +56,7 @@
com.opensymphony
xwork-core
- 2.1.6
+ 2.1.7-SNAPSHOT
sources
@@ -229,7 +229,7 @@
com.opensymphony
xwork-core
- 2.1.6
+ 2.1.7-SNAPSHOT
diff --git a/core/src/main/java/org/apache/struts2/util/RegexPatternMatcher.java b/core/src/main/java/org/apache/struts2/util/RegexPatternMatcher.java
new file mode 100644
index 000000000..61f7a599f
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/util/RegexPatternMatcher.java
@@ -0,0 +1,113 @@
+/*
+ * $Id: ServletContextAware.java 651946 2008-04-27 13:41:38Z apetrelli $
+ *
+ * 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.struts2.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang.xwork.StringUtils;
+
+import com.opensymphony.xwork2.util.PatternMatcher;
+
+/**
+ * Allows regular expressions to be used in action names. The regular expressions
+ * can be in the form {FIELD_NAME} or {FIELD_NAME:REGULAR_EXPRESSION}. For example:
+ *
+ *
+ * <action name="/{bio:.+}/test/{name}" class="org.apache.struts2.showcase.UITagExample">
+ * <result>/tags/ui/example.jsp</result>
+ * </action>
+ *
+ *
+ * For this to work it is important to set the following:
+ *
+ * <constant name="struts.enable.SlashesInActionNames" value="true"/>
+ * <constant name="struts.mapper.alwaysSelectFullNamespace" value="false"/>
+ * <constant name="struts.patternMatcher" value="regex" />
+ *
+ */
+public class RegexPatternMatcher implements PatternMatcher {
+ private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
+
+ public RegexPatternMatcherExpression compilePattern(String data) {
+ Map params = new HashMap();
+
+ Matcher matcher = PATTERN.matcher(data);
+ int count = 0;
+ while (matcher.find()) {
+ String expression = matcher.group(1);
+ //check if it is a regex
+ int index = expression.indexOf(':');
+ if (index > 0) {
+ String paramName = expression.substring(0, index);
+ String regex = StringUtils.substring(expression, index + 1);
+ if (StringUtils.isBlank(regex)) {
+ throw new IllegalArgumentException("invalid expression [" + expression + "], named parameter regular exression "
+ + "must be in the format {PARAM_NAME:REGEX}");
+ }
+
+ params.put(++count, paramName);
+
+ } else {
+ params.put(++count, expression);
+ }
+ }
+
+ //generate a new pattern used to match URIs
+ //replace {X:B} by (B)
+ String newPattern = data.replaceAll("(\\{.*?:(.*?)\\})", "($2)");
+
+ //replace {X} by (.*?)
+ newPattern = newPattern.replaceAll("(\\{.*?\\})", "(.*?)");
+ return new RegexPatternMatcherExpression(Pattern.compile(newPattern), params);
+ }
+
+ public boolean isLiteral(String pattern) {
+ return (pattern == null || pattern.indexOf('{') == -1);
+ }
+
+ public boolean match(Map map, String data, RegexPatternMatcherExpression expr) {
+ Matcher matcher = expr.getPattern().matcher(data);
+ Map params = expr.getParams();
+
+ if (matcher.matches()) {
+ map.put("0", data);
+
+ //extract values and get param names from the expression
+ for (int i = 1; i <= matcher.groupCount(); i++) {
+ String paramName = params.get(i);
+ String value = matcher.group(i);
+
+ //by name
+ map.put(paramName, value);
+ //by index so the old {1} still works
+ map.put(String.valueOf(i), value);
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/apache/struts2/util/RegexPatternMatcherExpression.java b/core/src/main/java/org/apache/struts2/util/RegexPatternMatcherExpression.java
new file mode 100644
index 000000000..b0399ab34
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/util/RegexPatternMatcherExpression.java
@@ -0,0 +1,49 @@
+/*
+ * $Id: ServletContextAware.java 651946 2008-04-27 13:41:38Z apetrelli $
+ *
+ * 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.struts2.util;
+
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Holds a compiled expression to match URLs
+ * @see RegexPatternMatcher
+ */
+public class RegexPatternMatcherExpression {
+ //pattern that matches the whole string
+ private final Pattern pattern;
+
+ //maps group index (from pattern) to parameter names
+ private final Map params;
+
+ public RegexPatternMatcherExpression(Pattern pattern, Map params) {
+ this.pattern = pattern;
+ this.params = params;
+ }
+
+ public Pattern getPattern() {
+ return pattern;
+ }
+
+ public Map getParams() {
+ return params;
+ }
+}
diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml
index f0790452a..fc06df11c 100644
--- a/core/src/main/resources/struts-default.xml
+++ b/core/src/main/resources/struts-default.xml
@@ -38,6 +38,7 @@
+
diff --git a/core/src/test/java/org/apache/struts2/util/RegexPatternMatcherTest.java b/core/src/test/java/org/apache/struts2/util/RegexPatternMatcherTest.java
new file mode 100644
index 000000000..9186c9a7a
--- /dev/null
+++ b/core/src/test/java/org/apache/struts2/util/RegexPatternMatcherTest.java
@@ -0,0 +1,169 @@
+/*
+ * $Id: ServletContextAware.java 651946 2008-04-27 13:41:38Z apetrelli $
+ *
+ * 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.struts2.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import junit.framework.TestCase;
+
+
+public class RegexPatternMatcherTest extends TestCase {
+ private RegexPatternMatcher matcher = new RegexPatternMatcher();
+
+ public void testIsLiteral() {
+ assertTrue(matcher.isLiteral(null));
+ assertTrue(matcher.isLiteral(""));
+ assertTrue(matcher.isLiteral(" \t"));
+ assertTrue(matcher.isLiteral("something"));
+
+ assertFalse(matcher.isLiteral("{"));
+ }
+
+ public void testCompile0() {
+ RegexPatternMatcherExpression expr = matcher.compilePattern("/some/{test}");
+ assertNotNull(expr);
+
+ //params
+ Map params = expr.getParams();
+ assertNotNull(params);
+ assertEquals(1, params.size());
+ assertEquals("test", params.get(1));
+
+ //pattern
+ Pattern pattern = expr.getPattern();
+ assertNotNull(pattern);
+ assertEquals("/some/(.*?)", pattern.pattern());
+ }
+
+ public void testCompile1() {
+ RegexPatternMatcherExpression expr = matcher.compilePattern("/{test}/some/{test1}/");
+ assertNotNull(expr);
+
+ //params
+ Map params = expr.getParams();
+ assertNotNull(params);
+ assertEquals(2, params.size());
+ assertEquals("test", params.get(1));
+ assertEquals("test1", params.get(2));
+
+ //pattern
+ Pattern pattern = expr.getPattern();
+ assertNotNull(pattern);
+ assertEquals("/(.*?)/some/(.*?)/", pattern.pattern());
+ }
+
+ public void testCompileNamedParams0() {
+ RegexPatternMatcherExpression expr = matcher.compilePattern("/some/{test:.+}");
+ assertNotNull(expr);
+
+ //params
+ Map params = expr.getParams();
+ assertNotNull(params);
+ assertEquals(1, params.size());
+ assertEquals("test", params.get(1));
+
+ //pattern
+ Pattern pattern = expr.getPattern();
+ assertNotNull(pattern);
+ assertEquals("/some/(.+)", pattern.pattern());
+ }
+
+ public void testCompileNamedParams1() {
+ RegexPatternMatcherExpression expr = matcher.compilePattern("/some/{test1:.+}/{test2:.*}");
+ assertNotNull(expr);
+
+ //params
+ Map params = expr.getParams();
+ assertNotNull(params);
+ assertEquals(2, params.size());
+ assertEquals("test1", params.get(1));
+ assertEquals("test2", params.get(2));
+
+ //pattern
+ Pattern pattern = expr.getPattern();
+ assertNotNull(pattern);
+ assertEquals("/some/(.+)/(.*)", pattern.pattern());
+ }
+
+ public void testMatch0() {
+ RegexPatternMatcherExpression expr = matcher.compilePattern("/some/{test}");
+
+ Map values = new HashMap();
+
+ assertTrue(matcher.match(values, "/some/val", expr));
+ assertEquals(3, values.size());
+ assertEquals("val", values.get("test"));
+ assertEquals("val", values.get("1"));
+
+ assertEquals("/some/val", values.get("0"));
+ }
+
+ public void testMatch1() {
+ RegexPatternMatcherExpression expr = matcher.compilePattern("/some/{test0}/some/{test1}");
+
+ Map values = new HashMap();
+
+ assertTrue(matcher.match(values, "/some/val0/some/val1", expr));
+ assertEquals(5, values.size());
+ assertEquals("val0", values.get("test0"));
+ assertEquals("val1", values.get("test1"));
+ assertEquals("val0", values.get("1"));
+ assertEquals("val1", values.get("2"));
+
+ assertEquals("/some/val0/some/val1", values.get("0"));
+ }
+
+ public void testMatch2() {
+ RegexPatternMatcherExpression expr = matcher.compilePattern("/some/{test0}/some/{test1}/.*");
+
+ Map values = new HashMap();
+
+ assertTrue(matcher.match(values, "/some/val0/some/val1/buaaa", expr));
+ assertEquals(5, values.size());
+ assertEquals("val0", values.get("test0"));
+ assertEquals("val1", values.get("test1"));
+ assertEquals("val0", values.get("1"));
+ assertEquals("val1", values.get("2"));
+
+ assertEquals("/some/val0/some/val1/buaaa", values.get("0"));
+ }
+
+ public void testCompileBad0() {
+ try {
+ matcher.compilePattern("/{test/some");
+ fail("Should have failed");
+ } catch (Exception e) {
+ //ok
+ }
+ }
+
+ public void testCompileBad1() {
+ try {
+ matcher.compilePattern("/test/{p:}");
+ fail("Should have failed");
+ } catch (Exception e) {
+ //ok
+ }
+ }
+
+}