View Javadoc

1   /*
2    * $Id: Action.java 471754 2006-11-06 14:55:09Z husted $
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  package org.apache.struts.action;
22  
23  import org.apache.struts.Globals;
24  import org.apache.struts.config.ModuleConfig;
25  import org.apache.struts.util.MessageResources;
26  import org.apache.struts.util.ModuleUtils;
27  import org.apache.struts.util.RequestUtils;
28  import org.apache.struts.util.TokenProcessor;
29  
30  import javax.servlet.ServletContext;
31  import javax.servlet.ServletRequest;
32  import javax.servlet.ServletResponse;
33  import javax.servlet.http.HttpServletRequest;
34  import javax.servlet.http.HttpServletResponse;
35  import javax.servlet.http.HttpSession;
36  
37  import java.util.Locale;
38  
39  /**
40   * <p>An <strong>Action</strong> is an adapter between the contents of an
41   * incoming HTTP request and the corresponding business logic that should be
42   * executed to process this request. The controller (RequestProcessor) will
43   * select an appropriate Action for each request, create an instance (if
44   * necessary), and call the <code>execute</code> method.</p>
45   *
46   * <p>Actions must be programmed in a thread-safe manner, because the
47   * controller will share the same instance for multiple simultaneous requests.
48   * This means you should design with the following items in mind: </p>
49   *
50   * <ul>
51   *
52   * <li>Instance and static variables MUST NOT be used to store information
53   * related to the state of a particular request. They MAY be used to share
54   * global resources across requests for the same action.</li>
55   *
56   * <li>Access to other resources (JavaBeans, session variables, etc.) MUST be
57   * synchronized if those resources require protection. (Generally, however,
58   * resource classes should be designed to provide their own protection where
59   * necessary.</li>
60   *
61   * </ul>
62   *
63   * <p>When an <code>Action</code> instance is first created, the controller
64   * will call <code>setServlet</code> with a non-null argument to identify the
65   * servlet instance to which this Action is attached. When the servlet is to
66   * be shut down (or restarted), the <code>setServlet</code> method will be
67   * called with a <code>null</code> argument, which can be used to clean up any
68   * allocated resources in use by this Action.</p>
69   *
70   * @version $Rev: 471754 $ $Date: 2005-08-26 21:58:39 -0400 (Fri, 26 Aug 2005)
71   *          $
72   */
73  public class Action {
74      /**
75       * <p>An instance of <code>TokenProcessor</code> to use for token
76       * functionality.</p>
77       */
78      private static TokenProcessor token = TokenProcessor.getInstance();
79  
80      // NOTE: We can make the tken  variable protected and remove Action's
81      // token methods or leave it private and allow the token methods to
82      // delegate their calls.
83      // ----------------------------------------------------- Instance Variables
84  
85      /**
86       * <p>The servlet to which we are attached.</p>
87       */
88      protected transient ActionServlet servlet = null;
89  
90      // ------------------------------------------------------------- Properties
91  
92      /**
93       * <p>Return the servlet instance to which we are attached.</p>
94       *
95       * @return The servlet instance to which we are attached.
96       */
97      public ActionServlet getServlet() {
98          return (this.servlet);
99      }
100 
101     /**
102      * <p>Set the servlet instance to which we are attached (if
103      * <code>servlet</code> is non-null), or release any allocated resources
104      * (if <code>servlet</code> is null).</p>
105      *
106      * @param servlet The new controller servlet, if any
107      */
108     public void setServlet(ActionServlet servlet) {
109         this.servlet = servlet;
110 
111         // :FIXME: Is this suppose to release resources?
112     }
113 
114     // --------------------------------------------------------- Public Methods
115 
116     /**
117      * <p>Process the specified non-HTTP request, and create the corresponding
118      * non-HTTP response (or forward to another web component that will create
119      * it), with provision for handling exceptions thrown by the business
120      * logic. Return an {@link ActionForward} instance describing where and
121      * how control should be forwarded, or <code>null</code> if the response
122      * has already been completed.</p>
123      *
124      * <p>The default implementation attempts to forward to the HTTP version
125      * of this method.</p>
126      *
127      * @param mapping  The ActionMapping used to select this instance
128      * @param form     The optional ActionForm bean for this request (if any)
129      * @param request  The non-HTTP request we are processing
130      * @param response The non-HTTP response we are creating
131      * @return The forward to which control should be transferred, or
132      *         <code>null</code> if the response has been completed.
133      * @throws Exception if the application business logic throws an
134      *                   exception.
135      * @since Struts 1.1
136      */
137     public ActionForward execute(ActionMapping mapping, ActionForm form,
138         ServletRequest request, ServletResponse response)
139         throws Exception {
140         try {
141             return execute(mapping, form, (HttpServletRequest) request,
142                 (HttpServletResponse) response);
143         } catch (ClassCastException e) {
144             return null;
145         }
146     }
147 
148     /**
149      * <p>Process the specified HTTP request, and create the corresponding
150      * HTTP response (or forward to another web component that will create
151      * it), with provision for handling exceptions thrown by the business
152      * logic. Return an {@link ActionForward} instance describing where and
153      * how control should be forwarded, or <code>null</code> if the response
154      * has already been completed.</p>
155      *
156      * @param mapping  The ActionMapping used to select this instance
157      * @param form     The optional ActionForm bean for this request (if any)
158      * @param request  The HTTP request we are processing
159      * @param response The HTTP response we are creating
160      * @return The forward to which control should be transferred, or
161      *         <code>null</code> if the response has been completed.
162      * @throws Exception if the application business logic throws an
163      *                   exception
164      * @since Struts 1.1
165      */
166     public ActionForward execute(ActionMapping mapping, ActionForm form,
167         HttpServletRequest request, HttpServletResponse response)
168         throws Exception {
169         return null;
170     }
171 
172     // ---------------------------------------------------- Protected Methods
173 
174     /**
175      * Adds the specified messages keys into the appropriate request attribute
176      * for use by the &lt;html:messages&gt; tag (if messages="true" is set),
177      * if any messages are required. Initialize the attribute if it has not
178      * already been. Otherwise, ensure that the request attribute is not set.
179      *
180      * @param request  The servlet request we are processing
181      * @param messages Messages object
182      * @since Struts 1.2.1
183      */
184     protected void addMessages(HttpServletRequest request,
185         ActionMessages messages) {
186         if (messages == null) {
187             //  bad programmer! *slap*
188             return;
189         }
190 
191         // get any existing messages from the request, or make a new one
192         ActionMessages requestMessages =
193             (ActionMessages) request.getAttribute(Globals.MESSAGE_KEY);
194 
195         if (requestMessages == null) {
196             requestMessages = new ActionMessages();
197         }
198 
199         // add incoming messages
200         requestMessages.add(messages);
201 
202         // if still empty, just wipe it out from the request
203         if (requestMessages.isEmpty()) {
204             request.removeAttribute(Globals.MESSAGE_KEY);
205 
206             return;
207         }
208 
209         // Save the messages
210         request.setAttribute(Globals.MESSAGE_KEY, requestMessages);
211     }
212 
213     /**
214      * Adds the specified errors keys into the appropriate request attribute
215      * for use by the &lt;html:errors&gt; tag, if any messages are required.
216      * Initialize the attribute if it has not already been. Otherwise, ensure
217      * that the request attribute is not set.
218      *
219      * @param request The servlet request we are processing
220      * @param errors  Errors object
221      * @since Struts 1.2.1
222      */
223     protected void addErrors(HttpServletRequest request, ActionMessages errors) {
224         if (errors == null) {
225             //  bad programmer! *slap*
226             return;
227         }
228 
229         // get any existing errors from the request, or make a new one
230         ActionMessages requestErrors =
231             (ActionMessages) request.getAttribute(Globals.ERROR_KEY);
232 
233         if (requestErrors == null) {
234             requestErrors = new ActionMessages();
235         }
236 
237         // add incoming errors
238         requestErrors.add(errors);
239 
240         // if still empty, just wipe it out from the request
241         if (requestErrors.isEmpty()) {
242             request.removeAttribute(Globals.ERROR_KEY);
243 
244             return;
245         }
246 
247         // Save the errors
248         request.setAttribute(Globals.ERROR_KEY, requestErrors);
249     }
250 
251     /**
252      * <p>Generate a new transaction token, to be used for enforcing a single
253      * request for a particular transaction.</p>
254      *
255      * @param request The request we are processing
256      * @return The new transaction token.
257      */
258     protected String generateToken(HttpServletRequest request) {
259         return token.generateToken(request);
260     }
261 
262     /**
263      * Retrieves any existing errors placed in the request by previous
264      * actions. This method could be called instead of creating a <code>new
265      * ActionMessages()</code> at the beginning of an <code>Action</code>.
266      * This will prevent saveErrors() from wiping out any existing Errors
267      *
268      * @param request The servlet request we are processing
269      * @return the Errors that already exist in the request, or a new
270      *         ActionMessages object if empty.
271      * @since Struts 1.2.1
272      */
273     protected ActionMessages getErrors(HttpServletRequest request) {
274         ActionMessages errors =
275             (ActionMessages) request.getAttribute(Globals.ERROR_KEY);
276 
277         if (errors == null) {
278             errors = new ActionMessages();
279         }
280 
281         return errors;
282     }
283 
284     /**
285      * <p>Return the user's currently selected Locale.</p>
286      *
287      * @param request The request we are processing
288      * @return The user's currently selected Locale.
289      */
290     protected Locale getLocale(HttpServletRequest request) {
291         return RequestUtils.getUserLocale(request, null);
292     }
293 
294     /**
295      * <p> Retrieves any existing messages placed in the request by previous
296      * actions. This method could be called instead of creating a <code>new
297      * ActionMessages()</code> at the beginning of an <code>Action</code> This
298      * will prevent saveMessages() from wiping out any existing Messages </p>
299      *
300      * @param request The servlet request we are processing
301      * @return the Messages that already exist in the request, or a new
302      *         ActionMessages object if empty.
303      * @since Struts 1.2.1
304      */
305     protected ActionMessages getMessages(HttpServletRequest request) {
306         ActionMessages messages =
307             (ActionMessages) request.getAttribute(Globals.MESSAGE_KEY);
308 
309         if (messages == null) {
310             messages = new ActionMessages();
311         }
312 
313         return messages;
314     }
315 
316     /**
317      * <p>Return the default message resources for the current module.</p>
318      *
319      * @param request The servlet request we are processing
320      * @return The default message resources for the current module.
321      * @since Struts 1.1
322      */
323     protected MessageResources getResources(HttpServletRequest request) {
324         return ((MessageResources) request.getAttribute(Globals.MESSAGES_KEY));
325     }
326 
327     /**
328      * <p>Return the specified message resources for the current module.</p>
329      *
330      * @param request The servlet request we are processing
331      * @param key     The key specified in the message-resources element for
332      *                the requested bundle.
333      * @return The specified message resource for the current module.
334      * @since Struts 1.1
335      */
336     protected MessageResources getResources(HttpServletRequest request,
337         String key) {
338         // Identify the current module
339         ServletContext context = getServlet().getServletContext();
340         ModuleConfig moduleConfig =
341             ModuleUtils.getInstance().getModuleConfig(request, context);
342 
343         // Return the requested message resources instance
344         return (MessageResources) context.getAttribute(key
345             + moduleConfig.getPrefix());
346     }
347 
348     /**
349      * <p>Returns <code>true</code> if the current form's cancel button was
350      * pressed. This method will check if the <code>Globals.CANCEL_KEY</code>
351      * request attribute has been set, which normally occurs if the cancel
352      * button generated by <strong>CancelTag</strong> was pressed by the user
353      * in the current request. If <code>true</code>, validation performed by
354      * an <strong>ActionForm</strong>'s <code>validate()</code> method will
355      * have been skipped by the controller servlet.</p>
356      *
357      * <p> Since Action 1.3.0, the mapping for a cancellable Action must also have
358      * the new "cancellable" property set to true. If "cancellable" is not set, and
359      * the magic Cancel token is found in the request, the standard Composable
360      * Request Processor will throw an InvalidCancelException. </p>
361      *
362      * @param request The servlet request we are processing
363      * @return <code>true</code> if the cancel button was pressed;
364      *         <code>false</code> otherwise.
365      */
366     protected boolean isCancelled(HttpServletRequest request) {
367         return (request.getAttribute(Globals.CANCEL_KEY) != null);
368     }
369 
370     /**
371      * <p>Return <code>true</code> if there is a transaction token stored in
372      * the user's current session, and the value submitted as a request
373      * parameter with this action matches it. Returns <code>false</code> under
374      * any of the following circumstances:</p>
375      *
376      * <ul>
377      *
378      * <li>No session associated with this request</li>
379      *
380      * <li>No transaction token saved in the session</li>
381      *
382      * <li>No transaction token included as a request parameter</li>
383      *
384      * <li>The included transaction token value does not match the transaction
385      * token in the user's session</li>
386      *
387      * </ul>
388      *
389      * @param request The servlet request we are processing
390      * @return <code>true</code> if there is a transaction token and it is
391      *         valid; <code>false</code> otherwise.
392      */
393     protected boolean isTokenValid(HttpServletRequest request) {
394         return token.isTokenValid(request, false);
395     }
396 
397     /**
398      * <p>Return <code>true</code> if there is a transaction token stored in
399      * the user's current session, and the value submitted as a request
400      * parameter with this action matches it. Returns <code>false</code> under
401      * any of the following circumstances:</p>
402      *
403      * <ul>
404      *
405      * <li>No session associated with this request</li> <li>No transaction
406      * token saved in the session</li>
407      *
408      * <li>No transaction token included as a request parameter</li>
409      *
410      * <li>The included transaction token value does not match the transaction
411      * token in the user's session</li>
412      *
413      * </ul>
414      *
415      * @param request The servlet request we are processing
416      * @param reset   Should we reset the token after checking it?
417      * @return <code>true</code> if there is a transaction token and it is
418      *         valid; <code>false</code> otherwise.
419      */
420     protected boolean isTokenValid(HttpServletRequest request, boolean reset) {
421         return token.isTokenValid(request, reset);
422     }
423 
424     /**
425      * <p>Reset the saved transaction token in the user's session. This
426      * indicates that transactional token checking will not be needed on the
427      * next request that is submitted.</p>
428      *
429      * @param request The servlet request we are processing
430      */
431     protected void resetToken(HttpServletRequest request) {
432         token.resetToken(request);
433     }
434 
435     /**
436      * <p>Save the specified error messages keys into the appropriate request
437      * attribute for use by the &lt;html:errors&gt; tag, if any messages are
438      * required. Otherwise, ensure that the request attribute is not
439      * created.</p>
440      *
441      * @param request The servlet request we are processing
442      * @param errors  Error messages object
443      * @since Struts 1.2
444      */
445     protected void saveErrors(HttpServletRequest request, ActionMessages errors) {
446         // Remove any error messages attribute if none are required
447         if ((errors == null) || errors.isEmpty()) {
448             request.removeAttribute(Globals.ERROR_KEY);
449 
450             return;
451         }
452 
453         // Save the error messages we need
454         request.setAttribute(Globals.ERROR_KEY, errors);
455     }
456 
457     /**
458      * <p>Save the specified messages keys into the appropriate request
459      * attribute for use by the &lt;html:messages&gt; tag (if messages="true"
460      * is set), if any messages are required. Otherwise, ensure that the
461      * request attribute is not created.</p>
462      *
463      * @param request  The servlet request we are processing.
464      * @param messages The messages to save. <code>null</code> or empty
465      *                 messages removes any existing ActionMessages in the
466      *                 request.
467      * @since Struts 1.1
468      */
469     protected void saveMessages(HttpServletRequest request,
470         ActionMessages messages) {
471         // Remove any messages attribute if none are required
472         if ((messages == null) || messages.isEmpty()) {
473             request.removeAttribute(Globals.MESSAGE_KEY);
474 
475             return;
476         }
477 
478         // Save the messages we need
479         request.setAttribute(Globals.MESSAGE_KEY, messages);
480     }
481 
482     /**
483      * <p>Save the specified messages keys into the appropriate session
484      * attribute for use by the &lt;html:messages&gt; tag (if messages="true"
485      * is set), if any messages are required. Otherwise, ensure that the
486      * session attribute is not created.</p>
487      *
488      * @param session  The session to save the messages in.
489      * @param messages The messages to save. <code>null</code> or empty
490      *                 messages removes any existing ActionMessages in the
491      *                 session.
492      * @since Struts 1.2
493      */
494     protected void saveMessages(HttpSession session, ActionMessages messages) {
495         // Remove any messages attribute if none are required
496         if ((messages == null) || messages.isEmpty()) {
497             session.removeAttribute(Globals.MESSAGE_KEY);
498 
499             return;
500         }
501 
502         // Save the messages we need
503         session.setAttribute(Globals.MESSAGE_KEY, messages);
504     }
505 
506     /**
507      * <p>Save the specified error messages keys into the appropriate session
508      * attribute for use by the &lt;html:messages&gt; tag (if
509      * messages="false") or &lt;html:errors&gt;, if any error messages are
510      * required. Otherwise, ensure that the session attribute is empty.</p>
511      *
512      * @param session The session to save the error messages in.
513      * @param errors  The error messages to save. <code>null</code> or empty
514      *                messages removes any existing error ActionMessages in
515      *                the session.
516      * @since Struts 1.3
517      */
518     protected void saveErrors(HttpSession session, ActionMessages errors) {
519         // Remove the error attribute if none are required
520         if ((errors == null) || errors.isEmpty()) {
521             session.removeAttribute(Globals.ERROR_KEY);
522 
523             return;
524         }
525 
526         // Save the errors we need
527         session.setAttribute(Globals.ERROR_KEY, errors);
528     }
529 
530     /**
531      * <p>Save a new transaction token in the user's current session, creating
532      * a new session if necessary.</p>
533      *
534      * @param request The servlet request we are processing
535      */
536     protected void saveToken(HttpServletRequest request) {
537         token.saveToken(request);
538     }
539 
540     /**
541      * <p>Set the user's currently selected <code>Locale</code> into their
542      * <code>HttpSession</code>.</p>
543      *
544      * @param request The request we are processing
545      * @param locale  The user's selected Locale to be set, or null to select
546      *                the server's default Locale
547      */
548     protected void setLocale(HttpServletRequest request, Locale locale) {
549         HttpSession session = request.getSession();
550 
551         if (locale == null) {
552             locale = Locale.getDefault();
553         }
554 
555         session.setAttribute(Globals.LOCALE_KEY, locale);
556     }
557 }