/* * $Id$ * * 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.struts2.interceptor; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import org.apache.struts2.dispatcher.ServletRedirectResult; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.ValidationAware; import com.opensymphony.xwork2.interceptor.AbstractInterceptor; import com.opensymphony.xwork2.util.logging.Logger; import com.opensymphony.xwork2.util.logging.LoggerFactory; /** * <!-- START SNIPPET: description --> * * An interceptor to store a {@link ValidationAware} action's messages / errors and field errors into * HTTP Session, such that it will be retrievable at a later stage. This allows the action's message / * errors and field errors to be available longer that just the particular HTTP request. * * If no session exists, nothing will be stored and can be retrieved later. In other terms, * the application is responsible to open the session. * * <p/> * * In the 'STORE' mode, the interceptor will store the {@link ValidationAware} action's message / errors * and field errors into HTTP session. * * <p/> * * In the 'RETRIEVE' mode, the interceptor will retrieve the stored action's message / errors and field * errors and put them back into the {@link ValidationAware} action. * * <p/> * * In the 'AUTOMATIC' mode, the interceptor will always retrieve the stored action's message / errors * and field errors and put them back into the {@link ValidationAware} action, and after Action execution, * if the {@link com.opensymphony.xwork2.Result} is an instance of {@link ServletRedirectResult}, the action's message / errors * and field errors into automatically be stored in the HTTP session.. * * <p/> * * The interceptor does nothing in the 'NONE' mode, which is the default. * * <p/> * * The operation mode could be switched using :- <p/> * 1] Setting the iterceptor parameter eg. * <pre> * <action name="submitApplication" ...> * <interceptor-ref name="store"> * <param name="operationMode">STORE</param> * </interceptor-ref> * <interceptor-ref name="defaultStack" /> * .... * </action> * </pre> * * 2] Through request parameter (allowRequestParameterSwitch must be 'true' which is the default) * <pre> * // the request will have the operation mode in 'STORE' * http://localhost:8080/context/submitApplication.action?operationMode=STORE * </pre> * * <!-- END SNIPPET: description --> * * * <!-- START SNIPPET: parameters --> * * <ul> * <li>allowRequestParameterSwitch - To enable request parameter that could switch the operation mode * of this interceptor. </li> * <li>requestParameterSwitch - The request parameter that will indicate what mode this * interceptor is in. </li> * <li>operationMode - The operation mode this interceptor should be in * (either 'STORE', 'RETRIEVE', 'AUTOMATIC', or 'NONE'). 'NONE' being the default.</li> * </ul> * * <!-- END SNIPPET: parameters --> * * <p/> * * <!-- START SNIPPET: extending --> * * The following method could be overriden :- * <ul> * <li>getRequestOperationMode - get the operation mode of this interceptor based on the request parameters</li> * <li>mergeCollection - merge two collections</li> * <li>mergeMap - merge two map</li> * </ul> * * <!-- END SNIPPET: extending --> * * <pre> * <!-- START SNIPPET: example --> * * <action name="submitApplication" ....> * <interceptor-ref name="store"> * <param name="operationMode">STORE</param> * </interceptor-ref> * <interceptor-ref name="defaultStack" /> * <result name="input" type="redirect">applicationFailed.action</result> * <result type="dispatcher">applicationSuccess.jsp</result> * </action> * * <action name="applicationFailed" ....> * <interceptor-ref name="store"> * <param name="operationMode">RETRIEVE</param> * </interceptor-ref> * <result>applicationFailed.jsp</result> * </action> * * <!-- END SNIPPET: example --> * </pre> * * <!-- START SNIPPET: exampleDescription --> * * With the example above, 'submitApplication.action' will have the action messages / errors / field errors stored * in the HTTP Session. Later when needed, (in this case, when 'applicationFailed.action' is fired, it * will get the action messages / errors / field errors stored in the HTTP Session and put them back into * the action. * * <!-- END SNIPPET: exampleDescription --> * * @version $Date$ $Id$ */ public class MessageStoreInterceptor extends AbstractInterceptor { private static final long serialVersionUID = 9161650888603380164L; private static final Logger LOG = LoggerFactory.getLogger(MessageStoreInterceptor.class); public static final String AUTOMATIC_MODE = "AUTOMATIC"; public static final String STORE_MODE = "STORE"; public static final String RETRIEVE_MODE = "RETRIEVE"; public static final String NONE = "NONE"; private boolean allowRequestParameterSwitch = true; private String requestParameterSwitch = "operationMode"; private String operationMode = NONE; public static final String fieldErrorsSessionKey = "__MessageStoreInterceptor_FieldErrors_SessionKey"; public static final String actionErrorsSessionKey = "__MessageStoreInterceptor_ActionErrors_SessionKey"; public static final String actionMessagesSessionKey = "__MessageStoreInterceptor_ActionMessages_SessionKey"; public void setAllowRequestParameterSwitch(boolean allowRequestParameterSwitch) { this.allowRequestParameterSwitch = allowRequestParameterSwitch; } public boolean getAllowRequestParameterSwitch() { return this.allowRequestParameterSwitch; } public void setRequestParameterSwitch(String requestParameterSwitch) { this.requestParameterSwitch = requestParameterSwitch; } public String getRequestParameterSwitch() { return this.requestParameterSwitch; } public void setOperationMode(String operationMode) { this.operationMode = operationMode; } public String getOperationModel() { return this.operationMode; } public String intercept(ActionInvocation invocation) throws Exception { if (LOG.isDebugEnabled()) { LOG.debug("entering MessageStoreInterceptor ..."); } before(invocation); String result = invocation.invoke(); after(invocation, result); if (LOG.isDebugEnabled()) { LOG.debug("exit executing MessageStoreInterceptor"); } return result; } /** * Handle the retrieving of field errors / action messages / field errors, which is * done before action invocation, and the <code>operationMode</code> is 'RETRIEVE'. * * @param invocation * @throws Exception */ protected void before(ActionInvocation invocation) throws Exception { String reqOperationMode = getRequestOperationMode(invocation); if (RETRIEVE_MODE.equalsIgnoreCase(reqOperationMode) || RETRIEVE_MODE.equalsIgnoreCase(operationMode) || AUTOMATIC_MODE.equalsIgnoreCase(operationMode)) { Object action = invocation.getAction(); if (action instanceof ValidationAware) { // retrieve error / message from session Map session = (Map) invocation.getInvocationContext().get(ActionContext.SESSION); if (session == null) { if (LOG.isDebugEnabled()) { LOG.debug("Session is not open, no errors / messages could be retrieve for action ["+action+"]"); } return; } ValidationAware validationAwareAction = (ValidationAware) action; if (LOG.isDebugEnabled()) { LOG.debug("retrieve error / message from session to populate into action ["+action+"]"); } Collection actionErrors = (Collection) session.get(actionErrorsSessionKey); Collection actionMessages = (Collection) session.get(actionMessagesSessionKey); Map fieldErrors = (Map) session.get(fieldErrorsSessionKey); if (actionErrors != null && actionErrors.size() > 0) { Collection mergedActionErrors = mergeCollection(validationAwareAction.getActionErrors(), actionErrors); validationAwareAction.setActionErrors(mergedActionErrors); } if (actionMessages != null && actionMessages.size() > 0) { Collection mergedActionMessages = mergeCollection(validationAwareAction.getActionMessages(), actionMessages); validationAwareAction.setActionMessages(mergedActionMessages); } if (fieldErrors != null && fieldErrors.size() > 0) { Map mergedFieldErrors = mergeMap(validationAwareAction.getFieldErrors(), fieldErrors); validationAwareAction.setFieldErrors(mergedFieldErrors); } session.remove(actionErrorsSessionKey); session.remove(actionMessagesSessionKey); session.remove(fieldErrorsSessionKey); } } } /** * Handle the storing of field errors / action messages / field errors, which is * done after action invocation, and the <code>operationMode</code> is in 'STORE'. * * @param invocation * @param result * @throws Exception */ protected void after(ActionInvocation invocation, String result) throws Exception { String reqOperationMode = getRequestOperationMode(invocation); boolean isRedirect = invocation.getResult() instanceof ServletRedirectResult; if (STORE_MODE.equalsIgnoreCase(reqOperationMode) || STORE_MODE.equalsIgnoreCase(operationMode) || (AUTOMATIC_MODE.equalsIgnoreCase(operationMode) && isRedirect)) { Object action = invocation.getAction(); if (action instanceof ValidationAware) { // store error / messages into session Map session = (Map) invocation.getInvocationContext().get(ActionContext.SESSION); if (session == null) { if (LOG.isDebugEnabled()) { LOG.debug("Could not store action ["+action+"] error/messages into session, because session hasn't been opened yet."); } return; } if (LOG.isDebugEnabled()) { LOG.debug("store action ["+action+"] error/messages into session "); } ValidationAware validationAwareAction = (ValidationAware) action; session.put(actionErrorsSessionKey, validationAwareAction.getActionErrors()); session.put(actionMessagesSessionKey, validationAwareAction.getActionMessages()); session.put(fieldErrorsSessionKey, validationAwareAction.getFieldErrors()); } else if(LOG.isDebugEnabled()) { LOG.debug("Action ["+action+"] is not ValidationAware, no message / error that are storeable"); } } } /** * Get the operationMode through request paramter, if <code>allowRequestParameterSwitch</code> * is 'true', else it simply returns 'NONE', meaning its neither in the 'STORE_MODE' nor * 'RETRIEVE_MODE'. * * @return String */ protected String getRequestOperationMode(ActionInvocation invocation) { String reqOperationMode = NONE; if (allowRequestParameterSwitch) { Map reqParams = (Map) invocation.getInvocationContext().get(ActionContext.PARAMETERS); boolean containsParameter = reqParams.containsKey(requestParameterSwitch); if (containsParameter) { String[] reqParamsArr = (String[]) reqParams.get(requestParameterSwitch); if (reqParamsArr != null && reqParamsArr.length > 0) { reqOperationMode = reqParamsArr[0]; } } } return reqOperationMode; } /** * Merge <code>col1</code> and <code>col2</code> and return the composite * <code>Collection</code>. * * @param col1 * @param col2 * @return Collection */ protected Collection mergeCollection(Collection col1, Collection col2) { Collection _col1 = (col1 == null ? new ArrayList() : col1); Collection _col2 = (col2 == null ? new ArrayList() : col2); _col1.addAll(_col2); return _col1; } /** * Merge <code>map1</code> and <code>map2</code> and return the composite * <code>Map</code> * * @param map1 * @param map2 * @return Map */ protected Map mergeMap(Map map1, Map map2) { Map _map1 = (map1 == null ? new LinkedHashMap() : map1); Map _map2 = (map2 == null ? new LinkedHashMap() : map2); _map1.putAll(_map2); return _map1; } }