/*
 * $Id: FormComponent.java 664382 2008-06-07 20:45:12Z 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.faces.component;


import java.util.Map;
import javax.faces.component.UIForm;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.servlet.http.HttpSession;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.Globals;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.action.DynaActionFormClass;
import org.apache.struts.config.ActionConfig;
import org.apache.struts.config.FormBeanConfig;
import org.apache.struts.config.ModuleConfig;
import org.apache.struts.util.RequestUtils;


/**
 * <p><strong>FormComponent</strong> is a specialized subclass of
 * <code>javax.faces.component.UIForm</code> that supports automatic
 * creation of form beans in request or session scope.</p>
 *
 * @version $Rev: 664382 $ $Date: 2008-06-07 22:45:12 +0200 (Sam, 07 jui 2008) $
 */
public class FormComponent extends UIForm {


    // -------------------------------------------------------- Static Variables


    /**
     * <p>The <code>Log</code> instance for this class.</p>
     */
    protected static Log log = LogFactory.getLog(FormComponent.class);


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


    private String action = null;
    private String enctype = null;
    private String focus = null;
    private String focusIndex = null;
    private String onreset = null;
    private String onsubmit = null;
    private String style = null;
    private String styleClass = null;
    private String target = null;


    // ---------------------------------------------------- Component Properties


    /**
     * <p>Return the Struts action path to which this form should be submitted.
     * </p>
     */
    public String getAction() {

        if (this.action != null) {
            return (this.action);
        }
        ValueBinding vb = getValueBinding("action");
        if (vb != null) {
            return ((String) vb.getValue(getFacesContext()));
        } else {
            return (null);
        }

    }


    /**
     * <p>Set the Struts action to which this form should be submitted.</p>
     *
     * @param action The new action path
     */
    public void setAction(String action) {

        this.action = action;

    }


    /**
     * <p>Return the encoding type for this form submit.</p>
     */
    public String getEnctype() {

        if (this.enctype != null) {
            return (this.enctype);
        }
        ValueBinding vb = getValueBinding("enctype");
        if (vb != null) {
            return ((String) vb.getValue(getFacesContext()));
        } else {
            return (null);
        }

    }


    /**
     * <p>Set the encoding type for this form submit.</p>
     *
     * @param enctype The new enctype path
     */
    public void setEnctype(String enctype) {

        this.enctype = enctype;

    }


    /**
     * <p>Return the component family to which this component belongs.</p>
     */
    public String getFamily() {

        return "org.apache.struts.faces.Form";

    }


    /**
     * <p>Return the focus element name.</p>
     */
    public String getFocus() {

        if (this.focus != null) {
            return (this.focus);
        }
        ValueBinding vb = getValueBinding("focus");
        if (vb != null) {
            return ((String) vb.getValue(getFacesContext()));
        } else {
            return (null);
        }

    }


    /**
     * <p>Set the focus element name.</p>
     *
     * @param focus The new focus path
     */
    public void setFocus(String focus) {

        this.focus = focus;

    }


    /**
     * <p>Return the focus element index.</p>
     */
    public String getFocusIndex() {

        if (this.focusIndex != null) {
            return (this.focusIndex);
        }
        ValueBinding vb = getValueBinding("focusIndex");
        if (vb != null) {
            return ((String) vb.getValue(getFacesContext()));
        } else {
            return (null);
        }

    }


    /**
     * <p>Set the focus element index.</p>
     *
     * @param focusIndex The new focusIndex path
     */
    public void setFocusIndex(String focusIndex) {

        this.focusIndex = focusIndex;

    }


    /**
     * <p>Return the JavaScript to execute on form reset.</p>
     */
    public String getOnreset() {

        if (this.onreset != null) {
            return (this.onreset);
        }
        ValueBinding vb = getValueBinding("onreset");
        if (vb != null) {
            return ((String) vb.getValue(getFacesContext()));
        } else {
            return (null);
        }

    }


    /**
     * <p>Set the JavaScript to execute on form reset.</p>
     *
     * @param onreset The new onreset path
     */
    public void setOnreset(String onreset) {

        this.onreset = onreset;

    }


    /**
     * <p>Return the JavaScript to execute on form submit.</p>
     */
    public String getOnsubmit() {

        if (this.onsubmit != null) {
            return (this.onsubmit);
        }
        ValueBinding vb = getValueBinding("onsubmit");
        if (vb != null) {
            return ((String) vb.getValue(getFacesContext()));
        } else {
            return (null);
        }

    }


