/*
 * $Id: TestActionServlet.java 471754 2006-11-06 14:55:09Z husted $
 *
 * 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.net.URL;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.UnavailableException;

import org.apache.struts.config.ActionConfig;
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.ModuleConfig;
import org.apache.struts.config.ModuleConfigFactory;
import org.junit.Assert;
import org.junit.Test;

/**
 * Suite of unit tests for the <code>org.apache.struts.action.ActionServlet</code>
 * class.
 */
public class TestActionServlet {

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

    // ------------------------------------------ Constructors, suite, and main

    // ------------------------------------------------- setUp() and tearDown()

    /**
     * create ExceptionConfig
     *
     * @return ExceptionConfig
     */
    protected ExceptionConfig createExceptionConfig() {
        // Setup the exception handler
        ExceptionConfig baseException = new ExceptionConfig();
        baseException.setType("java.lang.NullPointerException");
        baseException.setKey("msg.exception.npe");
        return baseException;
    }

    /**
     * create ActionMapping
     *
     * @return ActionMapping
     */
    protected ActionMapping createActionMapping() {

        // Setup the action config
        ActionMapping baseAction = new ActionMapping();
        baseAction.setPath("/index");
        baseAction.setType("org.apache.struts.actions.DummyAction");
        baseAction.setName("someForm");
        baseAction.setInput("/input.jsp");
        baseAction.addForwardConfig(new ActionForward("next", "/next.jsp", false));
        baseAction.addForwardConfig(new ActionForward("prev", "/prev.jsp", false));

        ExceptionConfig exceptionConfig = new ExceptionConfig();

        exceptionConfig.setType("java.sql.SQLException");
        exceptionConfig.setKey("msg.exception.sql");
        baseAction.addExceptionConfig(exceptionConfig);

        return baseAction;
    }

    /**
     * create FormBeanConfig
     *
     * @return FormBeanConfig
     */
    protected FormBeanConfig createFormBeanConfig() {
        // Setup the base form
        FormBeanConfig baseFormBean = new FormBeanConfig();
        baseFormBean.setName("baseForm");
        baseFormBean.setType("org.apache.struts.mock.MockActionForm");

        // Set up id, name, and score
        FormPropertyConfig property = new FormPropertyConfig();

        property.setName("id");
        property.setType("java.lang.String");
        baseFormBean.addFormPropertyConfig(property);

        property = new FormPropertyConfig();
        property.setName("name");
        property.setType("java.lang.String");
        baseFormBean.addFormPropertyConfig(property);

        property = new FormPropertyConfig();
        property.setName("score");
        property.setType("java.lang.String");
        baseFormBean.addFormPropertyConfig(property);

        return baseFormBean;
    }

    // ----------------------------- initInternal() and destroyInternal() tests

    /**
     * Verify that we can initialize and destroy our internal message
     * resources object.
     */
    @Test
    public void testInitDestroyInternal() {
        ActionServlet servlet = new ActionServlet();

        Assert.assertNotNull("internal was initialized", servlet.getInternal());
        Assert.assertNotNull("internal of correct type", servlet.getInternal());
    }

    /**
     * Test class loader resolution and splitting.
     * @throws Exception Exception
     */
    @Test
    public void notestSplitAndResolvePaths() throws Exception {
        ActionServlet servlet = new ActionServlet();
        List<URL> list = servlet.splitAndResolvePaths(
                "org/apache/struts/config/struts-config.xml");

        Assert.assertNotNull(list);
        Assert.assertEquals("List size should be 1", list.size(), 1);

        list = servlet.splitAndResolvePaths(
                "org/apache/struts/config/struts-config.xml, "
                + "org/apache/struts/config/struts-config-1.1.xml");
        Assert.assertNotNull(list);
        Assert.assertEquals("List size should be 2, was " + list.size(), list.size(), 2);

        list = servlet.splitAndResolvePaths("META-INF/MANIFEST.MF");
        Assert.assertNotNull(list);
        Assert.assertTrue("Number of manifests should be more than 5, was "
            + list.size(), list.size() > 5);

        // test invalid path
        try {
            list = servlet.splitAndResolvePaths(
                    "org/apache/struts/config/struts-asdfasdfconfig.xml");
            Assert.assertNotNull(list);
            Assert.fail("Should have thrown an exception on bad path");
        } catch (final UnavailableException ex) {
            // correct behavior since internal error resources aren't loaded
            ex.printStackTrace();
        }
    }

