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}