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 }