1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
78
79
80
81
82 /***
83 * <p>The servlet to which we are attached.</p>
84 */
85 protected transient ActionServlet servlet = null;
86
87
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
109 }
110
111
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
170
171 /***
172 * Adds the specified messages keys into the appropriate request attribute
173 * for use by the <html:messages> 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
185 return;
186 }
187
188
189 ActionMessages requestMessages =
190 (ActionMessages) request.getAttribute(Globals.MESSAGE_KEY);
191
192 if (requestMessages == null) {
193 requestMessages = new ActionMessages();
194 }
195
196
197 requestMessages.add(messages);
198
199
200 if (requestMessages.isEmpty()) {
201 request.removeAttribute(Globals.MESSAGE_KEY);
202
203 return;
204 }
205
206
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 <html:errors> 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
223 return;
224 }
225
226
227 ActionMessages requestErrors =
228 (ActionMessages) request.getAttribute(Globals.ERROR_KEY);
229
230 if (requestErrors == null) {
231 requestErrors = new ActionMessages();
232 }
233
234
235 requestErrors.add(errors);
236
237
238 if (requestErrors.isEmpty()) {
239 request.removeAttribute(Globals.ERROR_KEY);
240
241 return;
242 }
243
244
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
336 ServletContext context = getServlet().getServletContext();
337 ModuleConfig moduleConfig =
338 ModuleUtils.getInstance().getModuleConfig(request, context);
339
340
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 <html:errors> 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
444 if ((errors == null) || errors.isEmpty()) {
445 request.removeAttribute(Globals.ERROR_KEY);
446
447 return;
448 }
449
450
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 <html:messages> 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
469 if ((messages == null) || messages.isEmpty()) {
470 request.removeAttribute(Globals.MESSAGE_KEY);
471
472 return;
473 }
474
475
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 <html:messages> 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
493 if ((messages == null) || messages.isEmpty()) {
494 session.removeAttribute(Globals.MESSAGE_KEY);
495
496 return;
497 }
498
499
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 <html:messages> tag (if
506 * messages="false") or <html:errors>, 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
517 if ((errors == null) || errors.isEmpty()) {
518 session.removeAttribute(Globals.ERROR_KEY);
519
520 return;
521 }
522
523
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 }