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 <html:messages> 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 <html:errors> 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 <html:errors> 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 <html:messages> 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 <html:messages> 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 <html:messages> tag (if
548 * messages="false") or <html:errors>, 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 }