    // --------------------------------------------------- FormBeanConfig Tests

    /**
     * Test that nothing fails if there are no extensions.
     */
    @Test
    public void testInitModuleFormBeansNoExtends() {
        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addFormBeanConfig(createFormBeanConfig());

        ActionServlet actionServlet = new ActionServlet();

        try {
            actionServlet.initModuleExceptionConfigs(moduleConfig);
        } catch (final ServletException e) {
            Assert.fail("Unexpected exception caught." + e);
        }
    }

    /**
     * Test that initModuleFormBeans throws an exception when a form with a
     * null type is present.
     */
    @Test
    public void testInitModuleFormBeansNullFormType() {
        FormBeanConfig formBean = new FormBeanConfig();
        formBean.setName("noTypeForm");

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addFormBeanConfig(formBean);

        ActionServlet actionServlet = new ActionServlet();

        try {
            actionServlet.initModuleFormBeans(moduleConfig);
            Assert.fail("An exception should've been thrown here.");
        } catch (final UnavailableException e) {
            // success
            e.printStackTrace();
        } catch (final ServletException e) {
            Assert.fail("Unrecognized exception thrown: " + e);
        }
    }

    /**
     * Test that initModuleFormBeans throws an exception when a form whose
     * prop type is null is present.
     */
    @Test
    public void testInitModuleFormBeansNullPropType() {
        FormBeanConfig baseFormBean = createFormBeanConfig();
        baseFormBean.findFormPropertyConfig("name").setType(null);

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addFormBeanConfig(baseFormBean);

        ActionServlet actionServlet = new ActionServlet();

        try {
            actionServlet.initModuleFormBeans(moduleConfig);
            Assert.fail("An exception should've been thrown here.");
        } catch (final UnavailableException e) {
            // success
            e.printStackTrace();
        } catch (final ServletException e) {
            Assert.fail("Unrecognized exception thrown: " + e);
        }
    }

    /**
     * Test that processFormBeanExtension() calls processExtends()
     * @throws ServletException ServletException
     */
    @Test
    public void testProcessFormBeanExtension() throws ServletException {
        CustomFormBeanConfig form = new CustomFormBeanConfig();

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");

        ActionServlet actionServlet = new ActionServlet();
        actionServlet.processFormBeanExtension(form, moduleConfig);

        Assert.assertTrue("processExtends() was not called", form.processExtendsCalled());
    }

    /**
     * Make sure processFormBeanConfigClass() returns an instance of the
     * correct class if the base config is using a custom class.
     * @throws Exception Exception
     */
    @Test
    public void testProcessFormBeanConfigClass() throws Exception {
        CustomFormBeanConfig customBase = new CustomFormBeanConfig();
        customBase.setName("customBase");

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addFormBeanConfig(customBase);

        FormBeanConfig customSub = new FormBeanConfig();

        customSub.setName("customSub");
        customSub.setExtends("customBase");
        customSub.setType("org.apache.struts.action.DynaActionForm");
        moduleConfig.addFormBeanConfig(customSub);

        ActionServlet actionServlet = new ActionServlet();
        FormBeanConfig result = actionServlet.processFormBeanConfigClass(
                customSub, moduleConfig);

        Assert.assertTrue("Incorrect class of form bean config",
            result instanceof CustomFormBeanConfig);
        Assert.assertEquals("Incorrect name", customSub.getName(), result.getName());
        Assert.assertEquals("Incorrect type", customSub.getType(), result.getType());
        Assert.assertEquals("Incorrect extends", customSub.getExtends(),
            result.getExtends());
        Assert.assertEquals("Incorrect 'restricted' value",
                Boolean.valueOf(customSub.isRestricted()), Boolean.valueOf(result.isRestricted()));

        Assert.assertSame("Result was not registered in the module config", result,
                moduleConfig.findFormBeanConfig("customSub"));
    }

    /**
     * Make sure processFormBeanConfigClass() returns what it was given if the
     * form passed to it doesn't extend anything.
     * @throws Exception Exception
     */
    @Test
    public void testProcessFormBeanConfigClassNoExtends() throws Exception {
        FormBeanConfig baseFormBean = createFormBeanConfig();

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addFormBeanConfig(baseFormBean);

        FormBeanConfig result = null;

        ActionServlet actionServlet = new ActionServlet();

        try {
            result = actionServlet.processFormBeanConfigClass(baseFormBean, moduleConfig);
        } catch (final UnavailableException e) {
            Assert.fail("An exception should not be thrown when there's nothing to do" + e);
        }

        Assert.assertSame("Result should be the same as the input.", baseFormBean, result);
    }

