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