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