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: Turbine.java 1857581 2019-04-15 13:51:10Z gk $
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      * @param config
370      *            the Servlet Configuration
371      * @param context
372      *            Servlet Context
373      * @return The resolved Configuration Path
374      * @throws IOException
375      *             if configuration path not found
376      * @throws ConfigurationException
377      *             if failed to configure
378      */
379     protected Path configureApplication(ServletConfig config, ServletContext context)
380             throws IOException, ConfigurationException
381     {
382         ConfigurationStyle confStyle = ConfigurationStyle.UNSET;
383         // first test
384         String confFile = findInitParameter(context, config,
385                 TurbineConfig.CONFIGURATION_PATH_KEY,
386                 null);
387         if (StringUtils.isNotEmpty(confFile))
388         {
389             confStyle = ConfigurationStyle.XML;
390         }
391         else // second test
392         {
393             confFile = findInitParameter(context, config,
394                     TurbineConfig.PROPERTIES_PATH_KEY,
395                     null);
396             if (StringUtils.isNotEmpty(confFile))
397             {
398                 confStyle = ConfigurationStyle.PROPERTIES;
399             }
400         }
401         // more tests ..
402         // last test
403         if (confStyle == ConfigurationStyle.UNSET)
404         { // last resort
405             confFile = findInitParameter(context, config,
406                     TurbineConfig.PROPERTIES_PATH_KEY,
407                     TurbineConfig.PROPERTIES_PATH_DEFAULT);
408             confStyle = ConfigurationStyle.PROPERTIES;
409         }
410 
411         // First report
412         log.debug("Loading configuration ({}) from {}", confStyle, confFile);
413 
414         // now begin loading
415         Parameters params = new Parameters();
416         File confPath = getApplicationRootAsFile();
417 
418         if (confFile.startsWith("/"))
419         {
420             confFile = confFile.substring(1); // cft. RFC2396 should not start
421                                               // with a slash, if not absolute
422                                               // path
423         }
424 
425         Path confFileRelativePath = Paths.get(confFile);// relative to later
426                                                         // join
427         Path targetPath = Paths.get(confPath.toURI());
428         targetPath = targetPath.resolve(confFileRelativePath);
429 
430         // Get the target path directory
431         Path targetPathDirectory = targetPath.getParent();
432         if (targetPathDirectory != null)
433         {
434             // set the configuration path
435             confPath = targetPathDirectory.normalize().toFile();
436 
437             Path targetFilePath = targetPath.getFileName();
438             if (targetFilePath != null)
439             {
440                 // set the configuration file name
441                 confFile = targetFilePath.toString();
442             }
443 
444         }
445 
446         switch (confStyle)
447         {
448             case XML:
449                 // relative base path used for this and child configuration
450                 // files
451                 CombinedConfigurationBuilder combinedBuilder = new CombinedConfigurationBuilder()
452                         .configure(params.fileBased()
453                                 .setFileName(confFile)
454                                 .setListDelimiterHandler(new DefaultListDelimiterHandler(','))
455                                 .setLocationStrategy(new HomeDirectoryLocationStrategy(confPath.getCanonicalPath(), false)));
456                 configuration = combinedBuilder.getConfiguration();
457                 break;
458 
459             case PROPERTIES:
460                 FileBasedConfigurationBuilder<PropertiesConfiguration> propertiesBuilder = new FileBasedConfigurationBuilder<>(
461                         PropertiesConfiguration.class)
462                                 .configure(params.properties()
463                                         .setFileName(confFile)
464                                         .setListDelimiterHandler(new DefaultListDelimiterHandler(','))
465                                         .setLocationStrategy(new HomeDirectoryLocationStrategy(confPath.getCanonicalPath(), false)));
466                 configuration = propertiesBuilder.getConfiguration();
467                 break;
468             case JSON:
469             case YAML:
470                 throw new NotImplementedException("JSON or XAML configuration style not yet implemented!");
471 
472             default:
473                 break;
474         }
475         // Now report our successful configuration to the world
476         log.info("Loaded configuration ({}) from {} style: {}",
477                 confStyle, confFile, configuration.toString());
478 
479         return targetPath;
480     }
481 
482     /**
483      * Finds the specified servlet configuration/initialization parameter,
484      * looking first for a servlet-specific parameter, then for a global
485      * parameter, and using the provided default if not found.
486      *
487      * @param context
488      *            the servlet context
489      * @param config
490      *            configuration object
491      * @param name
492      *            name of parameter
493      * @param defaultValue
494      *            of the parameter
495      * @return String value of the parameter
496      */
497     protected String findInitParameter(ServletContext context,
498             ServletConfig config, String name, String defaultValue)
499     {
500         String path = null;
501         String parameterName = name;
502 
503         // Try the name as provided first.
504         boolean usingNamespace = parameterName.startsWith(TurbineConstants.CONFIG_NAMESPACE);
505         while (true)
506         {
507             path = config.getInitParameter(parameterName);
508             if (StringUtils.isEmpty(path))
509             {
510                 path = context.getInitParameter(parameterName);
511                 if (StringUtils.isEmpty(path))
512                 {
513                     // The named parameter didn't yield a value.
514                     if (usingNamespace)
515                     {
516                         path = defaultValue;
517                     }
518                     else
519                     {
520                         // Try again using Turbine's namespace.
521                         parameterName = TurbineConstants.CONFIG_NAMESPACE + '.' + parameterName;
522                         usingNamespace = true;
523                         continue;
524                     }
525                 }
526             }
527             break;
528         }
529 
530         return path;
531     }
532 
533     /**
534      * Initializes the services which need <code>PipelineData</code> to
535      * initialize themselves (post startup).
536      *
537      * @param data
538      *            The first <code>GET</code> request.
539      */
540     public void init(PipelineData data)
541     {
542         synchronized (Turbine.class)
543         {
544             if (firstDoGet)
545             {
546                 // All we want to do here is save some servlet
547                 // information so that services and processes
548                 // that don't have direct access to a RunData
549                 // object can still know something about
550                 // the servlet environment.
551                 saveServletInfo(data);
552 
553                 // Initialize services with the PipelineData instance
554                 TurbineServicesg/apache/turbine/services/TurbineServices.html#TurbineServices">TurbineServices services = (TurbineServices) getServiceManager();
555 
556                 for (Iterator<String> i = services.getServiceNames(); i.hasNext();)
557                 {
558                     String serviceName = i.next();
559                     Object service = services.getService(serviceName);
560 
561                     if (service instanceof Initable)
562                     {
563                         try
564                         {
565                             ((Initable) service).init(data);
566                         }
567                         catch (InitializationException e)
568                         {
569                             log.warn("Could not initialize Initable {} with PipelineData", serviceName, e);
570                         }
571                     }
572                 }
573 
574                 // Mark that we're done.
575                 firstDoGet = false;
576                 log.info("Turbine: first Request successful");
577             }
578         }
579     }
580 
581     /**
582      * Return the current configuration with all keys included
583      *
584      * @return a Configuration Object
585      */
586     public static Configuration getConfiguration()
587     {
588         return configuration;
589     }
590 
591     /**
592      * Return the server name.
593      *
594      * @return String server name
595      */
596     public static String getServerName()
597     {
598         return getDefaultServerData().getServerName();
599     }
600 
601     /**
602      * Return the server scheme.
603      *
604      * @return String server scheme
605      */
606     public static String getServerScheme()
607     {
608         return getDefaultServerData().getServerScheme();
609     }
610 
611     /**
612      * Return the server port.
613      *
614      * @return String server port
615      */
616     public static String getServerPort()
617     {
618         return Integer.toString(getDefaultServerData().getServerPort());
619     }
620 
621     /**
622      * Get the script name. This is the initial script name. Actually this is
623      * probably not needed any more. I'll check. jvz.
624      *
625      * @return String initial script name.
626      */
627     public static String getScriptName()
628     {
629         return getDefaultServerData().getScriptName();
630     }
631 
632     /**
633      * Return the context path.
634      *
635      * @return String context path
636      */
637     public static String getContextPath()
638     {
639         return getDefaultServerData().getContextPath();
640     }
641 
642     /**
643      * Return all the Turbine Servlet information (Server Name, Port, Scheme in
644      * a ServerData structure. This is generated from the values set when
645      * initializing the Turbine and may not be correct if you're running in a
646      * clustered structure. You can provide default values in your configuration
647      * for cases where access is requied before your application is first
648      * accessed by a user. This might be used if you need a DataURI and have no
649      * RunData object handy.
650      *
651      * @return An initialized ServerData object
652      */
653     public static ServerData getDefaultServerData()
654     {
655         if (serverData == null)
656         {
657             String serverName = configuration.getString(TurbineConstants.DEFAULT_SERVER_NAME_KEY);
658             if (serverName == null)
659             {
660                 log.error("ServerData Information requested from Turbine before first request!");
661             }
662             else
663             {
664                 log.info("ServerData Information retrieved from configuration.");
665             }
666             // Will be overwritten once the first request is run;
667             serverData = new ServerData(serverName,
668                     configuration.getInt(TurbineConstants.DEFAULT_SERVER_PORT_KEY,
669                             URIConstants.HTTP_PORT),
670                     configuration.getString(TurbineConstants.DEFAULT_SERVER_SCHEME_KEY,
671                             URIConstants.HTTP),
672                     configuration.getString(TurbineConstants.DEFAULT_SCRIPT_NAME_KEY),
673                     configuration.getString(TurbineConstants.DEFAULT_CONTEXT_PATH_KEY));
674         }
675         return serverData;
676     }
677 
678     /**
679      * Set the servlet config for this turbine webapp.
680      *
681      * @param config
682      *            New servlet config
683      */
684     public static void setTurbineServletConfig(ServletConfig config)
685     {
686         servletConfig = config;
687     }
688 
689     /**
690      * Get the servlet config for this turbine webapp.
691      *
692      * @return ServletConfig
693      */
694     public static ServletConfig getTurbineServletConfig()
695     {
696         return servletConfig;
697     }
698 
699     /**
700      * Set the servlet context for this turbine webapp.
701      *
702      * @param context
703      *            New servlet context.
704      */
705     public static void setTurbineServletContext(ServletContext context)
706     {
707         servletContext = context;
708     }
709 
710     /**
711      * Get the servlet context for this turbine webapp.
712      *
713      * @return ServletContext
714      */
715     public static ServletContext getTurbineServletContext()
716     {
717         return servletContext;
718     }
719 
720     /**
721      * The <code>Servlet</code> destroy method. Invokes
722      * <code>ServiceBroker</code> tear down method.
723      */
724     @Override
725     public void destroy()
726     {
727         // Shut down all Turbine Services.
728         getServiceManager().shutdownServices();
729 
730         firstInit = true;
731         firstDoGet = true;
732         log.info("Turbine: Done shutting down!");
733     }
734 
735     /**
736      * The primary method invoked when the Turbine servlet is executed.
737      *
738      * @param req
739      *            Servlet request.
740      * @param res
741      *            Servlet response.
742      * @throws IOException
743      *             a servlet exception.
744      * @throws ServletException
745      *             a servlet exception.
746      */
747     @Override
748     public void doGet(HttpServletRequest req, HttpServletResponse res)
749             throws IOException, ServletException
750     {
751         // Check to make sure that we started up properly.
752         if (initFailure != null)
753         {
754             handleHorribleException(res, initFailure);
755             return;
756         }
757 
758         // Get general PipelineData here...
759         try (PipelineData pipelineData = getRunDataService().getRunData(req, res, getServletConfig()))
760         {
761             try
762             {
763                 // Perform turbine specific initialization below.
764                 Map<Class<?>, Object> runDataMap = new HashMap<Class<?>, Object>();
765                 runDataMap.put(RunData.class, pipelineData);
766                 // put the data into the pipeline
767                 pipelineData.put(RunData.class, runDataMap);
768 
769                 // If this is the first invocation, perform some
770                 // initialization. Certain services need RunData to initialize
771                 // themselves.
772                 if (firstDoGet)
773                 {
774                     init(pipelineData);
775                 }
776 
777                 // Stages of Pipeline implementation execution
778                 // configurable via attached Valve implementations in a
779                 // XML properties file.
780                 pipeline.invoke(pipelineData);
781             }
782             catch (Throwable t)
783             {
784                 handleException(pipelineData, res, t);
785             }
786         }
787         catch (Throwable t)
788         {
789             handleHorribleException(res, t);
790         }
791     }
792 
793     /**
794      * In this application doGet and doPost are the same thing.
795      *
796      * @param req
797      *            Servlet request.
798      * @param res
799      *            Servlet response.
800      * @throws IOException
801      *             a servlet exception.
802      * @throws ServletException
803      *             a servlet exception.
804      */
805     @Override
806     public void doPost(HttpServletRequest req, HttpServletResponse res)
807             throws IOException, ServletException
808     {
809         doGet(req, res);
810     }
811 
812     /**
813      * Return the servlet info.
814      *
815      * @return a string with the servlet information.
816      */
817     @Override
818     public String getServletInfo()
819     {
820         return "Turbine Servlet";
821     }
822 
823     /**
824      * This method is about making sure that we catch and display errors to the
825      * screen in one fashion or another. What happens is that it will attempt to
826      * show the error using your user defined Error Screen. If that fails, then
827      * it will resort to just displaying the error and logging it all over the
828      * place including the servlet engine log file, the Turbine log file and on
829      * the screen.
830      *
831      * @param pipelineData
832      *            A Turbine PipelineData object.
833      * @param res
834      *            Servlet response.
835      * @param t
836      *            The exception to report.
837      */
838     protected void handleException(PipelineData pipelineData, HttpServletResponse res,
839             Throwable t)
840     {
841         RunData"../../../org/apache/turbine/util/RunData.html#RunData">RunData data = (RunData) pipelineData;
842         // make sure that the stack trace makes it the log
843         log.error("Turbine.handleException: ", t);
844 
845         try
846         {
847             // This is where we capture all exceptions and show the
848             // Error Screen.
849             data.setStackTrace(ExceptionUtils.getStackTrace(t), t);
850 
851             // setup the screen
852             data.setScreen(configuration.getString(
853                     TurbineConstants.SCREEN_ERROR_KEY,
854                     TurbineConstants.SCREEN_ERROR_DEFAULT));
855 
856             // do more screen setup for template execution if needed
857             if (data.getTemplateInfo() != null)
858             {
859                 data.getTemplateInfo()
860                         .setScreenTemplate(configuration.getString(
861                                 TurbineConstants.TEMPLATE_ERROR_KEY,
862                                 TurbineConstants.TEMPLATE_ERROR_VM));
863             }
864 
865             // Make sure to not execute an action.
866             data.setAction("");
867 
868             PageLoader.getInstance().exec(pipelineData,
869                     configuration.getString(TurbineConstants.PAGE_DEFAULT_KEY,
870                             TurbineConstants.PAGE_DEFAULT_DEFAULT));
871 
872             data.getResponse().setContentType(data.getContentType());
873             data.getResponse().setStatus(data.getStatusCode());
874         }
875         // Attempt to do *something* at this point...
876         catch (Throwable reallyScrewedNow)
877         {
878             handleHorribleException(res, reallyScrewedNow);
879         }
880     }
881 
882     /**
883      * This method handles exception cases where no PipelineData object exists
884      *
885      * @param res
886      *            Servlet response.
887      * @param t
888      *            The exception to report.
889      */
890     protected void handleHorribleException(HttpServletResponse res, Throwable t)
891     {
892         try
893         {
894             res.setContentType(TurbineConstants.DEFAULT_TEXT_CONTENT_TYPE);
895             res.setStatus(200);
896             PrintWriter writer = res.getWriter();
897             writer.println("Horrible Exception: ");
898             t.printStackTrace(writer);
899         }
900         catch (Exception ignored)
901         {
902             // ignore
903         }
904 
905         log.error(t.getMessage(), t);
906     }
907 
908     /**
909      * Save some information about this servlet so that it can be utilized by
910      * object instances that do not have direct access to PipelineData.
911      *
912      * @param data
913      *            Turbine request data
914      */
915     public static synchronized void saveServletInfo(PipelineData data)
916     {
917         // Store the context path for tools like ContentURI and
918         // the UIManager that use webapp context path information
919         // for constructing URLs.
920 
921         //
922         // Bundle all the information above up into a convenient structure
923         //
924         ServerData requestServerData = data.get(Turbine.class, ServerData.class);
925         serverData = (ServerData) requestServerData.clone();
926     }
927 
928     /**
929      * Checks Log4j 2 Context, loads log4File, if configured and configuration
930      * is not already located.
931      *
932      * @param logConf
933      *            Configuration file path
934      * @throws IOException
935      *             if path not found
936      */
937     protected void configureLogging(Path logConf) throws IOException
938     {
939         LoggerContext context = (LoggerContext) LogManager.getContext(false);
940 
941         if (context.getConfiguration().getConfigurationSource().getLocation() == null)
942         {
943             Path log4jFile = resolveLog4j2(logConf.getParent());
944             // configured + no other log4j configuration already found
945             if (log4jFile != null)
946             {
947                 LogManager.getContext(null, false, log4jFile.toUri());
948             }
949         }
950         log.info("resolved log4j2 location: {}", context.getConfiguration().getConfigurationSource().getLocation());
951     }
952 
953     /**
954      * Check {@linkplain TurbineConstants#LOG4J2_CONFIG_FILE} in Turbine
955      * configuration.
956      *
957      * @param logConfPath
958      *            configuration directory
959      * @return Resolved log4j2 {@link Path} or null, if not found or configured
960      *         "none".
961      */
962     protected Path resolveLog4j2(Path logConfPath)
963     {
964         String log4jFile = configuration.getString(TurbineConstants.LOG4J2_CONFIG_FILE,
965                 TurbineConstants.LOG4J2_CONFIG_FILE_DEFAULT);
966 
967         if (log4jFile.startsWith("/"))
968         {
969             log4jFile = log4jFile.substring(1);
970         }
971         Path log4jTarget = null;
972         if (StringUtils.isNotEmpty(log4jFile) && !log4jFile.equalsIgnoreCase("none"))
973         {
974             // log4j must either share path with configuration path or resolved
975             // relatively
976 
977             if (logConfPath != null)
978             {
979                 Path log4jFilePath = Paths.get(log4jFile);
980                 Path logFilePath = logConfPath.resolve(log4jFilePath);
981                 if (logFilePath != null && logFilePath.toFile().exists())
982                 {
983                     log4jTarget = logFilePath.normalize();
984                 }
985                 else
986                 {
987                     // fall back just using the filename, if path match
988                     if (log4jFilePath != null && log4jFilePath.getParent() != null && logConfPath.endsWith(log4jFilePath.getParent()))
989                     {
990                         logFilePath = logConfPath.resolve(log4jFilePath.getFileName());
991                         if (logFilePath != null && logFilePath.toFile().exists())
992                         {
993                             log4jTarget = logFilePath.normalize();
994                         }
995                     }
996                 }
997             }
998         }
999         return log4jTarget;
1000     }
1001 
1002     /**
1003      * Set the application root for the webapp.
1004      *
1005      * @param val
1006      *            New app root.
1007      */
1008     public static void setApplicationRoot(String val)
1009     {
1010         applicationRoot = val;
1011     }
1012 
1013     /**
1014      * Get the application root for this Turbine webapp.
1015      *
1016      * @return String applicationRoot
1017      */
1018     public static String getApplicationRoot()
1019     {
1020         return applicationRoot;
1021     }
1022 
1023     /**
1024      * Get the application root for this Turbine webapp as a file object.
1025      *
1026      * @return File applicationRootFile
1027      */
1028     public static File getApplicationRootAsFile()
1029     {
1030         return new File(applicationRoot);
1031     }
1032 
1033     /**
1034      * Used to get the real path of configuration and resource information. This
1035      * can be used by an app being developed in a standard CVS layout.
1036      *
1037      * @param path
1038      *            path translated to the application root
1039      * @return the real path
1040      */
1041     public static String getRealPath(String path)
1042     {
1043         if (path.startsWith("/"))
1044         {
1045             return new File(getApplicationRootAsFile(), path.substring(1)).getAbsolutePath();
1046         }
1047 
1048         return new File(getApplicationRootAsFile(), path).getAbsolutePath();
1049     }
1050 
1051     /**
1052      * Return an instance of the currently configured Service Manager
1053      *
1054      * @return A service Manager instance
1055      */
1056     private ServiceManager getServiceManager()
1057     {
1058         return TurbineServices.getInstance();
1059     }
1060 
1061     /**
1062      * Returns the default input encoding for the servlet.
1063      *
1064      * @return the default input encoding.
1065      *
1066      * @deprecated Use
1067      *             {@link org.apache.turbine.pipeline.DefaultSetEncodingValve}
1068      *             to set default encoding
1069      */
1070     @Deprecated
1071     public static String getDefaultInputEncoding()
1072     {
1073         return LocaleUtils.getDefaultInputEncoding();
1074     }
1075 
1076     /**
1077      * Static Helper method for looking up the RunDataService
1078      *
1079      * @return A RunDataService
1080      */
1081     private RunDataService getRunDataService()
1082     {
1083         return (RunDataService) getServiceManager().getService(RunDataService.SERVICE_NAME);
1084     }
1085 }