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