/*
 * $Id: ConfigRuleSet.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.config;

import org.apache.commons.digester3.AbstractObjectCreationFactory;
import org.apache.commons.digester3.Digester;
import org.apache.commons.digester3.Rule;
import org.apache.commons.digester3.RuleSetBase;
import org.apache.commons.digester3.SetPropertyRule;
import org.apache.struts.action.ActionFormBean;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.util.ResponseUtils;
import org.xml.sax.Attributes;

/**
 * <p>The set of Digester rules required to parse a Struts configuration file
 * (<code>struts-config.xml</code>).</p>
 * @version $Rev: 471754 $ $Date: 2005-08-16 15:53:27 -0400 (Tue, 16 Aug 2005)
 *          $
 * @since Struts 1.1
 */
public class ConfigRuleSet extends RuleSetBase {

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

    /**
     * <p>Add the set of Rule instances defined in this RuleSet to the
     * specified <code>Digester</code> instance, associating them with our
     * namespace URI (if any).  This method should only be called by a
     * Digester instance.  These rules assume that an instance of
     * <code>org.apache.struts.config.ModuleConfig</code> is pushed onto the
     * evaluation stack before parsing begins.</p>
     * @param digester Digester instance to which the new Rule instances
     *                 should be added.
     */
    @Override
    public void addRuleInstances(final Digester digester) {
        ClassLoader cl = digester.getClassLoader();

        digester.addRule(
                "struts-config/action-mappings",
                new SetActionMappingClassRule());

        digester.addFactoryCreate(
                "struts-config/action-mappings/action",
                new ActionMappingFactory(cl));
        digester.addSetProperties("struts-config/action-mappings/action");
        digester.addSetNext(
                "struts-config/action-mappings/action",
                "addActionConfig", "org.apache.struts.config.ActionConfig");

        digester.addRule(
                "struts-config/action-mappings/action/set-property",
                new BaseConfigSetPropertyRule());

        digester.addObjectCreate(
                "struts-config/action-mappings/action/exception",
                "org.apache.struts.config.ExceptionConfig", "className");
        digester.addSetProperties(
                "struts-config/action-mappings/action/exception");
        digester.addSetNext(
                "struts-config/action-mappings/action/exception",
                "addExceptionConfig", "org.apache.struts.config.ExceptionConfig");

        digester.addRule(
                "struts-config/action-mappings/action/exception/set-property",
                new BaseConfigSetPropertyRule());

        digester.addFactoryCreate(
                "struts-config/action-mappings/action/forward",
                new ActionForwardFactory(cl));
        digester.addSetProperties(
                "struts-config/action-mappings/action/forward");
        digester.addSetNext(
                "struts-config/action-mappings/action/forward",
                "addForwardConfig", "org.apache.struts.config.ForwardConfig");

        digester.addRule(
                "struts-config/action-mappings/action/forward/set-property",
                new BaseConfigSetPropertyRule());

        digester.addObjectCreate(
                "struts-config/controller",
                "org.apache.struts.config.ControllerConfig", "className");
        digester.addSetProperties("struts-config/controller");
        digester.addSetNext(
                "struts-config/controller", "setControllerConfig",
                "org.apache.struts.config.ControllerConfig");

        digester.addRule(
                "struts-config/controller/set-property",
                new BaseConfigSetPropertyRule());

        digester.addRule(
                "struts-config/form-beans",
                new SetActionFormBeanClassRule());

        digester.addFactoryCreate(
                "struts-config/form-beans/form-bean",
                new ActionFormBeanFactory(cl));
        digester.addSetProperties("struts-config/form-beans/form-bean");
        digester.addSetNext(
                "struts-config/form-beans/form-bean",
                "addFormBeanConfig", "org.apache.struts.config.FormBeanConfig");

        digester.addObjectCreate(
                "struts-config/form-beans/form-bean/form-property",
                "org.apache.struts.config.FormPropertyConfig", "className");
        digester.addSetProperties(
                "struts-config/form-beans/form-bean/form-property");
        digester.addSetNext(
                "struts-config/form-beans/form-bean/form-property",
                "addFormPropertyConfig",
                "org.apache.struts.config.FormPropertyConfig");

        digester.addRule(
                "struts-config/form-beans/form-bean/form-property/set-property",
                new BaseConfigSetPropertyRule());

        digester.addRule(
                "struts-config/form-beans/form-bean/set-property",
                new BaseConfigSetPropertyRule());

        digester.addObjectCreate(
                "struts-config/global-exceptions/exception",
                "org.apache.struts.config.ExceptionConfig", "className");
        digester.addSetProperties("struts-config/global-exceptions/exception");
        digester.addSetNext(
                "struts-config/global-exceptions/exception",
                "addExceptionConfig", "org.apache.struts.config.ExceptionConfig");

        digester.addRule(
                "struts-config/global-exceptions/exception/set-property",
                new BaseConfigSetPropertyRule());

        digester.addRule(
                "struts-config/global-forwards",
                new SetActionForwardClassRule());

        digester.addFactoryCreate(
                "struts-config/global-forwards/forward",
                new GlobalForwardFactory(cl));
        digester.addSetProperties("struts-config/global-forwards/forward");
        digester.addSetNext(
                "struts-config/global-forwards/forward",
                "addForwardConfig", "org.apache.struts.config.ForwardConfig");

        digester.addRule(
                "struts-config/global-forwards/forward/set-property",
                new BaseConfigSetPropertyRule());

        digester.addObjectCreate(
                "struts-config/message-resources",
                "org.apache.struts.config.MessageResourcesConfig", "className");
        digester.addSetProperties("struts-config/message-resources");
        digester.addSetNext(
                "struts-config/message-resources",
                "addMessageResourcesConfig",
                "org.apache.struts.config.MessageResourcesConfig");

        digester.addRule(
                "struts-config/message-resources/set-property",
                new BaseConfigSetPropertyRule());

        digester.addObjectCreate(
                "struts-config/plug-in",
                "org.apache.struts.config.PlugInConfig");
        digester.addSetProperties("struts-config/plug-in");
        digester.addSetNext(
                "struts-config/plug-in", "addPlugInConfig",
                "org.apache.struts.config.PlugInConfig");

        digester.addRule(
                "struts-config/plug-in/set-property",
                new PlugInSetPropertyRule());

        // PluginConfig does not extend BaseConfig, at least for now.
    }