    /**
     * Make sure processFormBeanConfigClass() returns the same class instance
     * if the base config isn't using a custom class.
     * @throws Exception Exception
     */
    @Test
    public void testProcessFormBeanConfigClassSubFormCustomClass() throws Exception {
        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addFormBeanConfig(createFormBeanConfig());

        FormBeanConfig customSub = new FormBeanConfig();

        customSub.setName("customSub");
        customSub.setExtends("baseForm");
        moduleConfig.addFormBeanConfig(customSub);

        ActionServlet actionServlet = new ActionServlet();

        FormBeanConfig result = actionServlet.processFormBeanConfigClass(
                customSub, moduleConfig);

        Assert.assertSame("The instance returned should be the param given it.",
            customSub, result);
    }

    /**
     * Make sure the code throws the correct exception when it can't create an
     * instance of the base config's custom class.
     */
    @Test
    public void notestProcessFormBeanConfigClassError() {
        CustomFormBeanConfigArg customBase = new CustomFormBeanConfigArg("customBase");

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addFormBeanConfig(customBase);

        FormBeanConfig customSub = new FormBeanConfig();

        customSub.setName("customSub");
        customSub.setExtends("customBase");
        moduleConfig.addFormBeanConfig(customSub);

        ActionServlet actionServlet = new ActionServlet();

        try {
            actionServlet.processFormBeanConfigClass(customSub, moduleConfig);
            Assert.fail("Exception should be thrown");
        } catch (final UnavailableException e) {
            // success
            e.printStackTrace();
        } catch (final ServletException e) {
            Assert.fail("Unexpected exception thrown." + e);
        }
    }

    /**
     * Test the case where the subform has already specified its own form bean
     * config class.  If the code still attempts to create a new instance, an
     * error will be thrown.
     */
    @Test
    public void testProcessFormBeanConfigClassOverriddenSubFormClass() {
        CustomFormBeanConfigArg customBase = new CustomFormBeanConfigArg("customBase");

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addFormBeanConfig(customBase);

        FormBeanConfig customSub = new CustomFormBeanConfigArg("customSub");

        customSub.setExtends("customBase");
        moduleConfig.addFormBeanConfig(customSub);

        ActionServlet actionServlet = new ActionServlet();

        try {
            actionServlet.processFormBeanConfigClass(customSub, moduleConfig);
        } catch (final ServletException e) {
            Assert.fail("Exception should not be thrown" + e);
        }
    }

    // -------------------------------------------------- ExceptionConfig Tests

    /**
     * Test that nothing fails if there are no extensions.
     */
    @Test
    public void testInitModuleExceptionConfigsNoExtends() {
        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addExceptionConfig(createExceptionConfig());

        ActionServlet actionServlet = new ActionServlet();

        try {
            actionServlet.initModuleExceptionConfigs(moduleConfig);
        } catch (final ServletException e) {
            Assert.fail("Unexpected exception caught." + e);
        }
    }

    /**
     * Test that initModuleExceptionConfigs throws an exception when a handler
     * with a null key is present.
     */
    @Test
    public void testInitModuleExceptionConfigsNullFormType() {
        ExceptionConfig handler = new ExceptionConfig();
        handler.setType("java.lang.NullPointerException");

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addExceptionConfig(handler);

        ActionServlet actionServlet = new ActionServlet();

        try {
            actionServlet.initModuleExceptionConfigs(moduleConfig);
            Assert.fail("An exception should've been thrown here.");
        } catch (final UnavailableException e) {
            // success
            e.printStackTrace();
        } catch (final ServletException e) {
            Assert.fail("Unrecognized exception thrown: " + e);
        }
    }

    /**
     * Test that processExceptionExtension() calls processExtends()
     * @throws ServletException ServletException
     */
    @Test
    public void testProcessExceptionExtension() throws ServletException {
        CustomExceptionConfig handler = new CustomExceptionConfig();
        handler.setType("java.lang.NullPointerException");

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addExceptionConfig(handler);

        ActionServlet actionServlet = new ActionServlet();
        actionServlet.processExceptionExtension(handler, moduleConfig, null);

        Assert.assertTrue("processExtends() was not called", handler.isProcessExtendsCalled());
    }

