/*
 * $Id: ActionServlet.java 592630 2007-11-07 06:47:58Z pbenedict $
 *
 * 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.struts.action;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.beanutils.converters.BigDecimalConverter;
import org.apache.commons.beanutils.converters.BigIntegerConverter;
import org.apache.commons.beanutils.converters.BooleanConverter;
import org.apache.commons.beanutils.converters.ByteConverter;
import org.apache.commons.beanutils.converters.CharacterConverter;
import org.apache.commons.beanutils.converters.DoubleConverter;
import org.apache.commons.beanutils.converters.FloatConverter;
import org.apache.commons.beanutils.converters.IntegerConverter;
import org.apache.commons.beanutils.converters.LongConverter;
import org.apache.commons.beanutils.converters.ShortConverter;
import org.apache.commons.chain2.config.ConfigParser;
import org.apache.commons.chain2.config.xml.XmlConfigParser;
import org.apache.commons.chain2.impl.CatalogFactoryBase;
import org.apache.commons.digester3.Digester;
import org.apache.commons.digester3.RuleSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.Globals;
import org.apache.struts.config.ActionConfig;
import org.apache.struts.config.ConfigRuleSet;
import org.apache.struts.config.ExceptionConfig;
import org.apache.struts.config.FormBeanConfig;
import org.apache.struts.config.FormPropertyConfig;
import org.apache.struts.config.ForwardConfig;
import org.apache.struts.config.MessageResourcesConfig;
import org.apache.struts.config.ModuleConfig;
import org.apache.struts.config.ModuleConfigFactory;
import org.apache.struts.config.PlugInConfig;
import org.apache.struts.util.MessageResources;
import org.apache.struts.util.MessageResourcesFactory;
import org.apache.struts.util.ModuleUtils;
import org.apache.struts.util.ResponseUtils;
import org.xml.sax.SAXException;

/**
 * <p><strong>ActionServlet</strong> provides the "controller" in the
 * Model-View-Controller (MVC) design pattern for web applications that is
 * commonly known as "Model 2".  This nomenclature originated with a
 * description in the JavaServerPages Specification, version 0.92, and has
 * persisted ever since (in the absence of a better name).</p>
 *
 * <p>Generally, a "Model 2" application is architected as follows:</p>
 *
 * <ul>
 *
 * <li>The user interface will generally be created with server pages, which
 * will not themselves contain any business logic. These pages represent the
 * "view" component of an MVC architecture.</li>
 *
 * <li>Forms and hyperlinks in the user interface that require business logic
 * to be executed will be submitted to a request URI that is mapped to this
 * servlet.</li>
 *
 * <li>There can be <b>one</b> instance of this servlet class, which receives
 * and processes all requests that change the state of a user's interaction
 * with the application. The servlet delegates the handling of a request to a
 * {@link RequestProcessor} object. This component represents the "controller"
 * component of an MVC architecture. </li>
 *
 * <li>The <code>RequestProcessor</code> selects and invokes an {@link Action}
 * class to perform the requested business logic, or delegates the response to
 * another resource.</li>
 *
 * <li>The <code>Action</code> classes can manipulate the state of the
 * application's interaction with the user, typically by creating or modifying
 * JavaBeans that are stored as request or session attributes (depending on
 * how long they need to be available). Such JavaBeans represent the "model"
 * component of an MVC architecture.</li>
 *
 * <li>Instead of producing the next page of the user interface directly,
 * <code>Action</code> classes generally return an {@link ActionForward} to
 * indicate which resource should handle the response. If the
 * <code>Action</code> does not return null, the <code>RequestProcessor</code>
 * forwards or redirects to the specified resource (by utilizing
 * <code>RequestDispatcher.forward</code> or <code>Response.sendRedirect</code>)
 * so as to produce the next page of the user interface.</li>
 *
 * </ul>
 *
 * <p>The standard version of <code>RequestsProcessor</code> implements the
 * following logic for each incoming HTTP request. You can override some or
 * all of this functionality by subclassing this object and implementing your
 * own version of the processing.</p>
 *
 * <ul>
 *
 * <li>Identify, from the incoming request URI, the substring that will be
 * used to select an action procedure.</li>
 *
 * <li>Use this substring to map to the Java class name of the corresponding
 * action class (an implementation of the <code>Action</code> interface).
 * </li>
 *
 * <li>If this is the first request for a particular <code>Action</code>
 * class, instantiate an instance of that class and cache it for future
 * use.</li>
 *
 * <li>Optionally populate the properties of an <code>ActionForm</code> bean
 * associated with this mapping.</li>
 *
 * <li>Call the <code>execute</code> method of this <code>Action</code> class,
 * passing on a reference to the mapping that was used, the relevant form-bean
 * (if any), and the request and the response that were passed to the
 * controller by the servlet container (thereby providing access to any
 * specialized properties of the mapping itself as well as to the
 * ServletContext). </li>
 *
 * </ul>
 *
 * <p>The standard version of <code>ActionServlet</code> is configured based
 * on the following servlet initialization parameters, which you will specify
 * in the web application deployment descriptor (<code>/WEB-INF/web.xml</code>)
 * for your application.  Subclasses that specialize this servlet are free to
 * define additional initialization parameters. </p>
 *
 * <ul>
 *
 * <li><strong>config</strong> - Comma-separated list of context-relative
 * path(s) to the XML resource(s) containing the configuration information for
 * the default module.  (Multiple files support since Struts 1.1)
 * [/WEB-INF/struts-config.xml].</li>
 *
 * <li><strong>config/${module}</strong> - Comma-separated list of
 * Context-relative path(s) to the XML resource(s) containing the
 * configuration information for the module that will use the specified prefix
 * (/${module}). This can be repeated as many times as required for multiple
 * modules. (Since Struts 1.1)</li>
 *
 * <li><strong>configFactory</strong> - The Java class name of the
 * <code>ModuleConfigFactory</code> used to create the implementation of the
 * ModuleConfig interface. </li>
 *
 * <li><strong>convertNull</strong> - Force simulation of the Struts 1.0
 * behavior when populating forms. If set to true, the numeric Java wrapper
 * class types (like <code>java.lang.Integer</code>) will default to null
 * (rather than 0). (Since Struts 1.1) [false] </li>
 *
 * <li><strong>rulesets </strong> - Comma-delimited list of fully qualified
 * classnames of additional <code>org.apache.commons.digester.RuleSet</code>
 * instances that should be added to the <code>Digester</code> that will be
 * processing <code>struts-config.xml</code> files.  By default, only the
 * <code>RuleSet</code> for the standard configuration elements is loaded.
 * (Since Struts 1.1)</li>
 *
 * <li><strong>validating</strong> - Should we use a validating XML parser to
 * process the configuration file (strongly recommended)? [true]</li>
 *
 * <li><strong>chainConfig</strong> - Comma-separated list of either
 * context-relative or classloader path(s) to load commons-chain catalog
 * definitions from.  If none specified, the default Struts catalog that is
 * provided with Struts will be used.</li>
 *
 * </ul>
 *
 * @version $Rev: 592630 $ $Date: 2005-10-14 19:54:16 -0400 (Fri, 14 Oct 2005)
 *          $
 */
