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 }