    /**
     * Make sure processExceptionConfigClass() returns an instance of the
     * correct class if the base config is using a custom class.
     * @throws Exception Exception
     */
    @Test
    public void testProcessExceptionConfigClass() throws Exception {
        CustomExceptionConfig customBase = new CustomExceptionConfig();
        customBase.setType("java.lang.NullPointerException");
        customBase.setKey("msg.exception.npe");

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addExceptionConfig(customBase);

        ExceptionConfig customSub = new ExceptionConfig();

        customSub.setType("java.lang.IllegalStateException");
        customSub.setExtends("java.lang.NullPointerException");
        moduleConfig.addExceptionConfig(customSub);

        ActionServlet actionServlet = new ActionServlet();

        ExceptionConfig result = actionServlet.processExceptionConfigClass(
                customSub, moduleConfig, null);

        Assert.assertTrue("Incorrect class of exception config",
            result instanceof CustomExceptionConfig);
        Assert.assertEquals("Incorrect type", customSub.getType(), result.getType());
        Assert.assertEquals("Incorrect key", customSub.getKey(), result.getKey());
        Assert.assertEquals("Incorrect extends", customSub.getExtends(), result.getExtends());

        Assert.assertSame("Result was not registered in the module config", result,
            moduleConfig.findExceptionConfig("java.lang.IllegalStateException"));
    }

    /**
     * Make sure processExceptionConfigClass() returns what it was given if
     * the handler passed to it doesn't extend anything.
     * @throws Exception Exception
     */
    @Test
    public void testProcessExceptionConfigClassNoExtends() throws Exception {
        ExceptionConfig baseException = createExceptionConfig();
        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addExceptionConfig(baseException);

        ActionServlet actionServlet = new ActionServlet();

        ExceptionConfig result = null;
        try {
            result = actionServlet.processExceptionConfigClass(
                    baseException, moduleConfig, null);
        } catch (final UnavailableException e) {
            Assert.fail("An exception should not be thrown when there's nothing to do" + e);
        }

        Assert.assertSame("Result should be the same as the input.", baseException, result);
    }

    /**
     * Make sure processExceptionConfigClass() returns the same class instance
     * if the base config isn't using a custom class.
     * @throws Exception Exception
     */
    @Test
    public void testProcessExceptionConfigClassSubConfigCustomClass() throws Exception {
        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addExceptionConfig(createExceptionConfig());

        ExceptionConfig customSub = new ExceptionConfig();

        customSub.setType("java.lang.IllegalStateException");
        customSub.setExtends("java.lang.NullPointerException");
        moduleConfig.addExceptionConfig(customSub);

        ActionServlet actionServlet = new ActionServlet();

        ExceptionConfig result = actionServlet.processExceptionConfigClass(
                customSub, moduleConfig, null);

        Assert.assertSame("The instance returned should be the param given it.",
            customSub, result);
    }

    /**
     * Make sure the code throws the correct exception when it can't create an
     * instance of the base config's custom class.
     */
    @Test
    public void notestProcessExceptionConfigClassError() {
        ExceptionConfig customBase =
            new CustomExceptionConfigArg("java.lang.NullPointerException");

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addExceptionConfig(customBase);

        ExceptionConfig customSub = new ExceptionConfig();

        customSub.setType("java.lang.IllegalStateException");
        customSub.setExtends("java.lang.NullPointerException");
        moduleConfig.addExceptionConfig(customSub);

        ActionServlet actionServlet = new ActionServlet();

        try {
            actionServlet.processExceptionConfigClass(customSub, moduleConfig, null);
            Assert.fail("Exception should be thrown");
        } catch (final UnavailableException e) {
            // success
            e.printStackTrace();
        } catch (final ServletException e) {
            Assert.fail("Unexpected exception thrown." + e);
        }
    }

    /**
     * Test the case where the subconfig has already specified its own config
     * class.  If the code still attempts to create a new instance, an error
     * will be thrown.
     */
    @Test
    public void testProcessExceptionConfigClassOverriddenSubFormClass() {
        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addExceptionConfig(createExceptionConfig());

        ExceptionConfig customSub =
            new CustomExceptionConfigArg("java.lang.IllegalStateException");
        customSub.setExtends("java.lang.NullPointerException");
        moduleConfig.addExceptionConfig(customSub);

        ActionServlet actionServlet = new ActionServlet();

        try {
            actionServlet.processExceptionConfigClass(customSub, moduleConfig, null);
        } catch (final ServletException e) {
            Assert.fail("Exception should not be thrown" + e);
        }
    }

    // ---------------------------------------------------- ForwardConfig Tests

