View Javadoc
1   package org.apache.turbine;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.PrintWriter;
26  import java.nio.file.Path;
27  import java.nio.file.Paths;
28  import java.util.HashMap;
29  import java.util.Iterator;
30  import java.util.Map;
31  
32  import javax.servlet.ServletConfig;
33  import javax.servlet.ServletContext;
34  import javax.servlet.ServletException;
35  import javax.servlet.annotation.MultipartConfig;
36  import javax.servlet.annotation.WebInitParam;
37  import javax.servlet.annotation.WebServlet;
38  import javax.servlet.http.HttpServlet;
39  import javax.servlet.http.HttpServletRequest;
40  import javax.servlet.http.HttpServletResponse;
41  import javax.xml.bind.JAXBContext;
42  import javax.xml.bind.Unmarshaller;
43  
44  import org.apache.commons.configuration2.Configuration;
45  import org.apache.commons.configuration2.PropertiesConfiguration;
46  import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
47  import org.apache.commons.configuration2.builder.combined.CombinedConfigurationBuilder;
48  import org.apache.commons.configuration2.builder.fluent.Parameters;
49  import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
50  import org.apache.commons.configuration2.ex.ConfigurationException;
51  import org.apache.commons.configuration2.io.HomeDirectoryLocationStrategy;
52  import org.apache.commons.lang3.NotImplementedException;
53  import org.apache.commons.lang3.StringUtils;
54  import org.apache.commons.lang3.exception.ExceptionUtils;
55  import org.apache.logging.log4j.LogManager;
56  import org.apache.logging.log4j.Logger;
57  import org.apache.logging.log4j.core.LoggerContext;
58  import org.apache.turbine.modules.PageLoader;
59  import org.apache.turbine.pipeline.Pipeline;
60  import org.apache.turbine.pipeline.PipelineData;
61  import org.apache.turbine.pipeline.TurbinePipeline;
62  import org.apache.turbine.services.Initable;
63  import org.apache.turbine.services.InitializationException;
64  import org.apache.turbine.services.ServiceManager;
65  import org.apache.turbine.services.TurbineServices;
66  import org.apache.turbine.services.rundata.RunDataService;
67  import org.apache.turbine.services.template.TemplateService;
68  import org.apache.turbine.util.LocaleUtils;
69  import org.apache.turbine.util.RunData;
70  import org.apache.turbine.util.ServerData;
71  import org.apache.turbine.util.TurbineConfig;
72  import org.apache.turbine.util.TurbineException;
73  import org.apache.turbine.util.uri.URIConstants;
74  
75  /**
76   * <p>
77   * Turbine is the main servlet for the entire system. If you need to perform
78   * initialization of a service, then you should implement the Services API and
79   * let your code be initialized by it.
80   * </p>
81   *
82   * <p>
83   * Turbine servlet recognizes the following initialization parameters.
84   * </p>
85   *
86   * <ul>
87   * <li><code>properties</code> the path to TurbineResources.properties file used
88   * to configure Turbine, relative to the application root.</li>
89   * <li><code>configuration</code> the path to TurbineConfiguration.xml file used
90   * to configure Turbine from various sources, relative to the application
91   * root.</li>
92   * <li><code>applicationRoot</code> this parameter defaults to the web context
93   * of the servlet container. You can use this parameter to specify the directory
94   * within the server's filesystem, that is the base of your web
95   * application.</li>
96   * </ul>
97   *
98   * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
99   * @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
118 public 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                 TemplateServicee/turbine/services/template/TemplateService.html#TemplateService">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                 TurbineServicesg/apache/turbine/services/TurbineServices.html#TurbineServices">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"../../../org/apache/turbine/util/RunData.html#RunData">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 }