You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by mr...@apache.org on 2007/10/12 16:08:07 UTC

svn commit: r584166 - in /struts/struts2/trunk: apps/showcase/src/main/resources/ core/src/main/java/org/apache/struts2/config/ core/src/main/java/org/apache/struts2/dispatcher/ core/src/test/java/org/apache/struts2/config/ core/src/test/java/org/apach...

Author: mrdon
Date: Fri Oct 12 07:07:52 2007
New Revision: 584166

URL: http://svn.apache.org/viewvc?rev=584166&view=rev
Log:
Moving zero config code into codebehind plugin, minor fix to showcase
WW-2247

Added:
    struts/struts2/trunk/plugins/codebehind/src/main/java/org/apache/struts2/config/
    struts/struts2/trunk/plugins/codebehind/src/main/java/org/apache/struts2/config/ClasspathPackageProvider.java
    struts/struts2/trunk/plugins/codebehind/src/main/java/org/apache/struts2/config/Namespace.java
      - copied unchanged from r584160, struts/struts2/trunk/core/src/main/java/org/apache/struts2/config/Namespace.java
    struts/struts2/trunk/plugins/codebehind/src/main/java/org/apache/struts2/config/ParentPackage.java
      - copied unchanged from r584160, struts/struts2/trunk/core/src/main/java/org/apache/struts2/config/ParentPackage.java
    struts/struts2/trunk/plugins/codebehind/src/main/java/org/apache/struts2/config/Result.java
      - copied unchanged from r584160, struts/struts2/trunk/core/src/main/java/org/apache/struts2/config/Result.java
    struts/struts2/trunk/plugins/codebehind/src/main/java/org/apache/struts2/config/Results.java
      - copied unchanged from r584160, struts/struts2/trunk/core/src/main/java/org/apache/struts2/config/Results.java
    struts/struts2/trunk/plugins/codebehind/src/test/java/org/apache/struts2/config/
    struts/struts2/trunk/plugins/codebehind/src/test/java/org/apache/struts2/config/ClasspathPackageProviderTest.java
    struts/struts2/trunk/plugins/codebehind/src/test/java/org/apache/struts2/config/CustomNamespaceAction.java
      - copied unchanged from r584160, struts/struts2/trunk/core/src/test/java/org/apache/struts2/config/CustomNamespaceAction.java
    struts/struts2/trunk/plugins/codebehind/src/test/java/org/apache/struts2/config/CustomParentPackageAction.java
      - copied unchanged from r584160, struts/struts2/trunk/core/src/test/java/org/apache/struts2/config/CustomParentPackageAction.java
    struts/struts2/trunk/plugins/codebehind/src/test/java/org/apache/struts2/config/cltest/
      - copied from r584160, struts/struts2/trunk/core/src/test/java/org/apache/struts2/config/cltest/
Removed:
    struts/struts2/trunk/core/src/main/java/org/apache/struts2/config/ClasspathConfigurationProvider.java
    struts/struts2/trunk/core/src/main/java/org/apache/struts2/config/Namespace.java
    struts/struts2/trunk/core/src/main/java/org/apache/struts2/config/ParentPackage.java
    struts/struts2/trunk/core/src/main/java/org/apache/struts2/config/Result.java
    struts/struts2/trunk/core/src/main/java/org/apache/struts2/config/Results.java
    struts/struts2/trunk/core/src/test/java/org/apache/struts2/config/ClasspathConfigurationProviderTest.java
    struts/struts2/trunk/core/src/test/java/org/apache/struts2/config/CustomNamespaceAction.java
    struts/struts2/trunk/core/src/test/java/org/apache/struts2/config/CustomParentPackageAction.java
    struts/struts2/trunk/core/src/test/java/org/apache/struts2/config/cltest/
Modified:
    struts/struts2/trunk/apps/showcase/src/main/resources/struts.xml
    struts/struts2/trunk/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
    struts/struts2/trunk/plugins/codebehind/src/main/resources/struts-plugin.xml
    struts/struts2/trunk/plugins/codebehind/src/test/java/org/apache/struts2/codebehind/CodebehindUnknownHandlerTest.java