    /**
     * <p> Class that records the name and value of a configuration property to be
     * used in configuring a <code>PlugIn</code> instance when instantiated. </p>
     */
    static final class PlugInSetPropertyRule extends Rule {

        /**
         * constructor
         */
        PlugInSetPropertyRule() {
            super();
        }

        /**
         * @see org.apache.commons.digester3.Rule#begin(
         * java.lang.String, java.lang.String, org.xml.sax.Attributes)
         */
        @Override
        public void begin(final String namespace, final String names, final Attributes attributes) {
            PlugInConfig plugInConfig = (PlugInConfig) getDigester().peek();

            plugInConfig.addProperty(attributes.getValue("property"),
                attributes.getValue("value"));
        }
    }

    /**
     * <p> Class that sets the name of the class to use when creating action form
     * bean instances. The value is set on the object on the top of the stack,
     * which must be a <code>org.apache.struts.config.ModuleConfig</code>. </p>
     */
    static final class SetActionFormBeanClassRule extends Rule {

        /**
         * constructor
         */
        SetActionFormBeanClassRule() {
            super();
        }

        /**
         * @see org.apache.commons.digester3.Rule#begin(
         * java.lang.String, java.lang.String, org.xml.sax.Attributes)
         */
        @Override
        public void begin(final String namespace, final String name, final Attributes attributes) {
            String className = attributes.getValue("type");

            if (className != null) {
                ModuleConfig mc = (ModuleConfig) getDigester().peek();
                mc.setActionFormBeanClass(className);
            }
        }
    }

    /**
     * <p> A variant of the standard Digester <code>SetPropertyRule</code>.  If
     * the element being processed has a "key" attribute, then the value will be
     * used to call <code>setProperty(key,value)</code> on the object on top of
     * the stack, which will be assumed to be of type <code>ActionConfig</code>.
     * Otherwise, the standard <code>SetPropertyRule</code> behavior is invoked,
     * and the value will be used to set a bean property on the object on top of
     * the Digester stack. In that case, the element being processed is assumed to
     * have attributes "property" and "value". </p>
     */
    static final class BaseConfigSetPropertyRule extends SetPropertyRule {

