Commits

Anonymous committed f9e5018

Made wildcard processing pluggable. Also, refactored how matching
was handled
XW-578

git-svn-id: http://svn.opensymphony.com/svn/xwork/trunk@1636e221344d-f017-0410-9bd5-d282ab1896d7

Comments (0)

Files changed (7)

src/java/com/opensymphony/xwork2/config/impl/AbstractMatcher.java

+/*
+ * $Id: ActionConfigMatcher.java 1630 2007-10-05 15:10:08Z mrdon $
+ *
+ * Copyright 2003,2004 The Apache Software Foundation.
+ *
+ * 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 com.opensymphony.xwork2.config.impl;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
+import com.opensymphony.xwork2.config.entities.ResultConfig;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.PatternMatcher;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+/**
+ * <p> Matches patterns against pre-compiled wildcard expressions pulled from
+ * target objects. It uses the wildcard matcher from the Apache Cocoon
+ * project. Patterns will be matched in the order they were added. The first 
+ * match wins, so more specific patterns should be defined before less specific 
+ * patterns.
+ * 
+ * @since 2.1
+ */
+public abstract class AbstractMatcher<E> implements Serializable {
+    /**
+     * <p> The logging instance </p>
+     */
+    private static final Logger log = LoggerFactory.getLogger(AbstractMatcher.class);
+
+    /**
+     * <p> Handles all wildcard pattern matching. </p>
+     */
+    private PatternMatcher<Object> wildcard;
+
+    /**
+     * <p> The compiled patterns and their associated target objects </p>
+     */
+    private List<Mapping<E>> compiledPatterns = new ArrayList<Mapping<E>>();;
+    
+    public AbstractMatcher(PatternMatcher<?> helper) {
+        this.wildcard = (PatternMatcher<Object>) helper;
+    }
+
+    /**
+     * <p>
+     * Finds and precompiles the wildcard patterns. Patterns will be evaluated
+     * in the order they were added. Only patterns that actually contain a
+     * wildcard will be compiled.
+     * </p>
+     * 
+     * <p>
+     * Patterns can optionally be matched "loosely". When the end of the pattern
+     * matches \*[^*]\*$ (wildcard, no wildcard, wildcard), if the pattern
+     * fails, it is also matched as if the last two characters didn't exist. The
+     * goal is to support the legacy "*!*" syntax, where the "!*" is optional.
+     * </p>
+     * 
+     * @param configs
+     *            An array of target objects to process
+     * @param looseMatch
+     *            To loosely match wildcards or not
+     */
+    public void addPattern(String name, E target, boolean looseMatch) {
+
+        Object pattern;
+
+        if ((name != null) && (name.indexOf('*') > -1)) {
+            if ((name.length() > 0) && (name.charAt(0) == '/')) {
+                name = name.substring(1);
+            }
+
+            if (log.isDebugEnabled()) {
+                log.debug("Compiling pattern '" + name + "'");
+            }
+
+            pattern = wildcard.compilePattern(name);
+            compiledPatterns.add(new Mapping<E>(name, pattern, target));
+            
+            int lastStar = name.lastIndexOf('*');
+            if (lastStar > 1 && lastStar == name.length() - 1) {
+                if (name.charAt(lastStar - 1) != '*') {
+                    pattern = wildcard.compilePattern(name.substring(0, lastStar - 1));
+                    compiledPatterns.add(new Mapping<E>(name, pattern, target));
+                }
+            }
+        }
+    }
+    
+    public void freeze() {
+        compiledPatterns = Collections.unmodifiableList(new ArrayList<Mapping<E>>());
+    }
+
+    /**
+     * <p> Matches the path against the compiled wildcard patterns. </p>
+     *
+     * @param potentialMatch The portion of the request URI for selecting a config.
+     * @return The action config if matched, else null
+     */
+    public E match(String potentialMatch) {
+        E config = null;
+
+        if (compiledPatterns.size() > 0) {
+            if (log.isDebugEnabled()) {
+                log.debug("Attempting to match '" + potentialMatch
+                    + "' to a wildcard pattern, "+ compiledPatterns.size()
+                    + " available");
+            }
+
+            HashMap<String,String> vars = new HashMap<String,String>();
+            for (Mapping<E> m : compiledPatterns) {
+                if (wildcard.match(vars, potentialMatch, m.getPattern())) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Value matches pattern '"
+                            + m.getOriginalPattern() + "'");
+                    }
+
+                    config =
+                        convert(potentialMatch, m.getTarget(), vars);
+                    break;
+                }
+            }
+        }
+
+        return config;
+    }
+
+    /**
+     * <p> Clones the target object and its children, replacing various
+     * properties with the values of the wildcard-matched strings. </p>
+     *
+     * @param path The requested path
+     * @param orig The original object
+     * @param vars A Map of wildcard-matched strings
+     * @return A cloned object with appropriate properties replaced with
+     *         wildcard-matched values
+     */
+    protected abstract E convert(String path, E orig, Map vars);
+
+    /**
+     * <p> Replaces parameter values
+     * </p>
+     *
+     * @param orig  The original parameters with placehold values
+     * @param vars  A Map of wildcard-matched strings
+     */
+    protected Map<String,Object> replaceParameters(Map<String, Object> orig, Map<String,String> vars) {
+        Map<String,Object> map = new LinkedHashMap<String,Object>();
+        for (String key : orig.keySet()) {
+            map.put(key, convertParam(String.valueOf(orig.get(key)), vars));
+        }
+        return map;
+    }
+
+    /**
+     * <p> Inserts into a value wildcard-matched strings where specified
+     * with the {x} syntax.  If a wildcard-matched value isn't found, the
+     * replacement token is turned into an empty string. 
+     * </p>
+     *
+     * @param val  The value to convert
+     * @param vars A Map of wildcard-matched strings
+     * @return The new value
+     */
+    protected String convertParam(String val, Map<String,String> vars) {
+        if (val == null) {
+            return null;
+        } 
+        
+        int len = val.length();
+        StringBuilder ret = new StringBuilder();
+        char c;
+        String varVal;
+        for (int x=0; x<len; x++) {
+            c = val.charAt(x);
+            if (x < len - 2 && 
+                    c == '{' && '}' == val.charAt(x+2)) {
+                varVal = (String)vars.get(String.valueOf(val.charAt(x + 1)));
+                if (varVal != null) {
+                    ret.append(varVal);
+                } 
+                x += 2;
+            } else {
+                ret.append(c);
+            }
+        }
+        
+        return ret.toString();
+    }
+
+    /**
+     * <p> Stores a compiled wildcard pattern and the object it came
+     * from. </p>
+     */
+    private static class Mapping<E> implements Serializable {
+        /**
+         * <p> The original pattern. </p>
+         */
+        private String original;
+
+        
+        /**
+         * <p> The compiled pattern. </p>
+         */
+        private Object pattern;
+
+        /**
+         * <p> The original object. </p>
+         */
+        private E config;
+
+        /**
+         * <p> Contructs a read-only Mapping instance. </p>
+         *
+         * @param original The original pattern
+         * @param pattern The compiled pattern
+         * @param config  The original object
+         */
+        public Mapping(String original, Object pattern, E config) {
+            this.original = original;
+            this.pattern = pattern;
+            this.config = config;
+        }
+
+        /**
+         * <p> Gets the compiled wildcard pattern. </p>
+         *
+         * @return The compiled pattern
+         */
+        public Object getPattern() {
+            return this.pattern;
+        }
+
+        /**
+         * <p> Gets the object that contains the pattern. </p>
+         *
+         * @return The associated object
+         */
+        public E getTarget() {
+            return this.config;
+        }
+        
+        /**
+         * <p> Gets the original wildcard pattern. </p>
+         *
+         * @return The original pattern
+         */
+        public String getOriginalPattern() {
+            return this.original;
+        }
+    }
+}

