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            }
217            catch(Exception exc)
218            {
219                log.error(exc.getMessage(), exc);
220                throw new NestedApplicationException(exc);
221            }
222        }
223        
224        /**
225         * Calls the current servlets <code>init</code> method. Is automatically
226         * done when calling {@link #createServlet}.
227         */
228        public void init()
229        {
230            try
231            {
232                servlet.init();
233            }
234            catch(ServletException exc)
235            {
236                log.error(exc.getMessage(), exc);
237                throw new NestedApplicationException(exc);
238            }
239        }
240        
241        /**
242         * Calls the current servlets <code>doDelete</code> method.
243         * If you set <i>doChain</i> to <code>true</code> (use {@link #setDoChain}),
244         * the filter chain will be called before <code>doDelete</code>.
245         */
246        public void doDelete()
247        {
248            mockFactory.getMockRequest().setMethod("DELETE");
249            callService();
250        }
251        
252        /**
253         * Calls the current servlets <code>doGet</code> method.
254         * If you set <i>doChain</i> to <code>true</code> (use {@link #setDoChain}),
255         * the filter chain will be called before <code>doGet</code>.
256         */          
257        public void doGet()
258        {
259            mockFactory.getMockRequest().setMethod("GET");
260            callService();
261        }
262        
263        /**
264         * Calls the current servlets <code>doOptions</code> method.
265         * If you set <i>doChain</i> to <code>true</code> (use {@link #setDoChain}),
266         * the filter chain will be called before <code>doOptions</code>.
267         */          
268        public void doOptions()
269        {
270            mockFactory.getMockRequest().setMethod("OPTIONS");
271            callService();
272        }
273        
274        /**
275         * Calls the current servlets <code>doPost</code> method.
276         * If you set <i>doChain</i> to <code>true</code> (use {@link #setDoChain}),
277         * the filter chain will be called before <code>doPost</code>.
278         */         
279        public void doPost()
280        {
281            mockFactory.getMockRequest().setMethod("POST");
282            callService();
283        }
284        
285        /**
286         * Calls the current servlets <code>doPut</code> method.
287         * If you set <i>doChain</i> to <code>true</code> (use {@link #setDoChain}),
288         * the filter chain will be called before <code>doPut</code>.
289         */         
290        public void doPut()
291        {
292            mockFactory.getMockRequest().setMethod("PUT");
293            callService();
294        }
295        
296        /**
297         * Calls the current servlets <code>doTrace</code> method.
298         * If you set <i>doChain</i> to <code>true</code> (use {@link #setDoChain}),
299         * the filter chain will be called before <code>doTrace</code>.
300         */          
301        public void doTrace()
302        {
303            mockFactory.getMockRequest().setMethod("TRACE");
304            callService();
305        }
306        
307        /**
308         * Calls the current servlets <code>doHead</code> method.
309         * If you set <i>doChain</i> to <code>true</code> (use {@link #setDoChain}),
310         * the filter chain will be called before <code>doHead</code>.
311         */          
312        public void doHead()
313        {
314            mockFactory.getMockRequest().setMethod("HEAD");
315            callService();
316        }
317        
318        /**
319         * Calls the current servlets <code>service</code> method.
320         * If you set <i>doChain</i> to <code>true</code> (use {@link #setDoChain}),
321         * the filter chain will be called before <code>service</code>.
322         */          
323        public void service()
324        {
325            callService();
326        }
327        
328        /**
329         * Returns the last request from the filter chain. Since
330         * filters can replace the request with a request wrapper,
331         * this method makes only sense after calling at least
332         * one filter, i.e. after calling {@link #doFilter} or
333         * after calling one servlet method with <i>doChain</i> 
334         * set to <code>true</code>.
335         * @return the filtered request
336         */  
337        public ServletRequest getFilteredRequest()
338        {
339            return mockFactory.getMockFilterChain().getLastRequest();
340        }
341        
342        /**
343         * Returns the last response from the filter chain. Since
344         * filters can replace the response with a response wrapper,
345         * this method makes only sense after calling at least
346         * one filter, i.e. after calling {@link #doFilter} or
347         * after calling one servlet method with <i>doChain</i> 
348         * set to <code>true</code>.
349         * @return the filtered response
350         */  
351        public ServletResponse getFilteredResponse()
352        {
353            return mockFactory.getMockFilterChain().getLastResponse();
354        }
355        
356        /**
357         * Returns the servlet output as a string. Flushes the output
358         * before returning it.
359         * @return the servlet output
360         */
361        public String getOutput()
362        {
363            try
364            {
365                mockFactory.getMockResponse().getWriter().flush();    
366            }
367            catch(Exception exc)
368            {
369                log.error(exc.getMessage(), exc);
370            }
371            return mockFactory.getMockResponse().getOutputStreamContent();
372        }
373        
374        /**
375         * Clears the output content
376         */ 
377        public void clearOutput()
378        {
379            mockFactory.getMockResponse().resetBuffer();
380        }
381        
382        private void callService()
383        {
384            try
385            {
386                if(doChain)
387                { 
388                    doFilter(); 
389                }
390                else
391                {
392                    servlet.service(mockFactory.getWrappedRequest(), mockFactory.getWrappedResponse());
393                }            
394            }
395            catch(Exception exc)
396            {
397                log.error(exc.getMessage(), exc);
398                throw new NestedApplicationException(exc);
399            }
400        }  
401    }