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 }