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 }