1 package org.apache.velocity.servlet; 2 3 /* 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 */ 21 22 import java.io.FileNotFoundException; 23 import java.io.IOException; 24 import java.io.OutputStreamWriter; 25 import java.io.PrintWriter; 26 import java.io.StringWriter; 27 import java.io.UnsupportedEncodingException; 28 import java.util.Properties; 29 30 import javax.servlet.ServletConfig; 31 import javax.servlet.ServletContext; 32 import javax.servlet.ServletException; 33 import javax.servlet.ServletOutputStream; 34 import javax.servlet.http.HttpServlet; 35 import javax.servlet.http.HttpServletRequest; 36 import javax.servlet.http.HttpServletResponse; 37 38 import org.apache.velocity.Template; 39 import org.apache.velocity.VelocityContext; 40 import org.apache.velocity.app.Velocity; 41 import org.apache.velocity.context.Context; 42 import org.apache.velocity.exception.MethodInvocationException; 43 import org.apache.velocity.exception.ParseErrorException; 44 import org.apache.velocity.exception.ResourceNotFoundException; 45 import org.apache.velocity.io.VelocityWriter; 46 import org.apache.velocity.runtime.RuntimeConstants; 47 import org.apache.velocity.runtime.RuntimeSingleton; 48 import org.apache.velocity.util.SimplePool; 49 50 /** 51 * Base class which simplifies the use of Velocity with Servlets. 52 * Extend this class, implement the <code>handleRequest()</code> method, 53 * and add your data to the context. Then call 54 * <code>getTemplate("myTemplate.wm")</code>. 55 * 56 * This class puts some things into the context object that you should 57 * be aware of: 58 * <pre> 59 * "req" - The HttpServletRequest object 60 * "res" - The HttpServletResponse object 61 * </pre> 62 * 63 * There are other methods you can override to access, alter or control 64 * any part of the request processing chain. Please see the javadocs for 65 * more information on : 66 * <ul> 67 * <li> loadConfiguration() : for setting up the Velocity runtime 68 * <li> createContext() : for creating and loading the Context 69 * <li> setContentType() : for changing the content type on a request 70 * by request basis 71 * <li> handleRequest() : you <b>must</b> implement this 72 * <li> mergeTemplate() : the template rendering process 73 * <li> requestCleanup() : post rendering resource or other cleanup 74 * <li> error() : error handling 75 * </ul> 76 * <br> 77 * If you put a String with key "contentType" object into the context within either your 78 * servlet or within your template, then that will be used to override 79 * the default content type specified in the properties file. 80 * 81 * @deprecated This servlet has been replaced by VelocityViewServlet, 82 * available from the Velocity-Tools sub-project. VelocityViewServlet 83 * provides support for quick, clean MVC web development. 84 * VelocityServlet will be removed in a future version of Velocity. 85 * 86 * @author Dave Bryson 87 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a> 88 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> 89 * @author <a href="kjohnson@transparent.com">Kent Johnson</a> 90 * @author <a href="dlr@finemaltcoding.com">Daniel Rall</a> 91 * $Id: VelocityServlet.java 463298 2006-10-12 16:10:32Z henning $ 92 */ 93 public abstract class VelocityServlet extends HttpServlet 94 { 95 /** 96 * The context key for the HTTP request object. 97 */ 98 public static final String REQUEST = "req"; 99 100 /** 101 * The context key for the HTTP response object. 102 */ 103 public static final String RESPONSE = "res"; 104 105 /** 106 * The HTTP content type context key. 107 */ 108 public static final String CONTENT_TYPE = "default.contentType"; 109 110 /** 111 * The default content type for the response 112 */ 113 public static final String DEFAULT_CONTENT_TYPE = "text/html"; 114 115 116 /** 117 * Encoding for the output stream 118 */ 119 public static final String DEFAULT_OUTPUT_ENCODING = "ISO-8859-1"; 120 121 /** 122 * The default content type, itself defaulting to {@link 123 * #DEFAULT_CONTENT_TYPE} if not configured. 124 */ 125 private static String defaultContentType; 126 127 /** 128 * This is the string that is looked for when getInitParameter is 129 * called (<code>org.apache.velocity.properties</code>). 130 */ 131 protected static final String INIT_PROPS_KEY = 132 "org.apache.velocity.properties"; 133 134 /** 135 * Use of this properties key has been deprecated, and will be 136 * removed in Velocity version 1.5. 137 */ 138 private static final String OLD_INIT_PROPS_KEY = "properties"; 139 140 /** 141 * Cache of writers 142 */ 143 144 private static SimplePool writerPool = new SimplePool(40); 145 146 /** 147 * Performs initialization of this servlet. Called by the servlet 148 * container on loading. 149 * 150 * @param config The servlet configuration to apply. 151 * 152 * @exception ServletException 153 */ 154 public void init( ServletConfig config ) 155 throws ServletException 156 { 157 super.init( config ); 158 159 /* 160 * do whatever we have to do to init Velocity 161 */ 162 initVelocity( config ); 163 164 /* 165 * Now that Velocity is initialized, cache some config. 166 */ 167 VelocityServlet.defaultContentType = 168 RuntimeSingleton.getString(CONTENT_TYPE, DEFAULT_CONTENT_TYPE); 169 } 170 171 /** 172 * Initializes the Velocity runtime, first calling 173 * loadConfiguration(ServletConvig) to get a 174 * java.util.Properties of configuration information 175 * and then calling Velocity.init(). Override this 176 * to do anything to the environment before the 177 * initialization of the singelton takes place, or to 178 * initialize the singleton in other ways. 179 * @param config 180 * @throws ServletException 181 */ 182 protected void initVelocity( ServletConfig config ) 183 throws ServletException 184 { 185 try 186 { 187 /* 188 * call the overridable method to allow the 189 * derived classes a shot at altering the configuration 190 * before initializing Runtime 191 */ 192 193 Properties props = loadConfiguration( config ); 194 195 Velocity.init( props ); 196 } 197 catch( Exception e ) 198 { 199 throw new ServletException("Error initializing Velocity: " + e, e); 200 } 201 } 202 203 /** 204 * Loads the configuration information and returns that 205 * information as a Properties, which will be used to 206 * initialize the Velocity runtime. 207 * <br><br> 208 * Currently, this method gets the initialization parameter 209 * VelocityServlet.INIT_PROPS_KEY, which should be a file containing 210 * the configuration information. 211 * <br><br> 212 * To configure your Servlet Spec 2.2 compliant servlet runner to pass 213 * this to you, put the following in your WEB-INF/web.xml file 214 * <br> 215 * <pre> 216 * <servlet> 217 * <servlet-name> YourServlet </servlet-name> 218 * <servlet-class> your.package.YourServlet </servlet-class> 219 * <init-param> 220 * <param-name> org.apache.velocity.properties </param-name> 221 * <param-value> velocity.properties </param-value> 222 * </init-param> 223 * </servlet> 224 * </pre> 225 * 226 * Alternately, if you wish to configure an entire context in this 227 * fashion, you may use the following: 228 * <br> 229 * <pre> 230 * <context-param> 231 * <param-name> org.apache.velocity.properties </param-name> 232 * <param-value> velocity.properties </param-value> 233 * <description> Path to Velocity configuration </description> 234 * </context-param> 235 * </pre> 236 * 237 * Derived classes may do the same, or take advantage of this code to do the loading for them via : 238 * <pre> 239 * Properties p = super.loadConfiguration( config ); 240 * </pre> 241 * and then add or modify the configuration values from the file. 242 * <br> 243 * 244 * @param config ServletConfig passed to the servlets init() function 245 * Can be used to access the real path via ServletContext (hint) 246 * @return java.util.Properties loaded with configuration values to be used 247 * to initialize the Velocity runtime. 248 * @throws FileNotFoundException if a specified file is not found. 249 * @throws IOException I/O problem accessing the specified file, if specified. 250 * @deprecated Use VelocityViewServlet from the Velocity Tools 251 * library instead. 252 */ 253 protected Properties loadConfiguration(ServletConfig config) 254 throws IOException, FileNotFoundException 255 { 256 // This is a little overly complex because of legacy support 257 // for the initialization properties key "properties". 258 // References to OLD_INIT_PROPS_KEY should be removed at 259 // Velocity version 1.5. 260 String propsFile = config.getInitParameter(INIT_PROPS_KEY); 261 if (propsFile == null || propsFile.length() == 0) 262 { 263 ServletContext sc = config.getServletContext(); 264 propsFile = config.getInitParameter(OLD_INIT_PROPS_KEY); 265 if (propsFile == null || propsFile.length() == 0) 266 { 267 propsFile = sc.getInitParameter(INIT_PROPS_KEY); 268 if (propsFile == null || propsFile.length() == 0) 269 { 270 propsFile = sc.getInitParameter(OLD_INIT_PROPS_KEY); 271 if (propsFile != null && propsFile.length() > 0) 272 { 273 sc.log("Use of the properties initialization " + 274 "parameter '" + OLD_INIT_PROPS_KEY + "' has " + 275 "been deprecated by '" + INIT_PROPS_KEY + '\''); 276 } 277 } 278 } 279 else 280 { 281 sc.log("Use of the properties initialization parameter '" + 282 OLD_INIT_PROPS_KEY + "' has been deprecated by '" + 283 INIT_PROPS_KEY + '\''); 284 } 285 } 286 287 /* 288 * This will attempt to find the location of the properties 289 * file from the relative path to the WAR archive (ie: 290 * docroot). Since JServ returns null for getRealPath() 291 * because it was never implemented correctly, then we know we 292 * will not have an issue with using it this way. I don't know 293 * if this will break other servlet engines, but it probably 294 * shouldn't since WAR files are the future anyways. 295 */ 296 297 Properties p = new Properties(); 298 299 if ( propsFile != null ) 300 { 301 p.load(getServletContext().getResourceAsStream(propsFile)); 302 } 303 304 return p; 305 } 306 307 /** 308 * Handles HTTP <code>GET</code> requests by calling {@link 309 * #doRequest(HttpServletRequest, HttpServletResponse)}. 310 * @param request 311 * @param response 312 * @throws ServletException 313 * @throws IOException 314 */ 315 public void doGet( HttpServletRequest request, HttpServletResponse response ) 316 throws ServletException, IOException 317 { 318 doRequest(request, response); 319 } 320 321 /** 322 * Handles HTTP <code>POST</code> requests by calling {@link 323 * #doRequest(HttpServletRequest, HttpServletResponse)}. 324 * @param request 325 * @param response 326 * @throws ServletException 327 * @throws IOException 328 */ 329 public void doPost( HttpServletRequest request, HttpServletResponse response ) 330 throws ServletException, IOException 331 { 332 doRequest(request, response); 333 } 334 335 /** 336 * Handles all requests (by default). 337 * 338 * @param request HttpServletRequest object containing client request 339 * @param response HttpServletResponse object for the response 340 * @throws ServletException 341 * @throws IOException 342 */ 343 protected void doRequest(HttpServletRequest request, HttpServletResponse response ) 344 throws ServletException, IOException 345 { 346 Context context = null; 347 try 348 { 349 /* 350 * first, get a context 351 */ 352 353 context = createContext( request, response ); 354 355 /* 356 * set the content type 357 */ 358 359 setContentType( request, response ); 360 361 /* 362 * let someone handle the request 363 */ 364 365 Template template = handleRequest( request, response, context ); 366 /* 367 * bail if we can't find the template 368 */ 369 370 if ( template == null ) 371 { 372 return; 373 } 374 375 /* 376 * now merge it 377 */ 378 379 mergeTemplate( template, context, response ); 380 } 381 catch (Exception e) 382 { 383 /* 384 * call the error handler to let the derived class 385 * do something useful with this failure. 386 */ 387 388 error( request, response, e); 389 } 390 finally 391 { 392 /* 393 * call cleanup routine to let a derived class do some cleanup 394 */ 395 396 requestCleanup( request, response, context ); 397 } 398 } 399 400 /** 401 * A cleanup routine which is called at the end of the {@link 402 * #doRequest(HttpServletRequest, HttpServletResponse)} 403 * processing sequence, allowing a derived class to do resource 404 * cleanup or other end of process cycle tasks. 405 * 406 * @param request servlet request from client 407 * @param response servlet reponse 408 * @param context context created by the createContext() method 409 */ 410 protected void requestCleanup( HttpServletRequest request, HttpServletResponse response, Context context ) 411 { 412 } 413 414 /** 415 * merges the template with the context. Only override this if you really, really 416 * really need to. (And don't call us with questions if it breaks :) 417 * 418 * @param template template object returned by the handleRequest() method 419 * @param context context created by the createContext() method 420 * @param response servlet reponse (use this to get the output stream or Writer 421 * @throws ResourceNotFoundException 422 * @throws ParseErrorException 423 * @throws MethodInvocationException 424 * @throws IOException 425 * @throws UnsupportedEncodingException 426 * @throws Exception 427 */ 428 protected void mergeTemplate( Template template, Context context, HttpServletResponse response ) 429 throws ResourceNotFoundException, ParseErrorException, 430 MethodInvocationException, IOException, UnsupportedEncodingException, Exception 431 { 432 ServletOutputStream output = response.getOutputStream(); 433 VelocityWriter vw = null; 434 // ASSUMPTION: response.setContentType() has been called. 435 String encoding = response.getCharacterEncoding(); 436 437 try 438 { 439 vw = (VelocityWriter) writerPool.get(); 440 441 if (vw == null) 442 { 443 vw = new VelocityWriter(new OutputStreamWriter(output, 444 encoding), 445 4 * 1024, true); 446 } 447 else 448 { 449 vw.recycle(new OutputStreamWriter(output, encoding)); 450 } 451 452 template.merge(context, vw); 453 } 454 finally 455 { 456 if (vw != null) 457 { 458 try 459 { 460 /* 461 * flush and put back into the pool 462 * don't close to allow us to play 463 * nicely with others. 464 */ 465 vw.flush(); 466 } 467 catch (IOException e) 468 { 469 // do nothing 470 } 471 472 /* 473 * Clear the VelocityWriter's reference to its 474 * internal OutputStreamWriter to allow the latter 475 * to be GC'd while vw is pooled. 476 */ 477 vw.recycle(null); 478 writerPool.put(vw); 479 } 480 } 481 } 482 483 /** 484 * Sets the content type of the response, defaulting to {@link 485 * #defaultContentType} if not overriden. Delegates to {@link 486 * #chooseCharacterEncoding(HttpServletRequest)} to select the 487 * appropriate character encoding. 488 * 489 * @param request The servlet request from the client. 490 * @param response The servlet reponse to the client. 491 */ 492 protected void setContentType(HttpServletRequest request, 493 HttpServletResponse response) 494 { 495 String contentType = VelocityServlet.defaultContentType; 496 int index = contentType.lastIndexOf(';') + 1; 497 if (index <= 0 || (index < contentType.length() && 498 contentType.indexOf("charset", index) == -1)) 499 { 500 // Append the character encoding which we'd like to use. 501 String encoding = chooseCharacterEncoding(request); 502 //RuntimeSingleton.debug("Chose output encoding of '" + 503 // encoding + '\''); 504 if (!DEFAULT_OUTPUT_ENCODING.equalsIgnoreCase(encoding)) 505 { 506 contentType += "; charset=" + encoding; 507 } 508 } 509 response.setContentType(contentType); 510 //RuntimeSingleton.debug("Response Content-Type set to '" + 511 // contentType + '\''); 512 } 513 514 /** 515 * Chooses the output character encoding to be used as the value 516 * for the "charset=" portion of the HTTP Content-Type header (and 517 * thus returned by <code>response.getCharacterEncoding()</code>). 518 * Called by {@link #setContentType(HttpServletRequest, 519 * HttpServletResponse)} if an encoding isn't already specified by 520 * Content-Type. By default, chooses the value of 521 * RuntimeSingleton's <code>output.encoding</code> property. 522 * 523 * @param request The servlet request from the client. 524 * @return The chosen character encoding. 525 */ 526 protected String chooseCharacterEncoding(HttpServletRequest request) 527 { 528 return RuntimeSingleton.getString(RuntimeConstants.OUTPUT_ENCODING, 529 DEFAULT_OUTPUT_ENCODING); 530 } 531 532 /** 533 * Returns a context suitable to pass to the handleRequest() method 534 * <br><br> 535 * Default implementation will create a VelocityContext object, 536 * put the HttpServletRequest and HttpServletResponse 537 * into the context accessable via the keys VelocityServlet.REQUEST and 538 * VelocityServlet.RESPONSE, respectively. 539 * 540 * @param request servlet request from client 541 * @param response servlet reponse to client 542 * 543 * @return context 544 */ 545 protected Context createContext(HttpServletRequest request, HttpServletResponse response ) 546 { 547 /* 548 * create a new context 549 */ 550 551 VelocityContext context = new VelocityContext(); 552 553 /* 554 * put the request/response objects into the context 555 * wrap the HttpServletRequest to solve the introspection 556 * problems 557 */ 558 559 context.put( REQUEST, request ); 560 context.put( RESPONSE, response ); 561 562 return context; 563 } 564 565 /** 566 * Retrieves the requested template. 567 * 568 * @param name The file name of the template to retrieve relative to the 569 * template root. 570 * @return The requested template. 571 * @throws ResourceNotFoundException if template not found 572 * from any available source. 573 * @throws ParseErrorException if template cannot be parsed due 574 * to syntax (or other) error. 575 * @throws Exception if an error occurs in template initialization 576 */ 577 public Template getTemplate( String name ) 578 throws ResourceNotFoundException, ParseErrorException, Exception 579 { 580 return RuntimeSingleton.getTemplate(name); 581 } 582 583 /** 584 * Retrieves the requested template with the specified 585 * character encoding. 586 * 587 * @param name The file name of the template to retrieve relative to the 588 * template root. 589 * @param encoding the character encoding of the template 590 * 591 * @return The requested template. 592 * @throws ResourceNotFoundException if template not found 593 * from any available source. 594 * @throws ParseErrorException if template cannot be parsed due 595 * to syntax (or other) error. 596 * @throws Exception if an error occurs in template initialization 597 * 598 * @since Velocity v1.1 599 */ 600 public Template getTemplate( String name, String encoding ) 601 throws ResourceNotFoundException, ParseErrorException, Exception 602 { 603 return RuntimeSingleton.getTemplate( name, encoding ); 604 } 605 606 /** 607 * Implement this method to add your application data to the context, 608 * calling the <code>getTemplate()</code> method to produce your return 609 * value. 610 * <br><br> 611 * In the event of a problem, you may handle the request directly 612 * and return <code>null</code> or throw a more meaningful exception 613 * for the error handler to catch. 614 * 615 * @param request servlet request from client 616 * @param response servlet reponse 617 * @param ctx The context to add your data to. 618 * @return The template to merge with your context or null, indicating 619 * that you handled the processing. 620 * @throws Exception 621 * 622 * @since Velocity v1.1 623 */ 624 protected Template handleRequest( HttpServletRequest request, HttpServletResponse response, Context ctx ) 625 throws Exception 626 { 627 /* 628 * invoke handleRequest 629 */ 630 631 Template t = handleRequest( ctx ); 632 633 /* 634 * if it returns null, this is the 'old' deprecated 635 * way, and we want to mimic the behavior for a little 636 * while anyway 637 */ 638 639 if (t == null) 640 { 641 throw new Exception ("handleRequest(Context) returned null - no template selected!" ); 642 } 643 644 return t; 645 } 646 647 /** 648 * Implement this method to add your application data to the context, 649 * calling the <code>getTemplate()</code> method to produce your return 650 * value. 651 * <br><br> 652 * In the event of a problem, you may simple return <code>null</code> 653 * or throw a more meaningful exception. 654 * 655 * @deprecated Use 656 * {@link #handleRequest( HttpServletRequest request, 657 * HttpServletResponse response, Context ctx )} 658 * 659 * @param ctx The context to add your data to. 660 * @return The template to merge with your context. 661 * @throws Exception 662 */ 663 protected Template handleRequest( Context ctx ) 664 throws Exception 665 { 666 throw new Exception ("You must override VelocityServlet.handleRequest( Context) " 667 + " or VelocityServlet.handleRequest( HttpServletRequest, " 668 + " HttpServletResponse, Context)" ); 669 } 670 671 /** 672 * Invoked when there is an error thrown in any part of doRequest() processing. 673 * <br><br> 674 * Default will send a simple HTML response indicating there was a problem. 675 * 676 * @param request original HttpServletRequest from servlet container. 677 * @param response HttpServletResponse object from servlet container. 678 * @param cause Exception that was thrown by some other part of process. 679 * @throws ServletException 680 * @throws IOException 681 */ 682 protected void error( HttpServletRequest request, HttpServletResponse response, Exception cause ) 683 throws ServletException, IOException 684 { 685 StringBuffer html = new StringBuffer(); 686 html.append("<html>"); 687 html.append("<title>Error</title>"); 688 html.append("<body bgcolor=\"#ffffff\">"); 689 html.append("<h2>VelocityServlet: Error processing the template</h2>"); 690 html.append("<pre>"); 691 String why = cause.getMessage(); 692 if (why != null && why.trim().length() > 0) 693 { 694 html.append(why); 695 html.append("<br>"); 696 } 697 698 StringWriter sw = new StringWriter(); 699 cause.printStackTrace( new PrintWriter( sw ) ); 700 701 html.append( sw.toString() ); 702 html.append("</pre>"); 703 html.append("</body>"); 704 html.append("</html>"); 705 response.getOutputStream().print( html.toString() ); 706 } 707 }