    /**
     * Test that nothing fails if there are no extensions.
     */
    @Test
    public void testInitModuleForwardConfigsNoExtends() {
        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addForwardConfig(new ActionForward("success", "/succes.jsp", false));

        ActionServlet actionServlet = new ActionServlet();

        try {
            actionServlet.initModuleForwards(moduleConfig);
        } catch (final ServletException e) {
            Assert.fail("Unexpected exception caught." + e);
        }
    }

    /**
     * Test that initModuleForwards throws an exception when a forward with a
     * null path is present.
     */
    @Test
    public void testInitModuleForwardsNullFormType() {
        ActionForward forward = new ActionForward("success", null, false);

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addForwardConfig(forward);

        ActionServlet actionServlet = new ActionServlet();

        try {
            actionServlet.initModuleForwards(moduleConfig);
            Assert.fail("An exception should've been thrown here.");
        } catch (final UnavailableException e) {
            // success
            e.printStackTrace();
        } catch (final ServletException e) {
            Assert.fail("Unrecognized exception thrown: " + e);
        }
    }

    /**
     * Test that processForwardExtension() calls processExtends()
     * @throws ServletException ServletException
     */
    @Test
    public void testProcessForwardExtension() throws ServletException {
        CustomForwardConfig forward = new CustomForwardConfig("forward", "/forward.jsp");

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addForwardConfig(forward);

        ActionServlet actionServlet = new ActionServlet();
        actionServlet.processForwardExtension(forward, moduleConfig, null);

        Assert.assertTrue("processExtends() was not called", forward.isProcessExtendsCalled());
    }

    /**
     * Make sure processForwardConfigClass() returns an instance of the
     * correct class if the base config is using a custom class.
     * @throws Exception Exception
     */
    @Test
    public void testProcessForwardConfigClass() throws Exception {
        CustomForwardConfig customBase = new CustomForwardConfig("success", "/success.jsp");

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addForwardConfig(customBase);

        ActionForward customSub = new ActionForward();

        customSub.setName("failure");
        customSub.setExtends("success");
        moduleConfig.addForwardConfig(customSub);

        ActionServlet actionServlet = new ActionServlet();

        ForwardConfig result = actionServlet.processForwardConfigClass(
                customSub, moduleConfig, null);

        Assert.assertTrue("Incorrect class of forward config",
            result instanceof CustomForwardConfig);
        Assert.assertEquals("Incorrect name", customSub.getName(), result.getName());
        Assert.assertEquals("Incorrect path", customSub.getPath(), result.getPath());
        Assert.assertEquals("Incorrect extends", customSub.getExtends(), result.getExtends());

        Assert.assertSame("Result was not registered in the module config", result,
                moduleConfig.findForwardConfig("failure"));
    }

    /**
     * Make sure processForwardConfigClass() returns what it was given if the
     * forward passed to it doesn't extend anything.
     * @throws Exception Exception
     */
    @Test
    public void testProcessForwardConfigClassNoExtends() throws Exception {

        ActionForward baseForward = new ActionForward("success", "/succes.jsp", false);

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addForwardConfig(baseForward);

        ActionServlet actionServlet = new ActionServlet();

        ForwardConfig result = null;
        try {
            result = actionServlet.processForwardConfigClass(
                    baseForward, moduleConfig, null);
        } catch (final UnavailableException e) {
            Assert.fail("An exception should not be thrown when there's nothing to do" + e);
        }

        Assert.assertSame("Result should be the same as the input.", baseForward, result);
    }

    /**
     * Make sure processForwardConfigClass() returns the same class instance
     * if the base config isn't using a custom class.
     * @throws Exception Exception
     */
    @Test
    public void testProcessForwardConfigClassSubConfigCustomClass() throws Exception {

        ActionForward baseForward = new ActionForward("success", "/succes.jsp", false);

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addForwardConfig(baseForward);

        ForwardConfig customSub = new ActionForward();
        customSub.setName("failure");
        customSub.setExtends("success");
        moduleConfig.addForwardConfig(customSub);

        ActionServlet actionServlet = new ActionServlet();

        ForwardConfig result = actionServlet.processForwardConfigClass(
                customSub, moduleConfig, null);

        Assert.assertSame("The instance returned should be the param given it.", customSub, result);
    }