Modified: struts/struts2/trunk/apps/showcase/src/main/resources/struts.xml
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/apps/showcase/src/main/resources/struts.xml?rev=584166&r1=584165&r2=584166&view=diff
==============================================================================
--- struts/struts2/trunk/apps/showcase/src/main/resources/struts.xml (original)
+++ struts/struts2/trunk/apps/showcase/src/main/resources/struts.xml Fri Oct 12 07:07:52 2007
@@ -67,6 +67,8 @@
             </interceptor-stack>
         </interceptors>
 
+        <default-action-ref name="showcase" />
+
         <action name="showcase">
             <result>showcase.jsp</result>
         </action>

Modified: struts/struts2/trunk/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java?rev=584166&r1=584165&r2=584166&view=diff
==============================================================================
--- struts/struts2/trunk/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java (original)
+++ struts/struts2/trunk/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java Fri Oct 12 07:07:52 2007
@@ -39,12 +39,9 @@
 import org.apache.struts2.StrutsConstants;
 import org.apache.struts2.StrutsStatics;
 import org.apache.struts2.config.BeanSelectionProvider;
-import org.apache.struts2.config.ClasspathConfigurationProvider;
 import org.apache.struts2.config.DefaultPropertiesProvider;
 import org.apache.struts2.config.LegacyPropertiesConfigurationProvider;
 import org.apache.struts2.config.StrutsXmlConfigurationProvider;
-import org.apache.struts2.config.ClasspathConfigurationProvider.ClasspathPageLocator;
-import org.apache.struts2.config.ClasspathConfigurationProvider.PageLocator;
 import org.apache.struts2.dispatcher.mapper.ActionMapping;
 import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
 import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
@@ -302,19 +299,6 @@
         }
     }
 
-    private void init_ZeroConfiguration() {
-        String packages = initParams.get("actionPackages");
-        if (packages != null) {
-            String[] names = packages.split("\\s*[,]\\s*");
-            // Initialize the classloader scanner with the configured packages
-            if (names.length > 0) {
-                ClasspathConfigurationProvider provider = new ClasspathConfigurationProvider(names);
-                provider.setPageLocator(new ServletContextPageLocator(servletContext));
-                configurationManager.addConfigurationProvider(provider);
-            }
-        }
-    }
-
     private void init_CustomConfigurationProviders() {
         String configProvs = initParams.get("configProviders");
         if (configProvs != null) {
@@ -434,7 +418,6 @@
     	init_DefaultProperties(); // [1]
         init_TraditionalXmlConfigurations(); // [2]
         init_LegacyStrutsProperties(); // [3]
-        init_ZeroConfiguration(); // [4]
         init_CustomConfigurationProviders(); // [5]
         init_MethodConfigurationProvider();
         init_FilterInitParameters() ; // [6]
@@ -757,32 +740,7 @@
         }
     }
 