src/java/com/opensymphony/xwork2/config/impl/ActionConfigMatcher.java

 import com.opensymphony.xwork2.config.entities.ActionConfig;
 import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
 import com.opensymphony.xwork2.config.entities.ResultConfig;
+import com.opensymphony.xwork2.util.PatternMatcher;
 import com.opensymphony.xwork2.util.WildcardHelper;
 import com.opensymphony.xwork2.util.logging.Logger;
 import com.opensymphony.xwork2.util.logging.LoggerFactory;
  * config file. The first match wins, so more specific patterns should be
  * defined before less specific patterns.
  */
-public class ActionConfigMatcher implements Serializable {
-    /**
-     * <p> The logging instance </p>
-     */
-    private static final Logger log = LoggerFactory.getLogger(ActionConfigMatcher.class);
-
-    /**
-     * <p> Handles all wildcard pattern matching. </p>
-     */
-    private static final WildcardHelper wildcard = new WildcardHelper();
-
-    /**
-     * <p> The compiled paths and their associated ActionConfig's </p>
-     */
-    private List compiledPaths;
-
+public class ActionConfigMatcher extends AbstractMatcher<ActionConfig> implements Serializable {
+   
     /**
      * <p> Finds and precompiles the wildcard patterns from the ActionConfig
      * "path" attributes. ActionConfig's will be evaluated in the order they
      * wildcard will be compiled. Patterns will matched strictly.</p>
      *
      * @param configs An array of ActionConfig's to process
+     * @deprecated Since 2.1, use {@link #ActionConfigMatcher(PatternMatcher, Map, boolean)} instead
      */
     public ActionConfigMatcher(Map<String, ActionConfig> configs) {
         this(configs, false);
      *
      * @param configs An array of ActionConfig's to process
      * @param looseMatch To loosely match wildcards or not
+     * @deprecated Since 2.1, use {@link #ActionConfigMatcher(PatternMatcher, Map, boolean)} instead
      */
     public ActionConfigMatcher(Map<String, ActionConfig> configs,
             boolean looseMatch) {
-        compiledPaths = new ArrayList();
 
-        int[] pattern;
-
-        for (String name : configs.keySet()) {
-
-            if ((name != null) && (name.indexOf('*') > -1)) {
-                if ((name.length() > 0) && (name.charAt(0) == '/')) {
-                    name = name.substring(1);
-                }
-
-                if (log.isDebugEnabled()) {
-                    log.debug("Compiling action config path '" + name + "'");
-                }
-
-                pattern = wildcard.compilePattern(name);
-                compiledPaths.add(new Mapping(name, pattern, configs.get(name)));
-                
-                int lastStar = name.lastIndexOf('*');
-                if (lastStar > 1 && lastStar == name.length() - 1) {
-                    if (name.charAt(lastStar - 1) != '*') {
-                        pattern = wildcard.compilePattern(name.substring(0, lastStar - 1));
-                        compiledPaths.add(new Mapping(name, pattern, configs.get(name)));
-                    }
-                }
-            }
-        }
+        this(new WildcardHelper(), configs, looseMatch);
     }
-
+    
     /**
-     * <p> Matches the path against the compiled wildcard patterns. </p>
+     * <p> Finds and precompiles the wildcard patterns from the ActionConfig
+     * "path" attributes. ActionConfig's will be evaluated in the order they
+     * exist in the config file. Only paths that actually contain a
+     * wildcard will be compiled. </p>
+     * 
+     * <p>Patterns can optionally be matched "loosely".  When
+     * the end of the pattern matches \*[^*]\*$ (wildcard, no wildcard,
+     * wildcard), if the pattern fails, it is also matched as if the 
+     * last two characters didn't exist.  The goal is to support the 
+     * legacy "*!*" syntax, where the "!*" is optional.</p> 
      *
-     * @param path The portion of the request URI for selecting a config.
-     * @return The action config if matched, else null
+     * @param configs An array of ActionConfig's to process
+     * @param looseMatch To loosely match wildcards or not
      */
-    public ActionConfig match(String path) {
-        ActionConfig config = null;
-
-        if (compiledPaths.size() > 0) {
-            if (log.isDebugEnabled()) {
-                log.debug("Attempting to match '" + path
-                    + "' to a wildcard pattern, "+ compiledPaths.size()
-                    + " available");
-            }
-
-            Mapping m;
-            HashMap vars = new HashMap();
-
-            for (Iterator i = compiledPaths.iterator(); i.hasNext();) {
-                m = (Mapping) i.next();
-                if (wildcard.match(vars, path, m.getPattern())) {
-                    if (log.isDebugEnabled()) {
-                        log.debug("Path matches pattern '"
-                            + m.getOriginal() + "'");
-                    }
-
-                    config =
-                        convertActionConfig(path,
-                            (ActionConfig) m.getActionConfig(), vars);
-                    break;
-                }
-            }
+    public ActionConfigMatcher(PatternMatcher<?> patternMatcher,
+            Map<String, ActionConfig> configs,
+            boolean looseMatch) {
+        super(patternMatcher);
+        for (String name : configs.keySet()) {
+            addPattern(name, configs.get(name), looseMatch);
         }
-
-        return config;
     }
 
     /**
      * @return A cloned ActionConfig with appropriate properties replaced with
      *         wildcard-matched values
      */
-    protected ActionConfig convertActionConfig(String path, ActionConfig orig,
+    protected ActionConfig convert(String path, ActionConfig orig,
         Map vars) {
         
         String className = convertParam(orig.getClassName(), vars);
         
         return config;
     }
-
-    /**
-     * <p> Replaces parameter values
-     * </p>
-     *
-     * @param orig  The original parameters with placehold values
-     * @param vars  A Map of wildcard-matched strings
-     */
-    protected Map<String,Object> replaceParameters(Map<String, Object> orig, Map vars) {
-        Map<String,Object> map = new LinkedHashMap<String,Object>();
-        for (String key : orig.keySet()) {
-            map.put(key, convertParam(String.valueOf(orig.get(key)), vars));
-        }
-        return map;
-    }
-
-    /**
-     * <p> Inserts into a value wildcard-matched strings where specified
-     * with the {x} syntax.  If a wildcard-matched value isn't found, the
-     * replacement token is turned into an empty string. 
-     * </p>
-     *
-     * @param val  The value to convert
-     * @param vars A Map of wildcard-matched strings
-     * @return The new value
-     */
-    protected String convertParam(String val, Map vars) {
-        if (val == null) {
-            return null;
-        } 
-        
-        int len = val.length();
-        StringBuilder ret = new StringBuilder();
-        char c;
-        String varVal;
-        for (int x=0; x<len; x++) {
-            c = val.charAt(x);
-            if (x < len - 2 && 
-                    c == '{' && '}' == val.charAt(x+2)) {
-                varVal = (String)vars.get(String.valueOf(val.charAt(x + 1)));
-                if (varVal != null) {
-                    ret.append(varVal);
-                } 
-                x += 2;
-            } else {
-                ret.append(c);
-            }
-        }
-        
-        return ret.toString();
-    }
-
-    /**
-     * <p> Stores a compiled wildcard pattern and the ActionConfig it came
-     * from. </p>
-     */
-    private class Mapping implements Serializable {
-        /**
-         * <p> The original pattern. </p>
-         */
-        private String original;
-
-        
-        /**
-         * <p> The compiled pattern. </p>
-         */
-        private int[] pattern;
-
-        /**
-         * <p> The original ActionConfig. </p>
-         */
-        private ActionConfig config;
-
-        /**
-         * <p> Contructs a read-only Mapping instance. </p>
-         *
-         * @param original The original pattern
-         * @param pattern The compiled pattern
-         * @param config  The original ActionConfig
-         */
-        public Mapping(String original, int[] pattern, ActionConfig config) {
-            this.original = original;
-            this.pattern = pattern;
-            this.config = config;
-        }
-
-        /**
-         * <p> Gets the compiled wildcard pattern. </p>
-         *
-         * @return The compiled pattern
-         */
-        public int[] getPattern() {
-            return this.pattern;
-        }
-
-        /**
-         * <p> Gets the ActionConfig that contains the pattern. </p>
-         *
-         * @return The associated ActionConfig
-         */
-        public ActionConfig getActionConfig() {
-            return this.config;
-        }
-        
-        /**
-         * <p> Gets the original wildcard pattern. </p>
-         *
-         * @return The original pattern
-         */
-        public String getOriginal() {
-            return this.original;
-        }
-    }
 }

src/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java

 import com.opensymphony.xwork2.ognl.OgnlValueStackFactory;
 import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
 import com.opensymphony.xwork2.util.CompoundRoot;
+import com.opensymphony.xwork2.util.PatternMatcher;
 import com.opensymphony.xwork2.util.ValueStack;
 import com.opensymphony.xwork2.util.ValueStackFactory;
+import com.opensymphony.xwork2.util.WildcardHelper;
 import com.opensymphony.xwork2.util.location.LocatableProperties;
 import com.opensymphony.xwork2.util.logging.Logger;
 import com.opensymphony.xwork2.util.logging.LoggerFactory;
             
             for (String ns : namespaceActionConfigs.keySet()) {
                 namespaceActionConfigMatchers.put(ns,
-                        new ActionConfigMatcher(namespaceActionConfigs.get(ns), true));
+                        new ActionConfigMatcher(container.getInstance(PatternMatcher.class),
+                                namespaceActionConfigs.get(ns), true));
             }
         }
 

src/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java

 import ognl.MethodAccessor;
 import ognl.PropertyAccessor;
 import com.opensymphony.xwork2.util.CompoundRoot;
+import com.opensymphony.xwork2.util.PatternMatcher;
 import com.opensymphony.xwork2.util.ValueStackFactory;
+import com.opensymphony.xwork2.util.WildcardHelper;
 import com.opensymphony.xwork2.validator.ActionValidatorManager;
 import com.opensymphony.xwork2.validator.AnnotationActionValidatorManager;
 import com.opensymphony.xwork2.validator.DefaultActionValidatorManager;
                .factory(ObjectTypeDeterminer.class, DefaultObjectTypeDeterminer.class, Scope.SINGLETON)
                .factory(XWorkConverter.class, Scope.SINGLETON)
                .factory(ValueStackFactory.class, OgnlValueStackFactory.class, Scope.SINGLETON)
+               .factory(PatternMatcher.class, WildcardHelper.class, Scope.SINGLETON)
                .factory(ReflectionProvider.class, OgnlReflectionProvider.class, Scope.SINGLETON)
                .factory(ReflectionContextFactory.class, OgnlReflectionContextFactory.class, Scope.SINGLETON)
                .factory(PropertyAccessor.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON)

src/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java

                             if ("true".equals(onlyStatic)) {
                                 // Force loading of class to detect no class def found exceptions
                                 cimpl.getDeclaredClasses();
+                                System.out.println("founds fields:"+cimpl.getDeclaredFields()+" for class "+cimpl);
+                                
 
                                 containerBuilder.injectStatics(cimpl);
                             } else {

src/java/com/opensymphony/xwork2/util/PatternMatcher.java

+/*
+ * $Id: WildcardHelper.java 1063 2006-07-10 00:30:29Z mrdon $
+ *
+ * Copyright 2003-2004 The Apache Software Foundation.
+ *
+ * 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 com.opensymphony.xwork2.util;
+
+import java.util.Map;
+
+/**
+ * Compiles and matches a pattern against a value
+ * 
+ * @since 2.1
+ */
+public interface PatternMatcher<E extends Object> {
+
+    /**
+     * <p> Translate the given <code>String</code> into an object
+     * representing the pattern matchable by this class. 
+     *
+     * @param data The string to translate.
+     * @return The encoded string 
+     * @throws NullPointerException If data is null.
+     */
+    public abstract E compilePattern(String data);
+
+    /**
+     * Match a pattern against a string 
+     *
+     * @param map  The map to store matched values
+     * @param data The string to match
+     * @param expr The compiled wildcard expression
+     * @return True if a match
+     * @throws NullPointerException If any parameters are null
+     */
+    public abstract boolean match(Map<String,String> map, String data, E expr);
+    
+}

src/java/com/opensymphony/xwork2/util/WildcardHelper.java

  * @version $Rev$ $Date: 2005-05-07 12:11:38 -0400 (Sat, 07 May 2005)
  *          $
  */
-public class WildcardHelper {
+public class WildcardHelper implements PatternMatcher<int[]> {
     /**
      * The int representing '*' in the pattern <code>int []</code>.
      */