    /**
     * <p>Set the JavaScript to execute on form submit.</p>
     *
     * @param onsubmit The new onsubmit path
     */
    public void setOnsubmit(String onsubmit) {

        this.onsubmit = onsubmit;

    }


    /**
     * <p>Return the CSS style(s) to be rendered for this component.</p>
     */
    public String getStyle() {

        ValueBinding vb = getValueBinding("style");
        if (vb != null) {
            return (String) vb.getValue(getFacesContext());
        } else {
            return style;
        }

    }


    /**
     * <p>Set the CSS style(s) to be rendered for this component.</p>
     *
     * @param style The new CSS style(s)
     */
    public void setStyle(String style) {

        this.style = style;

    }


    /**
     * <p>Return the CSS style class(es) to be rendered for this component.</p>
     */
    public String getStyleClass() {

        ValueBinding vb = getValueBinding("styleClass");
        if (vb != null) {
            return (String) vb.getValue(getFacesContext());
        } else {
            return styleClass;
        }

    }


    /**
     * <p>Set the CSS style class(es) to be rendered for this component.</p>
     *
     * @param styleClass The new CSS style class(es)
     */
    public void setStyleClass(String styleClass) {

        this.styleClass = styleClass;

    }


    /**
     * <p>Return the target frame for the response to this form submit.</p>
     */
    public String getTarget() {

        ValueBinding vb = getValueBinding("target");
        if (vb != null) {
            return (String) vb.getValue(getFacesContext());
        } else {
            return target;
        }

    }


    /**
     * <p>Set the target frame for the response to this form submit.</p>
     *
     * @param target The new CSS target(s)
     */
    public void setTarget(String target) {

        this.target = target;

    }


    // ---------------------------------------------------------- UIForm Methods


    /**
     * <p>Create an instance of the form bean (if necessary) before
     * delegating to the standard decoding process.</p>
     *
     * @param context FacesContext for the request we are processing
     */
    public void processDecodes(FacesContext context) {

        if (context == null) {
            throw new NullPointerException();
        }

        if (log.isDebugEnabled()) {
            log.debug("processDecodes(" + getClientId(context) + ")");
        }

        // Create the form bean (if necessary)
        Map params = context.getExternalContext().getRequestParameterMap();
        if (params.containsKey(getClientId(context))) {
            createActionForm(context);
        }

        // Perform the standard decode processing
        super.processDecodes(context);

    }


    /**
     * <p>Restore our state from the specified object.</p>
     *
     * @param context <code>FacesContext</code> for the current request
     * @param state Object containing our saved state
     */
    public void restoreState(FacesContext context, Object state) {

        Object values[] = (Object[]) state;
        super.restoreState(context, values[0]);
        action = (String) values[1];
        enctype = (String) values[2];
        focus = (String) values[3];
        focusIndex = (String) values[4];
        onreset = (String) values[5];
        onsubmit = (String) values[6];
        style = (String) values[7];
        styleClass = (String) values[8];
        target = (String) values[9];

    }


    /**
     * <p>Create and return an object representing our state to be saved.</p>
     *
     * @param context <code>FacesContext</code> for the current request
     */
    public Object saveState(FacesContext context) {

        Object values[] = new Object[10];
        values[0] = super.saveState(context);
        values[1] = action;
        values[2] = enctype;
        values[3] = focus;
        values[4] = focusIndex;
        values[5] = onreset;
        values[6] = onsubmit;
        values[7] = style;
        values[8] = styleClass;
        values[9] = target;
        return (values);

    }



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


