/*
 * $Id: ActionContextBase.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.contexts;

import java.util.Locale;
import java.util.Map;

import org.apache.commons.chain2.Context;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.chain.Constants;
import org.apache.struts.config.ActionConfig;
import org.apache.struts.config.FormBeanConfig;
import org.apache.struts.config.ForwardConfig;
import org.apache.struts.config.ModuleConfig;
import org.apache.struts.util.MessageResources;
import org.apache.struts.util.TokenProcessor;

/**
 * <p> Provide an abstract but semi-complete implementation of ActionContext
 * to serve as the base for concrete implementations. </p> <p> The abstract
 * methods to implement are the accessors for the named states,
 * <code>getApplicationScope</code>, <code>getRequestScope</code>, and
 * <code>getSessionScope</code>. </p>
 * @param <T> T
 */
public abstract class ActionContextBase<T extends Context<String, Object>>
        extends ContextWrapper<String, Object, T>
        implements ActionContext<String, Object> {

    /**
     * Constants.ACTION_KEY
     */
    public static final String ACTION_KEY = Constants.ACTION_KEY;

    /**
     * Constants.ACTION_CONFIG_KEY
     */
    public static final String ACTION_CONFIG_KEY = Constants.ACTION_CONFIG_KEY;

    /**
     * Constants.ACTION_FORM_KEY
     */
    public static final String ACTION_FORM_KEY = Constants.ACTION_FORM_KEY;

    /**
     * Constants.FORWARD_CONFIG_KEY
     */
    public static final String FORWARD_CONFIG_KEY = Constants.FORWARD_CONFIG_KEY;

    /**
     * Constants.MODULE_CONFIG_KEY
     */
    public static final String MODULE_CONFIG_KEY = Constants.MODULE_CONFIG_KEY;

    /**
     * Constants.EXCEPTION_KEY
     */
    public static final String EXCEPTION_KEY = Constants.EXCEPTION_KEY;

    /**
     * <p> Provide the default context attribute under which to store the
     * ActionMessage cache for errors. </p>
     */
    public static final String ERROR_ACTION_MESSAGES_KEY = "errors";

    /**
     * <p> Provide the default context attribute under which to store the
     * ActionMessage cache. </p>
     */
    public static final String MESSAGE_ACTION_MESSAGES_KEY = "messages";

    /**
     * Constants.MESSAGE_RESOURCES_KEY
     */
    public static final String MESSAGE_RESOURCES_KEY =
        Constants.MESSAGE_RESOURCES_KEY;

    /**
     * Constants.INCLUDE_KEY
     */
    public static final String INCLUDE_KEY = Constants.INCLUDE_KEY;

    /**
     * Constants.LOCALE_KEY
     */
    public static final String LOCALE_KEY = Constants.LOCALE_KEY;

    /**
     * Constants.CANCEL_KEY
     */
    public static final String CANCEL_KEY = Constants.CANCEL_KEY;

    /**
     * Constants.VALID_KEY
     */
    public static final String VALID_KEY = Constants.VALID_KEY;

    /**
     * Provide the default context attribute under which to store the
     * transaction token key.
     */
    public static final String TRANSACTION_TOKEN_KEY = "TRANSACTION_TOKEN_KEY";

    /**
     * Provide the default context attribute under which to store the token
     * key.
     */
    public static final String TOKEN_KEY = "TOKEN_KEY";

    /**
     * Store the TokenProcessor instance for this Context.
     */
    private final TokenProcessor token;

    /**
     * Store the Log instance for this Context.
     */
    private final Log logger;

    /**
     * Instantiate ActionContextBase, wrapping the given Context.
     *
     * @param context Context to wrap
     */
    public ActionContextBase(final T context) {
        super(context);
        this.token = TokenProcessor.getInstance();
        this.logger = LogFactory.getLog(this.getClass());
    }

    // -------------------------------
    // General Application Support
    // -------------------------------

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#release()
     */
    @Override
    public void release() {
        return;
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#getScope(java.lang.String)
     */
    @Override
    public Map<String, Object> getScope(final String scopeName) {
        if (REQUEST_SCOPE.equals(scopeName)) {
            return getRequestScope();
        }

        if (SESSION_SCOPE.equals(scopeName)) {
            return getSessionScope();
        }

        if (APPLICATION_SCOPE.equals(scopeName)) {
            return getApplicationScope();
        }

        throw new IllegalArgumentException("Invalid scope: " + scopeName);
    }

    // -------------------------------
    // General Struts properties
    // -------------------------------

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#setAction(
     * org.apache.struts.action.Action)
     */
    @Override
    public void setAction(final Action action) {
        put(ACTION_KEY, action);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#getAction()
     */
    @Override
    public Action getAction() {
        return (Action) get(ACTION_KEY);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#setActionForm(
     * org.apache.struts.action.ActionForm)
     */
    @Override
    public void setActionForm(final ActionForm form) {
        put(ACTION_FORM_KEY, form);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#getActionForm()
     */
    @Override
    public ActionForm getActionForm() {
        return (ActionForm) get(ACTION_FORM_KEY);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#setActionConfig(
     * org.apache.struts.config.ActionConfig)
     */
    @Override
    public void setActionConfig(final ActionConfig config) {
        put(ACTION_CONFIG_KEY, config);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#getActionConfig()
     */
    @Override
    public ActionConfig getActionConfig() {
        return (ActionConfig) get(ACTION_CONFIG_KEY);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#setForwardConfig(
     * org.apache.struts.config.ForwardConfig)
     */
    @Override
    public void setForwardConfig(final ForwardConfig forward) {
        put(FORWARD_CONFIG_KEY, forward);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#getForwardConfig()
     */
    @Override
    public ForwardConfig getForwardConfig() {
        return (ForwardConfig) get(FORWARD_CONFIG_KEY);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#setInclude(java.lang.String)
     */
    @Override
    public void setInclude(final String include) {
        put(INCLUDE_KEY, include);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#getInclude()
     */
    @Override
    public String getInclude() {
        return (String) get(INCLUDE_KEY);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#getFormValid()
     */
    @Override
    public Boolean getFormValid() {
        return (Boolean) get(VALID_KEY);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#setFormValid(java.lang.Boolean)
     */
    @Override
    public void setFormValid(final Boolean valid) {
        put(VALID_KEY, valid);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#getModuleConfig()
     */
    @Override
    public ModuleConfig getModuleConfig() {
        return (ModuleConfig) get(MODULE_CONFIG_KEY);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#setModuleConfig(
     * org.apache.struts.config.ModuleConfig)
     */
    @Override
    public void setModuleConfig(final ModuleConfig config) {
        put(MODULE_CONFIG_KEY, config);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#getException()
     */
    @Override
    public Exception getException() {
        return (Exception) get(EXCEPTION_KEY);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#setException(java.lang.Exception)
     */
    @Override
    public void setException(final Exception e) {
        put(EXCEPTION_KEY, e);
    }

    // -------------------------------
    // ActionMessage Processing
    // -------------------------------

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#addMessages(
     * org.apache.struts.action.ActionMessages)
     */
    @Override
    public void addMessages(final ActionMessages messages) {
        addActionMessages(MESSAGE_ACTION_MESSAGES_KEY, messages);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#addErrors(
     * org.apache.struts.action.ActionMessages)
     */
    @Override
    public void addErrors(final ActionMessages errors) {
        addActionMessages(ERROR_ACTION_MESSAGES_KEY, errors);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#getErrors()
     */
    @Override
    public ActionMessages getErrors() {
        return (ActionMessages) get(ERROR_ACTION_MESSAGES_KEY);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#getMessages()
     */
    @Override
    public ActionMessages getMessages() {
        return (ActionMessages) get(MESSAGE_ACTION_MESSAGES_KEY);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#saveErrors(
     * org.apache.struts.action.ActionMessages)
     */
    @Override
    public void saveErrors(final ActionMessages errors) {
        saveActionMessages(ERROR_ACTION_MESSAGES_KEY, errors);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#saveMessages(
     * org.apache.struts.action.ActionMessages)
     */
    @Override
    public void saveMessages(final ActionMessages messages) {
        saveActionMessages(MESSAGE_ACTION_MESSAGES_KEY, messages);
    }

    // ISSUE: do we want to add this to the public API?

    /**
     * <p> Add the given messages to a cache stored in this Context, under
     * key. </p>
     *
     * @param key      The attribute name for the message cache
     * @param messages The ActionMessages to add
     */
    public void addActionMessages(final String key, final ActionMessages messages) {
        if (messages == null) {
            // bad programmer! *slap*
            return;
        }

        // get any existing messages from the request, or make a new one
        ActionMessages requestMessages = retrieve(key);

        if (requestMessages == null) {
            requestMessages = new ActionMessages();
        }

        // add incoming messages
        requestMessages.add(messages);

        // if still empty, just wipe it out from the request
        remove(key);

        // save the messages
        saveActionMessages(key, requestMessages);
    }

    // ISSUE: do we want to add this to the public API?

    /**
     * <p> Save the given ActionMessages into the request scope under the
     * given key, clearing the attribute if the messages are empty or null.
     * </p>
     *
     * @param key      The attribute name for the message cache
     * @param messages The ActionMessages to add
     */
    public void saveActionMessages(final String key, final ActionMessages messages) {
        saveActionMessages(REQUEST_SCOPE, key, messages);
    }

    /**
     * <p>Save the given <code>messages</code> into the map identified by the
     * given <code>scopeId</code> under the given <code>key</code>.</p>
     *
     * @param scopeId String
     * @param key String
     * @param messages ActionMessages
     */
    public void saveActionMessages(final String scopeId, final String key,
            final ActionMessages messages) {
        Map<String, Object> scope = getScope(scopeId);

        if ((messages == null) || messages.isEmpty()) {
            scope.remove(key);

            return;
        }

        scope.put(key, messages);
    }

    // ISSUE: Should we deprecate this method, since it is misleading?
    // Do we need it for backward compatibility?

    /**
     * <p> Adapt a legacy form of SaveMessages to the ActionContext API by
     * storing the ActoinMessages under the default scope.
     *
     * @param scope    The scope for the internal cache
     * @param messages ActionMesssages to cache
     */
    @Override
    public void saveMessages(final String scope, final ActionMessages messages) {
        saveMessages(messages);
    }

    // -------------------------------
    // Token Processing
    // -------------------------------
    // ISSUE: Should there be a getToken method?
    // Is there a problem trying to map this method from Action
    // to ActionContext when we aren't necessarily sure how token
    // processing maps into a context with an ill-defined "session"?
    // There's no getToken() method, but maybe there should be. *

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#saveToken()
     */
    @Override
    public void saveToken() {

        put(TRANSACTION_TOKEN_KEY, generateToken());
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#generateToken()
     */
    @Override
    public String generateToken() {
        return this.token.generateToken(getTokenGeneratorId());
    }

    // ISSUE: The original implementation was based on the HttpSession
    // identifier; what would be a way to do that without depending on the
    // Servlet API?
    // REPLY: uuid's
    // http://java.sun.com/products/jini/2.0/doc/specs/api/net/jini/id/Uuid.html

    /**
     * @return Token
     */
    protected String getTokenGeneratorId() {
        return "";
    }

    /**
     * @return TokenProcessor
     */
    protected TokenProcessor getTokenProcessor() {
        return this.token;
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#isTokenValid()
     */
    @Override
    public boolean isTokenValid() {
        return isTokenValid(false);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#isTokenValid(boolean)
     */
    @Override
    public boolean isTokenValid(final boolean reset) {
        // Retrieve the transaction token from this session, and
        // reset it if requested
        String saved = retrieve(TRANSACTION_TOKEN_KEY);

        if (saved == null) {
            return false;
        }

        if (reset) {
            resetToken();
        }

        // Retrieve the transaction token included in this request
        String tk = retrieve(TOKEN_KEY);

        if (tk == null) {
            return false;
        }

        return saved.equals(tk);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#resetToken()
     */
    @Override
    public void resetToken() {
        remove(TRANSACTION_TOKEN_KEY);
    }

    // -------------------------------
    // Cancel Processing
    // -------------------------------

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#getCancelled()
     */
    @Override
    public Boolean getCancelled() {
        return (Boolean) get(CANCEL_KEY);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#setCancelled(java.lang.Boolean)
     */
    @Override
    public void setCancelled(final Boolean cancelled) {
        put(CANCEL_KEY, cancelled);
    }

    // -------------------------------
    // MessageResources Processing
    // -------------------------------

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#setMessageResources(
     * org.apache.struts.util.MessageResources)
     */
    @Override
    public void setMessageResources(final MessageResources messageResources) {
        put(MESSAGE_RESOURCES_KEY, messageResources);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#getMessageResources()
     */
    @Override
    public MessageResources getMessageResources() {
        return (MessageResources) get(MESSAGE_RESOURCES_KEY);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#getMessageResources(java.lang.String)
     */
    @Override
    public MessageResources getMessageResources(final String key) {
        return (MessageResources) get(key);
    }

    // -------------------------------
    // Locale Processing
    // -------------------------------

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#setLocale(java.util.Locale)
     */
    @Override
    public void setLocale(final Locale locale) {
        put(LOCALE_KEY, locale);
    }

    /**
     * @see org.apache.struts.chain.contexts.ActionContext#getLocale()
     */
    @Override
    public Locale getLocale() {
        return (Locale) get(LOCALE_KEY);
    }

    // -------------------------------
    // Convenience Methods: these are not part of the formal ActionContext API,
    // but are likely to be commonly useful.
    // -------------------------------

    /**
     * <p> Provide the currently configured commons-logging <code>Log</code>
     * instance. </p>
     *
     * @return Log instance for this context
     */
    public Log getLogger() {
        return this.logger;
    }

    /**
     * <p> Using this <code>ActionContext</code>'s default
     * <code>ModuleConfig</code>, return an existing <code>ActionForm</code>
     * in the specified scope, or create a new one and add it to the specified
     * scope. </p>
     *
     * @param formName  The name attribute of our ActionForm
     * @param scopeName The scope identier (request, session)
     * @return The ActionForm for this request
     *
     */
    @Override
    public ActionForm findOrCreateActionForm(final String formName, final String scopeName) {
        return findOrCreateActionForm(formName, scopeName, getModuleConfig());
    }

    /**
     * <p> In the context of the given <code>ModuleConfig</code> and this
     * <code>ActionContext</code>, look for an existing
     * <code>ActionForm</code> in the specified scope. If one is found, return
     * it; otherwise, create a new instance, add it to that scope, and then
     * return it. </p>
     * @param formName  The name attribute of our ActionForm
     * @param scopeName The scope identier (request, session)
     * @param moduleConfig ModuleConfig
     * @return The ActionForm for this request
     * @throws IllegalArgumentException If form config is missing from module
     *                                  or scopeName is invalid
     */
    public ActionForm findOrCreateActionForm(final String formName, final String scopeName,
            final ModuleConfig moduleConfig) {
        Map<String, Object> scope = getScope(scopeName);

        FormBeanConfig formBeanConfig =
            moduleConfig.findFormBeanConfig(formName);

        if (formBeanConfig == null) {
            throw new IllegalArgumentException("No form config found under "
                + formName + " in module " + moduleConfig.getPrefix());
        }

        ActionForm instance = (ActionForm) scope.get(formName);

        // ISSUE: Can we recycle the existing instance (if any)?
        if (instance != null) {
            getLogger().trace("Found an instance in scope " + scopeName
                + "; test for reusability");

            if (formBeanConfig.canReuse(instance)) {
                return instance;
            }
        }

        ActionForm form = formBeanConfig.createActionForm();

        // ISSUE: Should we check this call to put?
        scope.put(formName, form);

        return form;
    }
}
