001    package 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    
022    import java.io.BufferedReader;
023    import java.io.File;
024    import java.io.FileInputStream;
025    import java.io.FileNotFoundException;
026    import java.io.FileReader;
027    import java.io.IOException;
028    import java.io.Reader;
029    import java.io.UnsupportedEncodingException;
030    import java.util.HashMap;
031    import java.util.Iterator;
032    import java.util.Map;
033    import java.util.Properties;
034    
035    import javax.servlet.ServletConfig;
036    import javax.servlet.ServletContext;
037    import javax.servlet.ServletException;
038    import javax.servlet.http.HttpServlet;
039    import javax.servlet.http.HttpServletRequest;
040    import javax.servlet.http.HttpServletResponse;
041    import javax.xml.parsers.FactoryConfigurationError;
042    
043    import org.apache.commons.configuration.Configuration;
044    import org.apache.commons.configuration.ConfigurationFactory;
045    import org.apache.commons.configuration.PropertiesConfiguration;
046    import org.apache.commons.lang.StringUtils;
047    import org.apache.commons.lang.exception.ExceptionUtils;
048    import org.apache.commons.logging.Log;
049    import org.apache.commons.logging.LogFactory;
050    import org.apache.log4j.PropertyConfigurator;
051    import org.apache.log4j.xml.DOMConfigurator;
052    import org.apache.turbine.modules.PageLoader;
053    import org.apache.turbine.pipeline.Pipeline;
054    import org.apache.turbine.pipeline.PipelineData;
055    import org.apache.turbine.pipeline.TurbinePipeline;
056    import org.apache.turbine.services.Initable;
057    import org.apache.turbine.services.InitializationException;
058    import org.apache.turbine.services.ServiceManager;
059    import org.apache.turbine.services.TurbineServices;
060    import org.apache.turbine.services.rundata.RunDataService;
061    import org.apache.turbine.services.template.TemplateService;
062    import org.apache.turbine.services.template.TurbineTemplate;
063    import org.apache.turbine.util.RunData;
064    import org.apache.turbine.util.ServerData;
065    import org.apache.turbine.util.TurbineConfig;
066    import org.apache.turbine.util.TurbineException;
067    import org.apache.turbine.util.uri.URIConstants;
068    
069    import com.thoughtworks.xstream.XStream;
070    import com.thoughtworks.xstream.io.xml.DomDriver;
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 1410943 2012-11-18 18:00:57Z tv $
108     */
109    public 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         * somtimes 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        public void init() throws ServletException
184        {
185            synchronized (Turbine.class)
186            {
187                super.init();
188                ServletConfig config = getServletConfig();
189    
190                if (!firstInit)
191                {
192                    log.info("Double initialization of Turbine was attempted!");
193                    return;
194                }
195                // executing init will trigger some static initializers, so we have
196                // only one chance.
197                firstInit = false;
198    
199                try
200                {
201                    ServletContext context = config.getServletContext();
202    
203                    configure(config, context);
204    
205                    TemplateService templateService = TurbineTemplate.getService();
206                    if (templateService == null)
207                    {
208                        throw new TurbineException(
209                                "No Template Service configured!");
210                    }
211    
212                    if (getRunDataService() == null)
213                    {
214                        throw new TurbineException(
215                                "No RunData Service configured!");
216                    }
217    
218                }
219                catch (Exception e)
220                {
221                    // save the exception to complain loudly later :-)
222                    initFailure = e;
223                    log.fatal("Turbine: init() failed: ", e);
224                    throw new ServletException("Turbine: init() failed", e);
225                }
226    
227                log.info("Turbine: init() Ready to Rumble!");
228            }
229        }
230    
231        /**
232         * Read the master configuration file in, configure logging
233         * and start up any early services.
234         *
235         * @param config The Servlet Configuration supplied by the container
236         * @param context The Servlet Context supplied by the container
237         *
238         * @throws Exception A problem occurred while reading the configuration or performing early startup
239         */
240    
241        protected void configure(ServletConfig config, ServletContext context)
242                throws Exception
243        {
244    
245            // Set the application root. This defaults to the webapp
246            // context if not otherwise set. This is to allow 2.1 apps
247            // to be developed from CVS. This feature will carry over
248            // into 3.0.
249            applicationRoot = findInitParameter(context, config,
250                    TurbineConstants.APPLICATION_ROOT_KEY,
251                    TurbineConstants.APPLICATION_ROOT_DEFAULT);
252    
253            webappRoot = config.getServletContext().getRealPath("/");
254            // log.info("Web Application root is " + webappRoot);
255            // log.info("Application root is "     + applicationRoot);
256    
257            if (applicationRoot == null || applicationRoot.equals(TurbineConstants.WEB_CONTEXT))
258            {
259                applicationRoot = webappRoot;
260                // log.info("got empty or 'webContext' Application root. Application root now: " + applicationRoot);
261            }
262    
263            // Set the applicationRoot for this webapp.
264            setApplicationRoot(applicationRoot);
265    
266            // Create any directories that need to be setup for
267            // a running Turbine application.
268            createRuntimeDirectories(context, config);
269    
270            //
271            // Now we run the Turbine configuration code. There are two ways
272            // to configure Turbine:
273            //
274            // a) By supplying an web.xml init parameter called "configuration"
275            //
276            // <init-param>
277            //   <param-name>configuration</param-name>
278            //   <param-value>/WEB-INF/conf/turbine.xml</param-value>
279            // </init-param>
280            //
281            // This loads an XML based configuration file.
282            //
283            // b) By supplying an web.xml init parameter called "properties"
284            //
285            // <init-param>
286            //   <param-name>properties</param-name>
287            //   <param-value>/WEB-INF/conf/TurbineResources.properties</param-value>
288            // </init-param>
289            //
290            // This loads a Properties based configuration file. Actually, these are
291            // extended properties as provided by commons-configuration
292            //
293            // If neither a) nor b) is supplied, Turbine will fall back to the
294            // known behaviour of loading a properties file called
295            // /WEB-INF/conf/TurbineResources.properties relative to the
296            // web application root.
297    
298            String confFile= findInitParameter(context, config,
299                    TurbineConfig.CONFIGURATION_PATH_KEY,
300                    null);
301    
302            String confPath;
303            String confStyle = "unset";
304    
305            if (StringUtils.isNotEmpty(confFile))
306            {
307                confPath = getRealPath(confFile);
308                ConfigurationFactory configurationFactory = new ConfigurationFactory(confPath);
309                configurationFactory.setBasePath(getApplicationRoot());
310                configuration = configurationFactory.getConfiguration();
311                confStyle = "XML";
312            }
313            else
314            {
315                confFile = findInitParameter(context, config,
316                        TurbineConfig.PROPERTIES_PATH_KEY,
317                        TurbineConfig.PROPERTIES_PATH_DEFAULT);
318    
319                confPath = getRealPath(confFile);
320    
321                // This should eventually be a Configuration
322                // interface so that service and app configuration
323                // can be stored anywhere.
324                configuration = new PropertiesConfiguration(confPath);
325                confStyle = "Properties";
326            }
327    
328    
329            //
330            // Set up logging as soon as possible
331            //
332            configureLogging();
333    
334            // Now report our successful configuration to the world
335            log.info("Loaded configuration  (" + confStyle + ") from " + confFile + " (" + confPath + ")");
336    
337            setTurbineServletConfig(config);
338            setTurbineServletContext(context);
339    
340            getServiceManager().setApplicationRoot(applicationRoot);
341    
342            // We want to set a few values in the configuration so
343            // that ${variable} interpolation will work for
344            //
345            // ${applicationRoot}
346            // ${webappRoot}
347            configuration.setProperty(TurbineConstants.APPLICATION_ROOT_KEY, applicationRoot);
348            configuration.setProperty(TurbineConstants.WEBAPP_ROOT_KEY, webappRoot);
349    
350            // Get the default input encoding
351            inputEncoding = configuration.getString(
352                    TurbineConstants.PARAMETER_ENCODING_KEY,
353                    TurbineConstants.PARAMETER_ENCODING_DEFAULT);
354    
355            if (log.isDebugEnabled())
356            {
357                log.debug("Input Encoding has been set to " + inputEncoding);
358            }
359    
360            getServiceManager().setConfiguration(configuration);
361    
362            // Initialize the service manager. Services
363            // that have its 'earlyInit' property set to
364            // a value of 'true' will be started when
365            // the service manager is initialized.
366            getServiceManager().init();
367    
368            // Retrieve the pipeline class and then initialize it.  The pipeline
369            // handles the processing of a webrequest/response cycle.
370    
371                String descriptorPath =
372                            configuration.getString(
373                              "pipeline.default.descriptor",
374                                              TurbinePipeline.CLASSIC_PIPELINE);
375    
376            descriptorPath = getRealPath(descriptorPath);
377    
378                    log.debug("Using descriptor path: " + descriptorPath);
379            Reader reader = new BufferedReader(new FileReader(descriptorPath));
380            XStream pipelineMapper = new XStream(new DomDriver()); // does not require XPP3 library
381            pipeline = (Pipeline) pipelineMapper.fromXML(reader);
382    
383                    log.debug("Initializing pipeline");
384    
385                    pipeline.initialize();
386        }
387    
388        /**
389         * Configure the logging facilities of Turbine
390         *
391         * @throws IOException if the configuration file handling fails.
392         */
393        protected void configureLogging() throws IOException
394        {
395            String log4jFile = configuration.getString(TurbineConstants.LOG4J_CONFIG_FILE,
396                    TurbineConstants.LOG4J_CONFIG_FILE_DEFAULT);
397    
398            if (StringUtils.isNotEmpty(log4jFile) &&
399                    !log4jFile.equalsIgnoreCase("none"))
400            {
401                log4jFile = getRealPath(log4jFile);
402                boolean success = false;
403    
404                if (log4jFile.endsWith(".xml"))
405                {
406                    // load XML type configuration
407                    // NOTE: Only system property expansion available
408                    try
409                    {
410                        DOMConfigurator.configure(log4jFile);
411                        success = true;
412                    }
413                    catch (FactoryConfigurationError e)
414                    {
415                        System.err.println("Could not configure Log4J from configuration file "
416                                + log4jFile + ": ");
417                        e.printStackTrace();
418                    }
419                }
420                else
421                {
422                    //
423                    // Load the config file above into a Properties object and
424                    // fix up the Application root
425                    //
426                    Properties p = new Properties();
427                    FileInputStream fis = null;
428    
429                    try
430                    {
431                        fis = new FileInputStream(log4jFile);
432                        p.load(fis);
433                        p.setProperty(TurbineConstants.APPLICATION_ROOT_KEY, getApplicationRoot());
434                        PropertyConfigurator.configure(p);
435                        success = true;
436                    }
437                    catch (FileNotFoundException fnf)
438                    {
439                        System.err.println("Could not open Log4J configuration file "
440                                + log4jFile + ": ");
441                        fnf.printStackTrace();
442                    }
443                    finally
444                    {
445                        if (fis != null)
446                        {
447                            fis.close();
448                        }
449                    }
450                }
451    
452                if (success)
453                {
454                    // Rebuild our log object with a configured commons-logging
455                    log = LogFactory.getLog(this.getClass());
456                    log.info("Configured log4j from " + log4jFile);
457                }
458            }
459        }
460        /**
461         * Create any directories that might be needed during
462         * runtime. Right now this includes:
463         *
464         * <ul>
465         *
466         * <li>The directory to write the log files to (relative to the
467         * web application root), or <code>null</code> for the default of
468         * <code>/logs</code>.  The directory is specified via the {@link
469         * TurbineConstants#LOGGING_ROOT} parameter.</li>
470         *
471         * </ul>
472         *
473         * @param context Global initialization parameters.
474         * @param config Initialization parameters specific to the Turbine
475         * servlet.
476         */
477        protected void createRuntimeDirectories(ServletContext context,
478                                                     ServletConfig config)
479        {
480            String path = findInitParameter(context, config,
481                                            TurbineConstants.LOGGING_ROOT_KEY,
482                                            TurbineConstants.LOGGING_ROOT_DEFAULT);
483    
484            File logDir = new File(getRealPath(path));
485            if (!logDir.exists())
486            {
487                // Create the logging directory
488                if (!logDir.mkdirs())
489                {
490                    System.err.println("Cannot create directory for logs!");
491                }
492            }
493        }
494    
495        /**
496         * Finds the specified servlet configuration/initialization
497         * parameter, looking first for a servlet-specific parameter, then
498         * for a global parameter, and using the provided default if not
499         * found.
500         */
501        protected String findInitParameter(ServletContext context,
502                ServletConfig config, String name, String defaultValue)
503        {
504            String path = null;
505    
506            // Try the name as provided first.
507            boolean usingNamespace = name.startsWith(TurbineConstants.CONFIG_NAMESPACE);
508            while (true)
509            {
510                path = config.getInitParameter(name);
511                if (StringUtils.isEmpty(path))
512                {
513                    path = context.getInitParameter(name);
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                            name = TurbineConstants.CONFIG_NAMESPACE + '.' + name;
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 The first <code>GET</code> request.
541         */
542        public void init(PipelineData data)
543        {
544            synchronized (Turbine.class)
545            {
546                if (firstDoGet)
547                {
548                    // All we want to do here is save some servlet
549                    // information so that services and processes
550                    // that don't have direct access to a RunData
551                    // object can still know something about
552                    // the servlet environment.
553                    saveServletInfo(data);
554    
555                    // Initialize services with the PipelineData instance
556                    TurbineServices services = (TurbineServices)TurbineServices.getInstance();
557    
558                    for (Iterator i = services.getServiceNames(); i.hasNext();)
559                    {
560                            String serviceName = (String)i.next();
561                            Object service = services.getService(serviceName);
562    
563                            if (service instanceof Initable)
564                            {
565                                    try
566                                    {
567                                                            ((Initable)service).init(data);
568                                                    }
569                                    catch (InitializationException e)
570                                    {
571                                            log.warn("Could not initialize Initable " + serviceName + " with PipelineData", e);
572                                                    }
573                            }
574                    }
575    
576                    // Mark that we're done.
577                    firstDoGet = false;
578                    log.info("Turbine: first Request successful");
579                }
580            }
581        }
582    
583        /**
584         * Return the current configuration with all keys included
585         *
586         * @return a Configuration Object
587         */
588        public static Configuration getConfiguration()
589        {
590            return configuration;
591        }
592    
593        /**
594         * Return the server name.
595         *
596         * @return String server name
597         */
598        public static String getServerName()
599        {
600            return getDefaultServerData().getServerName();
601        }
602    
603        /**
604         * Return the server scheme.
605         *
606         * @return String server scheme
607         */
608        public static String getServerScheme()
609        {
610            return getDefaultServerData().getServerScheme();
611        }
612    
613        /**
614         * Return the server port.
615         *
616         * @return String server port
617         */
618        public static String getServerPort()
619        {
620            return Integer.toString(getDefaultServerData().getServerPort());
621        }
622    
623        /**
624         * Get the script name. This is the initial script name.
625         * Actually this is probably not needed any more. I'll
626         * 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,
647         * Scheme in a ServerData structure. This is generated from the
648         * values set when initializing the Turbine and may not be correct
649         * if you're running in a clustered structure. You can provide default
650         * values in your configuration for cases where access is requied before
651         * your application is first accessed by a user.  This might be used
652         * if you need a DataURI and have no 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
661                        = configuration.getString(TurbineConstants.DEFAULT_SERVER_NAME_KEY);
662                if (serverName == null)
663                {
664                    log.error("ServerData Information requested from Turbine before first request!");
665                }
666                else
667                {
668                    log.info("ServerData Information retrieved from configuration.");
669                }
670                // Will be overwritten once the first request is run;
671                serverData = new ServerData(serverName,
672                        configuration.getInt(TurbineConstants.DEFAULT_SERVER_PORT_KEY,
673                                URIConstants.HTTP_PORT),
674                        configuration.getString(TurbineConstants.DEFAULT_SERVER_SCHEME_KEY,
675                                URIConstants.HTTP),
676                        configuration.getString(TurbineConstants.DEFAULT_SCRIPT_NAME_KEY),
677                        configuration.getString(TurbineConstants.DEFAULT_CONTEXT_PATH_KEY));
678            }
679            return serverData;
680        }
681    
682        /**
683         * Set the servlet config for this turbine webapp.
684         *
685         * @param config 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 New servlet context.
706         */
707        public static void setTurbineServletContext(ServletContext context)
708        {
709            servletContext = context;
710        }
711    
712        /**
713         * Get the servlet context for this turbine webapp.
714         *
715         * @return ServletContext
716         */
717        public static ServletContext getTurbineServletContext()
718        {
719            return servletContext;
720        }
721    
722        /**
723         * The <code>Servlet</code> destroy method.  Invokes
724         * <code>ServiceBroker</code> tear down method.
725         */
726        public void destroy()
727        {
728            // Shut down all Turbine Services.
729            getServiceManager().shutdownServices();
730    
731            firstInit = true;
732            firstDoGet = true;
733            log.info("Turbine: Done shutting down!");
734        }
735    
736        /**
737         * The primary method invoked when the Turbine servlet is executed.
738         *
739         * @param req Servlet request.
740         * @param res Servlet response.
741         * @exception IOException a servlet exception.
742         * @exception ServletException a servlet exception.
743         */
744        public void doGet(HttpServletRequest req, HttpServletResponse res)
745                throws IOException, ServletException
746        {
747            PipelineData pipelineData = null;
748    
749            try
750            {
751                // Check to make sure that we started up properly.
752                if (initFailure != null)
753                {
754                    throw initFailure;
755                }
756    
757                //
758                // If the servlet container gives us no clear indication about the
759                // Encoding of the contents, set it to our default value.
760                if (req.getCharacterEncoding() == null)
761                {
762                    if (log.isDebugEnabled())
763                    {
764                        log.debug("Changing Input Encoding to " + inputEncoding);
765                    }
766    
767                    try
768                    {
769                        req.setCharacterEncoding(inputEncoding);
770                    }
771                    catch (UnsupportedEncodingException uee)
772                    {
773                        log.warn("Could not change request encoding to " + inputEncoding, uee);
774                    }
775                }
776    
777                // Get general RunData here...
778                // Perform turbine specific initialization below.
779                pipelineData = getRunDataService().getRunData(req, res, getServletConfig());
780                Map<Class<?>, Object> runDataMap = new HashMap<Class<?>, Object>();
781                runDataMap.put(RunData.class, pipelineData);
782                // put the data into the pipeline
783                pipelineData.put(RunData.class, runDataMap);
784    
785                // If this is the first invocation, perform some
786                // initialization.  Certain services need RunData to initialize
787                // themselves.
788                if (firstDoGet)
789                {
790                    init(pipelineData);
791                }
792    
793                // Stages of Pipeline implementation execution
794                            // configurable via attached Valve implementations in a
795                            // XML properties file.
796                            pipeline.invoke(pipelineData);
797    
798            }
799            catch (Exception e)
800            {
801                handleException(pipelineData, res, e);
802            }
803            catch (Throwable t)
804            {
805                handleException(pipelineData, res, t);
806            }
807            finally
808            {
809                // Return the used RunData to the factory for recycling.
810                getRunDataService().putRunData((RunData)pipelineData);
811            }
812        }
813    
814        /**
815         * In this application doGet and doPost are the same thing.
816         *
817         * @param req Servlet request.
818         * @param res Servlet response.
819         * @exception IOException a servlet exception.
820         * @exception ServletException a servlet exception.
821         */
822        public void doPost(HttpServletRequest req, HttpServletResponse res)
823                throws IOException, ServletException
824        {
825            doGet(req, res);
826        }
827    
828        /**
829         * Return the servlet info.
830         *
831         * @return a string with the servlet information.
832         */
833        public String getServletInfo()
834        {
835            return "Turbine Servlet";
836        }
837    
838        /**
839         * This method is about making sure that we catch and display
840         * errors to the screen in one fashion or another. What happens is
841         * that it will attempt to show the error using your user defined
842         * Error Screen. If that fails, then it will resort to just
843         * displaying the error and logging it all over the place
844         * including the servlet engine log file, the Turbine log file and
845         * on the screen.
846         *
847         * @param data A Turbine PipelineData object.
848         * @param res Servlet response.
849         * @param t The exception to report.
850         */
851        protected void handleException(PipelineData pipelineData, HttpServletResponse res,
852                                           Throwable t)
853        {
854            RunData data = getRunData(pipelineData);
855            // make sure that the stack trace makes it the log
856            log.error("Turbine.handleException: ", t);
857    
858            String mimeType = "text/plain";
859            try
860            {
861                // This is where we capture all exceptions and show the
862                // Error Screen.
863                data.setStackTrace(ExceptionUtils.getStackTrace(t), t);
864    
865                // setup the screen
866                data.setScreen(configuration.getString(
867                        TurbineConstants.SCREEN_ERROR_KEY,
868                        TurbineConstants.SCREEN_ERROR_DEFAULT));
869    
870                // do more screen setup for template execution if needed
871                if (data.getTemplateInfo() != null)
872                {
873                    data.getTemplateInfo()
874                        .setScreenTemplate(configuration.getString(
875                                TurbineConstants.TEMPLATE_ERROR_KEY,
876                                TurbineConstants.TEMPLATE_ERROR_VM));
877                }
878    
879                // Make sure to not execute an action.
880                data.setAction("");
881    
882                PageLoader.getInstance().exec(pipelineData,
883                        configuration.getString(TurbineConstants.PAGE_DEFAULT_KEY,
884                                TurbineConstants.PAGE_DEFAULT_DEFAULT));
885    
886                data.getResponse().setContentType(data.getContentType());
887                data.getResponse().setStatus(data.getStatusCode());
888            }
889            // Catch this one because it occurs if some code hasn't been
890            // completely re-compiled after a change..
891            catch (java.lang.NoSuchFieldError e)
892            {
893                try
894                {
895                    data.getResponse().setContentType(mimeType);
896                    data.getResponse().setStatus(200);
897                }
898                catch (Exception ignored)
899                {
900                    // ignore
901                }
902    
903                try
904                {
905                                    data.getResponse().getWriter().print("java.lang.NoSuchFieldError: "
906                            + "Please recompile all of your source code.");
907                }
908                catch (IOException ignored)
909                {
910                    // ignore
911                }
912    
913                log.error(data.getStackTrace(), e);
914            }
915            // Attempt to do *something* at this point...
916            catch (Throwable reallyScrewedNow)
917            {
918                StringBuffer msg = new StringBuffer();
919                msg.append("Horrible Exception: ");
920                if (data != null)
921                {
922                    msg.append(data.getStackTrace());
923                }
924                else
925                {
926                    msg.append(t);
927                }
928                try
929                {
930                    res.setContentType(mimeType);
931                    res.setStatus(200);
932                    res.getWriter().print(msg.toString());
933                }
934                catch (Exception ignored)
935                {
936                    // ignore
937                }
938    
939                log.error(reallyScrewedNow.getMessage(), reallyScrewedNow);
940            }
941        }
942    
943        /**
944         * Save some information about this servlet so that
945         * it can be utilized by object instances that do not
946         * have direct access to RunData.
947         *
948         * @param data Turbine request data
949         */
950        public static synchronized void saveServletInfo(PipelineData data)
951        {
952            // Store the context path for tools like ContentURI and
953            // the UIManager that use webapp context path information
954            // for constructing URLs.
955    
956            //
957            // Bundle all the information above up into a convenient structure
958            //
959            ServerData requestServerData = data.get(Turbine.class, ServerData.class);
960            serverData = (ServerData) requestServerData.clone();
961        }
962    
963        /**
964         * Set the application root for the webapp.
965         *
966         * @param val New app root.
967         */
968        public static void setApplicationRoot(String val)
969        {
970            applicationRoot = val;
971        }
972    
973        /**
974         * Get the application root for this Turbine webapp. This
975         * concept was started in 3.0 and will allow an app to be
976         * developed from a standard CVS layout. With a simple
977         * switch the app will work fully within the servlet
978         * container for deployment.
979         *
980         * @return String applicationRoot
981         */
982        public static String getApplicationRoot()
983        {
984            return applicationRoot;
985        }
986    
987        /**
988         * Used to get the real path of configuration and resource
989         * information. This can be used by an app being
990         * developed in a standard CVS layout.
991         *
992         * @param path path translated to the application root
993         * @return the real path
994         */
995        public static String getRealPath(String path)
996        {
997            if (path.startsWith("/"))
998            {
999                return new File(getApplicationRoot(), path.substring(1)).getAbsolutePath();
1000            }
1001    
1002            return new File(getApplicationRoot(), path).getAbsolutePath();
1003        }
1004    
1005        /**
1006         * Return an instance of the currently configured Service Manager
1007         *
1008         * @return A service Manager instance
1009         */
1010        private ServiceManager getServiceManager()
1011        {
1012            return TurbineServices.getInstance();
1013        }
1014    
1015        /**
1016         * Get a RunData from the pipelineData. Once RunData is fully replaced
1017         * by PipelineData this should not be required.
1018         * @param pipelineData
1019         * @return
1020         */
1021        private RunData getRunData(PipelineData pipelineData)
1022        {
1023            RunData data = null;
1024            data = (RunData)pipelineData;
1025            return data;
1026        }
1027    
1028    
1029        /**
1030         * Returns the default input encoding for the servlet.
1031         *
1032         * @return the default input encoding.
1033         */
1034        public String getDefaultInputEncoding()
1035        {
1036            return inputEncoding;
1037        }
1038    
1039        /**
1040         * Static Helper method for looking up the RunDataService
1041         * @return A RunDataService
1042         */
1043        private RunDataService getRunDataService()
1044        {
1045            return (RunDataService) TurbineServices
1046                .getInstance().getService(RunDataService.SERVICE_NAME);
1047        }
1048    }