        /**
         * constructor
         */
        BaseConfigSetPropertyRule() {
            super("property", "value");
        }

        /**
         * @param attributes Attributes
         * @throws Exception Exception
         */
        @Override
        public void begin(final String ns, final String nameValue, final Attributes attributes)
                throws Exception {
            if (attributes.getIndex("key") == -1) {
                super.begin(null, null, attributes);
                return;
            }

            if (attributes.getIndex("property") != -1) {
                throw new IllegalArgumentException(
                    "<set-property> accepts only one of 'key' or 'property' attributes.");
            }

            Object topOfStack = getDigester().peek();

            if (topOfStack instanceof BaseConfig) {
                BaseConfig config = (BaseConfig) topOfStack;

                config.setProperty(attributes.getValue("key"), attributes.getValue("value"));
            } else {
                throw new IllegalArgumentException(
                    "'key' attribute of <set-property> only applicable "
                    + "to subclasses of BaseConfig; "
                    + "object on top of stack is " + topOfStack + " [key: "
                    + attributes.getValue("key") + ", value: "
                    + attributes.getValue("value") + "]");
            }
        }
    }

    /**
     * <p> An object creation factory which creates action form bean instances,
     * taking into account the default class name, which may have been specified
     * on the parent element and which is made available through the object on the
     * top of the stack, which must be a <code>org.apache.struts.config.ModuleConfig</code>.
     * </p>
     */
    static final class ActionFormBeanFactory extends AbstractObjectCreationFactory<ActionFormBean> {

        private final ClassLoader cl;

        /**
         * @param loader ClassLoader
         */
        ActionFormBeanFactory(final ClassLoader loader) {
            super();
            this.cl = loader;
        }

        /**
         * @see org.apache.commons.digester3.AbstractObjectCreationFactory#
         * createObject(org.xml.sax.Attributes)
         */
        @Override
        public ActionFormBean createObject(final Attributes attributes) {
            // Identify the name of the class to instantiate
            String className = attributes.getValue("className");
            if (className == null) {
                ModuleConfig mc = (ModuleConfig) getDigester().peek();

                className = mc.getActionFormBeanClass();
            }

            // Instantiate the new object and return it
            ActionFormBean actionFormBean = null;

            try {
                actionFormBean = ResponseUtils.applicationInstance(className, this.cl);
            } catch (final ReflectiveOperationException e) {
                getDigester().getLogger().error("ActionFormBeanFactory.createObject: ", e);
            }

            return actionFormBean;
        }
    }

    /**
     * <p> Class that sets the name of the class to use when creating action
     * mapping instances. The value is set on the object on the top of the stack,
     * which must be a <code>org.apache.struts.config.ModuleConfig</code>. </p>
     */
    static final class SetActionMappingClassRule extends Rule {

        /**
         * constructor
         */
        SetActionMappingClassRule() {
            super();
        }

        /**
         * @see org.apache.commons.digester3.Rule#begin(
         * java.lang.String, java.lang.String, org.xml.sax.Attributes)
         */
        @Override
        public void begin(final String namespace, final String name, final Attributes attributes) {
            String className = attributes.getValue("type");

            if (className != null) {
                ModuleConfig mc = (ModuleConfig) getDigester().peek();
                mc.setActionMappingClass(className);
            }
        }
    }

    /**
     * <p> An object creation factory which creates action mapping instances,
     * taking into account the default class name, which may have been specified
     * on the parent element and which is made available through the object on the
     * top of the stack, which must be a <code>org.apache.struts.config.ModuleConfig</code>.
     * </p>
     */
    static final class ActionMappingFactory extends AbstractObjectCreationFactory<ActionMapping> {

        private final ClassLoader cl;

        /**
         * @param loader ClassLoader
         */
        ActionMappingFactory(final ClassLoader loader) {
            super();
            this.cl = loader;
        }

