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 }