001 package com.mockrunner.struts; 002 003 import java.io.File; 004 import java.io.FileInputStream; 005 import java.util.ArrayList; 006 import java.util.Iterator; 007 import java.util.List; 008 import java.util.Locale; 009 010 import javax.servlet.ServletException; 011 import javax.sql.DataSource; 012 013 import org.apache.commons.beanutils.BeanUtils; 014 import org.apache.commons.logging.Log; 015 import org.apache.commons.logging.LogFactory; 016 import org.apache.commons.validator.ValidatorResources; 017 import org.apache.struts.Globals; 018 import org.apache.struts.action.Action; 019 import org.apache.struts.action.ActionErrors; 020 import org.apache.struts.action.ActionForm; 021 import org.apache.struts.action.ActionForward; 022 import org.apache.struts.action.ActionMapping; 023 import org.apache.struts.action.ActionMessage; 024 import org.apache.struts.action.ActionMessages; 025 import org.apache.struts.action.DynaActionForm; 026 import org.apache.struts.action.DynaActionFormClass; 027 import org.apache.struts.config.FormBeanConfig; 028 import org.apache.struts.config.MessageResourcesConfig; 029 import org.apache.struts.taglib.html.Constants; 030 import org.apache.struts.util.MessageResources; 031 import org.apache.struts.validator.ValidatorPlugIn; 032 033 import com.mockrunner.base.HTMLOutputModule; 034 import com.mockrunner.base.NestedApplicationException; 035 import com.mockrunner.base.VerifyFailedException; 036 import com.mockrunner.mock.web.ActionMockObjectFactory; 037 import com.mockrunner.mock.web.MockActionForward; 038 import com.mockrunner.mock.web.MockActionMapping; 039 import com.mockrunner.mock.web.MockPageContext; 040 import com.mockrunner.util.common.StreamUtil; 041 042 /** 043 * Module for Struts action tests. Simulates Struts 044 * without reading the <i>struts-config.xml</i> file. 045 * Per default this class does everything like Struts 046 * when calling an action but you can change the behaviour 047 * (e.g. disable form population). 048 * Please note: If your action throws an exception and an 049 * exception handler is registered (use {@link #addExceptionHandler}), 050 * the handler will be called to handle the exception. 051 * Otherwise the exception will be rethrown as {@link com.mockrunner.base.NestedApplicationException}. 052 */ 053 public class ActionTestModule extends HTMLOutputModule 054 { 055 private final static Log log = LogFactory.getLog(ActionTestModule.class); 056 private ActionMockObjectFactory mockFactory; 057 private MockActionForward forward; 058 private ActionForm formObj; 059 private Action actionObj; 060 private boolean reset; 061 private boolean doPopulate; 062 private boolean recognizeInSession; 063 private String messageAttributeKey; 064 private String errorAttributeKey; 065 private List exceptionHandlers; 066 067 public ActionTestModule(ActionMockObjectFactory mockFactory) 068 { 069 super(mockFactory); 070 this.mockFactory = mockFactory; 071 reset = true; 072 doPopulate = true; 073 recognizeInSession = true; 074 messageAttributeKey = Globals.MESSAGE_KEY; 075 errorAttributeKey = Globals.ERROR_KEY; 076 exceptionHandlers = new ArrayList(); 077 } 078 079 /** 080 * Set if the reset method should be called before 081 * populating a form with {@link #populateRequestToForm}. 082 * Default is <code>true</code> which is the standard Struts 083 * behaviour. 084 * @param reset should reset be called 085 */ 086 public void setReset(boolean reset) 087 { 088 this.reset = reset; 089 } 090 091 /** 092 * Set if the form should be populated with the request 093 * parameters before calling the action. 094 * Default is <code>true</code> which is the standard Struts 095 * behaviour. 096 * @param doPopulate should population be performed 097 */ 098 public void setDoPopulate(boolean doPopulate) 099 { 100 this.doPopulate = doPopulate; 101 } 102 103 /** 104 * Set if messages that are saved to the session (instead of 105 * the request) should be recognized. 106 * Default is <code>true</code>. 107 * @param recognizeInSession should messages in the session be recognized 108 */ 109 public void setRecognizeMessagesInSession(boolean recognizeInSession) 110 { 111 this.recognizeInSession = recognizeInSession; 112 } 113 114 /** 115 * Name of the key under which messages are stored. Default is 116 * <code>Globals.MESSAGE_KEY</code>. 117 * @param messageAttributeKey the message key 118 */ 119 public void setMessageAttributeKey(String messageAttributeKey) 120 { 121 this.messageAttributeKey = messageAttributeKey; 122 } 123 124 /** 125 * Name of the key under which errors are stored. Default is 126 * <code>Globals.ERROR_KEY</code>. 127 * @param errorAttributeKey the message key 128 */ 129 public void setErrorAttributeKey(String errorAttributeKey) 130 { 131 this.errorAttributeKey = errorAttributeKey; 132 } 133 134 /** 135 * Convenience method for map backed properties. Creates a String 136 * <i>value(property)</i>. 137 * @param property the property 138 * @return the String in map backed propery style 139 */ 140 public String addMappedPropertyRequestPrefix(String property) 141 { 142 return "value(" + property + ")"; 143 } 144 145 /** 146 * Sets the parameter by calling <code>ActionMapping.setParameter</code> 147 * on the action mapping returned by {@link #getActionMapping}. 148 * You can test your Actions with different parameter settings in the 149 * same test method. 150 * @param parameter the parameter 151 */ 152 public void setParameter(String parameter) 153 { 154 getActionMapping().setParameter(parameter); 155 } 156 157 /** 158 * Sets if form validation should be performed before calling the action. 159 * Calls <code>ActionMapping.setValidate</code> on the action mapping returned 160 * by {@link #getActionMapping}. Default is <code>false</code>. 161 * @param validate should validation be performed 162 */ 163 public void setValidate(boolean validate) 164 { 165 getActionMapping().setValidate(validate); 166 } 167 168 /** 169 * Sets the input attribute. If form validation fails, the 170 * input attribute can be verified with {@link #verifyForward}. 171 * Calls <code>ActionMapping.setInput</code> on the action mapping returned 172 * by {@link #getActionMapping}. 173 * @param input the input attribute 174 */ 175 public void setInput(String input) 176 { 177 getActionMapping().setInput(input); 178 } 179 180 /** 181 * Registers an exception handler. The exception handler will 182 * be called if an action throws an exception. Usually, you 183 * will pass an instance of {@link DefaultExceptionHandlerConfig} 184 * to this method. {@link DefaultExceptionHandlerConfig} 185 * relies on Struts <code>ExceptionHandler</code> classes. 186 * In special cases, you may add own implementations of 187 * {@link ExceptionHandlerConfig}, that may be independent from 188 * the Struts exception handling mechanism. 189 * If no matching handler is registered, the exception will be rethrown 190 * as {@link com.mockrunner.base.NestedApplicationException}. 191 * @param handler the exception handler 192 */ 193 public void addExceptionHandler(ExceptionHandlerConfig handler) 194 { 195 if(null != handler) 196 { 197 exceptionHandlers.add(handler); 198 } 199 } 200 201 /** 202 * Sets the specified messages resources as a request attribute 203 * using <code>Globals.MESSAGES_KEY</code> as the key. You can 204 * use this method, if your action calls 205 * <code>Action.getResources(HttpServletRequest)</code>. 206 * The deprecated method <code>Action.getResources()</code> 207 * takes the resources from the servlet context with the same key. 208 * If your action uses this method, you have to set the resources 209 * manually to the servlet context. 210 * @param resources the messages resources 211 */ 212 public void setResources(MessageResources resources) 213 { 214 mockFactory.getWrappedRequest().setAttribute(Globals.MESSAGES_KEY, resources); 215 } 216 217 /** 218 * Sets the specified messages resources as a servlet context 219 * attribute using the specified key and the module config prefix. 220 * You can use this method, if your action calls 221 * <code>Action.getResources(HttpServletRequest, String)</code>. 222 * Please note that the {@link com.mockrunner.mock.web.MockModuleConfig} 223 * is set by Mockrunner as the current module. It has the name <i>testmodule</i>. 224 * This can be changed with <code>ModuleConfig.setPrefix</code>. 225 * @param key the key of the messages resources 226 * @param resources the messages resources 227 */ 228 public void setResources(String key, MessageResources resources) 229 { 230 MessageResourcesConfig config = new MessageResourcesConfig(); 231 config.setKey(key); 232 mockFactory.getMockModuleConfig().addMessageResourcesConfig(config); 233 key = key + mockFactory.getMockModuleConfig().getPrefix(); 234 mockFactory.getMockServletContext().setAttribute(key, resources); 235 } 236 237 /** 238 * Sets the specified <code>DataSource</code>. 239 * You can use this method, if your action calls 240 * <code>Action.getDataSource(HttpServletRequest)</code>. 241 * @param dataSource <code>DataSource</code> 242 */ 243 public void setDataSource(DataSource dataSource) 244 { 245 setDataSource("org.apache.struts.action.DATA_SOURCE", dataSource); 246 } 247 248 /** 249 * Sets the specified <code>DataSource</code>. 250 * You can use this method, if your action calls 251 * <code>Action.getDataSource(HttpServletRequest, String)</code>. 252 * @param key the key of the <code>DataSource</code> 253 * @param dataSource <code>DataSource</code> 254 */ 255 public void setDataSource(String key, DataSource dataSource) 256 { 257 key = key + mockFactory.getMockModuleConfig().getPrefix(); 258 mockFactory.getMockServletContext().setAttribute(key, dataSource); 259 } 260 261 /** 262 * Sets the specified locale as a session attribute 263 * using <code>Globals.LOCALE_KEY</code> as the key. You can 264 * use this method, if your action calls 265 * <code>Action.getLocale(HttpServletRequest)</code>. 266 * @param locale the locale 267 */ 268 public void setLocale(Locale locale) 269 { 270 mockFactory.getMockSession().setAttribute(Globals.LOCALE_KEY, locale); 271 } 272 273 /** 274 * Creates a valid <code>ValidatorResources</code> object based 275 * on the specified config files. Since the parsing of the files 276 * is time consuming, it makes sense to cache the result. 277 * You can set the returned <code>ValidatorResources</code> object 278 * with {@link #setValidatorResources}. It is then used in 279 * all validations. 280 * @param resourcesFiles the array of config files 281 */ 282 public ValidatorResources createValidatorResources(String[] resourcesFiles) 283 { 284 if(resourcesFiles.length == 0) return null; 285 setUpServletContextResourcePath(resourcesFiles); 286 String resourceString = resourcesFiles[0]; 287 for(int ii = 1; ii < resourcesFiles.length; ii++) 288 { 289 resourceString += "," + resourcesFiles[ii]; 290 } 291 ValidatorPlugIn plugIn = new ValidatorPlugIn(); 292 plugIn.setPathnames(resourceString); 293 try 294 { 295 plugIn.init(mockFactory.getMockActionServlet(), mockFactory.getMockModuleConfig()); 296 } 297 catch(ServletException exc) 298 { 299 log.error("Error initializing ValidatorPlugIn", exc); 300 throw new RuntimeException("Error initializing ValidatorPlugIn: " + exc.getMessage()); 301 } 302 String key = ValidatorPlugIn.VALIDATOR_KEY + mockFactory.getMockModuleConfig().getPrefix(); 303 return (ValidatorResources)mockFactory.getMockServletContext().getAttribute(key); 304 } 305 306 private void setUpServletContextResourcePath(String[] resourcesFiles) 307 { 308 for(int ii = 0; ii < resourcesFiles.length; ii++) 309 { 310 String file = resourcesFiles[ii]; 311 try 312 { 313 File streamFile = new File(file); 314 FileInputStream stream = new FileInputStream(streamFile); 315 byte[] fileData = StreamUtil.getStreamAsByteArray(stream); 316 mockFactory.getMockServletContext().setResourceAsStream(file, fileData); 317 mockFactory.getMockServletContext().setResource(file, streamFile.toURL()); 318 } 319 catch(Exception exc) 320 { 321 throw new NestedApplicationException(exc); 322 } 323 } 324 } 325 326 /** 327 * Sets the specified <code>ValidatorResources</code>. The easiest 328 * way to create <code>ValidatorResources</code> is the method 329 * {@link #createValidatorResources}. 330 * @param validatorResources the <code>ValidatorResources</code> 331 */ 332 public void setValidatorResources(ValidatorResources validatorResources) 333 { 334 String key = ValidatorPlugIn.VALIDATOR_KEY + mockFactory.getMockModuleConfig().getPrefix(); 335 mockFactory.getMockServletContext().setAttribute(key, validatorResources); 336 } 337 338 /** 339 * Verifies the forward path returned by the action. 340 * If your action uses <code>mapping.findForward("success")</code> 341 * to find the forward, you can use this method or 342 * {@link #verifyForwardName} to test the <code>success</code> forward 343 * name. If your action creates an <code>ActionForward</code> on its 344 * own you can use this method to verify the forward <code>path</code>. 345 * @param path the expected path 346 * @throws VerifyFailedException if verification fails 347 */ 348 public void verifyForward(String path) 349 { 350 if(null == getActionForward()) 351 { 352 throw new VerifyFailedException("ActionForward == null"); 353 } 354 else if (!getActionForward().verifyPath(path)) 355 { 356 throw new VerifyFailedException("expected " + path + ", received " + getActionForward().getPath()); 357 } 358 } 359 360 /** 361 * Verifies the forward name returned by the action. 362 * If your action uses <code>mapping.findForward("success")</code> 363 * to find the forward, you can use this method or 364 * {@link #verifyForward} to test the <code>success</code> forward 365 * name. If your action creates an <code>ActionForward</code> on its 366 * own you can use this method to verify the forward <code>name</code>. 367 * @param name the expected name 368 * @throws VerifyFailedException if verification fails 369 */ 370 public void verifyForwardName(String name) 371 { 372 if(null == getActionForward()) 373 { 374 throw new VerifyFailedException("ActionForward == null"); 375 } 376 else if (!getActionForward().verifyName(name)) 377 { 378 throw new VerifyFailedException("expected " + name + ", received " + getActionForward().getName()); 379 } 380 } 381 382 /** 383 * Verifies the redirect attribute. 384 * @param redirect the expected redirect attribute 385 * @throws VerifyFailedException if verification fails 386 */ 387 public void verifyRedirect(boolean redirect) 388 { 389 if(null == getActionForward()) 390 { 391 throw new VerifyFailedException("ActionForward == null"); 392 } 393 else if(!getActionForward().verifyRedirect(redirect)) 394 { 395 throw new VerifyFailedException("expected " + redirect + ", received " + getActionForward().getRedirect()); 396 } 397 } 398 399 /** 400 * Verifies that there are no action errors present. 401 * @throws VerifyFailedException if verification fails 402 */ 403 public void verifyNoActionErrors() 404 { 405 verifyNoActionMessages(getActionErrors()); 406 } 407 408 /** 409 * Verifies that there are no action messages present. 410 * @throws VerifyFailedException if verification fails 411 */ 412 public void verifyNoActionMessages() 413 { 414 verifyNoActionMessages(getActionMessages()); 415 } 416 417 private void verifyNoActionMessages(ActionMessages messages) 418 { 419 if(containsMessages(messages)) 420 { 421 StringBuffer buffer = new StringBuffer(); 422 buffer.append("has the following messages/errors: "); 423 Iterator iterator = messages.get(); 424 while(iterator.hasNext()) 425 { 426 ActionMessage message = (ActionMessage)iterator.next(); 427 buffer.append(message.getKey() + ";"); 428 } 429 throw new VerifyFailedException(buffer.toString()); 430 } 431 } 432 433 /** 434 * Verifies that there are action errors present. 435 * @throws VerifyFailedException if verification fails 436 */ 437 public void verifyHasActionErrors() 438 { 439 if(!containsMessages(getActionErrors())) 440 { 441 throw new VerifyFailedException("no action errors"); 442 } 443 } 444 445 /** 446 * Verifies that there are action messages present. 447 * @throws VerifyFailedException if verification fails 448 */ 449 public void verifyHasActionMessages() 450 { 451 if(!containsMessages(getActionMessages())) 452 { 453 throw new VerifyFailedException("no action messages"); 454 } 455 } 456 457 /** 458 * Verifies that an action error with the specified key 459 * is present. 460 * @param errorKey the expected error key 461 * @throws VerifyFailedException if verification fails 462 */ 463 public void verifyActionErrorPresent(String errorKey) 464 { 465 verifyActionMessagePresent(errorKey, getActionErrors()); 466 } 467 468 /** 469 * Verifies that an action message with the specified key 470 * is present. 471 * @param messageKey the expected message key 472 * @throws VerifyFailedException if verification fails 473 */ 474 public void verifyActionMessagePresent(String messageKey) 475 { 476 verifyActionMessagePresent(messageKey, getActionMessages()); 477 } 478 479 private void verifyActionMessagePresent(String messageKey, ActionMessages messages) 480 { 481 if(!containsMessages(messages)) throw new VerifyFailedException("no action messages/errors"); 482 Iterator iterator = messages.get(); 483 while (iterator.hasNext()) 484 { 485 ActionMessage message = (ActionMessage) iterator.next(); 486 if(message.getKey().equals(messageKey)) 487 { 488 return; 489 } 490 } 491 throw new VerifyFailedException("message/error " + messageKey + " not present"); 492 } 493 494 /** 495 * Verifies that an action error with the specified key 496 * is not present. 497 * @param errorKey the error key 498 * @throws VerifyFailedException if verification fails 499 */ 500 public void verifyActionErrorNotPresent(String errorKey) 501 { 502 verifyActionMessageNotPresent(errorKey, getActionErrors()); 503 } 504 505 /** 506 * Verifies that an action message with the specified key 507 * is not present. 508 * @param messageKey the message key 509 * @throws VerifyFailedException if verification fails 510 */ 511 public void verifyActionMessageNotPresent(String messageKey) 512 { 513 verifyActionMessageNotPresent(messageKey, getActionMessages()); 514 } 515 516 private void verifyActionMessageNotPresent(String messageKey, ActionMessages messages) 517 { 518 if(!containsMessages(messages)) return; 519 Iterator iterator = messages.get(); 520 while(iterator.hasNext()) 521 { 522 ActionMessage message = (ActionMessage) iterator.next(); 523 if(message.getKey().equals(messageKey)) 524 { 525 throw new VerifyFailedException("message/error " + messageKey + " present"); 526 } 527 } 528 } 529 530 /** 531 * Verifies that the specified action errors are present. 532 * Regards number and order of action errors. 533 * @param errorKeys the array of expected error keys 534 * @throws VerifyFailedException if verification fails 535 */ 536 public void verifyActionErrors(String errorKeys[]) 537 { 538 verifyActionMessages(errorKeys, getActionErrors()); 539 } 540 541 /** 542 * Verifies that the specified action messages are present. 543 * Regards number and order of action messages. 544 * @param messageKeys the array of expected message keys 545 * @throws VerifyFailedException if verification fails 546 */ 547 public void verifyActionMessages(String messageKeys[]) 548 { 549 verifyActionMessages(messageKeys, getActionMessages()); 550 } 551 552 private void verifyActionMessages(String messageKeys[], ActionMessages messages) 553 { 554 if (!containsMessages(messages)) throw new VerifyFailedException("no action messages/errors"); 555 if(messages.size() != messageKeys.length) throw new VerifyFailedException("expected " + messageKeys.length + " messages/errors, received " + messages.size() + " messages/errors"); 556 Iterator iterator = messages.get(); 557 for(int ii = 0; ii < messageKeys.length; ii++) 558 { 559 ActionMessage message = (ActionMessage) iterator.next(); 560 if(!message.getKey().equals(messageKeys[ii])) 561 { 562 throw new VerifyFailedException("mismatch at position " + ii + ", actual: " + message.getKey() + ", expected: " + messageKeys[ii]); 563 } 564 } 565 } 566 567 /** 568 * Verifies the values of the action error with the 569 * specified key. Regards number and order of values. 570 * @param errorKey the error key 571 * @param values the exepcted values 572 * @throws VerifyFailedException if verification fails 573 */ 574 public void verifyActionErrorValues(String errorKey, Object[] values) 575 { 576 ActionMessage error = getActionErrorByKey(errorKey); 577 if(null == error) throw new VerifyFailedException("action error " + errorKey + " not present"); 578 verifyActionMessageValues(error, values); 579 } 580 581 /** 582 * Verifies the values of the action message with the 583 * specified key. Regards number and order of values. 584 * @param messageKey the message key 585 * @param values the exepcted values 586 * @throws VerifyFailedException if verification fails 587 */ 588 public void verifyActionMessageValues(String messageKey, Object[] values) 589 { 590 ActionMessage message = getActionMessageByKey(messageKey); 591 if(null == message) throw new VerifyFailedException("action message " + messageKey + " not present"); 592 verifyActionMessageValues(message, values); 593 } 594 595 private void verifyActionMessageValues(ActionMessage message, Object[] values) 596 { 597 Object[] actualValues = message.getValues(); 598 if(null == actualValues) throw new VerifyFailedException("action message/error " + message.getKey() + " has no values"); 599 if(values.length != actualValues.length) throw new VerifyFailedException("action message/error " + message.getKey() + " has " + actualValues + " values"); 600 for(int ii = 0; ii < actualValues.length; ii++) 601 { 602 if(!values[ii].equals(actualValues[ii])) 603 { 604 throw new VerifyFailedException("action message/error " + message.getKey() + ": expected value[" + ii + "]: " + values[ii] + " received value[" + ii + "]: " + actualValues[ii]); 605 } 606 } 607 } 608 609 /** 610 * Verifies the value of the action error with the 611 * specified key. Fails if the specified value does 612 * not match the actual value or if the error has more 613 * than one value. 614 * @param errorKey the error key 615 * @param value the exepcted value 616 * @throws VerifyFailedException if verification fails 617 */ 618 public void verifyActionErrorValue(String errorKey, Object value) 619 { 620 verifyActionErrorValues(errorKey, new Object[] { value }); 621 } 622 623 /** 624 * Verifies the value of the action message with the 625 * specified key. Fails if the specified value does 626 * not match the actual value or if the message has more 627 * than one value. 628 * @param messageKey the message key 629 * @param value the exepcted value 630 * @throws VerifyFailedException if verification fails 631 */ 632 public void verifyActionMessageValue(String messageKey, Object value) 633 { 634 verifyActionMessageValues(messageKey, new Object[] { value }); 635 } 636 637 /** 638 * Verifies that the specified error is stored for the specified 639 * property. 640 * @param errorKey the error key 641 * @param property the exepcted value 642 * @throws VerifyFailedException if verification fails 643 */ 644 public void verifyActionErrorProperty(String errorKey, String property) 645 { 646 verifyActionMessageProperty(errorKey, property, getActionErrors()); 647 } 648 649 /** 650 * Verifies that the specified message is stored for the specified 651 * property. 652 * @param messageKey the message key 653 * @param property the exepcted value 654 * @throws VerifyFailedException if verification fails 655 */ 656 public void verifyActionMessageProperty(String messageKey, String property) 657 { 658 verifyActionMessageProperty(messageKey, property, getActionMessages()); 659 } 660 661 private void verifyActionMessageProperty(String messageKey, String property, ActionMessages messages) 662 { 663 verifyActionMessagePresent(messageKey, messages); 664 Iterator iterator = messages.get(property); 665 while(iterator.hasNext()) 666 { 667 ActionMessage message = (ActionMessage)iterator.next(); 668 if(message.getKey().equals(messageKey)) return; 669 } 670 throw new VerifyFailedException("action message/error " + messageKey + " not present for property " + property); 671 } 672 673 /** 674 * Verifies the number of action errors. 675 * @param number the expected number of errors 676 * @throws VerifyFailedException if verification fails 677 */ 678 public void verifyNumberActionErrors(int number) 679 { 680 verifyNumberActionMessages(number, getActionErrors()); 681 } 682 683 /** 684 * Verifies the number of action messages. 685 * @param number the expected number of messages 686 * @throws VerifyFailedException if verification fails 687 */ 688 public void verifyNumberActionMessages(int number) 689 { 690 verifyNumberActionMessages(number, getActionMessages()); 691 } 692 693 private void verifyNumberActionMessages(int number, ActionMessages messages) 694 { 695 if (null != messages) 696 { 697 if (messages.size() == number) return; 698 throw new VerifyFailedException("expected " + number + " messages/errors, received " + messages.size() + " messages/errors"); 699 } 700 if (number == 0) return; 701 throw new VerifyFailedException("no action messages/errors"); 702 } 703 704 /** 705 * Returns the action error with the specified key or null 706 * if such an error does not exist. 707 * @param errorKey the error key 708 * @return the action error with the specified key 709 */ 710 public ActionMessage getActionErrorByKey(String errorKey) 711 { 712 return getActionMessageByKey(errorKey, getActionErrors()); 713 } 714 715 /** 716 * Returns the action message with the specified key or null 717 * if such a message does not exist. 718 * @param messageKey the message key 719 * @return the action message with the specified key 720 */ 721 public ActionMessage getActionMessageByKey(String messageKey) 722 { 723 return (ActionMessage)getActionMessageByKey(messageKey, getActionMessages()); 724 } 725 726 private ActionMessage getActionMessageByKey(String messageKey, ActionMessages messages) 727 { 728 if(null == messages) return null; 729 Iterator iterator = messages.get(); 730 while (iterator.hasNext()) 731 { 732 ActionMessage message = (ActionMessage) iterator.next(); 733 if (message.getKey().equals(messageKey)) 734 { 735 return message; 736 } 737 } 738 return null; 739 } 740 741 /** 742 * Sets the specified <code>ActionMessages</code> object 743 * as the currently present messages to the request. 744 * @param messages the ActionMessages object 745 */ 746 public void setActionMessages(ActionMessages messages) 747 { 748 mockFactory.getWrappedRequest().setAttribute(messageAttributeKey, messages); 749 } 750 751 /** 752 * Sets the specified <code>ActionMessages</code> object 753 * as the currently present messages to the session. 754 * @param messages the ActionMessages object 755 */ 756 public void setActionMessagesToSession(ActionMessages messages) 757 { 758 mockFactory.getMockSession().setAttribute(messageAttributeKey, messages); 759 } 760 761 /** 762 * Get the currently present action messages. Can be called 763 * after {@link #actionPerform} to get the messages the action 764 * has set. If messages in the session are recognized 765 * (use {@link #setRecognizeMessagesInSession}), this method 766 * returns the union of request and session messages. Otherwise, 767 * it only returns the request messages. 768 * @return the action messages 769 */ 770 public ActionMessages getActionMessages() 771 { 772 ActionMessages requestMessages = getActionMessagesFromRequest(); 773 ActionMessages sessionMessages = getActionMessagesFromSession(); 774 if(recognizeInSession) 775 { 776 if(null == requestMessages || requestMessages.isEmpty()) return sessionMessages; 777 if(null == sessionMessages || sessionMessages.isEmpty()) return requestMessages; 778 requestMessages = new ActionMessages(requestMessages); 779 requestMessages.add(sessionMessages); 780 } 781 return requestMessages; 782 } 783 784 /** 785 * Get the currently present action messages from the request. 786 * @return the action messages 787 */ 788 public ActionMessages getActionMessagesFromRequest() 789 { 790 return (ActionMessages)mockFactory.getWrappedRequest().getAttribute(messageAttributeKey); 791 } 792 793 /** 794 * Get the currently present action messages from the session. 795 * @return the action messages 796 */ 797 public ActionMessages getActionMessagesFromSession() 798 { 799 return (ActionMessages)mockFactory.getMockSession().getAttribute(messageAttributeKey); 800 } 801 802 /** 803 * Returns if action messages are present. 804 * @return true if messages are present, false otherwise 805 */ 806 public boolean hasActionMessages() 807 { 808 ActionMessages messages = getActionMessages(); 809 return containsMessages(messages); 810 } 811 812 /** 813 * Sets the specified <code>ActionErrors</code> object 814 * as the currently present errors to the request. 815 * @param errors the ActionErrors object 816 */ 817 public void setActionErrors(ActionMessages errors) 818 { 819 mockFactory.getWrappedRequest().setAttribute(errorAttributeKey, errors); 820 } 821 822 /** 823 * Sets the specified <code>ActionErrors</code> object 824 * as the currently present errors to the session. 825 * @param errors the ActionErrors object 826 */ 827 public void setActionErrorsToSession(ActionMessages errors) 828 { 829 mockFactory.getMockSession().setAttribute(errorAttributeKey, errors); 830 } 831 832 /** 833 * Get the currently present action errors. Can be called 834 * after {@link #actionPerform} to get the errors the action 835 * has set. If messages in the session are recognized 836 * (use {@link #setRecognizeMessagesInSession}), this method 837 * returns the union of request and session errors. Otherwise, 838 * it only returns the request errors. 839 * @return the action errors 840 */ 841 public ActionMessages getActionErrors() 842 { 843 ActionMessages requestErrors = getActionErrorsFromRequest(); 844 ActionMessages sessionErrors = getActionErrorsFromSession(); 845 if(recognizeInSession) 846 { 847 if(null == requestErrors || requestErrors.isEmpty()) return sessionErrors; 848 if(null == sessionErrors || sessionErrors.isEmpty()) return requestErrors; 849 if((requestErrors instanceof ActionErrors) || (sessionErrors instanceof ActionErrors)) 850 { 851 ActionErrors tempErrors = new ActionErrors(); 852 tempErrors.add(requestErrors); 853 requestErrors = tempErrors; 854 } 855 else 856 { 857 requestErrors = new ActionMessages(requestErrors); 858 } 859 requestErrors.add(sessionErrors); 860 } 861 return requestErrors; 862 } 863 864 /** 865 * Get the currently present action errors from the request. 866 * @return the action messages 867 */ 868 public ActionMessages getActionErrorsFromRequest() 869 { 870 return (ActionMessages)mockFactory.getWrappedRequest().getAttribute(errorAttributeKey); 871 } 872 873 /** 874 * Get the currently present action errors from the session. 875 * @return the action messages 876 */ 877 public ActionMessages getActionErrorsFromSession() 878 { 879 return (ActionMessages)mockFactory.getMockSession().getAttribute(errorAttributeKey); 880 } 881 882 /** 883 * Returns if action errors are present. 884 * @return true if errors are present, false otherwise 885 */ 886 public boolean hasActionErrors() 887 { 888 ActionMessages errors = getActionErrors(); 889 return containsMessages(errors); 890 } 891 892 /** 893 * Delegates to {@link com.mockrunner.mock.web.ActionMockObjectFactory#getMockActionMapping}. 894 * @return the MockActionMapping 895 */ 896 public MockActionMapping getMockActionMapping() 897 { 898 return mockFactory.getMockActionMapping(); 899 } 900 901 /** 902 * Delegates to {@link com.mockrunner.mock.web.ActionMockObjectFactory#getActionMapping}. 903 * @return the MockActionMapping 904 */ 905 public ActionMapping getActionMapping() 906 { 907 return mockFactory.getActionMapping(); 908 } 909 910 /** 911 * Returns the <code>MockPageContext</code> object. 912 * Delegates to {@link com.mockrunner.mock.web.ActionMockObjectFactory#getMockPageContext}. 913 * @return the MockPageContext 914 */ 915 public MockPageContext getMockPageContext() 916 { 917 return mockFactory.getMockPageContext(); 918 } 919 920 /** 921 * Returns the current <code>ActionForward</code>. 922 * Can be called after {@link #actionPerform} to get 923 * the <code>ActionForward</code> the action 924 * has returned. 925 * @return the MockActionForward 926 */ 927 public MockActionForward getActionForward() 928 { 929 return forward; 930 } 931 932 /** 933 * Returns the last tested <code>Action</code> object. 934 * @return the <code>Action</code> object 935 */ 936 public Action getLastAction() 937 { 938 return actionObj; 939 } 940 941 /** 942 * Generates a token and sets it to the session and the request. 943 */ 944 public void generateValidToken() 945 { 946 String token = String.valueOf(Math.random()); 947 mockFactory.getMockSession().setAttribute(Globals.TRANSACTION_TOKEN_KEY, token); 948 addRequestParameter(Constants.TOKEN_KEY, token); 949 } 950 951 /** 952 * Returns the current <code>ActionForm</code>. 953 * @return the <code>ActionForm</code> object 954 */ 955 public ActionForm getActionForm() 956 { 957 return formObj; 958 } 959 960 /** 961 * Sets the specified <code>ActionForm</code> object as the 962 * current <code>ActionForm</code>. 963 * @param formObj the <code>ActionForm</code> object 964 */ 965 public void setActionForm(ActionForm formObj) 966 { 967 this.formObj = formObj; 968 } 969 970 /** 971 * Creates a new <code>ActionForm</code> object of the specified 972 * type and sets it as the current <code>ActionForm</code>. 973 * @param form the <code>Class</code> of the form 974 */ 975 public ActionForm createActionForm(Class form) 976 { 977 try 978 { 979 if (null == form) 980 { 981 formObj = null; 982 return null; 983 } 984 formObj = (ActionForm)form.newInstance(); 985 return formObj; 986 } 987 catch(Exception exc) 988 { 989 log.error(exc.getMessage(), exc); 990 throw new NestedApplicationException(exc); 991 } 992 } 993 994 /** 995 * Creates a new <code>DynaActionForm</code> based on the specified 996 * form config and sets it as the current <code>ActionForm</code>. 997 * @param formConfig the <code>FormBeanConfig</code> 998 */ 999 public DynaActionForm createDynaActionForm(FormBeanConfig formConfig) 1000 { 1001 try 1002 { 1003 if (null == formConfig) 1004 { 1005 formObj = null; 1006 return null; 1007 } 1008 DynaActionFormClass formClass = DynaActionFormClass.createDynaActionFormClass(formConfig); 1009 formObj = (DynaActionForm)formClass.newInstance(); 1010 return (DynaActionForm)formObj; 1011 } 1012 catch(Exception exc) 1013 { 1014 log.error(exc.getMessage(), exc); 1015 throw new NestedApplicationException(exc); 1016 } 1017 } 1018 1019 /** 1020 * Populates the current request parameters to the 1021 * <code>ActionForm</code>. The form will be reset 1022 * before populating if reset is enabled ({@link #setReset}. 1023 * If form validation is enabled (use {@link #setValidate}) the 1024 * form will be validated after populating it and the 1025 * appropriate <code>ActionErrors</code> will be set. 1026 */ 1027 public void populateRequestToForm() 1028 { 1029 try 1030 { 1031 handleActionForm(); 1032 } 1033 catch(Exception exc) 1034 { 1035 log.error(exc.getMessage(), exc); 1036 throw new NestedApplicationException(exc); 1037 } 1038 } 1039 1040 /** 1041 * Calls the action of the specified type using 1042 * no <code>ActionForm</code>. Sets the current action 1043 * form to <code>null</code>. 1044 * @param action the <code>Class</code> of the action 1045 * @return the resulting <code>ActionForward</code> 1046 */ 1047 public ActionForward actionPerform(Class action) 1048 { 1049 return actionPerform(action, (ActionForm) null); 1050 } 1051 1052 /** 1053 * Calls the specified action using 1054 * no <code>ActionForm</code>. Sets the current <code>ActionForm</code> 1055 * to <code>null</code>. 1056 * @param action the <code>Action</code> 1057 * @return the resulting <code>ActionForward</code> 1058 */ 1059 public ActionForward actionPerform(Action action) 1060 { 1061 return actionPerform(action, (ActionForm) null); 1062 } 1063 1064 /** 1065 * Calls the action of the specified type using 1066 * the <code>ActionForm</code> of the specified type. 1067 * Creates the appropriate <code>ActionForm</code>, sets it as the 1068 * current <code>ActionForm</code> and populates it before calling the action 1069 * (if populating is disabled, the form will not be populated, use 1070 * {@link #setDoPopulate}). 1071 * If form validation is enabled (use {@link #setValidate}) and 1072 * fails, the action will not be called. In this case, 1073 * the returned <code>ActionForward</code> is based on the 1074 * input attribute. (Set it with {@link #setInput}). 1075 * @param action the <code>Class</code> of the action 1076 * @param form the <code>Class</code> of the form 1077 * @return the resulting <code>ActionForward</code> 1078 */ 1079 public ActionForward actionPerform(Class action, Class form) 1080 { 1081 createActionForm(form); 1082 return actionPerform(action, formObj); 1083 } 1084 1085 /** 1086 * Calls the specified action using 1087 * the <code>ActionForm</code> of the specified type. 1088 * Creates the appropriate <code>ActionForm</code>, sets it as the 1089 * current <code>ActionForm</code> and populates it before calling the action 1090 * (if populating is disabled, the form will not be populated, use 1091 * {@link #setDoPopulate}). 1092 * If form validation is enabled (use {@link #setValidate}) and 1093 * fails, the action will not be called. In this case, 1094 * the returned <code>ActionForward</code> is based on the 1095 * input attribute. (Set it with {@link #setInput}). 1096 * @param action the <code>Action</code> 1097 * @param form the <code>Class</code> of the form 1098 * @return the resulting <code>ActionForward</code> 1099 */ 1100 public ActionForward actionPerform(Action action, Class form) 1101 { 1102 createActionForm(form); 1103 return actionPerform(action, formObj); 1104 } 1105 1106 /** 1107 * Calls the action of the specified type using 1108 * the specified <code>ActionForm</code> object. The form will 1109 * be set as the current <code>ActionForm</code> and 1110 * will be populated before the action is called (if populating is 1111 * disabled, the form will not be populated, use {@link #setDoPopulate}). 1112 * Please note that request parameters will eventually overwrite 1113 * form values. Furthermore the form will be reset 1114 * before populating it. If you do not want that, disable reset 1115 * using {@link #setReset}. If form validation is enabled 1116 * (use {@link #setValidate}) and fails, the action will not be 1117 * called. In this case, the returned <code>ActionForward</code> 1118 * is based on the input attribute. (Set it with {@link #setInput}). 1119 * @param action the <code>Class</code> of the action 1120 * @param form the <code>ActionForm</code> object 1121 * @return the resulting <code>ActionForward</code> 1122 */ 1123 public ActionForward actionPerform(Class action, ActionForm form) 1124 { 1125 Action actionToCall = null; 1126 try 1127 { 1128 actionToCall = (Action)action.newInstance(); 1129 } 1130 catch(Exception exc) 1131 { 1132 throw new NestedApplicationException(exc); 1133 } 1134 return actionPerform(actionToCall, form); 1135 } 1136 1137 /** 1138 * Calls the specified action using 1139 * the specified <code>ActionForm</code> object. The form will 1140 * be set as the current <code>ActionForm</code> and 1141 * will be populated before the action is called (if populating is 1142 * disabled, the form will not be populated, use {@link #setDoPopulate}). 1143 * Please note that request parameters will eventually overwrite 1144 * form values. Furthermore the form will be reset 1145 * before populating it. If you do not want that, disable reset 1146 * using {@link #setReset}. If form validation is enabled 1147 * (use {@link #setValidate}) and fails, the action will not be 1148 * called. In this case, the returned <code>ActionForward</code> 1149 * is based on the input attribute. (Set it with {@link #setInput}). 1150 * @param action the <code>Action</code> 1151 * @param form the <code>ActionForm</code> object 1152 * @return the resulting <code>ActionForward</code> 1153 */ 1154 public ActionForward actionPerform(Action action, ActionForm form) 1155 { 1156 try 1157 { 1158 actionObj = action; 1159 actionObj.setServlet(mockFactory.getMockActionServlet()); 1160 formObj = form; 1161 setActionErrors(null); 1162 getActionMapping().setType(action.getClass().getName()); 1163 if(null != formObj) 1164 { 1165 handleActionForm(); 1166 } 1167 if(!hasActionErrors()) 1168 { 1169 ActionForward currentForward = null; 1170 try 1171 { 1172 currentForward = (ActionForward)actionObj.execute(getActionMapping(), formObj, mockFactory.getWrappedRequest(), mockFactory.getWrappedResponse()); 1173 } 1174 catch(Exception exc) 1175 { 1176 ExceptionHandlerConfig handler = findExceptionHandler(exc); 1177 if(null == handler) 1178 { 1179 throw exc; 1180 } 1181 else 1182 { 1183 Object result = handler.handle(exc, getActionMapping(), formObj, mockFactory.getWrappedRequest(), mockFactory.getWrappedResponse()); 1184 if(result instanceof ActionForward) 1185 { 1186 currentForward = (ActionForward)result; 1187 } 1188 } 1189 } 1190 setResult(currentForward); 1191 } 1192 else 1193 { 1194 setResult(getActionMapping().getInputForward()); 1195 } 1196 } 1197 catch(Exception exc) 1198 { 1199 throw new NestedApplicationException(exc); 1200 } 1201 return getActionForward(); 1202 } 1203 1204 /** 1205 * Returns the HTML output as a string (if the action creates HTML output). 1206 * Flushes the output before returning it. 1207 * @return the output 1208 */ 1209 public String getOutput() 1210 { 1211 try 1212 { 1213 mockFactory.getMockResponse().getWriter().flush(); 1214 } 1215 catch(Exception exc) 1216 { 1217 log.error(exc.getMessage(), exc); 1218 } 1219 return mockFactory.getMockResponse().getOutputStreamContent(); 1220 } 1221 1222 private void setResult(ActionForward currentForward) 1223 { 1224 if (null == currentForward) 1225 { 1226 forward = null; 1227 } 1228 else 1229 { 1230 forward = new MockActionForward(currentForward); 1231 } 1232 } 1233 1234 private ExceptionHandlerConfig findExceptionHandler(Exception exc) 1235 { 1236 for(int ii = 0; ii < exceptionHandlers.size(); ii++) 1237 { 1238 ExceptionHandlerConfig next = (ExceptionHandlerConfig)exceptionHandlers.get(ii); 1239 if(next.canHandle(exc)) return next; 1240 } 1241 return null; 1242 } 1243 1244 private void handleActionForm() throws Exception 1245 { 1246 if(reset) getActionForm().reset(getActionMapping(), mockFactory.getWrappedRequest()); 1247 if(doPopulate) populateMockRequest(); 1248 formObj.setServlet(mockFactory.getMockActionServlet()); 1249 if(getActionMapping().getValidate()) 1250 { 1251 ActionMessages errors = formObj.validate(getActionMapping(), mockFactory.getWrappedRequest()); 1252 if (containsMessages(errors)) 1253 { 1254 mockFactory.getWrappedRequest().setAttribute(errorAttributeKey, errors); 1255 } 1256 } 1257 } 1258 1259 private void populateMockRequest() throws Exception 1260 { 1261 BeanUtils.populate(getActionForm(), mockFactory.getWrappedRequest().getParameterMap()); 1262 } 1263 1264 private boolean containsMessages(ActionMessages messages) 1265 { 1266 return (null != messages) && (messages.size() > 0); 1267 } 1268 }