001 package org.apache.turbine; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022 import java.io.BufferedReader; 023 import java.io.File; 024 import java.io.FileInputStream; 025 import java.io.FileNotFoundException; 026 import java.io.FileReader; 027 import java.io.IOException; 028 import java.io.Reader; 029 import java.io.UnsupportedEncodingException; 030 import java.util.Iterator; 031 import java.util.Properties; 032 033 import javax.servlet.ServletConfig; 034 import javax.servlet.ServletContext; 035 import javax.servlet.ServletException; 036 import javax.servlet.http.HttpServlet; 037 import javax.servlet.http.HttpServletRequest; 038 import javax.servlet.http.HttpServletResponse; 039 040 import org.apache.commons.configuration.Configuration; 041 import org.apache.commons.configuration.ConfigurationFactory; 042 import org.apache.commons.configuration.PropertiesConfiguration; 043 import org.apache.commons.lang.StringUtils; 044 import org.apache.commons.lang.exception.ExceptionUtils; 045 import org.apache.commons.logging.Log; 046 import org.apache.commons.logging.LogFactory; 047 import org.apache.log4j.PropertyConfigurator; 048 import org.apache.turbine.modules.PageLoader; 049 import org.apache.turbine.pipeline.Pipeline; 050 import org.apache.turbine.pipeline.PipelineData; 051 import org.apache.turbine.pipeline.TurbinePipeline; 052 import org.apache.turbine.services.Initable; 053 import org.apache.turbine.services.InitializationException; 054 import org.apache.turbine.services.ServiceManager; 055 import org.apache.turbine.services.TurbineServices; 056 import org.apache.turbine.services.rundata.RunDataService; 057 import org.apache.turbine.services.template.TemplateService; 058 import org.apache.turbine.services.template.TurbineTemplate; 059 import org.apache.turbine.util.RunData; 060 import org.apache.turbine.util.ServerData; 061 import org.apache.turbine.util.TurbineConfig; 062 import org.apache.turbine.util.TurbineException; 063 import org.apache.turbine.util.uri.URIConstants; 064 065 import com.thoughtworks.xstream.XStream; 066 import com.thoughtworks.xstream.io.xml.DomDriver; 067 068 /** 069 * Turbine is the main servlet for the entire system. It is <code>final</code> 070 * because you should <i>not</i> ever need to subclass this servlet. If you 071 * need to perform initialization of a service, then you should implement the 072 * Services API and let your code be initialized by it. 073 * If you need to override something in the <code>doGet()</code> or 074 * <code>doPost()</code> methods, edit the TurbineResources.properties file and 075 * specify your own classes there. 076 * <p> 077 * Turbine servlet recognizes the following initialization parameters. 078 * <ul> 079 * <li><code>properties</code> the path to TurbineResources.properties file 080 * used by the default implementation of <code>ResourceService</code>, relative 081 * to the application root.</li> 082 * <li><code>basedir</code> this parameter is used <strong>only</strong> if your 083 * application server does not support web applications, or the or does not 084 * support <code>ServletContext.getRealPath(String)</code> method correctly. 085 * You can use this parameter to specify the directory within the server's 086 * filesystem, that is the base of your web application.</li> 087 * </ul> 088 * 089 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a> 090 * @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a> 091 * @author <a href="mailto:greg@shwoop.com">Greg Ritter</a> 092 * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a> 093 * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a> 094 * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a> 095 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> 096 * @author <a href="mailto:sean@informage.net">Sean Legassick</a> 097 * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a> 098 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a> 099 * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a> 100 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a> 101 * @author <a href="mailto:peter@courcoux.biz">Peter Courcoux</a> 102 * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a> 103 * @version $Id: Turbine.java 1096586 2011-04-25 20:31:43Z ludwig $ 104 */ 105 public class Turbine 106 extends HttpServlet 107 { 108 /** Serialversion */ 109 private static final long serialVersionUID = -6317118078613623990L; 110 111 /** 112 * Name of path info parameter used to indicate the redirected stage of 113 * a given user's initial Turbine request 114 */ 115 public static final String REDIRECTED_PATHINFO_NAME = "redirected"; 116 117 /** The base directory key */ 118 public static final String BASEDIR_KEY = "basedir"; 119 120 /** 121 * In certain situations the init() method is called more than once, 122 * somtimes even concurrently. This causes bad things to happen, 123 * so we use this flag to prevent it. 124 */ 125 private static boolean firstInit = true; 126 127 /** 128 * The pipeline to use when processing requests. 129 */ 130 private static Pipeline pipeline = null; 131 132 /** Whether init succeeded or not. */ 133 private static Throwable initFailure = null; 134 135 /** 136 * Should initialization activities be performed during doGet() execution? 137 */ 138 private static boolean firstDoGet = true; 139 140 /** 141 * Keep all the properties of the web server in a convenient data 142 * structure 143 */ 144 private static ServerData serverData = null; 145 146 /** The base from which the Turbine application will operate. */ 147 private static String applicationRoot; 148 149 /** Servlet config for this Turbine webapp. */ 150 private static ServletConfig servletConfig; 151 152 /** Servlet context for this Turbine webapp. */ 153 private static ServletContext servletContext; 154 155 /** 156 * The webapp root where the Turbine application 157 * is running in the servlet container. 158 * This might differ from the application root. 159 */ 160 private static String webappRoot; 161 162 /** Our internal configuration object */ 163 private static Configuration configuration = null; 164 165 /** Default Input encoding if the servlet container does not report an encoding */ 166 private String inputEncoding = null; 167 168 /** Logging class from commons.logging */ 169 private static Log log = LogFactory.getLog(Turbine.class); 170 171 /** 172 * This init method will load the default resources from a 173 * properties file. 174 * 175 * This method is called by init(ServletConfig config) 176 * 177 * @exception ServletException a servlet exception. 178 */ 179 public final void init() throws ServletException 180 { 181 synchronized (this.getClass()) 182 { 183 super.init(); 184 ServletConfig config = getServletConfig(); 185 186 if (!firstInit) 187 { 188 log.info("Double initialization of Turbine was attempted!"); 189 return; 190 } 191 // executing init will trigger some static initializers, so we have 192 // only one chance. 193 firstInit = false; 194 195 try 196 { 197 ServletContext context = config.getServletContext(); 198 199 configure(config, context); 200 201 TemplateService templateService = TurbineTemplate.getService(); 202 if (templateService == null) 203 { 204 throw new TurbineException( 205 "No Template Service configured!"); 206 } 207 208 if (getRunDataService() == null) 209 { 210 throw new TurbineException( 211 "No RunData Service configured!"); 212 } 213 214 } 215 catch (Exception e) 216 { 217 // save the exception to complain loudly later :-) 218 initFailure = e; 219 log.fatal("Turbine: init() failed: ", e); 220 throw new ServletException("Turbine: init() failed", e); 221 } 222 223 log.info("Turbine: init() Ready to Rumble!"); 224 } 225 } 226 227 /** 228 * Read the master configuration file in, configure logging 229 * and start up any early services. 230 * 231 * @param config The Servlet Configuration supplied by the container 232 * @param context The Servlet Context supplied by the container 233 * 234 * @throws Exception A problem occured while reading the configuration or performing early startup 235 */ 236 237 private void configure(ServletConfig config, ServletContext context) 238 throws Exception 239 { 240 241 // Set the application root. This defaults to the webapp 242 // context if not otherwise set. This is to allow 2.1 apps 243 // to be developed from CVS. This feature will carry over 244 // into 3.0. 245 applicationRoot = findInitParameter(context, config, 246 TurbineConstants.APPLICATION_ROOT_KEY, 247 TurbineConstants.APPLICATION_ROOT_DEFAULT); 248 249 webappRoot = config.getServletContext().getRealPath("/"); 250 // log.info("Web Application root is " + webappRoot); 251 // log.info("Application root is " + applicationRoot); 252 253 if (applicationRoot == null || applicationRoot.equals(TurbineConstants.WEB_CONTEXT)) 254 { 255 applicationRoot = webappRoot; 256 // log.info("got empty or 'webContext' Application root. Application root now: " + applicationRoot); 257 } 258 259 // Set the applicationRoot for this webapp. 260 setApplicationRoot(applicationRoot); 261 262 // Create any directories that need to be setup for 263 // a running Turbine application. 264 createRuntimeDirectories(context, config); 265 266 // 267 // Now we run the Turbine configuration code. There are two ways 268 // to configure Turbine: 269 // 270 // a) By supplying an web.xml init parameter called "configuration" 271 // 272 // <init-param> 273 // <param-name>configuration</param-name> 274 // <param-value>/WEB-INF/conf/turbine.xml</param-value> 275 // </init-param> 276 // 277 // This loads an XML based configuration file. 278 // 279 // b) By supplying an web.xml init parameter called "properties" 280 // 281 // <init-param> 282 // <param-name>properties</param-name> 283 // <param-value>/WEB-INF/conf/TurbineResources.properties</param-value> 284 // </init-param> 285 // 286 // This loads a Properties based configuration file. Actually, these are 287 // extended properties as provided by commons-configuration 288 // 289 // If neither a) nor b) is supplied, Turbine will fall back to the 290 // known behaviour of loading a properties file called 291 // /WEB-INF/conf/TurbineResources.properties relative to the 292 // web application root. 293 294 String confFile= findInitParameter(context, config, 295 TurbineConfig.CONFIGURATION_PATH_KEY, 296 null); 297 298 String confPath; 299 String confStyle = "unset"; 300 301 if (StringUtils.isNotEmpty(confFile)) 302 { 303 confPath = getRealPath(confFile); 304 ConfigurationFactory configurationFactory = new ConfigurationFactory(confPath); 305 configurationFactory.setBasePath(getApplicationRoot()); 306 configuration = configurationFactory.getConfiguration(); 307 confStyle = "XML"; 308 } 309 else 310 { 311 confFile = findInitParameter(context, config, 312 TurbineConfig.PROPERTIES_PATH_KEY, 313 TurbineConfig.PROPERTIES_PATH_DEFAULT); 314 315 confPath = getRealPath(confFile); 316 317 // This should eventually be a Configuration 318 // interface so that service and app configuration 319 // can be stored anywhere. 320 configuration = new PropertiesConfiguration(confPath); 321 confStyle = "Properties"; 322 } 323 324 325 // 326 // Set up logging as soon as possible 327 // 328 String log4jFile = configuration.getString(TurbineConstants.LOG4J_CONFIG_FILE, 329 TurbineConstants.LOG4J_CONFIG_FILE_DEFAULT); 330 331 if (StringUtils.isNotEmpty(log4jFile) && 332 !log4jFile.equalsIgnoreCase("none")) 333 { 334 log4jFile = getRealPath(log4jFile); 335 336 // 337 // Load the config file above into a Properties object and 338 // fix up the Application root 339 // 340 Properties p = new Properties(); 341 try 342 { 343 p.load(new FileInputStream(log4jFile)); 344 p.setProperty(TurbineConstants.APPLICATION_ROOT_KEY, getApplicationRoot()); 345 PropertyConfigurator.configure(p); 346 347 // 348 // Rebuild our log object with a configured commons-logging 349 log = LogFactory.getLog(this.getClass()); 350 351 log.info("Configured log4j from " + log4jFile); 352 } 353 catch (FileNotFoundException fnf) 354 { 355 System.err.println("Could not open Log4J configuration file " 356 + log4jFile + ": "); 357 fnf.printStackTrace(); 358 } 359 } 360 361 // Now report our successful configuration to the world 362 log.info("Loaded configuration (" + confStyle + ") from " + confFile + " (" + confPath + ")"); 363 364 setTurbineServletConfig(config); 365 setTurbineServletContext(context); 366 367 getServiceManager().setApplicationRoot(applicationRoot); 368 369 // We want to set a few values in the configuration so 370 // that ${variable} interpolation will work for 371 // 372 // ${applicationRoot} 373 // ${webappRoot} 374 configuration.setProperty(TurbineConstants.APPLICATION_ROOT_KEY, applicationRoot); 375 configuration.setProperty(TurbineConstants.WEBAPP_ROOT_KEY, webappRoot); 376 377 // Get the default input encoding 378 inputEncoding = configuration.getString( 379 TurbineConstants.PARAMETER_ENCODING_KEY, 380 TurbineConstants.PARAMETER_ENCODING_DEFAULT); 381 382 if (log.isDebugEnabled()) 383 { 384 log.debug("Input Encoding has been set to " + inputEncoding); 385 } 386 387 getServiceManager().setConfiguration(configuration); 388 389 // Initialize the service manager. Services 390 // that have its 'earlyInit' property set to 391 // a value of 'true' will be started when 392 // the service manager is initialized. 393 getServiceManager().init(); 394 395 // Retrieve the pipeline class and then initialize it. The pipeline 396 // handles the processing of a webrequest/response cycle. 397 398 String descriptorPath = 399 configuration.getString( 400 "pipeline.default.descriptor", 401 TurbinePipeline.CLASSIC_PIPELINE); 402 403 descriptorPath = getRealPath(descriptorPath); 404 405 log.debug("Using descriptor path: " + descriptorPath); 406 Reader reader = new BufferedReader(new FileReader(descriptorPath)); 407 XStream pipelineMapper = new XStream(new DomDriver()); // does not require XPP3 library 408 pipeline = (Pipeline) pipelineMapper.fromXML(reader); 409 410 log.debug("Initializing pipeline"); 411 412 pipeline.initialize(); 413 } 414 415 /** 416 * Create any directories that might be needed during 417 * runtime. Right now this includes: 418 * 419 * <ul> 420 * 421 * <li>The directory to write the log files to (relative to the 422 * web application root), or <code>null</code> for the default of 423 * <code>/logs</code>. The directory is specified via the {@link 424 * TurbineConstants#LOGGING_ROOT} parameter.</li> 425 * 426 * </ul> 427 * 428 * @param context Global initialization parameters. 429 * @param config Initialization parameters specific to the Turbine 430 * servlet. 431 */ 432 private static void createRuntimeDirectories(ServletContext context, 433 ServletConfig config) 434 { 435 String path = findInitParameter(context, config, 436 TurbineConstants.LOGGING_ROOT_KEY, 437 TurbineConstants.LOGGING_ROOT_DEFAULT); 438 439 File logDir = new File(getRealPath(path)); 440 if (!logDir.exists()) 441 { 442 // Create the logging directory 443 if (!logDir.mkdirs()) 444 { 445 System.err.println("Cannot create directory for logs!"); 446 } 447 } 448 } 449 450 /** 451 * Finds the specified servlet configuration/initialization 452 * parameter, looking first for a servlet-specific parameter, then 453 * for a global parameter, and using the provided default if not 454 * found. 455 */ 456 protected static final String findInitParameter(ServletContext context, 457 ServletConfig config, String name, String defaultValue) 458 { 459 String path = null; 460 461 // Try the name as provided first. 462 boolean usingNamespace = name.startsWith(TurbineConstants.CONFIG_NAMESPACE); 463 while (true) 464 { 465 path = config.getInitParameter(name); 466 if (StringUtils.isEmpty(path)) 467 { 468 path = context.getInitParameter(name); 469 if (StringUtils.isEmpty(path)) 470 { 471 // The named parameter didn't yield a value. 472 if (usingNamespace) 473 { 474 path = defaultValue; 475 } 476 else 477 { 478 // Try again using Turbine's namespace. 479 name = TurbineConstants.CONFIG_NAMESPACE + '.' + name; 480 usingNamespace = true; 481 continue; 482 } 483 } 484 } 485 break; 486 } 487 488 return path; 489 } 490 491 /** 492 * Initializes the services which need <code>PipelineData</code> to 493 * initialize themselves (post startup). 494 * 495 * @param data The first <code>GET</code> request. 496 */ 497 public final void init(PipelineData data) 498 { 499 synchronized (Turbine.class) 500 { 501 if (firstDoGet) 502 { 503 // All we want to do here is save some servlet 504 // information so that services and processes 505 // that don't have direct access to a RunData 506 // object can still know something about 507 // the servlet environment. 508 saveServletInfo(data); 509 510 // Initialize services with the PipelineData instance 511 TurbineServices services = (TurbineServices)TurbineServices.getInstance(); 512 513 for (Iterator i = services.getServiceNames(); i.hasNext();) 514 { 515 String serviceName = (String)i.next(); 516 Object service = services.getService(serviceName); 517 518 if (service instanceof Initable) 519 { 520 try 521 { 522 ((Initable)service).init(data); 523 } 524 catch (InitializationException e) 525 { 526 log.warn("Could not initialize Initable " + serviceName + " with PipelineData", e); 527 } 528 } 529 } 530 531 // Mark that we're done. 532 firstDoGet = false; 533 log.info("Turbine: first Request successful"); 534 } 535 } 536 } 537 538 /** 539 * Return the current configuration with all keys included 540 * 541 * @return a Configuration Object 542 */ 543 public static Configuration getConfiguration() 544 { 545 return configuration; 546 } 547 548 /** 549 * Return the server name. 550 * 551 * @return String server name 552 */ 553 public static String getServerName() 554 { 555 return getDefaultServerData().getServerName(); 556 } 557 558 /** 559 * Return the server scheme. 560 * 561 * @return String server scheme 562 */ 563 public static String getServerScheme() 564 { 565 return getDefaultServerData().getServerScheme(); 566 } 567 568 /** 569 * Return the server port. 570 * 571 * @return String server port 572 */ 573 public static String getServerPort() 574 { 575 return Integer.toString(getDefaultServerData().getServerPort()); 576 } 577 578 /** 579 * Get the script name. This is the initial script name. 580 * Actually this is probably not needed any more. I'll 581 * check. jvz. 582 * 583 * @return String initial script name. 584 */ 585 public static String getScriptName() 586 { 587 return getDefaultServerData().getScriptName(); 588 } 589 590 /** 591 * Return the context path. 592 * 593 * @return String context path 594 */ 595 public static String getContextPath() 596 { 597 return getDefaultServerData().getContextPath(); 598 } 599 600 /** 601 * Return all the Turbine Servlet information (Server Name, Port, 602 * Scheme in a ServerData structure. This is generated from the 603 * values set when initializing the Turbine and may not be correct 604 * if you're running in a clustered structure. You can provide default 605 * values in your configuration for cases where access is requied before 606 * your application is first accessed by a user. This might be used 607 * if you need a DataURI and have no RunData object handy. 608 * 609 * @return An initialized ServerData object 610 */ 611 public static ServerData getDefaultServerData() 612 { 613 if (serverData == null) 614 { 615 String serverName 616 = configuration.getString(TurbineConstants.DEFAULT_SERVER_NAME_KEY); 617 if (serverName == null) 618 { 619 log.error("ServerData Information requested from Turbine before first request!"); 620 } 621 else 622 { 623 log.info("ServerData Information retrieved from configuration."); 624 } 625 // Will be overwritten once the first request is run; 626 serverData = new ServerData(serverName, 627 configuration.getInt(TurbineConstants.DEFAULT_SERVER_PORT_KEY, 628 URIConstants.HTTP_PORT), 629 configuration.getString(TurbineConstants.DEFAULT_SERVER_SCHEME_KEY, 630 URIConstants.HTTP), 631 configuration.getString(TurbineConstants.DEFAULT_SCRIPT_NAME_KEY), 632 configuration.getString(TurbineConstants.DEFAULT_CONTEXT_PATH_KEY)); 633 } 634 return serverData; 635 } 636 637 /** 638 * Set the servlet config for this turbine webapp. 639 * 640 * @param config New servlet config 641 */ 642 public static void setTurbineServletConfig(ServletConfig config) 643 { 644 servletConfig = config; 645 } 646 647 /** 648 * Get the servlet config for this turbine webapp. 649 * 650 * @return ServletConfig 651 */ 652 public static ServletConfig getTurbineServletConfig() 653 { 654 return servletConfig; 655 } 656 657 /** 658 * Set the servlet context for this turbine webapp. 659 * 660 * @param context New servlet context. 661 */ 662 public static void setTurbineServletContext(ServletContext context) 663 { 664 servletContext = context; 665 } 666 667 /** 668 * Get the servlet context for this turbine webapp. 669 * 670 * @return ServletContext 671 */ 672 public static ServletContext getTurbineServletContext() 673 { 674 return servletContext; 675 } 676 677 /** 678 * The <code>Servlet</code> destroy method. Invokes 679 * <code>ServiceBroker</code> tear down method. 680 */ 681 public final void destroy() 682 { 683 // Shut down all Turbine Services. 684 getServiceManager().shutdownServices(); 685 System.gc(); 686 687 firstInit = true; 688 firstDoGet = true; 689 log.info("Turbine: Done shutting down!"); 690 } 691 692 /** 693 * The primary method invoked when the Turbine servlet is executed. 694 * 695 * @param req Servlet request. 696 * @param res Servlet response. 697 * @exception IOException a servlet exception. 698 * @exception ServletException a servlet exception. 699 */ 700 public final void doGet(HttpServletRequest req, HttpServletResponse res) 701 throws IOException, ServletException 702 { 703 PipelineData pipelineData = null; 704 705 try 706 { 707 // Check to make sure that we started up properly. 708 if (initFailure != null) 709 { 710 throw initFailure; 711 } 712 713 // 714 // If the servlet container gives us no clear indication about the 715 // Encoding of the contents, set it to our default value. 716 if (req.getCharacterEncoding() == null) 717 { 718 if (log.isDebugEnabled()) 719 { 720 log.debug("Changing Input Encoding to " + inputEncoding); 721 } 722 723 try 724 { 725 req.setCharacterEncoding(inputEncoding); 726 } 727 catch (UnsupportedEncodingException uee) 728 { 729 log.warn("Could not change request encoding to " + inputEncoding, uee); 730 } 731 } 732 733 // Get general RunData here... 734 // Perform turbine specific initialization below. 735 pipelineData = getRunDataService().getRunData(req, res, getServletConfig()); 736 // Map runDataMap = new HashMap(); 737 //runDataMap.put(RunData.class, data); 738 // put the data into the pipeline 739 // pipelineData.put(RunData.class, runDataMap); 740 741 // If this is the first invocation, perform some 742 // initialization. Certain services need RunData to initialize 743 // themselves. 744 if (firstDoGet) 745 { 746 init(pipelineData); 747 } 748 749 // Stages of Pipeline implementation execution 750 // configurable via attached Valve implementations in a 751 // XML properties file. 752 pipeline.invoke(pipelineData); 753 754 } 755 catch (Exception e) 756 { 757 handleException(pipelineData, res, e); 758 } 759 catch (Throwable t) 760 { 761 handleException(pipelineData, res, t); 762 } 763 finally 764 { 765 // Return the used RunData to the factory for recycling. 766 getRunDataService().putRunData((RunData)pipelineData); 767 } 768 } 769 770 /** 771 * In this application doGet and doPost are the same thing. 772 * 773 * @param req Servlet request. 774 * @param res Servlet response. 775 * @exception IOException a servlet exception. 776 * @exception ServletException a servlet exception. 777 */ 778 public final void doPost(HttpServletRequest req, HttpServletResponse res) 779 throws IOException, ServletException 780 { 781 doGet(req, res); 782 } 783 784 /** 785 * Return the servlet info. 786 * 787 * @return a string with the servlet information. 788 */ 789 public final String getServletInfo() 790 { 791 return "Turbine Servlet"; 792 } 793 794 /** 795 * This method is about making sure that we catch and display 796 * errors to the screen in one fashion or another. What happens is 797 * that it will attempt to show the error using your user defined 798 * Error Screen. If that fails, then it will resort to just 799 * displaying the error and logging it all over the place 800 * including the servlet engine log file, the Turbine log file and 801 * on the screen. 802 * 803 * @param data A Turbine PipelineData object. 804 * @param res Servlet response. 805 * @param t The exception to report. 806 */ 807 private final void handleException(PipelineData pipelineData, HttpServletResponse res, 808 Throwable t) 809 { 810 RunData data = getRunData(pipelineData); 811 // make sure that the stack trace makes it the log 812 log.error("Turbine.handleException: ", t); 813 814 String mimeType = "text/plain"; 815 try 816 { 817 // This is where we capture all exceptions and show the 818 // Error Screen. 819 data.setStackTrace(ExceptionUtils.getStackTrace(t), t); 820 821 // setup the screen 822 data.setScreen(configuration.getString( 823 TurbineConstants.SCREEN_ERROR_KEY, 824 TurbineConstants.SCREEN_ERROR_DEFAULT)); 825 826 // do more screen setup for template execution if needed 827 if (data.getTemplateInfo() != null) 828 { 829 data.getTemplateInfo() 830 .setScreenTemplate(configuration.getString( 831 TurbineConstants.TEMPLATE_ERROR_KEY, 832 TurbineConstants.TEMPLATE_ERROR_VM)); 833 } 834 835 // Make sure to not execute an action. 836 data.setAction(""); 837 838 PageLoader.getInstance().exec(pipelineData, 839 configuration.getString(TurbineConstants.PAGE_DEFAULT_KEY, 840 TurbineConstants.PAGE_DEFAULT_DEFAULT)); 841 842 data.getResponse().setContentType(data.getContentType()); 843 data.getResponse().setStatus(data.getStatusCode()); 844 } 845 // Catch this one because it occurs if some code hasn't been 846 // completely re-compiled after a change.. 847 catch (java.lang.NoSuchFieldError e) 848 { 849 try 850 { 851 data.getResponse().setContentType(mimeType); 852 data.getResponse().setStatus(200); 853 } 854 catch (Exception ignored) 855 { 856 // ignore 857 } 858 859 try 860 { 861 data.getResponse().getWriter().print("java.lang.NoSuchFieldError: " 862 + "Please recompile all of your source code."); 863 } 864 catch (IOException ignored) 865 { 866 // ignore 867 } 868 869 log.error(data.getStackTrace(), e); 870 } 871 // Attempt to do *something* at this point... 872 catch (Throwable reallyScrewedNow) 873 { 874 StringBuffer msg = new StringBuffer(); 875 msg.append("Horrible Exception: "); 876 if (data != null) 877 { 878 msg.append(data.getStackTrace()); 879 } 880 else 881 { 882 msg.append(t); 883 } 884 try 885 { 886 res.setContentType(mimeType); 887 res.setStatus(200); 888 res.getWriter().print(msg.toString()); 889 } 890 catch (Exception ignored) 891 { 892 // ignore 893 } 894 895 log.error(reallyScrewedNow.getMessage(), reallyScrewedNow); 896 } 897 } 898 899 /** 900 * Save some information about this servlet so that 901 * it can be utilized by object instances that do not 902 * have direct access to RunData. 903 * 904 * @param data Turbine request data 905 */ 906 public static synchronized void saveServletInfo(PipelineData data) 907 { 908 // Store the context path for tools like ContentURI and 909 // the UIManager that use webapp context path information 910 // for constructing URLs. 911 912 // 913 // Bundle all the information above up into a convenient structure 914 // 915 ServerData requestServerData = (ServerData) data.get(Turbine.class, ServerData.class); 916 serverData = (ServerData) requestServerData.clone(); 917 } 918 919 /** 920 * Set the application root for the webapp. 921 * 922 * @param val New app root. 923 */ 924 public static void setApplicationRoot(String val) 925 { 926 applicationRoot = val; 927 } 928 929 /** 930 * Get the application root for this Turbine webapp. This 931 * concept was started in 3.0 and will allow an app to be 932 * developed from a standard CVS layout. With a simple 933 * switch the app will work fully within the servlet 934 * container for deployment. 935 * 936 * @return String applicationRoot 937 */ 938 public static String getApplicationRoot() 939 { 940 return applicationRoot; 941 } 942 943 /** 944 * Used to get the real path of configuration and resource 945 * information. This can be used by an app being 946 * developed in a standard CVS layout. 947 * 948 * @param path path translated to the application root 949 * @return the real path 950 */ 951 public static String getRealPath(String path) 952 { 953 if (path.startsWith("/")) 954 { 955 path = path.substring(1); 956 } 957 958 return new File(getApplicationRoot(), path).getAbsolutePath(); 959 } 960 961 /** 962 * Return an instance of the currently configured Service Manager 963 * 964 * @return A service Manager instance 965 */ 966 private ServiceManager getServiceManager() 967 { 968 return TurbineServices.getInstance(); 969 } 970 971 /** 972 * Get a RunData from the pipelineData. Once RunData is fully replaced 973 * by PipelineData this should not be required. 974 * @param pipelineData 975 * @return 976 */ 977 private RunData getRunData(PipelineData pipelineData) 978 { 979 RunData data = null; 980 data = (RunData)pipelineData; 981 return data; 982 } 983 984 985 /** 986 * Returns the default input encoding for the servlet. 987 * 988 * @return the default input encoding. 989 */ 990 public String getDefaultInputEncoding() { 991 return inputEncoding; 992 } 993 994 /** 995 * Static Helper method for looking up the RunDataService 996 * @return A RunDataService 997 */ 998 private static RunDataService getRunDataService() 999 { 1000 return (RunDataService) TurbineServices 1001 .getInstance().getService(RunDataService.SERVICE_NAME); 1002 } 1003 }