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