Browse Source

Support configuration via Apache Ant paths (not only regular expressions).

Ben Alex 21 năm trước cách đây
mục cha
commit
bd35a47233

+ 3 - 0
changelog.txt

@@ -1,9 +1,12 @@
 Changes in version 0.5 (2004-xx-xx)
 -----------------------------------
+
 * AuthenticationProcessingFilter by default finds configuration context using Spring's WebApplicationContextUtils.getWebApplicationContext()
 * AuthenticationProcessingFilter context may optionally be specified with 'contextConfigLocation' param (was previously 'appContextLocation')
 * SecurityEnforcementFilter by default finds configuration context using Spring's WebApplicationContextUtils.getWebApplicationContext()
 * SecurityEnforcementFilter context may optionally be specified with 'contextConfigLocation' param (was previously 'appContextLocation')
+* SecurityEnforcementFilter now supports URL definitions using the Apache Ant path syntax in addition to regular expressions
+* Documentation improvements
 
 Changes in version 0.4 (2004-04-03)
 -----------------------------------

+ 8 - 153
core/src/main/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionMap.java

@@ -17,164 +17,19 @@ package net.sf.acegisecurity.intercept.web;
 
 import net.sf.acegisecurity.ConfigAttributeDefinition;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import org.apache.oro.text.regex.MalformedPatternException;
-import org.apache.oro.text.regex.Pattern;
-import org.apache.oro.text.regex.PatternMatcher;
-import org.apache.oro.text.regex.Perl5Compiler;
-import org.apache.oro.text.regex.Perl5Matcher;
-
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-import java.util.Vector;
-
 
 /**
- * Maintains a <Code>List</code> of <code>ConfigAttributeDefinition</code>s
- * associated with different HTTP request URL patterns.
- * 
- * <P>
- * Regular expressions are used to match a HTTP request URL against a
- * <code>ConfigAttributeDefinition</code>.
- * </p>
- * 
- * <p>
- * The order of registering the regular expressions using the {@link
- * #addSecureUrl(String, ConfigAttributeDefinition)} is very important. The
- * system will identify the <B>first</B>  matching regular expression for a
- * given HTTP URL. It will not proceed to evaluate later regular expressions
- * if a match has already been found. Accordingly, the most specific regular
- * expressions should be registered first, with the most general regular
- * expressions registered last.
- * </p>
- * 
- * <P>
- * If no registered regular expressions match the HTTP URL, <code>null</code>
- * is returned.
- * </p>
+ * Exposes methods required so that a property editor can populate the relevant
+ * {@link FilterInvocationDefinitionSource}.
+ *
+ * @author Ben Alex
+ * @version $Id$
  */
-public class FilterInvocationDefinitionMap
-    extends AbstractFilterInvocationDefinitionSource {
-    //~ Static fields/initializers =============================================
-
-    private static final Log logger = LogFactory.getLog(FilterInvocationDefinitionMap.class);
-
-    //~ Instance fields ========================================================
-
-    private List requestMap = new Vector();
-    private boolean convertUrlToLowercaseBeforeComparison = false;
-
+public interface FilterInvocationDefinitionMap {
     //~ Methods ================================================================
 
-    public Iterator getConfigAttributeDefinitions() {
-        Set set = new HashSet();
-        Iterator iter = requestMap.iterator();
-
-        while (iter.hasNext()) {
-            EntryHolder entryHolder = (EntryHolder) iter.next();
-            set.add(entryHolder.getConfigAttributeDefinition());
-        }
-
-        return set.iterator();
-    }
-
     public void setConvertUrlToLowercaseBeforeComparison(
-        boolean convertUrlToLowercaseBeforeComparison) {
-        this.convertUrlToLowercaseBeforeComparison = convertUrlToLowercaseBeforeComparison;
-    }
-
-    public boolean isConvertUrlToLowercaseBeforeComparison() {
-        return convertUrlToLowercaseBeforeComparison;
-    }
-
-    public int getMapSize() {
-        return this.requestMap.size();
-    }
-
-    public void addSecureUrl(String perl5RegExp, ConfigAttributeDefinition attr) {
-        Pattern compiledPattern;
-        Perl5Compiler compiler = new Perl5Compiler();
-
-        try {
-            compiledPattern = compiler.compile(perl5RegExp,
-                    Perl5Compiler.READ_ONLY_MASK);
-        } catch (MalformedPatternException mpe) {
-            throw new IllegalArgumentException("Malformed regular expression: "
-                + perl5RegExp);
-        }
-
-        requestMap.add(new EntryHolder(compiledPattern, attr));
-
-        if (logger.isDebugEnabled()) {
-            logger.debug("Added regular expression: "
-                + compiledPattern.getPattern().toString() + "; attributes: "
-                + attr.toString());
-        }
-    }
-
-    protected ConfigAttributeDefinition lookupAttributes(
-        FilterInvocation filterInvocation) {
-        PatternMatcher matcher = new Perl5Matcher();
-
-        Iterator iter = requestMap.iterator();
-
-        String url = filterInvocation.getRequestUrl();
-
-        if (convertUrlToLowercaseBeforeComparison) {
-            url = url.toLowerCase();
-
-            if (logger.isDebugEnabled()) {
-                logger.debug("Converted URL to lowercase, from: '"
-                    + filterInvocation.getRequest() + "'; to: '" + url + "'");
-            }
-        }
-
-        while (iter.hasNext()) {
-            EntryHolder entryHolder = (EntryHolder) iter.next();
-
-            boolean matched = matcher.matches(url,
-                    entryHolder.getCompiledPattern());
-
-            if (logger.isDebugEnabled()) {
-                logger.debug("Candidate is: '" + url + "'; pattern is "
-                    + entryHolder.getCompiledPattern().getPattern()
-                    + "; matched=" + matched);
-            }
-
-            if (matched) {
-                return entryHolder.getConfigAttributeDefinition();
-            }
-        }
-
-        return null;
-    }
-
-    //~ Inner Classes ==========================================================
-
-    protected class EntryHolder {
-        private ConfigAttributeDefinition configAttributeDefinition;
-        private Pattern compiledPattern;
-
-        public EntryHolder(Pattern compiledPattern,
-            ConfigAttributeDefinition attr) {
-            this.compiledPattern = compiledPattern;
-            this.configAttributeDefinition = attr;
-        }
-
-        protected EntryHolder() {
-            throw new IllegalArgumentException("Cannot use default constructor");
-        }
-
-        public Pattern getCompiledPattern() {
-            return compiledPattern;
-        }
+        boolean convertUrlToLowercaseBeforeComparison);
 
-        public ConfigAttributeDefinition getConfigAttributeDefinition() {
-            return configAttributeDefinition;
-        }
-    }
+    public void addSecureUrl(String expression, ConfigAttributeDefinition attr);
 }