    /**
     * Make sure the code throws the correct exception when it can't create an
     * instance of the base config's custom class.
     */
    @Test
    public void notestProcessForwardConfigClassError() {
        ForwardConfig customBase = new CustomForwardConfigArg("success", "/success.jsp");

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addForwardConfig(customBase);

        ForwardConfig customSub = new ActionForward();

        customSub.setName("failure");
        customSub.setExtends("success");
        moduleConfig.addForwardConfig(customSub);

        ActionServlet actionServlet = new ActionServlet();

        try {
            actionServlet.processForwardConfigClass(customSub, moduleConfig, null);
            Assert.fail("Exception should be thrown");
        } catch (final UnavailableException e) {
            // success
            e.printStackTrace();
        } catch (final ServletException e) {
            Assert.fail("Unexpected exception thrown." + e);
        }
    }

    /**
     * Test the case where the subconfig has already specified its own config
     * class.  If the code still attempts to create a new instance, an error
     * will be thrown.
     */
    @Test
    public void testProcessForwardConfigClassOverriddenSubConfigClass() {

        ActionForward baseForward = new ActionForward("success", "/succes.jsp", false);

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addForwardConfig(baseForward);

        ForwardConfig customSub = new CustomForwardConfigArg("failure", "/failure.jsp");

        customSub.setExtends("success");
        moduleConfig.addForwardConfig(customSub);

        ActionServlet actionServlet = new ActionServlet();

        try {
            actionServlet.processForwardConfigClass(customSub, moduleConfig, null);
        } catch (final ServletException e) {
            Assert.fail("Exception should not be thrown" + e);
        }
    }

    // --------------------------------------------------- ActionConfig Tests

    /**
     * Test that nothing fails if there are no extensions.
     */
    @Test
    public void testInitModuleActionConfigsNoExtends() {
        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addActionConfig(createActionMapping());

        ActionServlet actionServlet = new ActionServlet();

        try {
            actionServlet.initModuleActions(moduleConfig);
        } catch (final ServletException e) {
            Assert.fail("Unexpected exception caught." + e);
        }
    }

    /**
     * Test that processActionConfigExtension() calls processExtends()
     * throws ServletException ServletException
     * @throws ServletException ServletException
     */
    @Test
    public void testProcessActionExtension() throws ServletException {
        CustomActionConfig action = new CustomActionConfig("/action");

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addActionConfig(action);

        ActionServlet actionServlet = new ActionServlet();
        actionServlet.processActionConfigExtension(action, moduleConfig);

        Assert.assertTrue("processExtends() was not called", action.isProcessExtendsCalled());
    }

    /**
     * Test that an ActionConfig's ForwardConfig can inherit from a
     * global ForwardConfig.
     * @throws ServletException ServletException
     */
    @Test
    public void testProcessActionExtensionWithForwardConfig() throws ServletException {
        ForwardConfig forwardConfig = new ForwardConfig();
        forwardConfig.setName("sub");
        forwardConfig.setExtends("success");

        ActionMapping baseAction = createActionMapping();
        baseAction.addForwardConfig(forwardConfig);

        ActionForward baseForward = new ActionForward("success", "/succes.jsp", false);

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addActionConfig(baseAction);
        moduleConfig.addForwardConfig(baseForward);

        ActionServlet actionServlet = new ActionServlet();
        actionServlet.processActionConfigExtension(baseAction, moduleConfig);

        forwardConfig = baseAction.findForwardConfig("sub");

        Assert.assertEquals("'sub' forward's inheritance was not processed.",
                baseForward.getPath(), forwardConfig.getPath());
    }

    /**
     * Test that an ActionConfig's ExceptionConfig can inherit from a
     * global ExceptionConfig.
     * @throws ServletException ServletException
     */
    @Test
    public void testProcessActionExtensionWithExceptionConfig() throws ServletException {
        ExceptionConfig exceptionConfig = new ExceptionConfig();
        exceptionConfig.setType("SomeException");
        exceptionConfig.setExtends("java.lang.NullPointerException");

        ActionMapping baseAction = createActionMapping();
        baseAction.addExceptionConfig(exceptionConfig);

        ExceptionConfig baseException = createExceptionConfig();

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addActionConfig(baseAction);
        moduleConfig.addExceptionConfig(baseException);

        ActionServlet actionServlet = new ActionServlet();
        actionServlet.processActionConfigExtension(baseAction, moduleConfig);

        exceptionConfig = baseAction.findExceptionConfig("SomeException");

        Assert.assertEquals("SomeException's inheritance was not processed.",
                baseException.getKey(), exceptionConfig.getKey());
    }

