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