+ 23 - 4
core/src/main/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionSourceEditor.java

@@ -31,11 +31,21 @@ import java.io.StringReader;
 
 
 /**
- * Property editor to assist with the setup of  {@link
+ * Property editor to assist with the setup of a {@link
  * FilterInvocationDefinitionSource}.
  * 
  * <p>
- * The class creates and populates a {@link FilterInvocationDefinitionMap}.
+ * The class creates and populates a {@link
+ * RegExpBasedFilterInvocationDefinitionMap} or {@link
+ * PathBasedFilterInvocationDefinitionMap} (depending on the type of patterns
+ * presented).
+ * </p>
+ * 
+ * <P>
+ * By default the class treats presented patterns as regular expressions. If
+ * the keyword <code>PATTERN_TYPE_APACHE_ANT</code> is present (case
+ * sensitive), patterns will be treated as Apache Ant paths rather than
+ * regular expressions.
  * </p>
  *
  * @author Ben Alex
@@ -50,11 +60,20 @@ public class FilterInvocationDefinitionSourceEditor
     //~ Methods ================================================================
 
     public void setAsText(String s) throws IllegalArgumentException {
-        FilterInvocationDefinitionMap source = new FilterInvocationDefinitionMap();
+        FilterInvocationDefinitionMap source = new RegExpBasedFilterInvocationDefinitionMap();
 
         if ((s == null) || "".equals(s)) {
-            // Leave value in property editor null
+            // Leave target object empty
         } else {
+            // Check if we need to override the default definition map
+            if (s.lastIndexOf("PATTERN_TYPE_APACHE_ANT") != -1) {
+                source = new PathBasedFilterInvocationDefinitionMap();
+
+                if (logger.isDebugEnabled()) {
+                    logger.debug(("Detected PATTERN_TYPE_APACHE_ANT directive; using Apache Ant style path expressions"));
+                }
+            }
+
             BufferedReader br = new BufferedReader(new StringReader(s));
             int counter = 0;
             String line;

+ 158 - 0
core/src/main/java/org/acegisecurity/intercept/web/PathBasedFilterInvocationDefinitionMap.java

@@ -0,0 +1,158 @@
+/* Copyright 2004 Acegi Technology Pty Limited
+ *
+ * Licensed 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 net.sf.acegisecurity.intercept.web;
+
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.PathMatcher;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+
+
+/**
+ * Maintains a <Code>List</code> of <code>ConfigAttributeDefinition</code>s
+ * associated with different HTTP request URL Apache Ant path-based patterns.
+ * 
+ * <P>
+ * Apache Ant path expressions are used to match a HTTP request URL against a
+ * <code>ConfigAttributeDefinition</code>.
+ * </p>
+ * 
+ * <p>
+ * The order of registering the Ant paths using the {@link
+ * #addSecureUrl(String, ConfigAttributeDefinition)} is very important. The
+ * system will identify the <B>first</B>  matching path for a given HTTP URL.
+ * It will not proceed to evaluate later paths if a match has already been
+ * found. Accordingly, the most specific paths should be registered first,
+ * with the most general paths registered last.
+ * </p>
+ * 
+ * <P>
+ * If no registered paths match the HTTP URL, <code>null</code> is returned.
+ * </p>
+ */
+public class PathBasedFilterInvocationDefinitionMap
+    extends AbstractFilterInvocationDefinitionSource
+    implements FilterInvocationDefinitionMap {
+    //~ Static fields/initializers =============================================
+
+    private static final Log logger = LogFactory.getLog(PathBasedFilterInvocationDefinitionMap.class);
+
+    //~ Instance fields ========================================================
+
+    private List requestMap = new Vector();
+    private boolean convertUrlToLowercaseBeforeComparison = false;
+
+    //~ Methods ================================================================
+
+    public Iterator getConfigAttributeDefinitions() {
+        Set set = new HashSet();
+        Iterator iter = requestMap.iterator();
+
+        while (iter.hasNext()) {
+            EntryHolder entryHolder = (EntryHolder) iter.next();
+            set.add(entryHolder.getConfigAttributeDefinition());
+        }
+
+        return set.iterator();
+    }
+
+    public void setConvertUrlToLowercaseBeforeComparison(
+        boolean convertUrlToLowercaseBeforeComparison) {
+        this.convertUrlToLowercaseBeforeComparison = convertUrlToLowercaseBeforeComparison;
+    }
+
+    public boolean isConvertUrlToLowercaseBeforeComparison() {
+        return convertUrlToLowercaseBeforeComparison;
+    }
+
+    public int getMapSize() {
+        return this.requestMap.size();
+    }
+
+    public void addSecureUrl(String antPath, ConfigAttributeDefinition attr) {
+        requestMap.add(new EntryHolder(antPath, attr));
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Added Ant path: " + antPath + "; attributes: "
+                + attr.toString());
+        }
+    }
+
+    protected ConfigAttributeDefinition lookupAttributes(
+        FilterInvocation filterInvocation) {
+        Iterator iter = requestMap.iterator();
+
+        String url = filterInvocation.getRequestUrl();
+
+        if (convertUrlToLowercaseBeforeComparison) {
+            url = url.toLowerCase();
+
+            if (logger.isDebugEnabled()) {
+                logger.debug("Converted URL to lowercase, from: '"
+                    + filterInvocation.getRequest() + "'; to: '" + url + "'");
+            }
+        }
+
+        while (iter.hasNext()) {
+            EntryHolder entryHolder = (EntryHolder) iter.next();
+
+            boolean matched = PathMatcher.match(entryHolder.getAntPath(), url);
+
+            if (logger.isDebugEnabled()) {
+                logger.debug("Candidate is: '" + url + "'; pattern is "
+                    + entryHolder.getAntPath() + "; matched=" + matched);
+            }
+
+            if (matched) {
+                return entryHolder.getConfigAttributeDefinition();
+            }
+        }
+
+        return null;
+    }
+
+    //~ Inner Classes ==========================================================
+
+    protected class EntryHolder {
+        private ConfigAttributeDefinition configAttributeDefinition;
+        private String antPath;
+
+        public EntryHolder(String antPath, ConfigAttributeDefinition attr) {
+            this.antPath = antPath;
+            this.configAttributeDefinition = attr;
+        }
+
+        protected EntryHolder() {
+            throw new IllegalArgumentException("Cannot use default constructor");
+        }
+
+        public String getAntPath() {
+            return antPath;
+        }
+
+        public ConfigAttributeDefinition getConfigAttributeDefinition() {
+            return configAttributeDefinition;
+        }
+    }
+}

