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 }