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 }