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