    /**
     * <p>Create an appropriate form bean in the appropriate scope, if one
     * does not already exist.</p>
     *
     * @param context FacesContext for the current request
     *
     * @exception IllegalArgumentException if no ActionConfig for the
     *  specified action attribute can be located
     * @exception IllegalArgumentException if no FormBeanConfig for the
     *  specified form bean can be located
     * @exception IllegalArgumentException if no ModuleConfig can be
     *  located for this application module
     */
    public void createActionForm(FacesContext context) {

        // Look up the application module configuration information we need
        ModuleConfig moduleConfig = lookupModuleConfig(context);

        // Look up the ActionConfig we are processing
        String action = getAction();
        ActionConfig actionConfig = moduleConfig.findActionConfig(action);
        if (actionConfig == null) {
            throw new IllegalArgumentException("Cannot find action '" +
                                               action + "' configuration");
        }

        // Does this ActionConfig specify a form bean?
        String name = actionConfig.getName();
        if (name == null) {
            return;
        }

        // Look up the FormBeanConfig we are processing
        FormBeanConfig fbConfig = moduleConfig.findFormBeanConfig(name);
        if (fbConfig == null) {
            throw new IllegalArgumentException("Cannot find form bean '" +
                                               name + "' configuration");
        }

        // Does a usable form bean attribute already exist?
        String attribute = actionConfig.getAttribute();
        String scope = actionConfig.getScope();
        ActionForm instance = null;
        if ("request".equals(scope)) {
            instance = (ActionForm)
                context.getExternalContext().getRequestMap().get(attribute);
        } else if ("session".equals(scope)) {
            HttpSession session = (HttpSession)
                context.getExternalContext().getSession(true);
            instance = (ActionForm)
                context.getExternalContext().getSessionMap().get(attribute);
        }
        if (instance != null) {
            if (fbConfig.getDynamic()) {
                String className =
                    ((DynaBean) instance).getDynaClass().getName();
                if (className.equals(fbConfig.getName())) {
                    if (log.isDebugEnabled()) {
                        log.debug
                            (" Recycling existing DynaActionForm instance " +
                             "of type '" + className + "'");
                    }
                    return;
                }
            } else {
                try {
                    Class configClass =
                        RequestUtils.applicationClass(fbConfig.getType());
                    if (configClass.isAssignableFrom(instance.getClass())) {
                        if (log.isDebugEnabled()) {
                            log.debug
                                (" Recycling existing ActionForm instance " +
                                 "of class '" + instance.getClass().getName()
                                 + "'");
                        }
                        return;
                    }
                } catch (Throwable t) {
                	IllegalArgumentException t2 = new IllegalArgumentException
                        ("Cannot load form bean class '" +
                         fbConfig.getType() + "'");
                    t2.initCause(t);
                    throw t2;
                }
            }
        }

        // Create a new form bean instance
        if (fbConfig.getDynamic()) {
            try {
                DynaActionFormClass dynaClass =
                    DynaActionFormClass.createDynaActionFormClass(fbConfig);
                instance = (ActionForm) dynaClass.newInstance();
                if (log.isDebugEnabled()) {
                    log.debug
                        (" Creating new DynaActionForm instance " +
                         "of type '" + fbConfig.getType() + "'");
                    log.trace(" --> " + instance);
                }
            } catch (Throwable t) {
            	IllegalArgumentException t2 = new IllegalArgumentException
                    ("Cannot create form bean of type '" +
                     fbConfig.getType() + "'");
                t2.initCause(t);
                throw t2;
            }
        } else {
            try {
                instance = (ActionForm)
                    RequestUtils.applicationInstance(fbConfig.getType());
                if (log.isDebugEnabled()) {
                    log.debug
                        (" Creating new ActionForm instance " +
                         "of type '" + fbConfig.getType() + "'");
                    log.trace(" --> " + instance);
                }
            } catch (Throwable t) {
            	IllegalArgumentException t2 = new IllegalArgumentException
                    ("Cannot create form bean of class '" +
                     fbConfig.getType() + "'");
                t2.initCause(t);
                throw t2;
            }
        }

        // Configure and cache the form bean instance in the correct scope
        ActionServlet servlet = (ActionServlet)
            context.getExternalContext().getApplicationMap().get
            (Globals.ACTION_SERVLET_KEY);
        instance.setServlet(servlet);
        if ("request".equals(scope)) {
            context.getExternalContext().getRequestMap().put
                (attribute, instance);
        } else if ("session".equals(scope)) {
            context.getExternalContext().getSessionMap().put
                (attribute, instance);
        }

    }


    /**
     * <p>Return the <code>ModuleConfig</code> for the application module
     * this form is being processed for.</p>
     *
     * @param context The <code>FacesContext</code> for the current request
     *
     * @exception IllegalArgumentException if no <code>ModuleConfig</code>
     *  can be found
     */
    public ModuleConfig lookupModuleConfig(FacesContext context) {

        // Look up the application module configuration information we need
        ModuleConfig modConfig = (ModuleConfig)
            context.getExternalContext().getRequestMap().get
            (Globals.MODULE_KEY);
        if (modConfig == null) {
            modConfig = (ModuleConfig)
                context.getExternalContext().getApplicationMap().get
                (Globals.MODULE_KEY);
        }
        if (modConfig == null) {
            throw new IllegalArgumentException
                ("Cannot find module configuration");
        }
        return (modConfig);

    }


}