    /**
     * Make sure processActionConfigClass() returns an instance of the correct
     * class if the base config is using a custom class.
     * @throws Exception Exception
     */
    @Test
    public void testProcessActionConfigClass() throws Exception {
        CustomActionConfig customBase = new CustomActionConfig("/base");

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addActionConfig(customBase);

        ActionMapping customSub = new ActionMapping();
        customSub.setPath("/sub");
        customSub.setExtends("/base");

        moduleConfig.addActionConfig(customSub);

        ActionServlet actionServlet = new ActionServlet();

        ActionConfig result = actionServlet.processActionConfigClass(customSub, moduleConfig);

        Assert.assertTrue("Incorrect class of action config",
            result instanceof CustomActionConfig);
        Assert.assertEquals("Incorrect path", customSub.getPath(), result.getPath());
        Assert.assertEquals("Incorrect extends", customSub.getExtends(),
            result.getExtends());

        Assert.assertSame("Result was not registered in the module config", result,
            moduleConfig.findActionConfig("/sub"));
    }

    /**
     * Make sure processActionConfigClass() returns what it was given if the
     * action passed to it doesn't extend anything.
     */
    @Test
    public void testProcessActionConfigClassNoExtends() {
        ActionMapping baseAction = createActionMapping();

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addActionConfig(baseAction);

        ActionServlet actionServlet = new ActionServlet();

        ActionConfig result = null;
        try {
            result = actionServlet.processActionConfigClass(baseAction, moduleConfig);
        } catch (final ServletException e) {
            Assert.fail("An exception should not be thrown here" + e);
        }

        Assert.assertSame("Result should be the same as the input.", baseAction, result);
    }

    /**
     * Make sure processActionConfigClass() returns the same class instance if
     * the base config isn't using a custom class.
     */
    @Test
    public void testProcessActionConfigClassSubConfigCustomClass() {
        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addActionConfig(createActionMapping());

        ActionConfig customSub = new ActionMapping();
        customSub.setPath("/sub");
        customSub.setExtends("/index");

        moduleConfig.addActionConfig(customSub);

        ActionServlet actionServlet = new ActionServlet();

        try {
            ActionConfig result = actionServlet.processActionConfigClass(customSub, moduleConfig);

            Assert.assertSame("The instance returned should be the param given it.",
                customSub, result);
        } catch (final ServletException e) {
            Assert.fail(e.getMessage());
        }
    }

    /**
     * Make sure the code throws the correct exception when it can't create an
     * instance of the base config's custom class.
     */
    @Test
    public void notestProcessActionConfigClassError() {
        ActionConfig customBase = new CustomActionConfigArg("/index");

        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addActionConfig(customBase);

        ActionConfig customSub = new ActionMapping();
        customSub.setPath("/sub");
        customSub.setExtends("/index");

        moduleConfig.addActionConfig(customSub);

        ActionServlet actionServlet = new ActionServlet();

        try {
            actionServlet.processActionConfigClass(customSub, moduleConfig);
            Assert.fail("Exception should be thrown");
        } catch (final UnavailableException e) {
            // success
            e.printStackTrace();
        } catch (final ServletException e) {
            Assert.fail("Unexpected exception thrown." + e);
        }
    }

    /**
     * Test the case where the subconfig has already specified its own config
     * class.  If the code still attempts to create a new instance, an error
     * will be thrown.
     */
    @Test
    public void testProcessActionConfigClassOverriddenSubConfigClass() {
        ModuleConfig moduleConfig = ModuleConfigFactory.createFactory().createModuleConfig("");
        moduleConfig.addActionConfig(createActionMapping());

        ActionConfig customSub = new CustomActionConfigArg("/sub");
        customSub.setExtends("/index");

        moduleConfig.addActionConfig(customSub);

        ActionServlet actionServlet = new ActionServlet();

        try {
            actionServlet.processActionConfigClass(customSub, moduleConfig);
        } catch (final ServletException e) {
            Assert.fail("Exception should not be thrown" + e);
        }
    }

    /**
     * Used for testing custom FormBeanConfig classes.
     */
    public static class CustomFormBeanConfig extends FormBeanConfig {
        /** serialVersionUID */
        private static final long serialVersionUID = 5312111603155266154L;

        /** processExtendsCalled */
        private boolean processExtendsCalled = false;

        /** CustomFormBeanConfig */
        public CustomFormBeanConfig() {
            super();
        }

        /**
         * @return processExtendsCalled
         */
        public boolean processExtendsCalled() {
            return this.processExtendsCalled;
        }

