Commits

Anonymous committed 82e6d88

Adding namespace wildcards, adding new pattern matcher that uses named variables, better handling of multiple pattern matchers
XW-580

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

Comments (0)

Files changed (8)

src/java/com/opensymphony/xwork2/config/entities/ActionConfig.java

         this(methodName, className, packageName, parameters, results, interceptors, Collections.EMPTY_LIST);
     }
 
+    /**
+     * Clones an ActionConfig, copying data into new maps and lists
+     * @param orig The ActionConfig to clone
+     * @Since 2.1
+     */
+    public ActionConfig(ActionConfig orig) {
+        this(orig.getMethodName(), orig.getClassName(), orig.getPackageName(), new LinkedHashMap<String,Object>(orig.getParams()),
+                new LinkedHashMap<String,ResultConfig>(orig.getResults()),
+                new ArrayList<InterceptorMapping>(orig.getInterceptors()), new ArrayList<ExceptionMappingConfig>(orig.getExceptionMappings()));
+    }
+
     public ActionConfig(String methodName, String className, String packageName, Map<String, Object> parameters,
                         Map<String, ResultConfig> results, List<InterceptorMapping> interceptors, List<ExceptionMappingConfig> exceptionMappings) {
         this.methodName = methodName;

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

  */
 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;
 
+import java.io.Serializable;
+import java.util.*;
+
 /**
  * <p> Matches patterns against pre-compiled wildcard expressions pulled from
  * target objects. It uses the wildcard matcher from the Apache Cocoon
     /**
      * <p> Handles all wildcard pattern matching. </p>
      */
-    private PatternMatcher<Object> wildcard;
+    PatternMatcher<Object> wildcard;
 
     /**
      * <p> The compiled patterns and their associated target objects </p>
      */
-    private List<Mapping<E>> compiledPatterns = new ArrayList<Mapping<E>>();;
+    List<Mapping<E>> compiledPatterns = new ArrayList<Mapping<E>>();;
     
     public AbstractMatcher(PatternMatcher<?> helper) {
         this.wildcard = (PatternMatcher<Object>) helper;
      * goal is to support the legacy "*!*" syntax, where the "!*" is optional.
      * </p>
      * 
-     * @param configs
-     *            An array of target objects to process
+     * @param name The pattern
+     * @param target The object to associate with the pattern
      * @param looseMatch
      *            To loosely match wildcards or not
      */
 
         Object pattern;
 
-        if ((name != null) && (name.indexOf('*') > -1)) {
-            if ((name.length() > 0) && (name.charAt(0) == '/')) {
+        if (!wildcard.isLiteral(name)) {
+            if (looseMatch && (name.length() > 0) && (name.charAt(0) == '/')) {
                 name = name.substring(1);
             }
 
 
             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));
+
+            if (looseMatch) {
+                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));
+                    }
                 }
             }
         }

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

  */
 package com.opensymphony.xwork2.config.impl;
 
-import java.io.Serializable;
-import java.util.ArrayList;
-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.util.PatternMatcher;
 import com.opensymphony.xwork2.util.WildcardHelper;
-import com.opensymphony.xwork2.util.logging.Logger;
-import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * <p> Matches paths against pre-compiled wildcard expressions pulled from
      * @return A cloned ActionConfig with appropriate properties replaced with
      *         wildcard-matched values
      */
