Author: tmjee Date: Sat Aug 19 22:41:25 2006 New Revision: 432943 URL: http://svn.apache.org/viewvc?rev=432943&view=rev Log: WW-731 - Add the capability to automatically save messages between Actions
Added: struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/MessageStoreInterceptor.java struts/struts2/trunk/core/src/test/java/org/apache/struts2/interceptor/MessageStoreInterceptorTest.java Modified: struts/struts2/trunk/core/src/main/resources/struts-default.xml Added: struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/MessageStoreInterceptor.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/MessageStoreInterceptor.java?rev=432943&view=auto ============================================================================== --- struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/MessageStoreInterceptor.java (added) +++ struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/MessageStoreInterceptor.java Sat Aug 19 22:41:25 2006 @@ -0,0 +1,331 @@ +/* + * $Id: FileUploadInterceptor.java 421776 2006-07-14 00:47:56Z tmjee $ + * + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ValidationAware; +import com.opensymphony.xwork2.interceptor.Interceptor; + +/** + * <!-- START SNIPPET: description --> + * + * An interceptor to store [EMAIL PROTECTED] ValidationAware} action's messages / errors and field errors into + * Http Session, such that it will be retrieveable at a later stage. This allows the action's message / + * errors and field errors to be available longer that just the particular http request. + * + * <p/> + * + * In the 'STORE' mode, the interceptor will store the [EMAIL PROTECTED] 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 [EMAIL PROTECTED] ValidationAware} action. + * + * <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">l;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' 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 implements Interceptor { + + private static final long serialVersionUID = 4491997514314242420L; + + private static final Log _log = LogFactory.getLog(MessageStoreInterceptor.class); + + + 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 String fieldErrorsSessionKey = "__MessageStoreInterceptor_FieldErrors_SessionKey"; + public static String actionErrorsSessionKey = "__MessageStoreInterceptor_ActionErrors_SessionKey"; + public static 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 void destroy() { + } + + public void init() { + } + + public String intercept(ActionInvocation invocation) throws Exception { + _log.debug("entering MessageStoreInterceptor ..."); + + before(invocation); + String result = invocation.invoke(); + after(invocation, result); + + _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)) { + + Object action = invocation.getAction(); + if (action instanceof ValidationAware) { + // retrieve error / message from session + Map session = (Map) invocation.getInvocationContext().get(ActionContext.SESSION); + ValidationAware validationAwareAction = (ValidationAware) action; + + _log.debug("retrieve error / message from session to populate into action ["+action+"]"); + + ValidationAware vaidationAwareAction = (ValidationAware) 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); + if (STORE_MODE.equalsIgnoreCase(reqOperationMode) || + STORE_MODE.equalsIgnoreCase(operationMode)) { + + Object action = invocation.getAction(); + if (action instanceof ValidationAware) { + // store error / messages into session + Map session = (Map) invocation.getInvocationContext().get(ActionContext.SESSION); + + _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 { + _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; + } + +} Modified: struts/struts2/trunk/core/src/main/resources/struts-default.xml URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/resources/struts-default.xml?rev=432943&r1=432942&r2=432943&view=diff ============================================================================== --- struts/struts2/trunk/core/src/main/resources/struts-default.xml (original) +++ struts/struts2/trunk/core/src/main/resources/struts-default.xml Sat Aug 19 22:41:25 2006 @@ -50,6 +50,7 @@ <interceptor name="token-session" class="org.apache.struts2.interceptor.TokenSessionStoreInterceptor"/> <interceptor name="validation" class="com.opensymphony.xwork2.validator.ValidationInterceptor"/> <interceptor name="workflow" class="com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor"/> + <interceptor name="store" class="org.apache.struts2.interceptor.MessageStoreInterceptor" /> <!-- JSF interceptors, one per lifecycle phase --> <interceptor class="org.apache.struts2.jsf.FacesSetupInterceptor" name="jsfSetup" /> Added: struts/struts2/trunk/core/src/test/java/org/apache/struts2/interceptor/MessageStoreInterceptorTest.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/test/java/org/apache/struts2/interceptor/MessageStoreInterceptorTest.java?rev=432943&view=auto ============================================================================== --- struts/struts2/trunk/core/src/test/java/org/apache/struts2/interceptor/MessageStoreInterceptorTest.java (added) +++ struts/struts2/trunk/core/src/test/java/org/apache/struts2/interceptor/MessageStoreInterceptorTest.java Sat Aug 19 22:41:25 2006 @@ -0,0 +1,244 @@ +/* + * $Id: FileUploadInterceptorTest.java 420385 2006-07-10 00:57:05Z tmjee $ + * + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.struts2.StrutsTestCase; +import org.easymock.EasyMock; + +import com.opensymphony.xwork2.Action; +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ActionSupport; + + +/** + * Test case for MessageStoreInterceptor. + * + * @version $Date$ $Id$ + */ +public class MessageStoreInterceptorTest extends StrutsTestCase { + + public void testStoreMessage() throws Exception { + MessageStoreInterceptor interceptor = new MessageStoreInterceptor(); + interceptor.setAllowRequestParameterSwitch(true); + interceptor.setOperationMode(MessageStoreInterceptor.STORE_MODE); + + + Map paramMap = new LinkedHashMap(); + Map sessionMap = new LinkedHashMap(); + + ActionSupport action = new ActionSupport(); + action.addActionError("some action error 1"); + action.addActionError("some action error 2"); + action.addActionMessage("some action message 1"); + action.addActionMessage("some action message 2"); + action.addFieldError("field1", "some field error 1"); + action.addFieldError("field2", "some field error 2"); + + ActionContext actionContext = new ActionContext(new HashMap()); + actionContext.put(ActionContext.PARAMETERS, paramMap); + actionContext.put(ActionContext.SESSION, sessionMap); + + // Mock (ActionInvocation) + ActionInvocation mockActionInvocation = EasyMock.createControl().createMock(ActionInvocation.class); + mockActionInvocation.getInvocationContext(); + EasyMock.expectLastCall().andReturn(actionContext); + EasyMock.expectLastCall().anyTimes(); + + mockActionInvocation.invoke(); + EasyMock.expectLastCall().andReturn(Action.SUCCESS); + + mockActionInvocation.getAction(); + EasyMock.expectLastCall().andReturn(action); + + + EasyMock.replay(mockActionInvocation); + + interceptor.init(); + interceptor.intercept(mockActionInvocation); + interceptor.destroy(); + + assertEquals(sessionMap.size(), 3); + assertTrue(sessionMap.containsKey(MessageStoreInterceptor.actionErrorsSessionKey)); + assertTrue(sessionMap.containsKey(MessageStoreInterceptor.actionMessagesSessionKey)); + assertTrue(sessionMap.containsKey(MessageStoreInterceptor.fieldErrorsSessionKey)); + + List actionErrors = (List) sessionMap.get(MessageStoreInterceptor.actionErrorsSessionKey); + List actionMessages = (List) sessionMap.get(MessageStoreInterceptor.actionMessagesSessionKey); + Map fieldErrors = (Map) sessionMap.get(MessageStoreInterceptor.fieldErrorsSessionKey); + + assertEquals(actionErrors.size(), 2); + assertEquals(actionMessages.size(), 2); + assertEquals(fieldErrors.size(), 2); + + assertTrue(actionErrors.contains("some action error 1")); + assertTrue(actionErrors.contains("some action error 2")); + assertTrue(actionMessages.contains("some action message 1")); + assertTrue(actionMessages.contains("some action message 2")); + assertTrue(fieldErrors.containsKey("field1")); + assertTrue(fieldErrors.containsKey("field2")); + assertEquals(((List)fieldErrors.get("field1")).size(), 1); + assertEquals(((List)fieldErrors.get("field2")).size(), 1); + assertEquals(((List)fieldErrors.get("field1")).get(0), "some field error 1"); + assertEquals(((List)fieldErrors.get("field2")).get(0), "some field error 2"); + + EasyMock.verify(mockActionInvocation); + } + + public void testRetrieveMessage() throws Exception { + MessageStoreInterceptor interceptor = new MessageStoreInterceptor(); + interceptor.setOperationMode(MessageStoreInterceptor.RETRIEVE_MODE); + interceptor.setAllowRequestParameterSwitch(true); + + + ActionSupport action = new ActionSupport(); + + ActionInvocation mockActionInvocation = EasyMock.createControl().createMock(ActionInvocation.class); + mockActionInvocation.invoke(); + EasyMock.expectLastCall().andReturn(Action.SUCCESS); + + Map paramsMap = new LinkedHashMap(); + Map sessionMap = new LinkedHashMap(); + + List actionErrors = new ArrayList(); + List actionMessages = new ArrayList(); + Map fieldErrors = new LinkedHashMap(); + + actionErrors.add("some action error 1"); + actionErrors.add("some action error 2"); + actionMessages.add("some action messages 1"); + actionMessages.add("some action messages 2"); + List field1Errors = new ArrayList(); + field1Errors.add("some field error 1"); + List field2Errors = new ArrayList(); + field2Errors.add("some field error 2"); + fieldErrors.put("field1", field1Errors); + fieldErrors.put("field2", field2Errors); + + sessionMap.put(MessageStoreInterceptor.actionErrorsSessionKey, actionErrors); + sessionMap.put(MessageStoreInterceptor.actionMessagesSessionKey, actionMessages); + sessionMap.put(MessageStoreInterceptor.fieldErrorsSessionKey, fieldErrors); + + + ActionContext actionContext = new ActionContext(new HashMap()); + actionContext.put(ActionContext.PARAMETERS, paramsMap); + actionContext.put(ActionContext.SESSION, sessionMap); + + mockActionInvocation.getInvocationContext(); + EasyMock.expectLastCall().andReturn(actionContext); + EasyMock.expectLastCall().anyTimes(); + + mockActionInvocation.getAction(); + EasyMock.expectLastCall().andReturn(action); + + EasyMock.replay(mockActionInvocation); + + interceptor.init(); + interceptor.intercept(mockActionInvocation); + interceptor.destroy(); + + assertEquals(action.getActionErrors().size(), 2); + assertEquals(action.getActionMessages().size(), 2); + assertEquals(action.getFieldErrors().size(), 2); + assertTrue(action.getActionErrors().contains("some action error 1")); + assertTrue(action.getActionErrors().contains("some action error 2")); + assertTrue(action.getActionMessages().contains("some action messages 1")); + assertTrue(action.getActionMessages().contains("some action messages 2")); + assertEquals(((List)action.getFieldErrors().get("field1")).size(), 1); + assertEquals(((List)action.getFieldErrors().get("field2")).size(), 1); + assertEquals(((List)action.getFieldErrors().get("field1")).get(0), "some field error 1"); + assertEquals(((List)action.getFieldErrors().get("field2")).get(0), "some field error 2"); + + EasyMock.verify(mockActionInvocation); + } + + public void testRequestOperationMode1() throws Exception { + + Map paramMap = new LinkedHashMap(); + paramMap.put("operationMode", new String[] { MessageStoreInterceptor.RETRIEVE_MODE }); + + ActionContext actionContext = new ActionContext(new HashMap()); + actionContext.put(ActionContext.PARAMETERS, paramMap); + + ActionInvocation mockActionInvocation = EasyMock.createControl().createMock(ActionInvocation.class); + mockActionInvocation.getInvocationContext(); + EasyMock.expectLastCall().andReturn(actionContext); + EasyMock.expectLastCall().anyTimes(); + + EasyMock.replay(mockActionInvocation); + + MessageStoreInterceptor interceptor = new MessageStoreInterceptor(); + String operationMode = interceptor.getRequestOperationMode(mockActionInvocation); + + assertEquals(operationMode, MessageStoreInterceptor.RETRIEVE_MODE); + + EasyMock.verify(mockActionInvocation); + } + + public void testRequestOperationMode2() throws Exception { + + Map paramMap = new LinkedHashMap(); + paramMap.put("operationMode", new String[] { MessageStoreInterceptor.STORE_MODE }); + + ActionContext actionContext = new ActionContext(new HashMap()); + actionContext.put(ActionContext.PARAMETERS, paramMap); + + ActionInvocation mockActionInvocation = EasyMock.createControl().createMock(ActionInvocation.class); + mockActionInvocation.getInvocationContext(); + EasyMock.expectLastCall().andReturn(actionContext); + EasyMock.expectLastCall().anyTimes(); + + EasyMock.replay(mockActionInvocation); + + MessageStoreInterceptor interceptor = new MessageStoreInterceptor(); + String operationMode = interceptor.getRequestOperationMode(mockActionInvocation); + + assertEquals(operationMode, MessageStoreInterceptor.STORE_MODE); + + EasyMock.verify(mockActionInvocation); + } + + public void testRequestOperationMode3() throws Exception { + + Map paramMap = new LinkedHashMap(); + + ActionContext actionContext = new ActionContext(new HashMap()); + actionContext.put(ActionContext.PARAMETERS, paramMap); + + ActionInvocation mockActionInvocation = EasyMock.createControl().createMock(ActionInvocation.class); + mockActionInvocation.getInvocationContext(); + EasyMock.expectLastCall().andReturn(actionContext); + EasyMock.expectLastCall().anyTimes(); + + EasyMock.replay(mockActionInvocation); + + MessageStoreInterceptor interceptor = new MessageStoreInterceptor(); + String operationMode = interceptor.getRequestOperationMode(mockActionInvocation); + + assertEquals(operationMode, MessageStoreInterceptor.NONE); + + EasyMock.verify(mockActionInvocation); + + } +}