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 }