View Javadoc

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