-    protected ActionConfig convert(String path, ActionConfig orig,
+    public ActionConfig convert(String path, ActionConfig orig,
         Map vars) {
         
         String className = convertParam(orig.getClassName(), vars);

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

  */
 package com.opensymphony.xwork2.config.impl;
 
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-import ognl.PropertyAccessor;
-
 import com.opensymphony.xwork2.ActionContext;
 import com.opensymphony.xwork2.DefaultTextProvider;
 import com.opensymphony.xwork2.ObjectFactory;
 import com.opensymphony.xwork2.TextProvider;
-import com.opensymphony.xwork2.config.Configuration;
-import com.opensymphony.xwork2.config.ConfigurationException;
-import com.opensymphony.xwork2.config.ConfigurationProvider;
-import com.opensymphony.xwork2.config.ContainerProvider;
-import com.opensymphony.xwork2.config.PackageProvider;
-import com.opensymphony.xwork2.config.RuntimeConfiguration;
-import com.opensymphony.xwork2.config.entities.ActionConfig;
-import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
-import com.opensymphony.xwork2.config.entities.InterceptorMapping;
-import com.opensymphony.xwork2.config.entities.PackageConfig;
-import com.opensymphony.xwork2.config.entities.ResultConfig;
-import com.opensymphony.xwork2.config.entities.ResultTypeConfig;
+import com.opensymphony.xwork2.config.*;
+import com.opensymphony.xwork2.config.entities.*;
 import com.opensymphony.xwork2.config.providers.InterceptorBuilder;
 import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer;
 import com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer;
 import com.opensymphony.xwork2.conversion.impl.XWorkBasicConverter;
 import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
-import com.opensymphony.xwork2.inject.Container;
-import com.opensymphony.xwork2.inject.ContainerBuilder;
-import com.opensymphony.xwork2.inject.Context;
-import com.opensymphony.xwork2.inject.Factory;
-import com.opensymphony.xwork2.inject.Scope;
+import com.opensymphony.xwork2.inject.*;
 import com.opensymphony.xwork2.ognl.OgnlReflectionProvider;
 import com.opensymphony.xwork2.ognl.OgnlUtil;
 import com.opensymphony.xwork2.ognl.OgnlValueStackFactory;
 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;
 import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
+import ognl.PropertyAccessor;
+
+import java.util.*;
 
 
 /**
     private class RuntimeConfigurationImpl implements RuntimeConfiguration {
         private Map<String, Map<String, ActionConfig>> namespaceActionConfigs;
         private Map<String, ActionConfigMatcher> namespaceActionConfigMatchers;
+        private NamespaceMatcher namespaceMatcher;
         private Map<String, String> namespaceConfigs;
 
         public RuntimeConfigurationImpl(Map<String, Map<String, ActionConfig>> namespaceActionConfigs, Map<String, String> namespaceConfigs) {
             this.namespaceActionConfigs = namespaceActionConfigs;
             this.namespaceConfigs = namespaceConfigs;
+
+            PatternMatcher matcher = container.getInstance(PatternMatcher.class);
             
             this.namespaceActionConfigMatchers = new LinkedHashMap<String, ActionConfigMatcher>();
-            
+            this.namespaceMatcher = new NamespaceMatcher(matcher, namespaceActionConfigs.keySet());
+
             for (String ns : namespaceActionConfigs.keySet()) {
                 namespaceActionConfigMatchers.put(ns,
-                        new ActionConfigMatcher(container.getInstance(PatternMatcher.class),
+                        new ActionConfigMatcher(matcher,
                                 namespaceActionConfigs.get(ns), true));
             }
         }
          * @return the configuration information for action requested
          */
         public synchronized ActionConfig getActionConfig(String namespace, String name) {
+            ActionConfig config = findActionConfigInNamespace(namespace, name);
+
+            // try wildcarded namespaces
+            if (config == null) {
+                NamespaceMatch match = namespaceMatcher.match(namespace);
+                if (match != null) {
+                    config = findActionConfigInNamespace(match.getPattern(), name);
+
+                    // If config found, place all the matches found in the namespace processing in the action's parameters
+                    if (config != null) {
+                        config = new ActionConfig(config);
+                        config.getParams().putAll(match.getVariables());
+                    }
+                }
+            }
+
+            // fail over to empty namespace
+            if ((config == null) && (namespace != null) && (!namespace.trim().equals(""))) {
+                config = findActionConfigInNamespace("", name);
+            }
+
+
+            return config;
+        }
+
+        ActionConfig findActionConfigInNamespace(String namespace, String name) {
             ActionConfig config = null;
-            Map<String, ActionConfig> actions = namespaceActionConfigs.get((namespace == null) ? "" : namespace);
+            if (namespace == null) {
+                namespace = "";
+            }
+            Map<String, ActionConfig> actions = namespaceActionConfigs.get(namespace);
             if (actions != null) {
                 config = actions.get(name);
                 // Check wildcards
                     config = namespaceActionConfigMatchers.get(namespace).match(name);
                     // fail over to default action
                     if (config == null) {
-                        String defaultActionRef = namespaceConfigs.get((namespace == null) ? "" : namespace);
+                        String defaultActionRef = namespaceConfigs.get(namespace);
                         if (defaultActionRef != null) {
                             config = actions.get(defaultActionRef);
                         }
                     }
                 }
             }
-
-            // fail over to empty namespace
-            if ((config == null) && (namespace != null) && (!namespace.trim().equals(""))) {
-                actions = namespaceActionConfigs.get("");
-
-                if (actions != null) {
-                    config = actions.get(name);
-                    // Check wildcards
-                    if (config == null) {
-                        config = namespaceActionConfigMatchers.get("").match(name);
-                        // fail over to default action
-                        if (config == null) {
-                            String defaultActionRef = namespaceConfigs.get("");
-                            if (defaultActionRef != null) {
-                                config = actions.get(defaultActionRef);
-                            }
-                        }
-                    }
-                }
-            }
-
-
             return config;
         }
-        
+
         /**
          * Gets the configuration settings for every action.
          *

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

 public interface PatternMatcher<E extends Object> {
 
     /**
+     * Determines if the pattern is a simple literal string or contains wildcards that will need to be processed
+     * @param pattern The string pattern
+     * @return True if the pattern doesn't contain processing elements, false otherwise
+     */
+    boolean isLiteral(String pattern);
+
+    /**
      * <p> Translate the given <code>String</code> into an object
      * representing the pattern matchable by this class. 
      *
      * @return The encoded string 
      * @throws NullPointerException If data is null.
      */
-    public abstract E compilePattern(String data);
+    E compilePattern(String data);
 
     /**
      * Match a pattern against a string 
      * @return True if a match
      * @throws NullPointerException If any parameters are null
      */
-    public abstract boolean match(Map<String,String> map, String data, E expr);
+    boolean match(Map<String,String> map, String data, E expr);
     
 }

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

     protected static final int MATCH_END = -3;
 
     /**
+     * Determines if the pattern contains any * characters
+     *
+     * @param pattern The pattern
+     * @return True if no wildcards are found
+     */
+    public boolean isLiteral(String pattern) {
+        return (pattern == null || pattern.indexOf('*') == -1);
+    }
+
+    /**
      * <p> Translate the given <code>String</code> into a <code>int []</code>
      * representing the pattern matchable by this class. <br> This function
      * translates a <code>String</code> into an int array converting the

src/test/com/opensymphony/xwork2/config/ConfigurationTest.java

 
 import com.mockobjects.dynamic.C;
 import com.mockobjects.dynamic.Mock;
-import com.opensymphony.xwork2.*;
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ActionProxy;
+import com.opensymphony.xwork2.SimpleAction;
+import com.opensymphony.xwork2.XWorkTestCase;
 import com.opensymphony.xwork2.config.entities.ActionConfig;
 import com.opensymphony.xwork2.config.entities.InterceptorMapping;
 import com.opensymphony.xwork2.config.providers.MockConfigurationProvider;
 import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
 import com.opensymphony.xwork2.inject.ContainerBuilder;
-import com.opensymphony.xwork2.inject.Context;
-import com.opensymphony.xwork2.inject.Factory;
-import com.opensymphony.xwork2.inject.Inject;
-import com.opensymphony.xwork2.inject.Scope;
 import com.opensymphony.xwork2.mock.MockInterceptor;
 import com.opensymphony.xwork2.test.StubConfigurationProvider;
-import com.opensymphony.xwork2.util.XWorkTestCaseHelper;
 import com.opensymphony.xwork2.util.location.LocatableProperties;
 
 import java.util.HashMap;
         assertTrue("Wrong parameter, "+p.get("bar"), "input".equals(p.get("bar")));
     }
 
+    public void testWildcardNamespace() {
+        RuntimeConfiguration configuration = configurationManager.getConfiguration().getRuntimeConfiguration();
+
+        ActionConfig config = configuration.getActionConfig("/animals/dog", "commandTest");
+
+        assertNotNull(config);
+        assertTrue("Wrong class name, "+config.getClassName(),
+                "com.opensymphony.xwork2.SimpleAction".equals(config.getClassName()));
+
+        Map<String, Object> p = config.getParams();
+        assertTrue("Wrong parameter, "+p.get("0"), "/animals/dog".equals(p.get("0")));
+        assertTrue("Wrong parameter, "+p.get("1"), "dog".equals(p.get("1")));
+    }
+
     public void testGlobalResults() {
         try {
             ActionProxy proxy = actionProxyFactory.createActionProxy("", "Foo", null);

src/test/xwork-sample.xml

         </action>
     </package>
 
+    <package name="wildcardNamespaces" extends="default" namespace="/animals/*">
+        <action name="commandTest" class="com.opensymphony.xwork2.SimpleAction">
+            <result name="success" type="mock" />
+            <interceptor-ref name="static-params"/>
+        </action>
+    </package>
+
     <package name="multipleInheritance" extends="default,abstractPackage,bar" namespace="multipleInheritance">
         <action name="testMultipleInheritance" class="com.opensymphony.xwork2.SimpleAction">
             <result name="success" type="chain">