View Javadoc
1   package org.apache.turbine;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileNotFoundException;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.UnsupportedEncodingException;
28  import java.util.HashMap;
29  import java.util.Iterator;
30  import java.util.Map;
31  import java.util.Properties;
32  
33  import javax.servlet.ServletConfig;
34  import javax.servlet.ServletContext;
35  import javax.servlet.ServletException;
36  import javax.servlet.http.HttpServlet;
37  import javax.servlet.http.HttpServletRequest;
38  import javax.servlet.http.HttpServletResponse;
39  import javax.xml.bind.JAXBContext;
40  import javax.xml.bind.Unmarshaller;
41  import javax.xml.parsers.FactoryConfigurationError;
42  
43  import org.apache.commons.configuration.Configuration;
44  import org.apache.commons.configuration.DefaultConfigurationBuilder;
45  import org.apache.commons.configuration.PropertiesConfiguration;
46  import org.apache.commons.io.IOUtils;
47  import org.apache.commons.lang.StringUtils;
48  import org.apache.commons.lang.exception.ExceptionUtils;
49  import org.apache.commons.logging.Log;
50  import org.apache.commons.logging.LogFactory;
51  import org.apache.log4j.PropertyConfigurator;
52  import org.apache.log4j.xml.DOMConfigurator;
53  import org.apache.turbine.modules.PageLoader;
54  import org.apache.turbine.pipeline.Pipeline;
55  import org.apache.turbine.pipeline.PipelineData;
56  import org.apache.turbine.pipeline.TurbinePipeline;
57  import org.apache.turbine.services.Initable;
58  import org.apache.turbine.services.InitializationException;
59  import org.apache.turbine.services.ServiceManager;
60  import org.apache.turbine.services.TurbineServices;
61  import org.apache.turbine.services.rundata.RunDataService;
62  import org.apache.turbine.services.template.TemplateService;
63  import org.apache.turbine.util.RunData;
64  import org.apache.turbine.util.ServerData;
65  import org.apache.turbine.util.TurbineConfig;
66  import org.apache.turbine.util.TurbineException;
67  import org.apache.turbine.util.uri.URIConstants;
68  
69  /**
70   * Turbine is the main servlet for the entire system. It is <code>final</code>
71   * because you should <i>not</i> ever need to subclass this servlet.  If you
72   * need to perform initialization of a service, then you should implement the
73   * Services API and let your code be initialized by it.
74   * If you need to override something in the <code>doGet()</code> or
75   * <code>doPost()</code> methods, edit the TurbineResources.properties file and
76   * specify your own classes there.
77   * <p>
78   * Turbine servlet recognizes the following initialization parameters.
79   * <ul>
80   * <li><code>properties</code> the path to TurbineResources.properties file
81   * used by the default implementation of <code>ResourceService</code>, relative
82   * to the application root.</li>
83   * <li><code>basedir</code> this parameter is used <strong>only</strong> if your
84   * application server does not support web applications, or the or does not
85   * support <code>ServletContext.getRealPath(String)</code> method correctly.
86   * You can use this parameter to specify the directory within the server's
87   * filesystem, that is the base of your web application.</li>
88   * </ul>
89   *
90   * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
91   * @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a>
92   * @author <a href="mailto:greg@shwoop.com">Greg Ritter</a>
93   * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
94   * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
95   * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
96   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
97   * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
98   * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
99   * @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  */
106 public 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 }