-    /**
-     * Search classpath for a page.
-     */
-    private final class ServletContextPageLocator implements PageLocator {
-        private final ServletContext context;
-        private ClasspathPageLocator classpathPageLocator = new ClasspathPageLocator();
-
-        private ServletContextPageLocator(ServletContext context) {
-            this.context = context;
-        }
-
-        public URL locate(String path) {
-            URL url = null;
-            try {
-                url = context.getResource(path);
-                if (url == null) {
-                    url = classpathPageLocator.locate(path);
-                }
-            } catch (MalformedURLException e) {
-                if (LOG.isDebugEnabled()) {
-                    LOG.debug("Unable to resolve path "+path+" against the servlet context");
-                }
-            }
-            return url;
-        }
-    }
+    
 
     /**
      * Provide an accessor class for static XWork utility.

Added: struts/struts2/trunk/plugins/codebehind/src/main/java/org/apache/struts2/config/ClasspathPackageProvider.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/codebehind/src/main/java/org/apache/struts2/config/ClasspathPackageProvider.java?rev=584166&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/codebehind/src/main/java/org/apache/struts2/config/ClasspathPackageProvider.java (added)
+++ struts/struts2/trunk/plugins/codebehind/src/main/java/org/apache/struts2/config/ClasspathPackageProvider.java Fri Oct 12 07:07:52 2007
@@ -0,0 +1,570 @@
+/*
+ * $Id: ClasspathPackageProvider.java 582626 2007-10-07 13:26:12Z mrdon $
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.struts2.config;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Modifier;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+
+import com.opensymphony.xwork2.Action;
+import com.opensymphony.xwork2.config.Configuration;
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.ConfigurationProvider;
+import com.opensymphony.xwork2.config.PackageProvider;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+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.inject.ContainerBuilder;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.ClassLoaderUtil;
+import com.opensymphony.xwork2.util.ResolverUtil;
+import com.opensymphony.xwork2.util.TextUtils;
+import com.opensymphony.xwork2.util.ResolverUtil.ClassTest;
+import com.opensymphony.xwork2.util.location.LocatableProperties;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+/**
+ * ClasspathPackageProvider loads the configuration
+ * by scanning the classpath or selected packages for Action classes.
+ * <p>
+ * This provider is only invoked if one or more action packages are passed to the dispatcher,
+ * usually from the web.xml.
+ * Configurations are created for objects that either implement Action or have classnames that end with "Action".
+ */
+public class ClasspathPackageProvider implements PackageProvider {
+
+    /**
+     * The default page prefix (or "path").
+     * Some applications may place pages under "/WEB-INF" as an extreme security precaution.
+     */
+    private static final String DEFAULT_PAGE_PREFIX = "struts.configuration.classpath.defaultPagePrefix";
+
+    /**
+     * The default page prefix (none).
+     */
+    private String defaultPagePrefix = "";
+
+    /**
+     * The default page extension,  to use in place of ".jsp".
+     */
+    private static final String DEFAULT_PAGE_EXTENSION = "struts.configuration.classpath.defaultPageExtension";
+
+    /**
+     * The defacto default page extension, usually associated with JavaServer Pages.
+     */
+    private String defaultPageExtension = ".jsp";
+
+    /**
+     * A setting to indicate a custom default parent package,
+     * to use in place of "struts-default".
+     */
+    private static final String DEFAULT_PARENT_PACKAGE = "struts.configuration.classpath.defaultParentPackage";
+
+    /**
+     * Name of the framework's default configuration package,
+     * that application configuration packages automatically inherit.
+     */
+    private String defaultParentPackage = "struts-default";
+
+    /**
+     * The default page prefix (or "path").
+     * Some applications may place pages under "/WEB-INF" as an extreme security precaution.
+     */
+    private static final String FORCE_LOWER_CASE = "struts.configuration.classpath.forceLowerCase";
+
+    /**
+     * Whether to use a lowercase letter as the initial letter of an action.
+     * If false, actions will retain the initial uppercase letter from the Action class.
+     * (<code>view.action</code> (true) versus <code>View.action</code> (false)).
+     */
+    private boolean forceLowerCase = true;
+
+    /**
+     * Default suffix that can be used to indicate POJO "Action" classes.
+     */
+    private static final String ACTION = "Action";
+
+    /**
+     * Helper class to scan class path for server pages.
+     */
+    private PageLocator pageLocator = new ClasspathPageLocator();
+
+    /**
+     * Flag to indicate the packages have been loaded.
+     *
+     * @see #loadPackages
+     * @see #needsReload
+     */
+    private boolean initialized = false;
+
+    /**
+     * The package configurations for scanned Actions.
+     *
+     * @see #loadPackageConfig
+     */
+    private Map<String,PackageConfig> loadedPackageConfigs = new HashMap<String,PackageConfig>();
+
+    /**
+     * Logging instance for this class.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(ClasspathPackageProvider.class);
+
+    /**
+     * The XWork Configuration for this application.
+     *
+     * @see #init
+     */
+    private Configuration configuration;
+
+    private String actionPackages;
+
+    private ServletContext servletContext;
+
+    /**
+     * Create instance utilizing a list of packages to scan for Action classes.
+     *
+     * @param pkgs List of pacaktges to scan for Action Classes.
+     */
+    public ClasspathPackageProvider() {
+    }
+
+    /**
+     * PageLocator defines a locate method that can be used to discover server pages.
+     */
+    public static interface PageLocator {
+        public URL locate(String path);
+    }
+
+    /**
+     * ClasspathPathLocator searches the classpath for server pages.
+     */
+    public static class ClasspathPageLocator implements PageLocator {
+        public URL locate(String path) {
+            return ClassLoaderUtil.getResource(path, getClass());
+        }
+    }
+    
+    @Inject("actionPackages")
+    public void setActionPackages(String packages) {
+        this.actionPackages = packages;
+    }
+    
+    public void setServletContext(ServletContext ctx) {
+        this.servletContext = ctx;
+    }
+
+    /**
+     * Register a default parent package for the actions.
+     *
+     * @param defaultParentPackage the new defaultParentPackage
+     */
+    @Inject(value=DEFAULT_PARENT_PACKAGE, required=false)
+    public void setDefaultParentPackage(String defaultParentPackage) {
+        this.defaultParentPackage = defaultParentPackage;
+    }
+
+    /**
+     * Register a default page extension to use when locating pages.
+     *
+     * @param defaultPageExtension the new defaultPageExtension
+     */
+    @Inject(value=DEFAULT_PAGE_EXTENSION, required=false)
+    public void setDefaultPageExtension(String defaultPageExtension) {
+        this.defaultPageExtension = defaultPageExtension;
+    }
+
+    /**
+     * Reigster a default page prefix to use when locating pages.
+     *
+     * @param defaultPagePrefix the defaultPagePrefix to set
+     */
+    @Inject(value=DEFAULT_PAGE_PREFIX, required=false)
+    public void setDefaultPagePrefix(String defaultPagePrefix) {
+        this.defaultPagePrefix = defaultPagePrefix;
+    }
+    
+    /**
+     * Whether to use a lowercase letter as the initial letter of an action.
+     * 
+     * @param force If false, actions will retain the initial uppercase letter from the Action class.
+     * (<code>view.action</code> (true) versus <code>View.action</code> (false)).
+     */
+    @Inject(value=FORCE_LOWER_CASE, required=false)
+    public void setForceLowerCase(String force) {
+        this.forceLowerCase = "true".equals(force);
+    }
+
+    /**
+     * Register a PageLocation to use to scan for server pages.
+     *
+     * @param locator
+     */
+    public void setPageLocator(PageLocator locator) {
+        this.pageLocator = locator;
+    }
+
+    /**
+     * Scan a list of packages for Action classes.
+     *
+     * This method loads classes that implement the Action interface
+     * or have a class name that ends with the letters "Action".
+     *
+     * @param pkgs A list of packages to load
+     * @see #processActionClass
+     */
+    protected void loadPackages(String[] pkgs) {
+
+        ResolverUtil<Class> resolver = new ResolverUtil<Class>();
+        resolver.find(new ClassTest() {
+            // Match Action implementations and classes ending with "Action"
+            public boolean matches(Class type) {
+                // TODO: should also find annotated classes
+                return (Action.class.isAssignableFrom(type) ||
+                        type.getSimpleName().endsWith("Action"));
+            }
+
+        }, pkgs);
+
+        Set<? extends Class<? extends Class>> actionClasses = resolver.getClasses();
+        for (Object obj : actionClasses) {
+           Class cls = (Class) obj;
+           if (!Modifier.isAbstract(cls.getModifiers())) {
+               processActionClass(cls, pkgs);
+           }
+        }
+
+        for (String key : loadedPackageConfigs.keySet()) {
+            configuration.addPackageConfig(key, loadedPackageConfigs.get(key));
+        }
+    }
+
+    /**
+     * Create a default action mapping for a class instance.
+     *
+     * The namespace annotation is honored, if found, otherwise
+     * the Java package is converted into the namespace
+     * by changing the dots (".") to slashes ("/").
+     *
+     * @param cls Action or POJO instance to process
+     * @param pkgs List of packages that were scanned for Actions
+     */
+    protected void processActionClass(Class cls, String[] pkgs) {
+        String name = cls.getName();
+        String actionPackage = cls.getPackage().getName();
+        String actionNamespace = null;
+        String actionName = null;
+        for (String pkg : pkgs) {
+            if (name.startsWith(pkg)) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("ClasspathPackageProvider: Processing class "+name);
+                }
+                name = name.substring(pkg.length() + 1);
+
+                actionNamespace = "";
+                actionName = name;
+                int pos = name.lastIndexOf('.');
+                if (pos > -1) {
+                    actionNamespace = "/" + name.substring(0, pos).replace('.','/');
+                    actionName = name.substring(pos+1);
+                }
+                break;
+            }
+        }
+
+        PackageConfig pkgConfig = loadPackageConfig(actionNamespace, actionPackage, cls);
+
+        // In case the package changed due to namespace annotation processing
+        if (!actionPackage.equals(pkgConfig.getName())) {
+            actionPackage = pkgConfig.getName();
+        }
+
+        Annotation annotation = cls.getAnnotation(ParentPackage.class);
+        if (annotation != null) {
+            String parent = ((ParentPackage)annotation).value();
+            PackageConfig parentPkg = configuration.getPackageConfig(parent);
+            if (parentPkg == null) {
+                throw new ConfigurationException("ClasspathPackageProvider: Unable to locate parent package "+parent, annotation);
+            }
+            pkgConfig.addParent(parentPkg);
+
+            if (!TextUtils.stringSet(pkgConfig.getNamespace()) && TextUtils.stringSet(parentPkg.getNamespace())) {
+                pkgConfig.setNamespace(parentPkg.getNamespace());
+            }
+        }
+
+        // Truncate Action suffix if found
+        if (actionName.endsWith(ACTION)) {
+            actionName = actionName.substring(0, actionName.length() - ACTION.length());
+        }
+
+        // Force initial letter of action to lowercase, if desired
+        if ((forceLowerCase) && (actionName.length() > 1)) {
+            int lowerPos = actionName.lastIndexOf('/') + 1;
+            StringBuilder sb = new StringBuilder();
+            sb.append(actionName.substring(0, lowerPos));
+            sb.append(Character.toLowerCase(actionName.charAt(lowerPos)));
+            sb.append(actionName.substring(lowerPos + 1));
+            actionName = sb.toString();
+        }
+
+        ActionConfig actionConfig = new ActionConfig();
+        actionConfig.setClassName(cls.getName());
+        actionConfig.setPackageName(actionPackage);
+
+        actionConfig.setResults(new ResultMap<String,ResultConfig>(cls, actionName, pkgConfig));
+        pkgConfig.addActionConfig(actionName, actionConfig);
+    }
+
+    /**
+     * Finds or creates the package configuration for an Action class.
+     *
+     * The namespace annotation is honored, if found,
+     * and the namespace is checked for a parent configuration.
+     *
+     * @param actionNamespace The configuration namespace
+     * @param actionPackage The Java package containing our Action classes
+     * @param actionClass The Action class instance
+     * @return PackageConfig object for the Action class
+     */
+    protected PackageConfig loadPackageConfig(String actionNamespace, String actionPackage, Class actionClass) {
+        PackageConfig parent = null;
+
+        if (actionClass != null) {
+            Namespace ns = (Namespace) actionClass.getAnnotation(Namespace.class);
+            if (ns != null) {
+                parent = loadPackageConfig(actionNamespace, actionPackage, null);
+                actionNamespace = ns.value();
+                actionPackage = actionClass.getName();
+            }
+        }
+
+        PackageConfig pkgConfig = loadedPackageConfigs.get(actionPackage);
+        if (pkgConfig == null) {
+            pkgConfig = new PackageConfig();
+            pkgConfig.setName(actionPackage);
+
+            if (parent == null) {
+                parent = configuration.getPackageConfig(defaultParentPackage);
+            }
+
+            if (parent == null) {
+                throw new ConfigurationException("ClasspathPackageProvider: Unable to locate default parent package: " +
+                        defaultParentPackage);
+            }
+            pkgConfig.addParent(parent);
+
+            pkgConfig.setNamespace(actionNamespace);
+
+            loadedPackageConfigs.put(actionPackage, pkgConfig);
+        }
+        return pkgConfig;
+    }
+
+    /**
+     * Default destructor. Override to provide behavior.
+     */
+    public void destroy() {
+
+    }
+
+    /**
+     * Register this application's configuration.
+     *
+     * @param config The configuration for this application.
+     */
+    public void init(Configuration config) {
+        this.configuration = config;
+    }
+
+    /**
+     * Clears and loads the list of packages registered at construction.
+     *
+     * @throws ConfigurationException
+     */
+    public void loadPackages() throws ConfigurationException {
+        loadedPackageConfigs.clear();
+        if (actionPackages != null) {
+            String[] names = actionPackages.split("\\s*[,]\\s*");
+            // Initialize the classloader scanner with the configured packages
+            if (names.length > 0) {
+                setPageLocator(new ServletContextPageLocator(servletContext));
+            }
+            loadPackages(names);
+        }
+        initialized = true;
+    }
+
+    /**
+     * Indicates whether the packages have been initialized.
+     *
+     * @return True if the packages have been initialized
+     */
+    public boolean needsReload() {
+        return !initialized;
+    }
+
+    /**
+     * Creates ResultConfig objects from result annotations,
+     * and if a result isn't found, creates it on the fly.
+     */
+    class ResultMap<K,V> extends HashMap<K,V> {
+        private Class actionClass;
+        private String actionName;
+        private PackageConfig pkgConfig;
+
+        public ResultMap(Class actionClass, String actionName, PackageConfig pkgConfig) {
+            this.actionClass = actionClass;
+            this.actionName = actionName;
+            this.pkgConfig = pkgConfig;
+
+            // check if any annotations are around
+            while (!actionClass.getName().equals(Object.class.getName())) {
+                //noinspection unchecked
+                Results results = (Results) actionClass.getAnnotation(Results.class);
+                if (results != null) {
+                    // first check here...
+                    for (int i = 0; i < results.value().length; i++) {
+                        Result result = results.value()[i];
+                        ResultConfig config = createResultConfig(result);
+                        put((K)config.getName(), (V)config);
+                    }
+                }
+
+                // what about a single Result annotation?
+                Result result = (Result) actionClass.getAnnotation(Result.class);
+                if (result != null) {
+                    ResultConfig config = createResultConfig(result);
+                    put((K)config.getName(), (V)config);
+                }
+
+                actionClass = actionClass.getSuperclass();
+            }
+        }
+
+        /**
+         * Extracts result name and value and calls {@link #createResultConfig}.
+         *
+         * @param result Result annotation reference representing result type to create
+         * @return New or cached ResultConfig object for result
+         */
+        protected ResultConfig createResultConfig(Result result) {
+            Class<? extends Object> cls = result.type();
+            if (cls == NullResult.class) {
+                cls = null;
+            }
+            return createResultConfig(result.name(), cls, result.value(), createParameterMap(result.params()));
+        }
+
+        protected Map<String, String> createParameterMap(String[] parms) {
+            Map<String, String> map = new HashMap<String, String>();
+            int subtract = parms.length % 2;
+            if(subtract != 0) {
+                LOG.warn("Odd number of result parameters key/values specified.  The final one will be ignored.");
+            }
+            for (int i = 0; i < parms.length - subtract; i++) {
+                String key = parms[i++];
+                String value = parms[i];
+                map.put(key, value);
+                if(LOG.isDebugEnabled()) {
+                    LOG.debug("Adding parmeter["+key+":"+value+"] to result.");
+                }
+            }
+            return map;
+        }
+
+        /**
+         * Creates a default ResultConfig,
+         * using either the resultClass or the default ResultType for configuration package
+         * associated this ResultMap class.
+         *
+         * @param key The result type name
+         * @param resultClass The class for the result type
+         * @param location Path to the resource represented by this type
+         * @return A ResultConfig for key mapped to location
+         */
+        private ResultConfig createResultConfig(Object key, Class<? extends Object> resultClass,
+                                                String location,
+                                                Map<? extends Object,? extends Object > configParams) {
+            if (resultClass == null) {
+                String defaultResultType = pkgConfig.getFullDefaultResultType();
+                ResultTypeConfig resultType = pkgConfig.getAllResultTypeConfigs().get(defaultResultType);
+                configParams = resultType.getParams();
+                String className = resultType.getClazz();
+                try {
+                    resultClass = ClassLoaderUtil.loadClass(className, getClass());
+                } catch (ClassNotFoundException ex) {
+                    throw new ConfigurationException("ClasspathPackageProvider: Unable to locate result class "+className, actionClass);
+                }
+            }
+
+            String defaultParam;
+            try {
+                defaultParam = (String) resultClass.getField("DEFAULT_PARAM").get(null);
+            } catch (Exception e) {
+                // not sure why this happened, but let's just use a sensible choice
+                defaultParam = "location";
+            }
+
+            HashMap params = new HashMap();
+            if (configParams != null) {
+                params.putAll(configParams);
+            }
+
+            params.put(defaultParam, location);
+            return new ResultConfig((String) key, resultClass.getName(), params);
+        }
+    }
+
+    /**
+     * Search classpath for a page.
+     */
+    private final class ServletContextPageLocator implements PageLocator {
+        private final ServletContext context;
+        private ClasspathPageLocator classpathPageLocator = new ClasspathPageLocator();
+
+        private ServletContextPageLocator(ServletContext context) {
+            this.context = context;
+        }
+
+        public URL locate(String path) {
+            URL url = null;
+            try {
+                url = context.getResource(path);
+                if (url == null) {
+                    url = classpathPageLocator.locate(path);
+                }
+            } catch (MalformedURLException e) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("Unable to resolve path "+path+" against the servlet context");
+                }
+            }
+            return url;
+        }
+    }
+}

