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