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