Modified: struts/struts2/trunk/plugins/codebehind/src/main/resources/struts-plugin.xml
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/codebehind/src/main/resources/struts-plugin.xml?rev=584166&r1=584165&r2=584166&view=diff
==============================================================================
--- struts/struts2/trunk/plugins/codebehind/src/main/resources/struts-plugin.xml (original)
+++ struts/struts2/trunk/plugins/codebehind/src/main/resources/struts-plugin.xml Fri Oct 12 07:07:52 2007
@@ -28,6 +28,7 @@
 
 <struts>
     <bean type="com.opensymphony.xwork2.UnknownHandler" class="org.apache.struts2.codebehind.CodebehindUnknownHandler" />
+    <bean type="com.opensymphony.xwork2.config.PackageProvider" name="codebehind" class="org.apache.struts2.config.ClasspathPackageProvider" />
 
     <constant name="struts.codebehind.pathPrefix" value="/"/>
     <constant name="struts.codebehind.defaultPackage" value="codebehind-default"/>

Modified: struts/struts2/trunk/plugins/codebehind/src/test/java/org/apache/struts2/codebehind/CodebehindUnknownHandlerTest.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/codebehind/src/test/java/org/apache/struts2/codebehind/CodebehindUnknownHandlerTest.java?rev=584166&r1=584165&r2=584166&view=diff
==============================================================================
--- struts/struts2/trunk/plugins/codebehind/src/test/java/org/apache/struts2/codebehind/CodebehindUnknownHandlerTest.java (original)
+++ struts/struts2/trunk/plugins/codebehind/src/test/java/org/apache/struts2/codebehind/CodebehindUnknownHandlerTest.java Fri Oct 12 07:07:52 2007
@@ -22,6 +22,7 @@
 
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.Collections;
 import java.util.HashMap;
 
 import javax.servlet.ServletContext;
