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