001    package com.mockrunner.servlet;
002    
003    import javax.servlet.Filter;
004    import javax.servlet.ServletException;
005    import javax.servlet.ServletRequest;
006    import javax.servlet.ServletResponse;
007    import javax.servlet.http.HttpServlet;
008    
009    import org.apache.commons.logging.Log;
010    import org.apache.commons.logging.LogFactory;
011    
012    import com.mockrunner.base.HTMLOutputModule;
013    import com.mockrunner.base.NestedApplicationException;
014    import com.mockrunner.mock.web.WebMockObjectFactory;
015    
016    /**
017     * Module for servlet and filter tests. Can test
018     * single servlets and filters and simulate a filter
019     * chain.
020     */
021    public class ServletTestModule extends HTMLOutputModule
022    {
023        private final static Log log = LogFactory.getLog(ServletTestModule.class);
024        private WebMockObjectFactory mockFactory;
025        private HttpServlet servlet;
026        private boolean doChain;
027          
028        public ServletTestModule(WebMockObjectFactory mockFactory)
029        {
030            super(mockFactory);
031            this.mockFactory = mockFactory;
032            doChain = false;
033        }
034        
035        /**
036         * Creates a servlet and initializes it. <code>servletClass</code> must
037         * be of the type <code>HttpServlet</code>, otherwise a
038         * <code>RuntimeException</code> will be thrown.
039         * Sets the specified servlet as the current servlet and
040         * initializes the filter chain with it.
041         * @param servletClass the class of the servlet
042         * @return instance of <code>HttpServlet</code>
043         * @throws RuntimeException if <code>servletClass</code> is not an
044         *         instance of <code>HttpServlet</code>
045         */
046        public HttpServlet createServlet(Class servletClass)
047        {
048            if(!HttpServlet.class.isAssignableFrom(servletClass))
049            {
050                throw new RuntimeException("servletClass must be an instance of javax.servlet.http.HttpServlet");
051            }
052            try
053            {
054                HttpServlet theServlet = (HttpServlet)servletClass.newInstance();
055                setServlet(theServlet, true);
056                return theServlet;
057            }
058            catch(Exception exc)
059            {
060                log.error(exc.getMessage(), exc);
061                throw new NestedApplicationException(exc);
062            }
063        }
064        
065        /**
066         * Sets the specified servlet as the current servlet without initializing it. 
067         * You have to set the <code>ServletConfig</code> on your own.
068         * Usually you can use 
069         * {@link com.mockrunner.mock.web.WebMockObjectFactory#getMockServletConfig}.
070         * @param servlet the servlet
071         */
072        public void setServlet(HttpServlet servlet)
073        {
074            setServlet(servlet, false);
075        }
076        
077        /**
078         * Sets the specified servlet as the current servlet.
079         * Initializes it, if <code>doInit</code> is <code>true</code>.
080         * @param servlet the servlet
081         * @param doInit should <code>init</code> be called
082         */
083        public void setServlet(HttpServlet servlet, boolean doInit)
084        {
085            try
086            {
087                    this.servlet = servlet;
088                    if(doInit)
089                    {
090                        servlet.init(mockFactory.getMockServletConfig());
091                    }
092                    mockFactory.getMockFilterChain().setServlet(servlet);
093            }
094            catch(Exception exc)
095            {
096                log.error(exc.getMessage(), exc);
097                throw new NestedApplicationException(exc);
098            }
099        }
100        
101        /**
102         * Returns the current servlet.
103         * @return the servlet
104         */
105        public HttpServlet getServlet()
106        {
107            return servlet;
108        }
109        
110        /**
111         * Creates a filter, initializes it and adds it to the
112         * filter chain. <code>filterClass</code> must be of the type 
113         * <code>Filter</code>, otherwise a <code>RuntimeException</code> 
114         * will be thrown. You can loop through the filter chain with
115         * {@link #doFilter}. If you set <code>doChain</code> to
116         * <code>true</code> every call of one of the servlet methods 
117         * will go through the filter chain before calling the servlet 
118         * method.
119         * @param filterClass the class of the filter
120         * @return instance of <code>Filter</code>
121         * @throws RuntimeException if <code>filterClass</code> is not an
122         *         instance of <code>Filter</code>
123         */
124        public Filter createFilter(Class filterClass)
125        {
126            if(!Filter.class.isAssignableFrom(filterClass))
127            {
128                throw new RuntimeException("filterClass must be an instance of javax.servlet.Filter");
129            }
130            try
131            {
132                Filter theFilter = (Filter)filterClass.newInstance();
133                addFilter(theFilter, true);
134                return theFilter;
135            }
136            catch(Exception exc)
137            {
138                log.error(exc.getMessage(), exc);
139                throw new NestedApplicationException(exc);
140            }
141        }
142        
143        /**
144         * Adds the specified filter to the filter chain without
145         * initializing it. 
146         * You have to set the <code>FilterConfig</code> on your own.
147         * Usually you can use 
148         * {@link com.mockrunner.mock.web.WebMockObjectFactory#getMockFilterConfig}.
149         * @param filter the filter
150         */
151        public void addFilter(Filter filter)
152        {
153            addFilter(filter, false);
154        }
155        
156        /**
157         * Adds the specified filter it to the filter chain. Initializes it,
158         * if <code>doInit</code> is <code>true</code>.
159         * @param filter the filter
160         * @param doInit should <code>init</code> be called
161         */
162        public void addFilter(Filter filter, boolean doInit)
163        {
164            if(doInit)
165            {
166                try
167                {
168                    filter.init(mockFactory.getMockFilterConfig());
169                }
170                catch(Exception exc)
171                {
172                    log.error(exc.getMessage(), exc);
173                    throw new NestedApplicationException(exc);
174                }
175            }
176            mockFactory.getMockFilterChain().addFilter(filter);
177        }
178        
179        /**
180         * Deletes all filters in the filter chain.
181         */
182        public void releaseFilters()
183        {
184            mockFactory.getMockFilterChain().release();
185            mockFactory.getMockFilterChain().setServlet(servlet);
186        }
187    
188        /**
189         * If <code>doChain</code> is set to <code>true</code>
190         * (default is <code>false</code>) every call of
191         * one of the servlet methods will go through the filter chain
192         * before calling the servlet method.
193         * @param doChain <code>true</code> if the chain should be called
194         */
195        public void setDoChain(boolean doChain)
196        {
197            this.doChain = doChain;
198        }
199        
200        /**
201         * Loops through the filter chain and calls the current servlets
202         * <code>service</code> method at the end (only if a current servlet
203         * is set). You can use it to test single filters or the interaction 
204         * of filters and servlets.
205         * If you set <i>doChain</i> to <code>true</code> (use {@link #setDoChain}),
206         * this method is called before any call of a servlet method. If a filter
207         * does not call it's chains <code>doFilter</code> method, the chain
208         * breaks and the servlet will not be called (just like it in the
209         * real container).
210         */
211        public void doFilter()
212        {
213            try
214            {
215                mockFactory.getMockFilterChain().doFilter(mockFactory.getWrappedRequest(), mockFactory.getWrappedResponse());
216                mockFactory.getMockFilterChain().reset();
217            }
218            catch(Exception exc)
219            {
220                log.error(exc.getMessage(), exc);
221                throw new NestedApplicationException(exc);
222            }
223        }
224        
225        /**
226         * Calls the current servlets <code>init</code> method. Is automatically
227         * done when calling {@link #createServlet}.
228         */
229        public void init()
230        {
231            try
232            {
233                servlet.init();
234            }
235            catch(ServletException exc)
236            {
237                log.error(exc.getMessage(), exc);
238                throw new NestedApplicationException(exc);
239            }
240        }
241        
242        /**
243         * Calls the current servlets <code>doDelete</code> method.
244         * If you set <i>doChain</i> to <code>true</code> (use {@link #setDoChain}),
245         * the filter chain will be called before <code>doDelete</code>.
246         */
247        public void doDelete()
248        {
249            mockFactory.getMockRequest().setMethod("DELETE");
250            callService();
251        }
252        
253        /**
254         * Calls the current servlets <code>doGet</code> method.
255         * If you set <i>doChain</i> to <code>true</code> (use {@link #setDoChain}),
256         * the filter chain will be called before <code>doGet</code>.
257         */          
258        public void doGet()
259        {
260            mockFactory.getMockRequest().setMethod("GET");
261            callService();
262        }
263        
264        /**
265         * Calls the current servlets <code>doOptions</code> method.
266         * If you set <i>doChain</i> to <code>true</code> (use {@link #setDoChain}),
267         * the filter chain will be called before <code>doOptions</code>.
268         */          
269        public void doOptions()
270        {
271            mockFactory.getMockRequest().setMethod("OPTIONS");
272            callService();
273        }
274        
275        /**
276         * Calls the current servlets <code>doPost</code> method.
277         * If you set <i>doChain</i> to <code>true</code> (use {@link #setDoChain}),
278         * the filter chain will be called before <code>doPost</code>.
279         */         
280        public void doPost()
281        {
282            mockFactory.getMockRequest().setMethod("POST");
283            callService();
284        }
285        
286        /**
287         * Calls the current servlets <code>doPut</code> method.
288         * If you set <i>doChain</i> to <code>true</code> (use {@link #setDoChain}),
289         * the filter chain will be called before <code>doPut</code>.
290         */         
291        public void doPut()
292        {
293            mockFactory.getMockRequest().setMethod("PUT");
294            callService();
295        }
296        
297        /**
298         * Calls the current servlets <code>doTrace</code> method.
299         * If you set <i>doChain</i> to <code>true</code> (use {@link #setDoChain}),
300         * the filter chain will be called before <code>doTrace</code>.
301         */          
302        public void doTrace()
303        {
304            mockFactory.getMockRequest().setMethod("TRACE");
305            callService();
306        }
307        
308        /**
309         * Calls the current servlets <code>doHead</code> method.
310         * If you set <i>doChain</i> to <code>true</code> (use {@link #setDoChain}),
311         * the filter chain will be called before <code>doHead</code>.
312         */          
313        public void doHead()
314        {
315            mockFactory.getMockRequest().setMethod("HEAD");
316            callService();
317        }
318        
319        /**
320         * Calls the current servlets <code>service</code> method.
321         * If you set <i>doChain</i> to <code>true</code> (use {@link #setDoChain}),
322         * the filter chain will be called before <code>service</code>.
323         */          
324        public void service()
325        {
326            callService();
327        }
328        
329        /**
330         * Returns the last request from the filter chain. Since
331         * filters can replace the request with a request wrapper,
332         * this method makes only sense after calling at least
333         * one filter, i.e. after calling {@link #doFilter} or
334         * after calling one servlet method with <i>doChain</i> 
335         * set to <code>true</code>.
336         * @return the filtered request
337         */  
338        public ServletRequest getFilteredRequest()
339        {
340            return mockFactory.getMockFilterChain().getLastRequest();
341        }
342        
343        /**
344         * Returns the last response from the filter chain. Since
345         * filters can replace the response with a response wrapper,
346         * this method makes only sense after calling at least
347         * one filter, i.e. after calling {@link #doFilter} or
348         * after calling one servlet method with <i>doChain</i> 
349         * set to <code>true</code>.
350         * @return the filtered response
351         */  
352        public ServletResponse getFilteredResponse()
353        {
354            return mockFactory.getMockFilterChain().getLastResponse();
355        }
356        
357        /**
358         * Returns the servlet output as a string. Flushes the output
359         * before returning it.
360         * @return the servlet output
361         */
362        public String getOutput()
363        {
364            try
365            {
366                mockFactory.getMockResponse().getWriter().flush();    
367            }
368            catch(Exception exc)
369            {
370                log.error(exc.getMessage(), exc);
371            }
372            return mockFactory.getMockResponse().getOutputStreamContent();
373        }
374        
375        /**
376         * Clears the output content
377         */ 
378        public void clearOutput()
379        {
380            mockFactory.getMockResponse().resetBuffer();
381        }
382        
383        private void callService()
384        {
385            try
386            {
387                if(doChain)
388                { 
389                    doFilter(); 
390                }
391                else
392                {
393                    servlet.service(mockFactory.getWrappedRequest(), mockFactory.getWrappedResponse());
394                }            
395            }
396            catch(Exception exc)
397            {
398                log.error(exc.getMessage(), exc);
399                throw new NestedApplicationException(exc);
400            }
401        }  
402    }