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