Author: musachy Date: Fri Sep 25 19:08:38 2009 New Revision: 818958 URL: http://svn.apache.org/viewvc?rev=818958&view=rev Log: WW-3262 improve wildcard to support regular expressions
bump xwork version Added: struts/struts2/trunk/core/src/main/java/org/apache/struts2/util/RegexPatternMatcher.java struts/struts2/trunk/core/src/main/java/org/apache/struts2/util/RegexPatternMatcherExpression.java struts/struts2/trunk/core/src/test/java/org/apache/struts2/util/RegexPatternMatcherTest.java Modified: struts/struts2/trunk/core/pom.xml struts/struts2/trunk/core/src/main/resources/struts-default.xml Modified: struts/struts2/trunk/core/pom.xml URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/pom.xml?rev=818958&r1=818957&r2=818958&view=diff ============================================================================== --- struts/struts2/trunk/core/pom.xml (original) +++ struts/struts2/trunk/core/pom.xml Fri Sep 25 19:08:38 2009 @@ -56,7 +56,7 @@ <artifactItem> <groupId>com.opensymphony</groupId> <artifactId>xwork-core</artifactId> - <version>2.1.6</version> + <version>2.1.7-SNAPSHOT</version> <classifier>sources</classifier> </artifactItem> </artifactItems> @@ -229,7 +229,7 @@ <dependency> <groupId>com.opensymphony</groupId> <artifactId>xwork-core</artifactId> - <version>2.1.6</version> + <version>2.1.7-SNAPSHOT</version> </dependency> <!--<dependency>--> Added: struts/struts2/trunk/core/src/main/java/org/apache/struts2/util/RegexPatternMatcher.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/java/org/apache/struts2/util/RegexPatternMatcher.java?rev=818958&view=auto ============================================================================== --- struts/struts2/trunk/core/src/main/java/org/apache/struts2/util/RegexPatternMatcher.java (added) +++ struts/struts2/trunk/core/src/main/java/org/apache/struts2/util/RegexPatternMatcher.java Fri Sep 25 19:08:38 2009 @@ -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: + * <br/> + * <pre> + * <action name="/{bio:.+}/test/{name}" class="org.apache.struts2.showcase.UITagExample"> + * <result>/tags/ui/example.jsp</result> + * </action> + * </pre> + * + * For this to work it is important to set the following: + * <pre> + * <constant name="struts.enable.SlashesInActionNames" value="true"/> + * <constant name="struts.mapper.alwaysSelectFullNamespace" value="false"/> + * <constant name="struts.patternMatcher" value="regex" /> + * </pre> + */ +public class RegexPatternMatcher implements PatternMatcher<RegexPatternMatcherExpression> { + private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}"); + + public RegexPatternMatcherExpression compilePattern(String data) { + Map<Integer, String> params = new HashMap<Integer, String>(); + + 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<String, String> map, String data, RegexPatternMatcherExpression expr) { + Matcher matcher = expr.getPattern().matcher(data); + Map<Integer, String> 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; + } + } + +} Added: struts/struts2/trunk/core/src/main/java/org/apache/struts2/util/RegexPatternMatcherExpression.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/java/org/apache/struts2/util/RegexPatternMatcherExpression.java?rev=818958&view=auto ============================================================================== --- struts/struts2/trunk/core/src/main/java/org/apache/struts2/util/RegexPatternMatcherExpression.java (added) +++ struts/struts2/trunk/core/src/main/java/org/apache/struts2/util/RegexPatternMatcherExpression.java Fri Sep 25 19:08:38 2009 @@ -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<Integer, String> params; + + public RegexPatternMatcherExpression(Pattern pattern, Map<Integer, String> params) { + this.pattern = pattern; + this.params = params; + } + + public Pattern getPattern() { + return pattern; + } + + public Map<Integer, String> getParams() { + return params; + } +} Modified: struts/struts2/trunk/core/src/main/resources/struts-default.xml URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/resources/struts-default.xml?rev=818958&r1=818957&r2=818958&view=diff ============================================================================== --- struts/struts2/trunk/core/src/main/resources/struts-default.xml (original) +++ struts/struts2/trunk/core/src/main/resources/struts-default.xml Fri Sep 25 19:08:38 2009 @@ -38,6 +38,7 @@ <bean type="com.opensymphony.xwork2.util.PatternMatcher" name="struts" class="com.opensymphony.xwork2.util.WildcardHelper" /> <bean type="com.opensymphony.xwork2.util.PatternMatcher" name="namedVariable" class="com.opensymphony.xwork2.util.NamedVariablePatternMatcher"/> + <bean type="com.opensymphony.xwork2.util.PatternMatcher" name="regex" class="org.apache.struts2.util.RegexPatternMatcher"/> <bean type="org.apache.struts2.dispatcher.mapper.ActionMapper" name="struts" class="org.apache.struts2.dispatcher.mapper.DefaultActionMapper" /> <bean type="org.apache.struts2.dispatcher.mapper.ActionMapper" name="composite" class="org.apache.struts2.dispatcher.mapper.CompositeActionMapper" /> Added: struts/struts2/trunk/core/src/test/java/org/apache/struts2/util/RegexPatternMatcherTest.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/test/java/org/apache/struts2/util/RegexPatternMatcherTest.java?rev=818958&view=auto ============================================================================== --- struts/struts2/trunk/core/src/test/java/org/apache/struts2/util/RegexPatternMatcherTest.java (added) +++ struts/struts2/trunk/core/src/test/java/org/apache/struts2/util/RegexPatternMatcherTest.java Fri Sep 25 19:08:38 2009 @@ -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<Integer, String> 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<Integer, String> 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<Integer, String> 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<Integer, String> 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<String, String> values = new HashMap<String, String>(); + + 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<String, String> values = new HashMap<String, String>(); + + 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<String, String> values = new HashMap<String, String>(); + + 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 + } + } + +}