View Javadoc

1   /*
2    * $Id: MessageStoreInterceptor.java 651946 2008-04-27 13:41:38Z apetrelli $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  package org.apache.struts2.interceptor;
23  
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.LinkedHashMap;
27  import java.util.Map;
28  
29  import org.apache.struts2.dispatcher.ServletRedirectResult;
30  
31  import com.opensymphony.xwork2.ActionContext;
32  import com.opensymphony.xwork2.ActionInvocation;
33  import com.opensymphony.xwork2.ValidationAware;
34  import com.opensymphony.xwork2.interceptor.Interceptor;
35  import com.opensymphony.xwork2.util.logging.Logger;
36  import com.opensymphony.xwork2.util.logging.LoggerFactory;
37  
38  /***
39   * <!-- START SNIPPET: description -->
40   *
41   * An interceptor to store a {@link ValidationAware} action's messages / errors and field errors into
42   * HTTP Session, such that it will be retrieveable at a later stage. This allows the action's message /
43   * errors and field errors to be available longer that just the particular HTTP request.
44   *
45   * <p/>
46   *
47   * In the 'STORE' mode, the interceptor will store the {@link ValidationAware} action's message / errors
48   * and field errors into HTTP session.
49   *
50   * <p/>
51   *
52   * In the 'RETRIEVE' mode, the interceptor will retrieve the stored action's message / errors  and field
53   * errors and put them back into the {@link ValidationAware} action.
54   * 
55   * <p/>
56   *
57   * In the 'AUTOMATIC' mode, the interceptor will always retrieve the stored action's message / errors 
58   * and field errors and put them back into the {@link ValidationAware} action, and after Action execution, 
59   * if the {@link Result} is an instance of {@link ServletRedirectResult}, the action's message / errors 
60   * and field errors into automatically be stored in the HTTP session..
61   *
62   * <p/>
63   *
64   * The interceptor does nothing in the 'NONE' mode, which is the default.
65   *
66   * <p/>
67   *
68   * The operation mode could be switched using :- <p/>
69   * 1] Setting the iterceptor parameter eg.
70   * <pre>
71   *   &lt;action name="submitApplication" ...&gt;
72   *      &lt;interceptor-ref name="store"&gt;
73   *         &lt;param name="operationMode"&gt;STORE&lt;/param&gt;
74   *      &lt;/interceptor-ref&gt;
75   *      &lt;interceptor-ref name="defaultStack" /&gt;
76   *      ....
77   *   &lt;/action&gt;
78   * </pre>
79   *
80   * 2] Through request parameter (allowRequestParameterSwitch must be 'true' which is the default)
81   * <pre>
82   *   // the request will have the operation mode in 'STORE'
83   *   http://localhost:8080/context/submitApplication.action?operationMode=STORE
84   * </pre>
85   *
86   * <!-- END SNIPPET: description -->
87   *
88   *
89   * <!-- START SNIPPET: parameters -->
90   *
91   * <ul>
92   *      <li>allowRequestParameterSwitch - To enable request parameter that could switch the operation mode
93   *                                        of this interceptor. </li>
94   *      <li>requestParameterSwitch - The request parameter that will indicate what mode this
95   *                                   interceptor is in. </li>
96   *      <li>operationMode - The operation mode this interceptor should be in
97   *                          (either 'STORE', 'RETRIEVE', 'AUTOMATIC', or 'NONE'). 'NONE' being the default.</li>
98   * </ul>
99   *
100  * <!-- END SNIPPET: parameters -->
101  *
102  * <p/>
103  *
104  * <!-- START SNIPPET: extending -->
105  *
106  * The following method could be overriden :-
107  * <ul>
108  *  <li>getRequestOperationMode - get the operation mode of this interceptor based on the request parameters</li>
109  *  <li>mergeCollection - merge two collections</li>
110  *  <li>mergeMap - merge two map</li>
111  * </ul>
112  *
113  * <!-- END SNIPPET: extending -->
114  *
115  * <pre>
116  * <!-- START SNIPPET: example -->
117  *
118  * &lt;action name="submitApplication" ....&gt;
119  *    &lt;interceptor-ref name="store"&gt;
120  *      &lt;param name="operationMode">STORE&lt;/param&gt;
121  *    &lt;/interceptor-ref&gt;
122  *    &lt;interceptor-ref name="defaultStack" /&gt;
123  *    &lt;result name="input" type="redirect">applicationFailed.action&lt;/result&gt;
124  *    &lt;result type="dispatcher"&gt;applicationSuccess.jsp&lt;/result&gt;
125  * &lt;/action&gt;
126  *
127  * &lt;action name="applicationFailed" ....&gt;
128  *    &lt;interceptor-ref name="store"&gt;
129  *       &lt;param name="operationMode"&gt;RETRIEVE&lt;/param&gt;
130  *    &lt;/interceptor-ref&gt;
131  *    &lt;result&gt;applicationFailed.jsp&lt;/result&gt;
132  * &lt;/action&gt;
133  *
134  * <!-- END SNIPPET: example -->
135  * </pre>
136  *
137  * <!-- START SNIPPET: exampleDescription -->
138  *
139  * With the example above, 'submitApplication.action' will have the action messages / errors / field errors stored
140  * in the HTTP Session. Later when needed, (in this case, when 'applicationFailed.action' is fired, it
141  * will get the action messages / errors / field errors stored in the HTTP Session and put them back into
142  * the action.
143  *
144  * <!-- END SNIPPET: exampleDescription -->
145  *
146  * @version $Date: 2008-04-27 13:41:38 +0000 (Sun, 27 Apr 2008) $ $Id: MessageStoreInterceptor.java 651946 2008-04-27 13:41:38Z apetrelli $
147  */
148 public class MessageStoreInterceptor implements Interceptor {
149 
150     private static final long serialVersionUID = 4491997514314242420L;
151 
152     private static final Logger LOG = LoggerFactory.getLogger(MessageStoreInterceptor.class);
153 
154     public static final String AUTOMATIC_MODE = "AUTOMATIC";
155     public static final String STORE_MODE = "STORE";
156     public static final String RETRIEVE_MODE = "RETRIEVE";
157     public static final String NONE = "NONE";
158 
159     private boolean allowRequestParameterSwitch = true;
160     private String requestParameterSwitch = "operationMode";
161     private String operationMode = NONE;
162 
163     public static String fieldErrorsSessionKey = "__MessageStoreInterceptor_FieldErrors_SessionKey";
164     public static String actionErrorsSessionKey = "__MessageStoreInterceptor_ActionErrors_SessionKey";
165     public static String actionMessagesSessionKey = "__MessageStoreInterceptor_ActionMessages_SessionKey";
166 
167 
168 
169     public void setAllowRequestParameterSwitch(boolean allowRequestParameterSwitch) {
170         this.allowRequestParameterSwitch = allowRequestParameterSwitch;
171     }
172     public boolean getAllowRequestParameterSwitch() {
173         return this.allowRequestParameterSwitch;
174     }
175 
176 
177     public void setRequestParameterSwitch(String requestParameterSwitch) {
178         this.requestParameterSwitch = requestParameterSwitch;
179     }
180     public String getRequestParameterSwitch() {
181         return this.requestParameterSwitch;
182     }
183 
184 
185 
186     public void setOperationMode(String operationMode) {
187         this.operationMode = operationMode;
188     }
189     public String getOperationModel() {
190         return this.operationMode;
191     }
192 
193 
194     public void destroy() {
195     }
196 
197     public void init() {
198     }
199 
200     public String intercept(ActionInvocation invocation) throws Exception {
201         LOG.debug("entering MessageStoreInterceptor ...");
202 
203         before(invocation);
204         String result = invocation.invoke();
205         after(invocation, result);
206 
207         LOG.debug("exit executing MessageStoreInterceptor");
208         return result;
209     }
210 
211     /***
212      * Handle the retrieving of field errors / action messages / field errors, which is
213      * done before action invocation, and the <code>operationMode</code> is 'RETRIEVE'.
214      *
215      * @param invocation
216      * @throws Exception
217      */
218     protected void before(ActionInvocation invocation) throws Exception {
219         String reqOperationMode = getRequestOperationMode(invocation);
220 
221         if (RETRIEVE_MODE.equalsIgnoreCase(reqOperationMode) ||
222                 RETRIEVE_MODE.equalsIgnoreCase(operationMode) ||
223                 AUTOMATIC_MODE.equalsIgnoreCase(operationMode)) {
224 
225             Object action = invocation.getAction();
226             if (action instanceof ValidationAware) {
227                 // retrieve error / message from session
228                 Map session = (Map) invocation.getInvocationContext().get(ActionContext.SESSION);
229                 ValidationAware validationAwareAction = (ValidationAware) action;
230 
231                 LOG.debug("retrieve error / message from session to populate into action ["+action+"]");
232 
233                 Collection actionErrors = (Collection) session.get(actionErrorsSessionKey);
234                 Collection actionMessages = (Collection) session.get(actionMessagesSessionKey);
235                 Map fieldErrors = (Map) session.get(fieldErrorsSessionKey);
236 
237                 if (actionErrors != null && actionErrors.size() > 0) {
238                     Collection mergedActionErrors = mergeCollection(validationAwareAction.getActionErrors(), actionErrors);
239                     validationAwareAction.setActionErrors(mergedActionErrors);
240                 }
241 
242                 if (actionMessages != null && actionMessages.size() > 0) {
243                     Collection mergedActionMessages = mergeCollection(validationAwareAction.getActionMessages(), actionMessages);
244                     validationAwareAction.setActionMessages(mergedActionMessages);
245                 }
246 
247                 if (fieldErrors != null && fieldErrors.size() > 0) {
248                     Map mergedFieldErrors = mergeMap(validationAwareAction.getFieldErrors(), fieldErrors);
249                     validationAwareAction.setFieldErrors(mergedFieldErrors);
250                 }
251                 session.remove(actionErrorsSessionKey);
252                 session.remove(actionMessagesSessionKey);
253                 session.remove(fieldErrorsSessionKey);
254             }
255         }
256     }
257 
258     /***
259      * Handle the storing of field errors / action messages / field errors, which is
260      * done after action invocation, and the <code>operationMode</code> is in 'STORE'.
261      *
262      * @param invocation
263      * @param result
264      * @throws Exception
265      */
266     protected void after(ActionInvocation invocation, String result) throws Exception {
267 
268         String reqOperationMode = getRequestOperationMode(invocation);
269         boolean isRedirect = invocation.getResult() instanceof ServletRedirectResult;
270         if (STORE_MODE.equalsIgnoreCase(reqOperationMode) ||
271                 STORE_MODE.equalsIgnoreCase(operationMode) ||
272                 (AUTOMATIC_MODE.equalsIgnoreCase(operationMode) && isRedirect)) {
273 
274             Object action = invocation.getAction();
275             if (action instanceof ValidationAware) {
276                 // store error / messages into session
277                 Map session = (Map) invocation.getInvocationContext().get(ActionContext.SESSION);
278 
279                 LOG.debug("store action ["+action+"] error/messages into session ");
280 
281                 ValidationAware validationAwareAction = (ValidationAware) action;
282                 session.put(actionErrorsSessionKey, validationAwareAction.getActionErrors());
283                 session.put(actionMessagesSessionKey, validationAwareAction.getActionMessages());
284                 session.put(fieldErrorsSessionKey, validationAwareAction.getFieldErrors());
285             }
286             else {
287                 LOG.debug("Action ["+action+"] is not ValidationAware, no message / error that are storeable");
288             }
289         }
290     }
291 
292 
293     /***
294      * Get the operationMode through request paramter, if <code>allowRequestParameterSwitch</code>
295      * is 'true', else it simply returns 'NONE', meaning its neither in the 'STORE_MODE' nor
296      * 'RETRIEVE_MODE'.
297      *
298      * @return String
299      */
300     protected String getRequestOperationMode(ActionInvocation invocation) {
301         String reqOperationMode = NONE;
302         if (allowRequestParameterSwitch) {
303             Map reqParams = (Map) invocation.getInvocationContext().get(ActionContext.PARAMETERS);
304             boolean containsParameter = reqParams.containsKey(requestParameterSwitch);
305             if (containsParameter) {
306                 String[] reqParamsArr = (String[]) reqParams.get(requestParameterSwitch);
307                 if (reqParamsArr != null && reqParamsArr.length > 0) {
308                     reqOperationMode = reqParamsArr[0];
309                 }
310             }
311         }
312         return reqOperationMode;
313     }
314 
315     /***
316      * Merge <code>col1</code> and <code>col2</code> and return the composite
317      * <code>Collection</code>.
318      *
319      * @param col1
320      * @param col2
321      * @return Collection
322      */
323     protected Collection mergeCollection(Collection col1, Collection col2) {
324         Collection _col1 = (col1 == null ? new ArrayList() : col1);
325         Collection _col2 = (col2 == null ? new ArrayList() : col2);
326         _col1.addAll(_col2);
327         return _col1;
328     }
329 
330     /***
331      * Merge <code>map1</code> and <code>map2</code> and return the composite
332      * <code>Map</code>
333      *
334      * @param map1
335      * @param map2
336      * @return Map
337      */
338     protected Map mergeMap(Map map1, Map map2) {
339         Map _map1 = (map1 == null ? new LinkedHashMap() : map1);
340         Map _map2 = (map2 == null ? new LinkedHashMap() : map2);
341         _map1.putAll(_map2);
342         return _map1;
343     }
344 
345 }