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