        /**
         * Set a flag so we know this method was called.
         */
        @Override
        public void processExtends(final Map<String, FormBeanConfig> moduleConfig) {
            this.processExtendsCalled = true;
        }
    }

    /**
     * Used to test cases where the subclass cannot be created with a no-arg
     * constructor.
     */
    private static final class CustomFormBeanConfigArg extends FormBeanConfig {
        /** serialVersionUID */
        private static final long serialVersionUID = -455258948780624575L;

        /**
         * @param name String
         */
        CustomFormBeanConfigArg(final String name) {
            super();
            setName(name);
        }
    }

    /**
     * Used for testing custom ExceptionConfig classes.
     */
    public static class CustomExceptionConfig extends ExceptionConfig {
        /** serialVersionUID */
        private static final long serialVersionUID = -2722522988693737768L;

        /** processExtendsCalled */
        private boolean processExtendsCalled = false;

        /**
         * CustomExceptionConfig
         */
        public CustomExceptionConfig() {
            super();
        }

        /**
         * Set a flag so we know this method was called.
         */
        @Override
        public void processExtends(final Map<String, ExceptionConfig> moduleConfig,
                final Map<String, ExceptionConfig> actionConfig) {
            this.processExtendsCalled = true;
        }

        /**
         * @return processExtendsCalled
         */
        public boolean isProcessExtendsCalled() {
            return this.processExtendsCalled;
        }
    }

    /**
     * Used to test cases where the subclass cannot be created with a no-arg
     * constructor.
     */
    private static final class CustomExceptionConfigArg extends ExceptionConfig {
        /** serialVersionUID */
        private static final long serialVersionUID = 4453990369781006902L;

        /**
         * @param type String
         */
        CustomExceptionConfigArg(final String type) {
            super();
            setType(type);
        }
    }

    /**
     * Used for testing custom ForwardConfig classes.
     */
    public static class CustomForwardConfig extends ForwardConfig {
        /** serialVersionUID */
        private static final long serialVersionUID = 5096162278387480886L;

        /** processExtendsCalled */
        private boolean processExtendsCalled = false;

        /**
         * CustomForwardConfig
         */
        public CustomForwardConfig() {
            super();
        }

        /**
         * @param name String
         * @param path String
         */
        public CustomForwardConfig(final String name, final String path) {
            super(name, path, false);
        }

        /**
         * Set a flag so we know this method was called.
         */
        @Override
        public void processExtends(final Map<String, ForwardConfig> moduleConfig,
                final Map<String, ForwardConfig> actionConfig) {
            this.processExtendsCalled = true;
        }

        /**
         * @return processExtendsCalled
         */
        public boolean isProcessExtendsCalled() {
            return this.processExtendsCalled;
        }
    }

    /**
     * Used to test cases where the subclass cannot be created with a no-arg
     * constructor.
     */
    private static final class CustomForwardConfigArg extends ForwardConfig {
        /** serialVersionUID */
        private static final long serialVersionUID = -2880188065203315383L;

        /**
         * @param name String
         * @param path String
         */
        CustomForwardConfigArg(final String name, final String path) {
            super();
            setName(name);
            setPath(path);
        }
    }

    /**
     * Used for testing custom ActionConfig classes.
     */
    public static class CustomActionConfig extends ActionConfig {
        /** serialVersionUID */
        private static final long serialVersionUID = -5608523390345631797L;

        /** processExtendsCalled */
        private boolean processExtendsCalled = false;

        /**
         * CustomActionConfig
         */
        public CustomActionConfig() {
            super();
        }

        /**
         * @param path String
         */
        public CustomActionConfig(final String path) {
            super();
            setPath(path);
        }

        /**
         * Set a flag so we know this method was called.
         */
        @Override
        public void processExtends(final Map<String, ActionConfig> moduleConfig) {
            this.processExtendsCalled = true;
        }

        /**
         * @return processExtendsCalled
         */
        public boolean isProcessExtendsCalled() {
            return this.processExtendsCalled;
        }
    }

    /**
     * Used to test cases where the subclass cannot be created with a no-arg
     * constructor.
     */
    private static final class CustomActionConfigArg extends ActionConfig {
        /** serialVersionUID */
        private static final long serialVersionUID = 8225457345532365941L;

        /**
         * @param path String
         */
        CustomActionConfigArg(final String path) {
            super();
            setPath(path);
        }
    }

    // [...]
}
