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