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