        /**
         * @see org.apache.commons.digester3.AbstractObjectCreationFactory#
         * createObject(org.xml.sax.Attributes)
         */
        @Override
        public ActionMapping createObject(final Attributes attributes) {
            // Identify the name of the class to instantiate
            String className = attributes.getValue("className");
            if (className == null) {
                ModuleConfig mc = (ModuleConfig) getDigester().peek();
                className = mc.getActionMappingClass();
            }

            // Instantiate the new object and return it
            ActionMapping actionMapping = null;

            try {
                actionMapping = ResponseUtils.applicationInstance(className, this.cl);
            } catch (final ReflectiveOperationException e) {
                getDigester().getLogger().error("ActionMappingFactory.createObject: ", e);
            }

            return actionMapping;
        }
    }

    /**
     * <p> Class that sets the name of the class to use when creating global
     * forward instances. The value is set on the object on the top of the stack,
     * which must be a <code>org.apache.struts.config.ModuleConfig</code>. </p>
     */
    static final class SetActionForwardClassRule extends Rule {

        /**
         * constructor
         */
        SetActionForwardClassRule() {
            super();
        }

        /**
         * @see org.apache.commons.digester3.Rule#begin(
         * java.lang.String, java.lang.String, org.xml.sax.Attributes)
         */
        @Override
        public void begin(final String namespace, final String name, final Attributes attributes) {
            String className = attributes.getValue("type");

            if (className != null) {
                ModuleConfig mc = (ModuleConfig) getDigester().peek();
                mc.setActionForwardClass(className);
            }
        }
    }

    /**
     * <p> An object creation factory which creates global forward instances,
     * taking into account the default class name, which may have been specified
     * on the parent element and which is made available through the object on the
     * top of the stack, which must be a <code>org.apache.struts.config.ModuleConfig</code>.
     * </p>
     */
    static final class GlobalForwardFactory extends AbstractObjectCreationFactory<ActionForward> {

        private final ClassLoader cl;

        /**
         * @param loader ClassLoader
         */
        GlobalForwardFactory(final ClassLoader loader) {
            super();
            this.cl = loader;
        }

        /**
         * @see org.apache.commons.digester3.AbstractObjectCreationFactory#
         * createObject(org.xml.sax.Attributes)
         */
        @Override
        public ActionForward createObject(final Attributes attributes) {
            // Identify the name of the class to instantiate
            String className = attributes.getValue("className");

            if (className == null) {
                ModuleConfig mc = (ModuleConfig) getDigester().peek();
                className = mc.getActionForwardClass();
            }

            // Instantiate the new object and return it
            ActionForward globalForward = null;

            try {
                globalForward = ResponseUtils.applicationInstance(className, this.cl);
            } catch (final ReflectiveOperationException e) {
                getDigester().getLogger().error("GlobalForwardFactory.createObject: ", e);
            }

            return globalForward;
        }
    }

    /**
     * <p> An object creation factory which creates action forward instances,
     * taking into account the default class name, which may have been specified
     * on the parent element and which is made available through the object on the
     * top of the stack, which must be a <code>org.apache.struts.config.ModuleConfig</code>.
     * </p>
     */
    static final class ActionForwardFactory extends AbstractObjectCreationFactory<ActionForward> {

        private final ClassLoader cl;

        /**
         * @param loader ClassLoader
         */
        ActionForwardFactory(final ClassLoader loader) {
            super();
            this.cl = loader;
        }

        /**
         * @see org.apache.commons.digester3.AbstractObjectCreationFactory#
         * createObject(org.xml.sax.Attributes)
         */
        @Override
        public ActionForward createObject(final Attributes attributes) {
            // Identify the name of the class to instantiate
            String className = attributes.getValue("className");

            if (className == null) {
                ModuleConfig mc = (ModuleConfig) getDigester().peek(1);
                className = mc.getActionForwardClass();
            }

            // Instantiate the new object and return it
            ActionForward actionForward = null;

            try {
                actionForward = ResponseUtils.applicationInstance(className, this.cl);
            } catch (final ReflectiveOperationException e) {
                getDigester().getLogger().error("ActionForwardFactory.createObject: ", e);
            }

            return actionForward;
        }
    }
}