public class ActionServlet extends HttpServlet {

    // ----------------------------------------------------- Instance Variables

    /**
     * <p>Comma-separated list of context-relative path(s) to our
     * configuration resource(s) for the default module.</p>
     */
    protected static final String CONFIG = "/WEB-INF/struts-config.xml";

    /**
     * <p>Comma-separated list of context or classloader-relative path(s) that
     * contain the configuration for the default commons-chain
     * catalog(s).</p>
     */
    protected static final String CHAIN_CONFFIG = "org/apache/struts/chain/chain-config.xml";

    /**
     * <p>The Java base name of our internal resources.</p>
     *
     * @since Struts 1.1
     */
    protected static final String INTERNAL_NAME = "org.apache.struts.action.ActionResources";

    /**
     * <p>The set of public identifiers, and corresponding resource names, for
     * the versions of the configuration file DTDs that we know about.  There
     * <strong>MUST</strong> be an even number of Strings in this list!</p>
     */
    static final String[] REGISTRATIONS = {
        "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN",
        "/org/apache/struts/resources/struts-config_1_3.dtd"
    };

    /** serialVersionUID */
    private static final long serialVersionUID = -4155002865548197862L;

    /**
     * <p>Commons Logging instance.</p>
     * @since Struts 1.1
     */
    private static final Log LOG = LogFactory.getLog(ActionServlet.class);


    // ---------------------------------------------------- HttpServlet Methods

    /**
     * <p>Gracefully shut down this controller servlet, releasing any
     * resources that were allocated at initialization.</p>
     */
    @Override
    public void destroy() {
        if (LOG.isDebugEnabled()) {
            LOG.debug(getInternal().getMessage("finalizing"));
        }

        destroyModules();
        getServletContext().removeAttribute(Globals.ACTION_SERVLET_KEY);

        CatalogFactoryBase.clear();
        PropertyUtils.clearDescriptors();

        // Release our LogFactory and Log instances (if any)
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        if (classLoader == null) {
            classLoader = ActionServlet.class.getClassLoader();
        }

        LogFactory.release(classLoader);

        // Servlet container doesn't have the latest version

        // of commons-logging-api.jar installed
        // :FIXME: Why is this dependent on the container's version of
        // commons-logging? Shouldn't this depend on the version packaged
        // with Struts?

        /*
          Reason: LogFactory.release(classLoader); was added as
          an attempt to investigate the OutOfMemory error reported on
          Bugzilla #14042. It was committed for version 1.136 by craigmcc
        */
    }

