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 }