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