@@ -34,9 +35,11 @@
 import com.mockobjects.dynamic.Mock;
 import com.opensymphony.xwork2.ActionContext;
 import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.ActionProxyFactory;
 import com.opensymphony.xwork2.ObjectFactory;
 import com.opensymphony.xwork2.Result;
 import com.opensymphony.xwork2.config.entities.ResultTypeConfig;
+import com.opensymphony.xwork2.util.XWorkTestCaseHelper;
 
 public class CodebehindUnknownHandlerTest extends StrutsTestCase {
 
@@ -44,7 +47,11 @@
     Mock mockServletContext;
     
     public void setUp() throws Exception {
-        super.setUp();
+        configurationManager = XWorkTestCaseHelper.setUp();
+        configuration = configurationManager.getConfiguration();
+        container = configuration.getContainer();
+        actionProxyFactory = container.getInstance(ActionProxyFactory.class);
+        initDispatcher(Collections.singletonMap("actionPackages", "foo.bar"));
         mockServletContext = new Mock(ServletContext.class);
         handler = new CodebehindUnknownHandler("codebehind-default", configuration);
         handler.setPathPrefix("/");

Added: struts/struts2/trunk/plugins/codebehind/src/test/java/org/apache/struts2/config/ClasspathPackageProviderTest.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/codebehind/src/test/java/org/apache/struts2/config/ClasspathPackageProviderTest.java?rev=584166&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/codebehind/src/test/java/org/apache/struts2/config/ClasspathPackageProviderTest.java (added)
+++ struts/struts2/trunk/plugins/codebehind/src/test/java/org/apache/struts2/config/ClasspathPackageProviderTest.java Fri Oct 12 07:07:52 2007
@@ -0,0 +1,103 @@
+/*
+ * $Id: ClasspathPackageProviderTest.java 501717 2007-01-31 03:51:11Z mrdon $
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.struts2.config;
+
+import java.util.Map;
+
+import org.apache.struts2.dispatcher.ServletDispatcherResult;
+
+import com.opensymphony.xwork2.config.Configuration;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+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.impl.DefaultConfiguration;
+
+import junit.framework.TestCase;
+
+public class ClasspathPackageProviderTest extends TestCase {
+
+    ClasspathPackageProvider provider;
+    Configuration config;
+
+    public void setUp() {
+        provider = new ClasspathPackageProvider();
+        provider.setActionPackages("org.apache.struts2.config");
+        config = new DefaultConfiguration();
+        PackageConfig strutsDefault = new PackageConfig("struts-default");
+        strutsDefault.addResultTypeConfig(new ResultTypeConfig("dispatcher", ServletDispatcherResult.class.getName(), "location"));
+        strutsDefault.setDefaultResultType("dispatcher");
+        config.addPackageConfig("struts-default", strutsDefault);
+        PackageConfig customPackage = new PackageConfig("custom-package");
+        customPackage.setNamespace("/custom");
+        config.addPackageConfig("custom-package", customPackage);
+        provider.init(config);
+        provider.loadPackages();
+    }
+
+    public void testFoundRootPackages() {
+        assertEquals(5, config.getPackageConfigs().size());
+        PackageConfig pkg = config.getPackageConfig("org.apache.struts2.config");
+        assertNotNull(pkg);
+        Map configs = pkg.getActionConfigs();
+        assertNotNull(configs);
+        // assertEquals(1, configs.size());
+        ActionConfig actionConfig = (ActionConfig) configs.get("customParentPackage");
+        assertNotNull(actionConfig);
+    }
+
+    public void testParentPackage() {
+        PackageConfig pkg = config.getPackageConfig("org.apache.struts2.config");
+        // assertEquals(2, pkg.getParents().size());
+        Map configs = pkg.getActionConfigs();
+        ActionConfig config = (ActionConfig) configs.get("customParentPackage");
+        assertNotNull(config);
+        assertEquals("/custom", pkg.getNamespace());
+    }
+
+    public void testCustomNamespace() {
+        PackageConfig pkg = config.getPackageConfig("org.apache.struts2.config.CustomNamespaceAction");
+        Map configs = pkg.getAllActionConfigs();
+        // assertEquals(2, configs.size());
+        ActionConfig config = (ActionConfig) configs.get("customNamespace");
+        assertEquals(config.getPackageName(), pkg.getName());
+        assertEquals(1, pkg.getParents().size());
+        assertNotNull(config);
+        assertEquals("/mynamespace", pkg.getNamespace());
+        ActionConfig ac = (ActionConfig) configs.get("customParentPackage");
+        assertNotNull(ac);
+    }
+
+    public void testResultAnnotations() {
+        PackageConfig pkg = config.getPackageConfig("org.apache.struts2.config.cltest");
+        assertEquals("/cltest", pkg.getNamespace());
+        ActionConfig acfg = pkg.getActionConfigs().get("twoResult");
+        assertNotNull(acfg);
+        assertEquals(3, acfg.getResults().size());
+    }
+
+    public void testActionImplementation() {
+        PackageConfig pkg = config.getPackageConfig("org.apache.struts2.config.cltest");
+        assertEquals("/cltest", pkg.getNamespace());
+        ActionConfig acfg = pkg.getActionConfigs().get("actionImpl");
+        assertNotNull(acfg);
+    }
+}