/*
* $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 org.apache.commons.chain.Context;
import org.apache.commons.chain.impl.ContextBase;
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;
import java.util.Locale;
import java.util.Map;
/**
* <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>
*/
public abstract class ActionContextBase extends ContextWrapper
implements ActionContext {
/**
* @see Constants.ACTION_KEY
*/
public static final String ACTION_KEY = Constants.ACTION_KEY;
/**
* @see
*/
public static final String ACTION_CONFIG_KEY = Constants.ACTION_CONFIG_KEY;
/**
* @see Constants.ACTION_FORM_KEY
*/
public static final String ACTION_FORM_KEY = Constants.ACTION_FORM_KEY;
/**
* @see Constants.FORWARD_CONFIG_KEY
*/
public static final String FORWARD_CONFIG_KEY =
Constants.FORWARD_CONFIG_KEY;
/**
* @see Constants.MODULE_CONFIG_KEY
*/
public static final String MODULE_CONFIG_KEY = Constants.MODULE_CONFIG_KEY;
/**
* @see 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";
/**
* @see Constants.MESSAGE_RESOURCES_KEY
*/
public static final String MESSAGE_RESOURCES_KEY =
Constants.MESSAGE_RESOURCES_KEY;
/**
* @see Constants.INCLUDE_KEY
*/
public static final String INCLUDE_KEY = Constants.INCLUDE_KEY;
/**
* @see Constants.LOCALE_KEY
*/
public static final String LOCALE_KEY = Constants.LOCALE_KEY;
/**
* @see Constants.CANCEL_KEY
*/
public static final String CANCEL_KEY = Constants.CANCEL_KEY;
/**
* @see 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.
*/
protected TokenProcessor token = null;
/**
* Store the Log instance for this Context.
*/
private Log logger = null;
/**
* Instantiate ActionContextBase, wrapping the given Context.
*
* @param context Context to wrap
*/
public ActionContextBase(Context context) {
super(context);
token = TokenProcessor.getInstance();
logger = LogFactory.getLog(this.getClass());
}
/**
* Instantiate ActionContextBase, wrapping a default ContextBase
* instance.
*/
public ActionContextBase() {
this(new ContextBase());
}
// -------------------------------
// General Application Support
// -------------------------------
public void release() {
this.token = null;
}
public abstract Map getApplicationScope();
public abstract Map getRequestScope();
public abstract Map getSessionScope();
public Map getScope(String scopeName) {
if (REQUEST_SCOPE.equals(scopeName)) {
return this.getRequestScope();
}
if (SESSION_SCOPE.equals(scopeName)) {
return this.getSessionScope();
}
if (APPLICATION_SCOPE.equals(scopeName)) {
return this.getApplicationScope();
}
throw new IllegalArgumentException("Invalid scope: " + scopeName);
}
// -------------------------------
// General Struts properties
// -------------------------------
public void setAction(Action action) {
this.put(ACTION_KEY, action);
}
public Action getAction() {
return (Action) this.get(ACTION_KEY);
}
public void setActionForm(ActionForm form) {
this.put(ACTION_FORM_KEY, form);
}
public ActionForm getActionForm() {
return (ActionForm) this.get(ACTION_FORM_KEY);
}
public void setActionConfig(ActionConfig config) {
this.put(ACTION_CONFIG_KEY, config);
}
public ActionConfig getActionConfig() {
return (ActionConfig) this.get(ACTION_CONFIG_KEY);
}
public void setForwardConfig(ForwardConfig forward) {
this.put(FORWARD_CONFIG_KEY, forward);
}
public ForwardConfig getForwardConfig() {
return (ForwardConfig) this.get(FORWARD_CONFIG_KEY);
}
public void setInclude(String include) {
this.put(INCLUDE_KEY, include);
}
public String getInclude() {
return (String) this.get(INCLUDE_KEY);
}
public Boolean getFormValid() {
return (Boolean) this.get(VALID_KEY);
}
public void setFormValid(Boolean valid) {
this.put(VALID_KEY, valid);
}
public ModuleConfig getModuleConfig() {
return (ModuleConfig) this.get(MODULE_CONFIG_KEY);
}
public void setModuleConfig(ModuleConfig config) {
this.put(MODULE_CONFIG_KEY, config);
}
public Exception getException() {
return (Exception) this.get(EXCEPTION_KEY);
}
public void setException(Exception e) {
this.put(EXCEPTION_KEY, e);
}
// -------------------------------
// ActionMessage Processing
// -------------------------------
public void addMessages(ActionMessages messages) {
this.addActionMessages(MESSAGE_ACTION_MESSAGES_KEY, messages);
}
public void addErrors(ActionMessages errors) {
this.addActionMessages(ERROR_ACTION_MESSAGES_KEY, errors);
}
public ActionMessages getErrors() {
return (ActionMessages) this.get(ERROR_ACTION_MESSAGES_KEY);
}
public ActionMessages getMessages() {
return (ActionMessages) this.get(MESSAGE_ACTION_MESSAGES_KEY);
}
public void saveErrors(ActionMessages errors) {
this.saveActionMessages(ERROR_ACTION_MESSAGES_KEY, errors);
}
public void saveMessages(ActionMessages messages) {
this.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(String key, ActionMessages messages) {
if (messages == null) {
// bad programmer! *slap*
return;
}
// get any existing messages from the request, or make a new one
ActionMessages requestMessages = (ActionMessages) this.get(key);
if (requestMessages == null) {
requestMessages = new ActionMessages();
}
// add incoming messages
requestMessages.add(messages);
// if still empty, just wipe it out from the request
this.remove(key);
// save the messages
this.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(String key, ActionMessages messages) {
this.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
* @param key
* @param messages
*/
public void saveActionMessages(String scopeId, String key,
ActionMessages messages) {
Map 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
*/
public void saveMessages(String scope, ActionMessages messages) {
this.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. *
public void saveToken() {
String token = this.generateToken();
this.put(TRANSACTION_TOKEN_KEY, token);
}
public String generateToken() {
return 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
protected String getTokenGeneratorId() {
return "";
}
public boolean isTokenValid() {
return this.isTokenValid(false);
}
public boolean isTokenValid(boolean reset) {
// Retrieve the transaction token from this session, and
// reset it if requested
String saved = (String) this.get(TRANSACTION_TOKEN_KEY);
if (saved == null) {
return false;
}
if (reset) {
this.resetToken();
}
// Retrieve the transaction token included in this request
String token = (String) this.get(TOKEN_KEY);
if (token == null) {
return false;
}
return saved.equals(token);
}
public void resetToken() {
this.remove(TRANSACTION_TOKEN_KEY);
}
// -------------------------------
// Cancel Processing
// -------------------------------
public Boolean getCancelled() {
return (Boolean) this.get(CANCEL_KEY);
}
public void setCancelled(Boolean cancelled) {
this.put(CANCEL_KEY, cancelled);
}
// -------------------------------
// MessageResources Processing
// -------------------------------
public void setMessageResources(MessageResources messageResources) {
this.put(MESSAGE_RESOURCES_KEY, messageResources);
}
public MessageResources getMessageResources() {
return (MessageResources) this.get(MESSAGE_RESOURCES_KEY);
}
public MessageResources getMessageResources(String key) {
return (MessageResources) this.get(key);
}
// -------------------------------
// Locale Processing
// -------------------------------
public void setLocale(Locale locale) {
this.put(LOCALE_KEY, locale);
}
public Locale getLocale() {
return (Locale) this.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> Set the commons-logging <code>Log</code> instance which should be
* used to LOG messages. This is initialized at instantiation time but may
* be overridden. Be advised not to set the value to null, as
* <code>ActionContextBase</code> uses the logger for some of its own
* operations. </p>
*/
public void setLogger(Log logger) {
this.logger = 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
* @throws IllegalAccessException If object cannot be created
* @throws InstantiationException If object cannot be created
* @see this.findOrCreateActionForm(String, String, ModuleConfig)
*/
public ActionForm findOrCreateActionForm(String formName, String scopeName)
throws IllegalAccessException, InstantiationException {
return this.findOrCreateActionForm(formName, scopeName,
this.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)
* @return The ActionForm for this request
* @throws IllegalAccessException If object cannot be created
* @throws InstantiationException If object cannot be created
* @throws IllegalArgumentException If form config is missing from module
* or scopeName is invalid
*/
public ActionForm findOrCreateActionForm(String formName, String scopeName,
ModuleConfig moduleConfig)
throws IllegalAccessException, InstantiationException {
Map scope = this.getScope(scopeName);
ActionForm instance;
FormBeanConfig formBeanConfig =
moduleConfig.findFormBeanConfig(formName);
if (formBeanConfig == null) {
throw new IllegalArgumentException("No form config found under "
+ formName + " in module " + moduleConfig.getPrefix());
}
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(this);
// ISSUE: Should we check this call to put?
scope.put(formName, form);
return form;
}
}