+ 181 - 0
core/src/main/java/org/acegisecurity/intercept/web/RegExpBasedFilterInvocationDefinitionMap.java

@@ -0,0 +1,181 @@
+/* Copyright 2004 Acegi Technology Pty Limited
+ *
+ * Licensed 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 net.sf.acegisecurity.intercept.web;
+
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.oro.text.regex.MalformedPatternException;
+import org.apache.oro.text.regex.Pattern;
+import org.apache.oro.text.regex.PatternMatcher;
+import org.apache.oro.text.regex.Perl5Compiler;
+import org.apache.oro.text.regex.Perl5Matcher;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+
+
+/**
+ * Maintains a <Code>List</code> of <code>ConfigAttributeDefinition</code>s
+ * associated with different HTTP request URL regular expression patterns.
+ * 
+ * <P>
+ * Regular expressions are used to match a HTTP request URL against a
+ * <code>ConfigAttributeDefinition</code>.
+ * </p>
+ * 
+ * <p>
+ * The order of registering the regular expressions using the {@link
+ * #addSecureUrl(String, ConfigAttributeDefinition)} is very important. The
+ * system will identify the <B>first</B>  matching regular expression for a
+ * given HTTP URL. It will not proceed to evaluate later regular expressions
+ * if a match has already been found. Accordingly, the most specific regular
+ * expressions should be registered first, with the most general regular
+ * expressions registered last.
+ * </p>
+ * 
+ * <P>
+ * If no registered regular expressions match the HTTP URL, <code>null</code>
+ * is returned.
+ * </p>
+ */
+public class RegExpBasedFilterInvocationDefinitionMap
+    extends AbstractFilterInvocationDefinitionSource
+    implements FilterInvocationDefinitionMap {
+    //~ Static fields/initializers =============================================
+
+    private static final Log logger = LogFactory.getLog(RegExpBasedFilterInvocationDefinitionMap.class);
+
+    //~ Instance fields ========================================================
+
+    private List requestMap = new Vector();
+    private boolean convertUrlToLowercaseBeforeComparison = false;
+
+    //~ Methods ================================================================
+
+    public Iterator getConfigAttributeDefinitions() {
+        Set set = new HashSet();
+        Iterator iter = requestMap.iterator();
+
+        while (iter.hasNext()) {
+            EntryHolder entryHolder = (EntryHolder) iter.next();
+            set.add(entryHolder.getConfigAttributeDefinition());
+        }
+
+        return set.iterator();
+    }
+
+    public void setConvertUrlToLowercaseBeforeComparison(
+        boolean convertUrlToLowercaseBeforeComparison) {
+        this.convertUrlToLowercaseBeforeComparison = convertUrlToLowercaseBeforeComparison;
+    }
+
+    public boolean isConvertUrlToLowercaseBeforeComparison() {
+        return convertUrlToLowercaseBeforeComparison;
+    }
+
+    public int getMapSize() {
+        return this.requestMap.size();
+    }
+
+    public void addSecureUrl(String perl5RegExp, ConfigAttributeDefinition attr) {
+        Pattern compiledPattern;
+        Perl5Compiler compiler = new Perl5Compiler();
+
+        try {
+            compiledPattern = compiler.compile(perl5RegExp,
+                    Perl5Compiler.READ_ONLY_MASK);
+        } catch (MalformedPatternException mpe) {
+            throw new IllegalArgumentException("Malformed regular expression: "
+                + perl5RegExp);
+        }
+
+        requestMap.add(new EntryHolder(compiledPattern, attr));
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Added regular expression: "
+                + compiledPattern.getPattern().toString() + "; attributes: "
+                + attr.toString());
+        }
+    }
+
+    protected ConfigAttributeDefinition lookupAttributes(
+        FilterInvocation filterInvocation) {
+        PatternMatcher matcher = new Perl5Matcher();
+
+        Iterator iter = requestMap.iterator();
+
+        String url = filterInvocation.getRequestUrl();
+
+        if (convertUrlToLowercaseBeforeComparison) {
+            url = url.toLowerCase();
+
+            if (logger.isDebugEnabled()) {
+                logger.debug("Converted URL to lowercase, from: '"
+                    + filterInvocation.getRequest() + "'; to: '" + url + "'");
+            }
+        }
+
+        while (iter.hasNext()) {
+            EntryHolder entryHolder = (EntryHolder) iter.next();
+
+            boolean matched = matcher.matches(url,
+                    entryHolder.getCompiledPattern());
+
+            if (logger.isDebugEnabled()) {
+                logger.debug("Candidate is: '" + url + "'; pattern is "
+                    + entryHolder.getCompiledPattern().getPattern()
+                    + "; matched=" + matched);
+            }
+
+            if (matched) {
+                return entryHolder.getConfigAttributeDefinition();
+            }
+        }
+
+        return null;
+    }
+
+    //~ Inner Classes ==========================================================
+
+    protected class EntryHolder {
+        private ConfigAttributeDefinition configAttributeDefinition;
+        private Pattern compiledPattern;
+
+        public EntryHolder(Pattern compiledPattern,
+            ConfigAttributeDefinition attr) {
+            this.compiledPattern = compiledPattern;
+            this.configAttributeDefinition = attr;
+        }
+
+        protected EntryHolder() {
+            throw new IllegalArgumentException("Cannot use default constructor");
+        }
+
+        public Pattern getCompiledPattern() {
+            return compiledPattern;
+        }
+
+        public ConfigAttributeDefinition getConfigAttributeDefinition() {
+            return configAttributeDefinition;
+        }
+    }
+}