    /**
     * <p>Initialize this servlet.  Most of the processing has been factored
     * into support methods so that you can override particular functionality
     * at a fairly granular level.</p>
     *
     * @throws ServletException if we cannot configure ourselves correctly
     */
    @Override
    public void init() throws ServletException {
        final String configPrefix = "config/";
        final int configPrefixLength = configPrefix.length() - 1;

        // Wraps the entire initialization in a try/catch to better handle
        // unexpected exceptions and errors to provide better feedback
        // to the developer
        try {
            initOther();
            initServlet();
            initChain();

            getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY, this);
            initModuleConfigFactory();
            String value = getServletConfig().getInitParameter("config");
            // Initialize modules as needed
            ModuleConfig moduleConfig = initModuleConfig("", value != null ? value : CONFIG);
            freeze(moduleConfig);

            for (final String name : Collections.list(getServletConfig().getInitParameterNames())) {
                if (name.startsWith(configPrefix)) {
                    String prefix = name.substring(configPrefixLength);

                    moduleConfig = initModuleConfig(
                            prefix, getServletConfig().getInitParameter(name));
                    freeze(moduleConfig);
                }
            }

            initModulePrefixes(getServletContext());

        } catch (final UnavailableException ex) {
            throw ex;
        } catch (final ServletException t) {
            // The follow error message is not retrieved from internal message
            // resources as they may not have been able to have been
            // initialized
            LOG.error("Unable to initialize Struts ActionServlet due to an "
                + "unexpected exception or error thrown, so marking the "
                + "servlet as unavailable.  Most likely, this is due to an "
                + "incorrect or missing library dependency.", t);
            throw new UnavailableException(t.getMessage());
        }
    }

    /**
     * freeze
     *
     * @param moduleConfig ModuleConfig
     * @throws ServletException ServletException
     */
    private void freeze(final ModuleConfig moduleConfig) throws ServletException {
        initModuleMessageResources(moduleConfig);
        initModulePlugIns(moduleConfig);
        initModuleFormBeans(moduleConfig);
        initModuleForwards(moduleConfig);
        initModuleExceptionConfigs(moduleConfig);
        initModuleActions(moduleConfig);
        moduleConfig.freeze();
    }

    /**
     * <p>Saves a String[] of module prefixes in the ServletContext under
     * Globals.MODULE_PREFIXES_KEY.  <strong>NOTE</strong> - the "" prefix for
     * the default module is not included in this list.</p>
     *
     * @param context The servlet context.
     * @since Struts 1.2
     */
    protected void initModulePrefixes(final ServletContext context) {
        ArrayList<String> prefixList = new ArrayList<>();

        for (final String name : Collections.list(context.getAttributeNames())) {
            if (!name.startsWith(Globals.MODULE_KEY)) {
                continue;
            }

            String prefix = name.substring(Globals.MODULE_KEY.length());
            if (prefix.length() > 0) {
                prefixList.add(prefix);
            }
        }

        String[] prefixes = prefixList.toArray(new String[prefixList.size()]);

        context.setAttribute(Globals.MODULE_PREFIXES_KEY, prefixes);
    }

    /**
     * <p>Process an HTTP "GET" request.</p>
     *
     * @param request  The servlet request we are processing
     * @param response The servlet response we are creating
     * @throws ServletException if a servlet exception occurs
     */
    @Override
    public void doGet(final HttpServletRequest request, final HttpServletResponse response)
            throws ServletException {
        process(request, response);
    }

    /**
     * <p>Process an HTTP "POST" request.</p>
     *
     * @param request  The servlet request we are processing
     * @param response The servlet response we are creating
     * @throws ServletException if a servlet exception occurs
     */
    @Override
    public void doPost(final HttpServletRequest request, final HttpServletResponse response)
            throws ServletException {
        process(request, response);
    }

    // --------------------------------------------------------- Public Methods

    /**
     * <p>Remember a servlet mapping from our web application deployment
     * descriptor, if it is for this servlet.</p>
     *
     * @param servletName The name of the servlet being mapped
     * @param urlPattern  The URL pattern to which this servlet is mapped
     */
    public void addServletMapping(final String servletName, final String urlPattern) {
        if (servletName != null && servletName.equals(getServletConfig().getServletName())) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Process servletName=" + servletName + ", urlPattern=" + urlPattern);
            }
            if (urlPattern != null) {
                getServletContext().setAttribute(Globals.SERVLET_KEY, urlPattern);
            }
        }
    }

    /**
     * <p>Return the <code>MessageResources</code> instance containing our
     * internal message strings.</p>
     *
     * @return the <code>MessageResources</code> instance containing our
     *         internal message strings.
     * @since Struts 1.1
     */
    public MessageResources getInternal() {
        return MessageResourcesFactory.getMessageResources(INTERNAL_NAME);
    }

    // ------------------------------------------------------ Protected Methods

    /**
     * <p>Gracefully terminate use of any modules associated with this
     * application (if any).</p>
     *
     * @since Struts 1.1
     */
    protected void destroyModules() {
        ArrayList<String> values =
                new ArrayList<>(Collections.list(getServletContext().getAttributeNames()));

        for (final String name : values) {
            Object value = getServletContext().getAttribute(name);
            if (!(value instanceof ModuleConfig)) {
                continue;
            }

            ModuleConfig config = (ModuleConfig) value;

            if (getProcessorForModule(config) != null) {
                getProcessorForModule(config).destroy();
            }

            getServletContext().removeAttribute(name);

            PlugIn[] plugIns = (PlugIn[]) getServletContext().getAttribute(
                    Globals.PLUG_INS_KEY + config.getPrefix());
            if (plugIns != null) {
                for (int i = 0; i < plugIns.length; i++) {
                    int j = plugIns.length - (i + 1);
                    plugIns[j].destroy();
                }

                getServletContext().removeAttribute(Globals.PLUG_INS_KEY + config.getPrefix());
            }
        }
    }

    /**
     * <p>Return the module configuration object for the currently selected
     * module.</p>
     *
     * @param request The servlet request we are processing
     * @return The module configuration object for the currently selected
     *         module.
     * @since Struts 1.1
     */
    protected ModuleConfig getModuleConfig(final HttpServletRequest request) {
        ModuleConfig config = (ModuleConfig) request.getAttribute(Globals.MODULE_KEY);
        if (config == null) {
            config = (ModuleConfig) getServletContext().getAttribute(Globals.MODULE_KEY);
        }
        return config;
    }

    /**
     * <p>Look up and return the {@link RequestProcessor} responsible for the
     * specified module, creating a new one if necessary.</p>
     *
     * @param config The module configuration for which to acquire and return
     *               a RequestProcessor.
     * @return The {@link RequestProcessor} responsible for the specified
     *         module,
     * @throws ServletException If we cannot instantiate a RequestProcessor
     *                          instance a {@link UnavailableException} is
     *                          thrown, meaning your application is not loaded
     *                          and will not be available.
     * @since Struts 1.1
     */
    protected synchronized RequestProcessor getRequestProcessor(
            final ModuleConfig config) throws ServletException {

        RequestProcessor processor = getProcessorForModule(config);
        if (processor == null) {
            try {
                processor = ResponseUtils.applicationInstance(
                        config.getControllerConfig().getProcessorClass());

            } catch (final ReflectiveOperationException e) {
                throw new UnavailableException(
                    "Cannot initialize RequestProcessor of class "
                    + config.getControllerConfig().getProcessorClass() + ": "
                    + e);
            }

            processor.init(this, config);

            String key = Globals.REQUEST_PROCESSOR_KEY + config.getPrefix();

            getServletContext().setAttribute(key, processor);
        }

        return processor;
    }

    /**
     * <p>Returns the RequestProcessor for the given module or null if one
     * does not exist.  This method will not create a RequestProcessor.</p>
     *
     * @param config The ModuleConfig.
     * @return The <code>RequestProcessor</code> for the given module, or
     *         <code>null</code> if one does not exist.
     */
    private RequestProcessor getProcessorForModule(final ModuleConfig config) {
        String key = Globals.REQUEST_PROCESSOR_KEY + config.getPrefix();
        return (RequestProcessor) getServletContext().getAttribute(key);
    }

    /**
     * <p>Initialize the factory used to create the module configuration.</p>
     *
     * @since Struts 1.2
     */
    protected void initModuleConfigFactory() {
        String configFactory = getServletConfig().getInitParameter("configFactory");
        if (configFactory != null) {
            ModuleConfigFactory.setFactoryClass(configFactory);
        }
    }

    /**
     * <p>Initialize the module configuration information for the specified
     * module.</p>
     *
     * @param prefix Module prefix for this module
     * @param paths  Comma-separated list of context-relative resource path(s)
     *               for this modules's configuration resource(s)
     * @return The new module configuration instance.
     * @throws ServletException if initialization cannot be performed
     * @since Struts 1.1
     */
    protected ModuleConfig initModuleConfig(final String prefix, final String paths)
            throws ServletException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Initializing module path '" + prefix
                + "' configuration from '" + paths + "'");
        }

        // Parse the configuration for this module
        ModuleConfigFactory factoryObject = ModuleConfigFactory.createFactory();
        ModuleConfig config = factoryObject.createModuleConfig(prefix);

        // Configure the Digester instance we will use
        Digester digester = initConfigDigester();

        for (final URL url : splitAndResolvePaths(paths)) {
            digester.push(config);
            parseModuleConfigFile(digester, url);
        }

        getServletContext().setAttribute(Globals.MODULE_KEY + config.getPrefix(), config);

        return config;
    }

    /**
     * <p>Parses one module config file.</p>
     *
     * @param digester Digester instance that does the parsing
     * @param url      The url to the config file to parse.
     * @throws UnavailableException if file cannot be read or parsed
     * @since Struts 1.3
     */
    protected void parseModuleConfigFile(final Digester digester, final URL url)
            throws UnavailableException {
        try {
            digester.parse(url);
        } catch (final IOException | SAXException e) {
            handleConfigException(url.toString(), e);
        }
    }

    /**
     * <p>Simplifies exception handling in the parseModuleConfigFile
     * method.<p>
     *
     * @param path The path to which the exception relates.
     * @param e    The exception to be wrapped and thrown.
     * @throws UnavailableException as a wrapper around Exception
     */
    private void handleConfigException(final String path, final Exception e)
            throws UnavailableException {
        String msg = getInternal().getMessage("configParse", path);
        LOG.error(msg, e);
        throw new UnavailableException(msg);
    }

    /**
     * <p>Handle errors related to creating an instance of the specified
     * class.</p>
     *
     * @param className The className that could not be instantiated.
     * @param e         The exception that was caught.
     * @throws ServletException to communicate the error.
     */
    private void handleCreationException(final String className, final Exception e)
            throws ServletException {
        String errorMessage = getInternal().getMessage("configExtends.creation", className);
        LOG.error(errorMessage, e);
        throw new UnavailableException(errorMessage);
    }

    /**
     * <p>Handle errors caused by required fields that were not
     * specified.</p>
     *
     * @param field      The name of the required field that was not found.
     * @param configType The type of configuration object of configName.
     * @param configName The name of the config that's missing the required
     *                   value.
     * @throws ServletException to communicate the error.
     */
    private void handleValueRequiredException(final String field, final String configType,
            final String configName) throws ServletException {
        String errorMessage = getInternal().getMessage(
                "configFieldRequired", field, configType, configName);

        LOG.error(errorMessage);
        throw new UnavailableException(errorMessage);
    }

    /**
     * <p>Initialize the plug ins for the specified module.</p>
     *
     * @param config ModuleConfig information for this module
     * @throws ServletException if initialization cannot be performed
     * @since Struts 1.1
     */
    protected void initModulePlugIns(final ModuleConfig config) throws ServletException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Initializing module path '" + config.getPrefix() + "' plug ins");
        }

        PlugInConfig[] plugInConfigs = config.findPlugInConfigs();
        PlugIn[] plugIns = new PlugIn[plugInConfigs.length];

        getServletContext().setAttribute(Globals.PLUG_INS_KEY + config.getPrefix(), plugIns);

        for (int i = 0; i < plugIns.length; i++) {
            try {
                plugIns[i] = ResponseUtils.applicationInstance(plugInConfigs[i].getClassName());
                BeanUtils.populate(plugIns[i], plugInConfigs[i].getProperties());

                // Pass the current plugIn config object to the PlugIn.
                // The property is set only if the plugin declares it.
                // This plugin config object is needed by Tiles
                try {
                    PropertyUtils.setProperty(plugIns[i],
                        "currentPlugInConfigObject", plugInConfigs[i]);
                } catch (final ReflectiveOperationException e) {
                    LOG.info(e.getMessage());

                    // Whenever we fail silently, we must document a valid
                    // reason for doing so.  Why should we fail silently if a
                    // property can't be set on the plugin?

                }

                plugIns[i].init(this, config);

            } catch (final ReflectiveOperationException e) {
                String errMsg = getInternal().getMessage(
                        "plugIn.init", plugInConfigs[i].getClassName());
                log(errMsg, e);
                throw new UnavailableException(errMsg);
            }
        }
    }

    /**
     * <p>Initialize the form beans for the specified module.</p>
     *
     * @param config ModuleConfig information for this module
     * @throws ServletException if initialization cannot be performed
     * @since Struts 1.3
     */
    protected void initModuleFormBeans(final ModuleConfig config) throws ServletException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Initializing module path '" + config.getPrefix() + "' form beans");
        }

        // Process form bean extensions.
        for (final FormBeanConfig beanConfig : config.findFormBeanConfigs()) {
            processFormBeanExtension(beanConfig, config);
        }

        for (final FormBeanConfig beanConfig : config.findFormBeanConfigs()) {
            // Verify that required fields are all present for the form config
            if (beanConfig.getType() == null) {
                handleValueRequiredException("type", beanConfig.getName(), "form bean");
            }

            // ... and the property configs
            for (final FormPropertyConfig property : beanConfig.findFormPropertyConfigs()) {
                if (property.getType() == null) {
                    handleValueRequiredException(
                            "type", property.getName(), "form property");
                }
            }
        }
    }

    /**
     * <p>Extend the form bean's configuration as necessary.</p>
     *
     * @param beanConfig   the configuration to process.
     * @param moduleConfig the module configuration for this module.
     * @throws ServletException if initialization cannot be performed.
     */
    protected void processFormBeanExtension(final FormBeanConfig beanConfig,
            final ModuleConfig moduleConfig) throws ServletException {
        if (!beanConfig.isExtensionProcessed()) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Processing extensions for '" + beanConfig.getName() + "'");
            }

            FormBeanConfig bc = processFormBeanConfigClass(beanConfig, moduleConfig);

            bc.processExtends(moduleConfig.getFormBeanConfigs());
        }
    }

    /**
     * <p>Checks if the current beanConfig is using the correct class based on
     * the class of its ancestor form bean config.</p>
     *
     * @param beanConfig   The form bean to check.
     * @param moduleConfig The config for the current module.
     * @return The form bean config using the correct class as determined by
     *         the config's ancestor and its own overridden value.
     * @throws UnavailableException if an instance of the form bean config
     *                              class cannot be created.
     * @throws ServletException     on class creation error
     */
    protected FormBeanConfig processFormBeanConfigClass(
            final FormBeanConfig beanConfig, final ModuleConfig moduleConfig)
            throws ServletException {

        String ancestor = beanConfig.getExtends();
        if (ancestor == null) {
            // Nothing to do, then
            return beanConfig;
        }

        // Make sure that this bean is of the right class
        FormBeanConfig baseConfig = moduleConfig.findFormBeanConfig(ancestor);
        if (baseConfig == null) {
            throw new UnavailableException("Unable to find " + "form bean '"
                + ancestor + "' to extend.");
        }

        // Was our bean's class overridden already?
        if (beanConfig.getClass().equals(FormBeanConfig.class)) {
            // Ensure that our bean is using the correct class
            if (!baseConfig.getClass().equals(beanConfig.getClass())) {
                // Replace the bean with an instance of the correct class
                FormBeanConfig newBeanConfig = null;
                String baseConfigClassName = baseConfig.getClass().getName();

                try {
                    newBeanConfig =
                        ResponseUtils.applicationInstance(baseConfigClassName);

                    // copy the values
                    BeanUtils.copyProperties(newBeanConfig, beanConfig);

                    for (final FormPropertyConfig fpc : beanConfig.findFormPropertyConfigs()) {
                        newBeanConfig.addFormPropertyConfig(fpc);
                    }

                } catch (final ReflectiveOperationException e) {
                    handleCreationException(baseConfigClassName, e);
                }

                // replace beanConfig with newBeanConfig
                moduleConfig.removeFormBeanConfig(beanConfig);
                moduleConfig.addFormBeanConfig(newBeanConfig);
                return newBeanConfig;
            }
        }

        return beanConfig;
    }

    /**
     * <p>Initialize the forwards for the specified module.</p>
     *
     * @param config ModuleConfig information for this module
     * @throws ServletException if initialization cannot be performed
     */
    protected void initModuleForwards(final ModuleConfig config)
            throws ServletException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Initializing module path '" + config.getPrefix() + "' forwards");
        }

        // Process forwards extensions.
        for (final ForwardConfig forward : config.findForwardConfigs()) {
            processForwardExtension(forward, config, null);
        }

        for (final ForwardConfig forward : config.findForwardConfigs()) {
            // Verify that required fields are all present for the forward
            if (forward.getPath() == null) {
                handleValueRequiredException(
                        "path", forward.getName(), "global forward");
            }
        }
    }

    /**
     * <p>Extend the forward's configuration as necessary.  If actionConfig is
     * provided, then this method will process the forwardConfig as part
     * of that actionConfig.  If actionConfig is null, the forwardConfig
     * will be processed as a global forward.</p>
     *
     * @param forwardConfig the configuration to process.
     * @param moduleConfig  the module configuration for this module.
     * @param actionConfig  If applicable, the config for the current action.
     * @throws ServletException if initialization cannot be performed.
     */
    protected void processForwardExtension(final ForwardConfig forwardConfig,
            final ModuleConfig moduleConfig, final ActionConfig actionConfig)
            throws ServletException {
        if (!forwardConfig.isExtensionProcessed()) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Processing extensions for '" + forwardConfig.getName() + "'");
            }

            ForwardConfig fc = processForwardConfigClass(
                    forwardConfig, moduleConfig, actionConfig);

            ActionConfig.extendsForwardConfig(moduleConfig, actionConfig, fc);
        }
    }

    /**
     * <p>Checks if the current forwardConfig is using the correct class based
     * on the class of its configuration ancestor.  If actionConfig is
     * provided, then this method will process the forwardConfig as part
     * of that actionConfig.  If actionConfig is null, the forwardConfig
     * will be processed as a global forward.</p>
     *
     * @param forwardConfig The forward to check.
     * @param moduleConfig  The config for the current module.
     * @param actionConfig  If applicable, the config for the current action.
     * @return The forward config using the correct class as determined by the
     *         config's ancestor and its own overridden value.
     * @throws UnavailableException if an instance of the forward config class
     *                              cannot be created.
     * @throws ServletException     on class creation error
     */
    protected ForwardConfig processForwardConfigClass(
            final ForwardConfig forwardConfig, final ModuleConfig moduleConfig,
            final ActionConfig actionConfig)
            throws ServletException {

        String ancestor = forwardConfig.getExtends();
        if (ancestor == null) {
            // Nothing to do, then
            return forwardConfig;
        }

        // Make sure that this config is of the right class
        ForwardConfig baseConfig = null;
        if (actionConfig != null) {
            // Look for this in the actionConfig
            baseConfig = actionConfig.findForwardConfig(ancestor);
        }

        if (baseConfig == null) {
            // Either this is a forwardConfig that inherits a global config,
            //  or actionConfig is null
            baseConfig = moduleConfig.findForwardConfig(ancestor);
        }

        if (baseConfig == null) {
            throw new UnavailableException("Unable to find " + "forward '"
                + ancestor + "' to extend.");
        }

        // Was our forwards's class overridden already?
        if (forwardConfig.getClass().equals(ActionForward.class)) {
            // Ensure that our forward is using the correct class
            if (!baseConfig.getClass().equals(forwardConfig.getClass())) {
                // Replace the config with an instance of the correct class
                ForwardConfig newForwardConfig = null;
                String baseConfigClassName = baseConfig.getClass().getName();

                try {
                    newForwardConfig = ResponseUtils.applicationInstance(
                                baseConfigClassName);

                    // copy the values
                    BeanUtils.copyProperties(newForwardConfig, forwardConfig);

                } catch (final ReflectiveOperationException e) {
                    handleCreationException(baseConfigClassName, e);
                }

                // replace forwardConfig with newForwardConfig
                if (actionConfig != null) {
                    actionConfig.removeForwardConfig(forwardConfig);
                    actionConfig.addForwardConfig(newForwardConfig);
                } else {
                    // this is a global forward
                    moduleConfig.removeForwardConfig(forwardConfig);
                    moduleConfig.addForwardConfig(newForwardConfig);
                }
                return newForwardConfig;
            }
        }

        return forwardConfig;
    }

    /**
     * <p>Initialize the exception handlers for the specified module.</p>
     *
     * @param config ModuleConfig information for this module
     * @throws ServletException if initialization cannot be performed
     * @since Struts 1.3
     */
    protected void initModuleExceptionConfigs(final ModuleConfig config)
            throws ServletException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Initializing module path '" + config.getPrefix() + "' forwards");
        }

        // Process exception config extensions.
        for (final ExceptionConfig exception : config.findExceptionConfigs()) {
            processExceptionExtension(exception, config, null);
        }

        for (final ExceptionConfig exception : config.findExceptionConfigs()) {
            // Verify that required fields are all present for the config
            if (exception.getKey() == null) {
                handleValueRequiredException(
                        "key", exception.getType(), "global exception config");
            }
        }
    }

    /**
     * <p>Extend the exception's configuration as necessary. If actionConfig is
     * provided, then this method will process the exceptionConfig as part
     * of that actionConfig.  If actionConfig is null, the exceptionConfig
     * will be processed as a global forward.</p>
     *
     * @param exceptionConfig the configuration to process.
     * @param moduleConfig    the module configuration for this module.
     * @param actionConfig  If applicable, the config for the current action.
     * @throws ServletException if initialization cannot be performed.
     */
    protected void processExceptionExtension(final ExceptionConfig exceptionConfig,
            final ModuleConfig moduleConfig, final ActionConfig actionConfig)
            throws ServletException {
        if (!exceptionConfig.isExtensionProcessed()) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Processing extensions for '" + exceptionConfig.getType() + "'");
            }

            ExceptionConfig ec = processExceptionConfigClass(
                    exceptionConfig, moduleConfig, actionConfig);

            ActionConfig.extendsExceptionConfig(moduleConfig, actionConfig, ec);
        }
    }

    /**
     * <p>Checks if the current exceptionConfig is using the correct class
     * based on the class of its configuration ancestor. If actionConfig is
     * provided, then this method will process the exceptionConfig as part
     * of that actionConfig.  If actionConfig is null, the exceptionConfig
     * will be processed as a global forward.</p>
     *
     * @param exceptionConfig The config to check.
     * @param moduleConfig    The config for the current module.
     * @param actionConfig  If applicable, the config for the current action.
     * @return The exception config using the correct class as determined by
     *         the config's ancestor and its own overridden value.
     * @throws ServletException if an instance of the exception config class
     *                          cannot be created.
     */
    protected ExceptionConfig processExceptionConfigClass(
            final ExceptionConfig exceptionConfig, final ModuleConfig moduleConfig,
            final ActionConfig actionConfig)
            throws ServletException {

        String ancestor = exceptionConfig.getExtends();
        if (ancestor == null) {
            // Nothing to do, then
            return exceptionConfig;
        }

        // Make sure that this config is of the right class
        ExceptionConfig baseConfig = null;
        if (actionConfig != null) {
            baseConfig = actionConfig.findExceptionConfig(ancestor);
        }

        if (baseConfig == null) {
            // This means either there's no actionConfig anyway, or the
            // ancestor is not defined within the action.
            baseConfig = moduleConfig.findExceptionConfig(ancestor);
        }

        if (baseConfig == null) {
            throw new UnavailableException("Unable to find "
                + "exception config '" + ancestor + "' to extend.");
        }

        // Was our config's class overridden already?
        if (exceptionConfig.getClass().equals(ExceptionConfig.class)) {
            // Ensure that our config is using the correct class
            if (!baseConfig.getClass().equals(exceptionConfig.getClass())) {
                // Replace the config with an instance of the correct class
                ExceptionConfig newExceptionConfig = null;
                String baseConfigClassName = baseConfig.getClass().getName();

                try {
                    newExceptionConfig = ResponseUtils.applicationInstance(
                            baseConfigClassName);

                    // copy the values
                    BeanUtils.copyProperties(newExceptionConfig, exceptionConfig);

                } catch (final ReflectiveOperationException e) {
                    handleCreationException(baseConfigClassName, e);
                }

                // replace exceptionConfig with newExceptionConfig
                if (actionConfig != null) {
                    actionConfig.removeExceptionConfig(exceptionConfig);
                    actionConfig.addExceptionConfig(newExceptionConfig);
                } else {
                    moduleConfig.removeExceptionConfig(exceptionConfig);
                    moduleConfig.addExceptionConfig(newExceptionConfig);
                }
                return newExceptionConfig;
            }
        }

        return exceptionConfig;
    }

    /**
     * <p>Initialize the action configs for the specified module.</p>
     *
     * @param config ModuleConfig information for this module
     * @throws ServletException if initialization cannot be performed
     * @since Struts 1.3
     */
    protected void initModuleActions(final ModuleConfig config) throws ServletException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Initializing module path '" + config.getPrefix() + "' action configs");
        }

        // Process ActionConfig extensions.
        for (final ActionConfig actionConfig : config.findActionConfigs()) {
            processActionConfigExtension(actionConfig, config);
        }

        for (final ActionConfig actionConfig : config.findActionConfigs()) {
            // Verify that required fields are all present for the forward configs
            for (final ForwardConfig forward : actionConfig.findForwardConfigs()) {
                if (forward.getPath() == null) {
                    handleValueRequiredException(
                            "path", forward.getName(), "action forward");
                }
            }

            // ... and the exception configs
            for (final ExceptionConfig exception : actionConfig.findExceptionConfigs()) {
                if (exception.getKey() == null) {
                    handleValueRequiredException(
                            "key", exception.getType(), "action exception config");
                }
            }
        }
    }

    /**
     * <p>Extend the action's configuration as necessary.</p>
     *
     * @param actionConfig the configuration to process.
     * @param moduleConfig the module configuration for this module.
     * @throws ServletException if initialization cannot be performed.
     */
    protected void processActionConfigExtension(final ActionConfig actionConfig,
            final ModuleConfig moduleConfig) throws ServletException {

        if (!actionConfig.isExtensionProcessed()) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Processing extensions for '" + actionConfig.getPath() + "'");
            }

            ActionConfig ac = processActionConfigClass(actionConfig, moduleConfig);
            ac.processExtends(moduleConfig.getActionConfigs());
        }

        // Process forwards extensions.
        for (final ForwardConfig forward : actionConfig.findForwardConfigs()) {
            processForwardExtension(forward, moduleConfig, actionConfig);
        }

        // Process exception extensions.
        for (final ExceptionConfig exception : actionConfig.findExceptionConfigs()) {
            processExceptionExtension(exception, moduleConfig, actionConfig);
        }
    }

    /**
     * <p>Checks if the current actionConfig is using the correct class based
     * on the class of its ancestor ActionConfig.</p>
     *
     * @param actionConfig The action config to check.
     * @param moduleConfig The config for the current module.
     * @return The config object using the correct class as determined by the
     *         config's ancestor and its own overridden value.
     * @throws ServletException if an instance of the action config class
     *                          cannot be created.
     */
    protected ActionConfig processActionConfigClass(final ActionConfig actionConfig,
            final ModuleConfig moduleConfig) throws ServletException {

        String ancestor = actionConfig.getExtends();
        if (ancestor == null) {
            // Nothing to do, then
            return actionConfig;
        }

        // Make sure that this config is of the right class
        ActionConfig baseConfig = moduleConfig.findActionConfig(ancestor);
        if (baseConfig == null) {
            throw new UnavailableException("Unable to find "
                + "action config for '" + ancestor + "' to extend.");
        }

        // Was our actionConfig's class overridden already?
        if (actionConfig.getClass().equals(ActionMapping.class)) {
            // Ensure that our config is using the correct class
            if (!baseConfig.getClass().equals(actionConfig.getClass())) {
                // Replace the config with an instance of the correct class
                ActionConfig newActionConfig = null;
                String baseConfigClassName = baseConfig.getClass().getName();

                try {
                    newActionConfig =
                        ResponseUtils.applicationInstance(baseConfigClassName);

                    // copy the values
                    BeanUtils.copyProperties(newActionConfig, actionConfig);

                    // copy the forward and exception configs, too
                    for (final ForwardConfig forward : actionConfig.findForwardConfigs()) {
                        newActionConfig.addForwardConfig(forward);
                    }

                    for (final ExceptionConfig exception : actionConfig.findExceptionConfigs()) {
                        newActionConfig.addExceptionConfig(exception);
                    }

                } catch (final ReflectiveOperationException e) {
                    handleCreationException(baseConfigClassName, e);
                }

                // replace actionConfig with newActionConfig
                moduleConfig.removeActionConfig(actionConfig);
                moduleConfig.addActionConfig(newActionConfig);
                return newActionConfig;
            }
        }

        return actionConfig;
    }

    /**
     * <p>Initialize the application <code>MessageResources</code> for the
     * specified module.</p>
     *
     * @param config ModuleConfig information for this module
     * @since Struts 1.1
     */
    protected void initModuleMessageResources(final ModuleConfig config) {
        for (final MessageResourcesConfig mrc : config.findMessageResourcesConfigs()) {
            if ((mrc.getFactory() == null) || (mrc.getParameter() == null)) {
                continue;
            }

            if (LOG.isDebugEnabled()) {
                LOG.debug("Initializing module path '" + config.getPrefix()
                    + "' message resources from '" + mrc.getParameter() + "'");
            }

            MessageResourcesFactory factoryObject =
                    MessageResourcesFactory.createFactory(mrc.getFactory());

            MessageResources resources = factoryObject.createResources(mrc);

            getServletContext().setAttribute(mrc.getKey() + config.getPrefix(), resources);
        }
    }

    /**
     * <p>Create (if needed) and return a new <code>Digester</code> instance
     * that has been initialized to process Struts module configuration files
     * and configure a corresponding <code>ModuleConfig</code> object (which
     * must be pushed on to the evaluation stack before parsing begins).</p>
     *
     * @return A new configured <code>Digester</code> instance.
     * @throws ServletException if a Digester cannot be configured
     * @since Struts 1.1
     */
    protected Digester initConfigDigester() throws ServletException {

        // Create a new Digester instance with standard capabilities
        Digester configDigester = new Digester();
        configDigester.setNamespaceAware(true);
        configDigester.setValidating(isValidating());
        configDigester.setUseContextClassLoader(true);
        configDigester.addRuleSet(new ConfigRuleSet());

        for (int i = 0; i < REGISTRATIONS.length; i += 2) {
            URL url = ActionServlet.class.getResource(REGISTRATIONS[i + 1]);
            if (url != null) {
                configDigester.register(REGISTRATIONS[i], url.toString());
            }
        }

        addRuleSets(configDigester);

        // Return the completely configured Digester instance
        return configDigester;
    }

    /**
     * <p>Add any custom RuleSet instances to configDigester that have been
     * specified in the <code>rulesets</code> init parameter.</p>
     * @param configDigester Digester
     * @throws ServletException if an error occurs
     */
    private void addRuleSets(final Digester configDigester)
            throws ServletException {

        String rulesets = getServletConfig().getInitParameter("rulesets");
        if (rulesets == null) {
            return;
        }

        for (String ruleset : rulesets.split(",")) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Configuring custom Digester Ruleset of type " + ruleset.trim());
            }

            try {
                RuleSet instance = ResponseUtils.applicationInstance(ruleset.trim());
                configDigester.addRuleSet(instance);

            } catch (final ReflectiveOperationException e) {
                LOG.error("Exception configuring custom Digester RuleSet", e);
                throw new ServletException(e);
            }
        }
    }

    /**
     * <p>Check the status of the <code>validating</code> initialization
     * parameter.</p>
     *
     * @return true if the module Digester should validate.
     */
    private boolean isValidating() {
        boolean validating = true;

        String value = getServletConfig().getInitParameter("validating");
        if ("false".equalsIgnoreCase(value) || "no".equalsIgnoreCase(value)
            || "n".equalsIgnoreCase(value) || "0".equalsIgnoreCase(value)) {
            validating = false;
        }

        return validating;
    }

    /**
     * <p>Parse the configuration documents specified by the
     * <code>chainConfig</code> init-param to configure the default {@link
     * org.apache.commons.chain2.Catalog} that is registered in the {@link
     * CatalogFactoryBase} instance for this application.</p>
     *
     * @throws ServletException if an error occurs.
     */
    protected void initChain() throws ServletException {
        // Parse the configuration file specified by path or resource
        try {
            String value = getServletConfig().getInitParameter("chainConfig");
            if (value == null) {
                value = CHAIN_CONFFIG;
            }

            ConfigParser parser = new XmlConfigParser();
            for (final URL resource : splitAndResolvePaths(value)) {
                LOG.info("Loading chain catalog from " + resource);
                parser.parse(resource);
            }

        } catch (final ServletException e) {
            LOG.error("Exception loading resources", e);
            throw e;
        }
    }

    /**
     * <p>Initialize other global characteristics of the controller
     * servlet.</p>
     */
    protected void initOther() {

        // Backwards compatibility for form beans of Java wrapper classes
        // Set to true for strict Struts 1.0 compatibility
        String value = getServletConfig().getInitParameter("convertNull");
        boolean convertNull = false;
        if ("true".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value)
            || "on".equalsIgnoreCase(value) || "y".equalsIgnoreCase(value)
            || "1".equalsIgnoreCase(value)) {
            convertNull = true;
        }

        if (convertNull) {
            ConvertUtils.deregister();
            ConvertUtils.register(new BigDecimalConverter(null), BigDecimal.class);
            ConvertUtils.register(new BigIntegerConverter(null), BigInteger.class);
            ConvertUtils.register(new BooleanConverter(null), Boolean.class);
            ConvertUtils.register(new ByteConverter(null), Byte.class);
            ConvertUtils.register(new CharacterConverter(null), Character.class);
            ConvertUtils.register(new DoubleConverter(null), Double.class);
            ConvertUtils.register(new FloatConverter(null), Float.class);
            ConvertUtils.register(new IntegerConverter(null), Integer.class);
            ConvertUtils.register(new LongConverter(null), Long.class);
            ConvertUtils.register(new ShortConverter(null), Short.class);
        }
    }

    /**
     * <p>Initialize the servlet mapping under which our controller servlet is
     * being accessed.  This will be used in the <code>&html:form&gt;</code>
     * tag to generate correct destination URLs for form submissions.</p>
     *
     * @throws ServletException if error happens while scanning web.xml
     */
    protected void initServlet() throws ServletException {

        // Prepare a Digester to scan the web application deployment descriptor
        Digester digester = new Digester();
        digester.push(this);
        digester.setNamespaceAware(true);
        digester.setValidating(false);

        // Register our local copy of the DTDs that we can find
        for (int i = 0; i < REGISTRATIONS.length; i += 2) {
            URL url = ActionServlet.class.getResource(REGISTRATIONS[i + 1]);
            if (url != null) {
                digester.register(REGISTRATIONS[i], url.toString());
            }
        }

        // Configure the processing rules that we need
        digester.addCallMethod("web-app/servlet-mapping", "addServletMapping", 2);
        digester.addCallParam("web-app/servlet-mapping/servlet-name", 0);
        digester.addCallParam("web-app/servlet-mapping/url-pattern", 1);

        // Process the web application deployment descriptor
        if (LOG.isDebugEnabled()) {
            LOG.debug("Scanning web.xml for controller servlet mapping");
        }

        MessageResources internal = getInternal();

        try (InputStream input =
            getServletContext().getResourceAsStream("/WEB-INF/web.xml")) {

            if (input == null) {
                LOG.error(internal.getMessage("configWebXml"));
                throw new ServletException(internal.getMessage("configWebXml"));
            }

            digester.parse(input);

        } catch (final IOException | SAXException e) {
            LOG.error(internal.getMessage("configWebXml"), e);
            throw new ServletException(e);
        }

        // Record a servlet context attribute (if appropriate)
        if (LOG.isDebugEnabled()) {
            LOG.debug("Mapping for servlet '" + getServletConfig().getServletName() + "' = '"
                + getServletContext().getAttribute(Globals.SERVLET_KEY) + "'");
        }
    }

    /**
     * <p>Takes a comma-delimited string and splits it into paths, then
     * resolves those paths using the ServletContext and appropriate
     * ClassLoader.  When loading from the classloader, multiple resources per
     * path are supported to support, for example, multiple jars containing
     * the same named config file.</p>
     *
     * @param paths A comma-delimited string of paths
     * @return A list of resolved URL's for all found resources
     * @throws ServletException if a servlet exception is thrown
     */
    protected List<URL> splitAndResolvePaths(final String paths)
            throws ServletException {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        if (loader == null) {
            loader = getClass().getClassLoader();
        }

        ArrayList<URL> resolvedUrls = new ArrayList<>();

        String path = null;
        String pt = paths;
        try {
            // Process each specified resource path
            while (!pt.isEmpty()) {

                int comma = pt.indexOf(',');
                if (comma >= 0) {
                    path = pt.substring(0, comma).trim();
                    pt = pt.substring(comma + 1);
                } else {
                    path = pt.trim();
                    pt = "";
                }

                if (path.isEmpty()) {
                    break;
                }

                if (path.charAt(0) == '/') {
                    URL resource = getServletContext().getResource(path);
                    resolvedUrls.add(resource);
                } else {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Unable to locate " + path
                            + " in the servlet context, trying classloader.");
                    }

                    List<URL> e = Collections.list(loader.getResources(path));
                    resolvedUrls.addAll(e);

                    if (e.isEmpty()) {
                        String msg = getInternal().getMessage("configMissing", path);
                        LOG.error(msg);
                        throw new UnavailableException(msg);
                    }
                }
            }

        } catch (final IOException e) {
            handleConfigException(path, e);
        }

        return resolvedUrls;
    }

    /**
     * <p>Perform the standard request processing for this request, and create
     * the corresponding response.</p>
     *
     * @param request  The servlet request we are processing
     * @param response The servlet response we are creating
     * @throws ServletException if a servlet exception is thrown
     */
    protected void process(final HttpServletRequest request, final HttpServletResponse response)
            throws ServletException {
        ModuleUtils.getInstance().selectModule(request, getServletContext());

        ModuleConfig config = getModuleConfig(request);

        RequestProcessor processor = getProcessorForModule(config);
        if (processor == null) {
            processor = getRequestProcessor(config);
        }

        processor.process(request, response);
    }
}
