/*
 * $Id: CopyFormToContext.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.chain.commands.generic;

import org.apache.commons.chain2.Processing;
import org.apache.struts.action.ActionForm;
import org.apache.struts.chain.commands.ActionCommandBase;
import org.apache.struts.chain.contexts.ActionContext;
import org.apache.struts.chain.contexts.ActionContextBase;
import org.apache.struts.config.ActionConfig;

/**
 * <p>Subclass this command and configure it as part of a per-forward chain to
 * perform any necessary pre-population or other preparation for a form before
 * control is dispatched to the view layer.</p>
 *
 * @version $Id: CopyFormToContext.java 471754 2006-11-06 14:55:09Z husted $
 */
public class CopyFormToContext implements ActionCommandBase {

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

    /**
     * <p>The name of a form bean as configured in a struts-config.xml file
     * for this module.  </p>
     *
     * <p> Either actionPath or both this and scope are required configuration
     * properties.</p>
     */
    private String formName = null;

    /**
     * <p>The name of a scope, such as "request" or "session" in which the
     * form to be prepared will be placed for reference by the view and other
     * parts of Struts.</p>
     *
     * <p>Either <code>actionPath</code> or both this and
     * <code>formName</code> are required configuration properties.</p>
     */
    private String scope = null;

    /**
     * <p>The path of an <code>&lt;action&gt;</code> mapping as configured in
     * a <code>struts-config.xml</code> file for this module.  This action
     * will be looked up, and its <code>name</code> and <code>scope</code>
     * values will be used as if those values were configured directly in this
     * instance's <code>formName</code> and <code>scope</code>
     * properties.</p>
     *
     * <p>Either <code>this</code> or both <code>scope</code> and
     * <code>formName</code> are required configuration properties.</p>
     */
    private String actionPath = null;

    /**
     * The context key under which the form which was looked up will be
     * stored. Defaults to "actionForm" but may be overridden in cases where
     * the "request" ActionForm must be preserved.
     */
    private String toKey = ActionContextBase.ACTION_FORM_KEY;

    // ------------------------------------------------------ Properties

    /**
     * <p>Return ActionPath property.</p>
     *
     * @return ActionPath property
     */
    public String getActionPath() {
        return this.actionPath;
    }

    /**
     * <p>Set ActionPath property.</p>
     *
     * @param val New value for ActionPath
     */
    public void setActionPath(final String val) {
        this.actionPath = val;
    }

    /**
     * <p>Return FormName property.</p>
     *
     * @return FormName property
     */
    public String getFormName() {
        return this.formName;
    }

    /**
     * <p>Set FormName property.</p>
     *
     * @param val New value for FormName
     */
    public void setFormName(final String val) {
        this.formName = val;
    }

    /**
     * <p>Return Scope property.</p>
     *
     * @return Scope property
     */
    public String getScope() {
        return this.scope;
    }

    /**
     * <p>Set Scope property.</p>
     *
     * @param val New value for Scope
     */
    public void setScope(final String val) {
        this.scope = val;
    }

    /**
     * <p>Return ToKey property.</p>
     *
     * @return ToKey property
     */
    public String getToKey() {
        return this.toKey;
    }

    /**
     * <p>Set ToKey property.</p>
     *
     * @param val New valuefor FormName
     */
    public void setToKey(final String val) {
        this.toKey = val;
    }

    // ------------------------------------------------------

    /**
     * <p>Look up an ActionForm instance based on the configured properties of
     * this command and copy it into the <code>Context</code>.  After this
     * command successfully executes, an ActionForm instance will exist in the
     * specified scope and will be available, for example for backing fields
     * in an HTML form.  It will also be in the <code>ActionContext</code>
     * available for another command to do pre population of values or other
     * preparation.</p>
     *
     * @param actionContext Our ActionContext
     * @return TRUE if processing should halt
     *
     */
    @Override
    public Processing execute(final ActionContext<String, Object> actionContext) {

        ActionForm form = findOrCreateForm(actionContext);
        if (isEmpty(getToKey())) {
            throw new IllegalStateException("Property 'toKey' must be defined.");
        }

        actionContext.put(getToKey(), form);

        return Processing.CONTINUE;
    }

    /**
     * <p>Based on the properties of this command and the given
     * <code>ActionContext</code>, find or create an ActionForm instance for
     * preparation.</p>
     *
     * @param context ActionContextBase class that we are processing
     * @return ActionForm instance
     * @throws IllegalArgumentException On ActionConfig not found
     * @throws IllegalStateException    On undefined scope and form bean
     */
    protected ActionForm findOrCreateForm(final ActionContext<String, Object> context) {

        String effectiveFormName;
        String effectiveScope;
        if (!(isEmpty(this.getActionPath()))) {
            ActionConfig actionConfig =
                context.getModuleConfig().findActionConfig(this.getActionPath());
            if (actionConfig == null) {
                throw new IllegalArgumentException(
                    "No ActionConfig found for path " + this.getActionPath());
            }

            effectiveFormName = actionConfig.getName();
            effectiveScope = actionConfig.getScope();
        } else {
            effectiveFormName = this.getFormName();
            effectiveScope = this.getScope();
        }

        if (isEmpty(effectiveScope) || isEmpty(effectiveFormName)) {
            throw new IllegalStateException("Both scope [" + effectiveScope
                + "] and formName [" + effectiveFormName + "] must be defined.");
        }

        return findOrCreateForm(context, effectiveFormName, effectiveScope);
    }

    /**
     * <p>Actually find or create an instance of ActionForm configured under
     * the form-bean-name <code>effectiveFormName</code>, looking in in the
     * <code>ActionContext's</code> scope as identified by
     * <code>effectiveScope</code>. If a form is created, it will also be
     * stored in that scope.</p>
     *
     * <p><b>NOTE:</b> This specific method depends on the instance of
     * <code>ActionContext</code> which is passed being a subclass of
     * <code>ActionContextBase</code>, which implements the utility method
     * <code>findOrCreateActionForm</code>. </p>
     *
     * @param ctx               The ActionContext we are processing
     * @param effectiveFormName the target form name
     * @param effectiveScope    The target scope
     * @return ActionForm instance, storing in scope if created
     */
    protected ActionForm findOrCreateForm(final ActionContext<String, Object> ctx,
            final String effectiveFormName, final String effectiveScope) {

        ActionForm form =
            ctx.findOrCreateActionForm(effectiveFormName, effectiveScope);

        return form;
    }

    /**
     * <p>Convenience method to test for an empty string.</p>
     *
     * @param test String to test
     * @return TRUE if test is null or zero-length
     */
    private boolean isEmpty(final String test) {
        return (test == null) || (test.trim().isEmpty());
    }
}