+ 23 - 13
core/src/test/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionSourceEditorTests.java

@@ -28,7 +28,7 @@ import java.util.Iterator;
 
 /**
  * Tests {@link FilterInvocationDefinitionSourceEditor} and its associated
- * {@link FilterInvocationDefinitionMap}.
+ * default {@link RegExpBasedFilterInvocationDefinitionMap}.
  *
  * @author Ben Alex
  * @version $Id$
@@ -59,7 +59,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
         editor.setAsText(
             "\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE\r\n\\A/secure/.*\\Z=ROLE_SUPERVISOR,ROLE_TELLER");
 
-        FilterInvocationDefinitionMap map = (FilterInvocationDefinitionMap) editor
+        RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor
             .getValue();
         assertFalse(map.isConvertUrlToLowercaseBeforeComparison());
     }
@@ -69,16 +69,26 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
         editor.setAsText(
             "CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON\r\n\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE\r\n\\A/secure/.*\\Z=ROLE_SUPERVISOR,ROLE_TELLER");
 
-        FilterInvocationDefinitionMap map = (FilterInvocationDefinitionMap) editor
+        RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor
             .getValue();
         assertTrue(map.isConvertUrlToLowercaseBeforeComparison());
     }
 
+    public void testDefaultIsRegularExpression() {
+        FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
+        editor.setAsText(
+            "\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE\r\n\\A/secure/.*\\Z=ROLE_SUPERVISOR,ROLE_TELLER");
+
+        FilterInvocationDefinitionMap map = (FilterInvocationDefinitionMap) editor
+            .getValue();
+        assertTrue(map instanceof RegExpBasedFilterInvocationDefinitionMap);
+    }
+
     public void testEmptyStringReturnsEmptyMap() {
         FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
         editor.setAsText("");
 
-        FilterInvocationDefinitionMap map = (FilterInvocationDefinitionMap) editor
+        RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor
             .getValue();
         assertEquals(0, map.getMapSize());
     }
@@ -100,7 +110,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
         editor.setAsText(
             "\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE\r\n\\A/secure/.*\\Z=ROLE_SUPERVISOR,ROLE_TELLER");
 
-        FilterInvocationDefinitionMap map = (FilterInvocationDefinitionMap) editor
+        RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor
             .getValue();
         Iterator iter = map.getConfigAttributeDefinitions();
         int counter = 0;
@@ -117,7 +127,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
         FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
         editor.setAsText("\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE,ANOTHER_ROLE");
 
-        FilterInvocationDefinitionMap map = (FilterInvocationDefinitionMap) editor
+        RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor
             .getValue();
 
         MockHttpServletRequest httpRequest = new MockHttpServletRequest(null,
@@ -136,14 +146,14 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
         editor.setAsText(
             "\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE\r\n\\A/secure/.*\\Z=ROLE_SUPERVISOR,ROLE_TELLER");
 
-        FilterInvocationDefinitionMap map = (FilterInvocationDefinitionMap) editor
+        RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor
             .getValue();
         assertEquals(2, map.getMapSize());
     }
 
     public void testNoArgsConstructor() {
         try {
-            new FilterInvocationDefinitionMap().new EntryHolder();
+            new RegExpBasedFilterInvocationDefinitionMap().new EntryHolder();
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
             assertTrue(true);
@@ -154,7 +164,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
         FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
         editor.setAsText(null);
 
-        FilterInvocationDefinitionMap map = (FilterInvocationDefinitionMap) editor
+        RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor
             .getValue();
         assertEquals(0, map.getMapSize());
     }
@@ -164,7 +174,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
         editor.setAsText(
             "\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE,ANOTHER_ROLE\r\n\\A/secure/.*\\Z=ROLE_SUPERVISOR,ROLE_TELLER");
 
-        FilterInvocationDefinitionMap map = (FilterInvocationDefinitionMap) editor
+        RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor
             .getValue();
 
         // Test ensures we match the first entry, not the second
@@ -188,7 +198,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
         editor.setAsText(
             "\\A/secure/.*\\Z=ROLE_SUPERVISOR,ROLE_TELLER\r\n\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE,ANOTHER_ROLE");
 
-        FilterInvocationDefinitionMap map = (FilterInvocationDefinitionMap) editor
+        RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor
             .getValue();
 
         MockHttpServletRequest httpRequest = new MockHttpServletRequest(null,
@@ -210,7 +220,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
         FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
         editor.setAsText("\\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE,ANOTHER_ROLE");
 
-        FilterInvocationDefinitionMap map = (FilterInvocationDefinitionMap) editor
+        RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor
             .getValue();
 
         MockHttpServletRequest httpRequest = new MockHttpServletRequest(null,
@@ -233,7 +243,7 @@ public class FilterInvocationDefinitionSourceEditorTests extends TestCase {
         editor.setAsText(
             "         \\A/secure/super.*\\Z=ROLE_WE_DONT_HAVE,ANOTHER_ROLE      \r\n   \r\n     \r\n   // comment line  \r\n   \\A/testing.*\\Z=ROLE_TEST   \r\n");
 
-        FilterInvocationDefinitionMap map = (FilterInvocationDefinitionMap) editor
+        RegExpBasedFilterInvocationDefinitionMap map = (RegExpBasedFilterInvocationDefinitionMap) editor
             .getValue();
         assertEquals(2, map.getMapSize());
     }

+ 223 - 0
core/src/test/java/org/acegisecurity/intercept/web/FilterInvocationDefinitionSourceEditorWithPathsTests.java

@@ -0,0 +1,223 @@
+/* Copyright 2004 Acegi Technology Pty Limited
+ *
+ * Licensed 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 net.sf.acegisecurity.intercept.web;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.MockFilterChain;
+import net.sf.acegisecurity.MockHttpServletRequest;
+import net.sf.acegisecurity.MockHttpServletResponse;
+import net.sf.acegisecurity.SecurityConfig;
+
+import java.util.Iterator;
+
+
+/**
+ * Tests {@link FilterInvocationDefinitionSourceEditor} and its associated
+ * {@link PathBasedFilterInvocationDefinitionMap}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class FilterInvocationDefinitionSourceEditorWithPathsTests
+    extends TestCase {
+    //~ Constructors ===========================================================
+
+    public FilterInvocationDefinitionSourceEditorWithPathsTests() {
+        super();
+    }
+
+    public FilterInvocationDefinitionSourceEditorWithPathsTests(String arg0) {
+        super(arg0);
+    }
+
+    //~ Methods ================================================================
+
+    public final void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(FilterInvocationDefinitionSourceEditorWithPathsTests.class);
+    }
+
+    public void testAntPathDirectiveIsDetected() {
+        FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
+        editor.setAsText(
+            "PATTERN_TYPE_APACHE_ANT\r\n/secure/super/*=ROLE_WE_DONT_HAVE\r\n/secure/*=ROLE_SUPERVISOR,ROLE_TELLER");
+
+        FilterInvocationDefinitionMap map = (FilterInvocationDefinitionMap) editor
+            .getValue();
+        assertTrue(map instanceof PathBasedFilterInvocationDefinitionMap);
+    }
+
+    public void testConvertUrlToLowercaseDefaultSettingUnchangedByEditor() {
+        FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
+        editor.setAsText(
+            "PATTERN_TYPE_APACHE_ANT\r\n/secure/super/*=ROLE_WE_DONT_HAVE\r\n/secure/*=ROLE_SUPERVISOR,ROLE_TELLER");
+
+        PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor
+            .getValue();
+        assertFalse(map.isConvertUrlToLowercaseBeforeComparison());
+    }
+
+    public void testConvertUrlToLowercaseSettingApplied() {
+        FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
+        editor.setAsText(
+            "CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON\r\nPATTERN_TYPE_APACHE_ANT\r\n/secure/super/*=ROLE_WE_DONT_HAVE\r\n/secure/*=ROLE_SUPERVISOR,ROLE_TELLER");
+
+        PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor
+            .getValue();
+        assertTrue(map.isConvertUrlToLowercaseBeforeComparison());
+    }
+
+    public void testIterator() {
+        FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
+        editor.setAsText(
+            "PATTERN_TYPE_APACHE_ANT\r\n/secure/super/*=ROLE_WE_DONT_HAVE\r\n/secure/*=ROLE_SUPERVISOR,ROLE_TELLER");
+
+        PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor
+            .getValue();
+        Iterator iter = map.getConfigAttributeDefinitions();
+        int counter = 0;
+
+        while (iter.hasNext()) {
+            iter.next();
+            counter++;
+        }
+
+        assertEquals(2, counter);
+    }
+
+    public void testMapReturnsNullWhenNoMatchFound() throws Exception {
+        FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
+        editor.setAsText(
+            "PATTERN_TYPE_APACHE_ANT\r\n/secure/super/*=ROLE_WE_DONT_HAVE");
+
+        PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor
+            .getValue();
+
+        MockHttpServletRequest httpRequest = new MockHttpServletRequest(null,
+                null);
+        httpRequest.setServletPath("/totally/different/path/index.html");
+
+        ConfigAttributeDefinition returned = map.getAttributes(new FilterInvocation(
+                    httpRequest, new MockHttpServletResponse(),
+                    new MockFilterChain()));
+
+        assertEquals(null, returned);
+    }
+
+    public void testMultiUrlParsing() {
+        FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
+        editor.setAsText(
+            "PATTERN_TYPE_APACHE_ANT\r\n/secure/super/*=ROLE_WE_DONT_HAVE\r\n/secure/*=ROLE_SUPERVISOR,ROLE_TELLER");
+
+        PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor
+            .getValue();
+        assertEquals(2, map.getMapSize());
+    }
+
+    public void testNoArgsConstructor() {
+        try {
+            new PathBasedFilterInvocationDefinitionMap().new EntryHolder();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testOrderOfEntriesIsPreservedOrderA() {
+        FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
+        editor.setAsText(
+            "PATTERN_TYPE_APACHE_ANT\r\n/secure/super/**=ROLE_WE_DONT_HAVE,ANOTHER_ROLE\r\n/secure/**=ROLE_SUPERVISOR,ROLE_TELLER");
+
+        PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor
+            .getValue();
+
+        // Test ensures we match the first entry, not the second
+        MockHttpServletRequest httpRequest = new MockHttpServletRequest(null,
+                null);
+        httpRequest.setServletPath("/secure/super/very_secret.html");
+
+        ConfigAttributeDefinition returned = map.getAttributes(new FilterInvocation(
+                    httpRequest, new MockHttpServletResponse(),
+                    new MockFilterChain()));
+
+        ConfigAttributeDefinition expected = new ConfigAttributeDefinition();
+        expected.addConfigAttribute(new SecurityConfig("ROLE_WE_DONT_HAVE"));
+        expected.addConfigAttribute(new SecurityConfig("ANOTHER_ROLE"));
+
+        assertEquals(expected, returned);
+    }
+
+    public void testOrderOfEntriesIsPreservedOrderB() {
+        FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
+        editor.setAsText(
+            "PATTERN_TYPE_APACHE_ANT\r\n/secure/**=ROLE_SUPERVISOR,ROLE_TELLER\r\n/secure/super/**=ROLE_WE_DONT_HAVE");
+
+        PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor
+            .getValue();
+
+        MockHttpServletRequest httpRequest = new MockHttpServletRequest(null,
+                null);
+        httpRequest.setServletPath("/secure/super/very_secret.html");
+
+        ConfigAttributeDefinition returned = map.getAttributes(new FilterInvocation(
+                    httpRequest, new MockHttpServletResponse(),
+                    new MockFilterChain()));
+
+        ConfigAttributeDefinition expected = new ConfigAttributeDefinition();
+        expected.addConfigAttribute(new SecurityConfig("ROLE_SUPERVISOR"));
+        expected.addConfigAttribute(new SecurityConfig("ROLE_TELLER"));
+
+        assertEquals(expected, returned);
+    }
+
+    public void testSingleUrlParsing() throws Exception {
+        FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
+        editor.setAsText(
+            "PATTERN_TYPE_APACHE_ANT\r\n/secure/super/*=ROLE_WE_DONT_HAVE,ANOTHER_ROLE");
+
+        PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor
+            .getValue();
+
+        MockHttpServletRequest httpRequest = new MockHttpServletRequest(null,
+                null);
+        httpRequest.setServletPath("/secure/super/very_secret.html");
+
+        ConfigAttributeDefinition returned = map.getAttributes(new FilterInvocation(
+                    httpRequest, new MockHttpServletResponse(),
+                    new MockFilterChain()));
+
+        ConfigAttributeDefinition expected = new ConfigAttributeDefinition();
+        expected.addConfigAttribute(new SecurityConfig("ROLE_WE_DONT_HAVE"));
+        expected.addConfigAttribute(new SecurityConfig("ANOTHER_ROLE"));
+
+        assertEquals(expected, returned);
+    }
+
+    public void testWhitespaceAndCommentsAndLinesWithoutEqualsSignsAreIgnored() {
+        FilterInvocationDefinitionSourceEditor editor = new FilterInvocationDefinitionSourceEditor();
+        editor.setAsText(
+            "         PATTERN_TYPE_APACHE_ANT\r\n    /secure/super/*=ROLE_WE_DONT_HAVE\r\n    /secure/*=ROLE_SUPERVISOR,ROLE_TELLER      \r\n   \r\n     \r\n   // comment line  \r\n    \r\n");
+
+        PathBasedFilterInvocationDefinitionMap map = (PathBasedFilterInvocationDefinitionMap) editor
+            .getValue();
+        assertEquals(2, map.getMapSize());
+    }
+}

+ 4 - 4
core/src/test/java/org/acegisecurity/intercept/web/FilterSecurityInterceptorTests.java

@@ -48,7 +48,7 @@ import javax.servlet.ServletResponse;
 
 
 /**
- * Tests {@link FiltSecurityInterceptor}.
+ * Tests {@link FilterSecurityInterceptor}.
  *
  * @author Ben Alex
  * @version $Id$
@@ -77,7 +77,7 @@ public class FilterSecurityInterceptorTests extends TestCase {
     public void testEnsuresAccessDecisionManagerSupportsFilterInvocationClass() {
         FilterSecurityInterceptor interceptor = new FilterSecurityInterceptor();
         interceptor.setAuthenticationManager(new MockAuthenticationManager());
-        interceptor.setObjectDefinitionSource(new FilterInvocationDefinitionMap());
+        interceptor.setObjectDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
         interceptor.setRunAsManager(new MockRunAsManager());
 
         interceptor.setAccessDecisionManager(new AccessDecisionManager() {
@@ -110,7 +110,7 @@ public class FilterSecurityInterceptorTests extends TestCase {
         FilterSecurityInterceptor interceptor = new FilterSecurityInterceptor();
         interceptor.setAccessDecisionManager(new MockAccessDecisionManager());
         interceptor.setAuthenticationManager(new MockAuthenticationManager());
-        interceptor.setObjectDefinitionSource(new FilterInvocationDefinitionMap());
+        interceptor.setObjectDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
 
         interceptor.setRunAsManager(new RunAsManager() {
                 public boolean supports(Class clazz) {
@@ -143,7 +143,7 @@ public class FilterSecurityInterceptorTests extends TestCase {
         interceptor.setAccessDecisionManager(new MockAccessDecisionManager());
         interceptor.setAuthenticationManager(new MockAuthenticationManager());
 
-        FilterInvocationDefinitionMap fidp = new FilterInvocationDefinitionMap();
+        RegExpBasedFilterInvocationDefinitionMap fidp = new RegExpBasedFilterInvocationDefinitionMap();
         interceptor.setObjectDefinitionSource(fidp);
         interceptor.setRunAsManager(new MockRunAsManager());
         interceptor.afterPropertiesSet();

+ 123 - 0
core/src/test/java/org/acegisecurity/intercept/web/PathBasedFilterDefinitionMapTests.java

@@ -0,0 +1,123 @@
+/* Copyright 2004 Acegi Technology Pty Limited
+ *
+ * Licensed 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 net.sf.acegisecurity.intercept.web;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.MockFilterChain;
+import net.sf.acegisecurity.MockHttpServletRequest;
+import net.sf.acegisecurity.MockHttpServletResponse;
+import net.sf.acegisecurity.SecurityConfig;
+
+
+/**
+ * Tests parts of {@link PathBasedFilterInvocationDefinitionMap} not tested by
+ * {@link FilterInvocationDefinitionSourceEditorWithPathsTests}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class PathBasedFilterDefinitionMapTests extends TestCase {
+    //~ Constructors ===========================================================
+
+    public PathBasedFilterDefinitionMapTests() {
+        super();
+    }
+
+    public PathBasedFilterDefinitionMapTests(String arg0) {
+        super(arg0);
+    }
+
+    //~ Methods ================================================================
+
+    public final void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(PathBasedFilterDefinitionMapTests.class);
+    }
+
+    public void testConvertUrlToLowercaseIsFalseByDefault() {
+        PathBasedFilterInvocationDefinitionMap map = new PathBasedFilterInvocationDefinitionMap();
+        assertFalse(map.isConvertUrlToLowercaseBeforeComparison());
+    }
+
+    public void testConvertUrlToLowercaseSetterRespected() {
+        PathBasedFilterInvocationDefinitionMap map = new PathBasedFilterInvocationDefinitionMap();
+        map.setConvertUrlToLowercaseBeforeComparison(true);
+        assertTrue(map.isConvertUrlToLowercaseBeforeComparison());
+    }
+
+    public void testLookupNotRequiringExactMatchSuccessIfNotMatching() {
+        PathBasedFilterInvocationDefinitionMap map = new PathBasedFilterInvocationDefinitionMap();
+        map.setConvertUrlToLowercaseBeforeComparison(true);
+        assertTrue(map.isConvertUrlToLowercaseBeforeComparison());
+
+        ConfigAttributeDefinition def = new ConfigAttributeDefinition();
+        def.addConfigAttribute(new SecurityConfig("ROLE_ONE"));
+        map.addSecureUrl("/secure/super/**", def);
+
+        // Build a HTTP request
+        MockHttpServletRequest req = new MockHttpServletRequest(null);
+        req.setServletPath("/SeCuRE/super/somefile.html");
+
+        FilterInvocation fi = new FilterInvocation(req,
+                new MockHttpServletResponse(), new MockFilterChain());
+
+        ConfigAttributeDefinition response = map.lookupAttributes(fi);
+        assertEquals(def, response);
+    }
+
+    public void testLookupRequiringExactMatchFailsIfNotMatching() {
+        PathBasedFilterInvocationDefinitionMap map = new PathBasedFilterInvocationDefinitionMap();
+        assertFalse(map.isConvertUrlToLowercaseBeforeComparison());
+
+        ConfigAttributeDefinition def = new ConfigAttributeDefinition();
+        def.addConfigAttribute(new SecurityConfig("ROLE_ONE"));
+        map.addSecureUrl("/secure/super/**", def);
+
+        // Build a HTTP request
+        MockHttpServletRequest req = new MockHttpServletRequest(null);
+        req.setServletPath("/SeCuRE/super/somefile.html");
+
+        FilterInvocation fi = new FilterInvocation(req,
+                new MockHttpServletResponse(), new MockFilterChain());
+
+        ConfigAttributeDefinition response = map.lookupAttributes(fi);
+        assertEquals(null, response);
+    }
+
+    public void testLookupRequiringExactMatchIsSuccessful() {
+        PathBasedFilterInvocationDefinitionMap map = new PathBasedFilterInvocationDefinitionMap();
+        assertFalse(map.isConvertUrlToLowercaseBeforeComparison());
+
+        ConfigAttributeDefinition def = new ConfigAttributeDefinition();
+        def.addConfigAttribute(new SecurityConfig("ROLE_ONE"));
+        map.addSecureUrl("/secure/super/**", def);
+
+        // Build a HTTP request
+        MockHttpServletRequest req = new MockHttpServletRequest(null);
+        req.setServletPath("/secure/super/somefile.html");
+
+        FilterInvocation fi = new FilterInvocation(req,
+                new MockHttpServletResponse(), new MockFilterChain());
+
+        ConfigAttributeDefinition response = map.lookupAttributes(fi);
+        assertEquals(def, response);
+    }
+}

+ 123 - 0
core/src/test/java/org/acegisecurity/intercept/web/RegExpBasedFilterDefinitionMapTests.java

@@ -0,0 +1,123 @@
+/* Copyright 2004 Acegi Technology Pty Limited
+ *
+ * Licensed 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 net.sf.acegisecurity.intercept.web;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.MockFilterChain;
+import net.sf.acegisecurity.MockHttpServletRequest;
+import net.sf.acegisecurity.MockHttpServletResponse;
+import net.sf.acegisecurity.SecurityConfig;
+
+
+/**
+ * Tests parts of {@link RegExpBasedFilterInvocationDefinitionMap} not tested by {@link
+ * FilterInvocationDefinitionSourceEditorTests}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class RegExpBasedFilterDefinitionMapTests extends TestCase {
+    //~ Constructors ===========================================================
+
+    public RegExpBasedFilterDefinitionMapTests() {
+        super();
+    }
+
+    public RegExpBasedFilterDefinitionMapTests(String arg0) {
+        super(arg0);
+    }
+
+    //~ Methods ================================================================
+
+    public final void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(RegExpBasedFilterDefinitionMapTests.class);
+    }
+
+    public void testConvertUrlToLowercaseIsFalseByDefault() {
+        RegExpBasedFilterInvocationDefinitionMap map = new RegExpBasedFilterInvocationDefinitionMap();
+        assertFalse(map.isConvertUrlToLowercaseBeforeComparison());
+    }
+
+    public void testConvertUrlToLowercaseSetterRespected() {
+        RegExpBasedFilterInvocationDefinitionMap map = new RegExpBasedFilterInvocationDefinitionMap();
+        map.setConvertUrlToLowercaseBeforeComparison(true);
+        assertTrue(map.isConvertUrlToLowercaseBeforeComparison());
+    }
+
+    public void testLookupNotRequiringExactMatchSuccessIfNotMatching() {
+        RegExpBasedFilterInvocationDefinitionMap map = new RegExpBasedFilterInvocationDefinitionMap();
+        map.setConvertUrlToLowercaseBeforeComparison(true);
+        assertTrue(map.isConvertUrlToLowercaseBeforeComparison());
+
+        ConfigAttributeDefinition def = new ConfigAttributeDefinition();
+        def.addConfigAttribute(new SecurityConfig("ROLE_ONE"));
+        map.addSecureUrl("\\A/secure/super.*\\Z", def);
+
+        // Build a HTTP request
+        MockHttpServletRequest req = new MockHttpServletRequest(null);
+        req.setServletPath("/SeCuRE/super/somefile.html");
+
+        FilterInvocation fi = new FilterInvocation(req,
+                new MockHttpServletResponse(), new MockFilterChain());
+
+        ConfigAttributeDefinition response = map.lookupAttributes(fi);
+        assertEquals(def, response);
+    }
+
+    public void testLookupRequiringExactMatchFailsIfNotMatching() {
+        RegExpBasedFilterInvocationDefinitionMap map = new RegExpBasedFilterInvocationDefinitionMap();
+        assertFalse(map.isConvertUrlToLowercaseBeforeComparison());
+
+        ConfigAttributeDefinition def = new ConfigAttributeDefinition();
+        def.addConfigAttribute(new SecurityConfig("ROLE_ONE"));
+        map.addSecureUrl("\\A/secure/super.*\\Z", def);
+
+        // Build a HTTP request
+        MockHttpServletRequest req = new MockHttpServletRequest(null);
+        req.setServletPath("/SeCuRE/super/somefile.html");
+
+        FilterInvocation fi = new FilterInvocation(req,
+                new MockHttpServletResponse(), new MockFilterChain());
+
+        ConfigAttributeDefinition response = map.lookupAttributes(fi);
+        assertEquals(null, response);
+    }
+
+    public void testLookupRequiringExactMatchIsSuccessful() {
+        RegExpBasedFilterInvocationDefinitionMap map = new RegExpBasedFilterInvocationDefinitionMap();
+        assertFalse(map.isConvertUrlToLowercaseBeforeComparison());
+
+        ConfigAttributeDefinition def = new ConfigAttributeDefinition();
+        def.addConfigAttribute(new SecurityConfig("ROLE_ONE"));
+        map.addSecureUrl("\\A/secure/super.*\\Z", def);
+
+        // Build a HTTP request
+        MockHttpServletRequest req = new MockHttpServletRequest(null);
+        req.setServletPath("/secure/super/somefile.html");
+
+        FilterInvocation fi = new FilterInvocation(req,
+                new MockHttpServletResponse(), new MockFilterChain());
+
+        ConfigAttributeDefinition response = map.lookupAttributes(fi);
+        assertEquals(def, response);
+    }
+}

+ 38 - 13
docs/reference/src/index.xml

@@ -562,7 +562,7 @@
   &lt;property name="objectDefinitionSource"&gt;
     &lt;value&gt;
       CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
-      \A/secure/super.*\Z=ROLE_WE_DONT_HAVE
+      \A/secure/super/.*\Z=ROLE_WE_DONT_HAVE
       \A/secure/.*\Z=ROLE_SUPERVISOR,ROLE_TELLER
     &lt;/value&gt;
   &lt;/property&gt;
@@ -611,23 +611,48 @@
         created by the property editor,
         <literal>FilterInvocationDefinitionSource</literal>, matches
         configuration attributes against <literal>FilterInvocations</literal>
-        based on regular expression evaluation of the request URL. It works
-        down the list in the order they are defined. Thus it is important that
-        more specific regular expressions are defined higher in the list than
-        less specific regular expressions. This is reflected in our example
-        above, where the more specific <literal>/secure/super</literal>
+        based on expression evaluation of the request URL. Two standard
+        expression syntaxes are supported. The default is to treat all
+        expressions as regular expressions. Alternatively, the presence of a
+        <literal>PATTERN_TYPE_APACHE_ANT</literal> directive will cause all
+        expressions to be treated as Apache Ant paths. It is not possible to
+        mix expression syntaxes within the same definition. For example, the
+        earlier configuration could be generated using Apache Ant paths as
+        follows: </para>
+
+        <para><programlisting>&lt;bean id="filterInvocationInterceptor" class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor"&gt;
+  &lt;property name="authenticationManager"&gt;&lt;ref bean="authenticationManager"/&gt;&lt;/property&gt;
+  &lt;property name="accessDecisionManager"&gt;&lt;ref bean="accessDecisionManager"/&gt;&lt;/property&gt;
+  &lt;property name="runAsManager"&gt;&lt;ref bean="runAsManager"/&gt;&lt;/property&gt;
+  &lt;property name="objectDefinitionSource"&gt;
+    &lt;value&gt;
+      CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
+      PATTERN_TYPE_APACHE_ANT
+      /secure/super/**=ROLE_WE_DONT_HAVE
+      /secure/**=ROLE_SUPERVISOR,ROLE_TELLER
+    &lt;/value&gt;
+  &lt;/property&gt;
+&lt;/bean&gt;</programlisting></para>
+
+        <para>Irrespective of the type of expression syntax used, expressions
+        are always evaluated in the order they are defined. Thus it is
+        important that more specific expressions are defined higher in the
+        list than less specific expressions. This is reflected in our example
+        above, where the more specific <literal>/secure/super/</literal>
         pattern appears higher than the less specific
-        <literal>/super</literal> pattern. If they were reversed, the
-        <literal>/super</literal> pattern would always match and the
-        /<literal>secure/super</literal> pattern would never be evaluated. The
-        special keyword
+        <literal>/super/</literal> pattern. If they were reversed, the
+        <literal>/super/</literal> pattern would always match and the
+        <literal>/secure/super/</literal> pattern would never be evaluated.
+        </para>
+
+        <para>The special keyword
         <literal>CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON</literal> causes
         the <literal>FilterInvocationDefinitionSource</literal> to
         automatically convert a request URL to lowercase before comparison
-        against the regular expressions. Whilst by default the case of the
-        request URL is not converted, it is generally recommended to use
+        against the expressions. Whilst by default the case of the request URL
+        is not converted, it is generally recommended to use
         <literal>CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON</literal> and
-        write each regular expression assuming lowercase.</para>
+        write each expression assuming lowercase.</para>
 
         <para>As with other security interceptors, the
         <literal>validateConfigAttributes</literal> property is observed. When