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 }