    001package org.apache.turbine;
    002
    003/*
    004 * Licensed to the Apache Software Foundation (ASF) under one
    005 * or more contributor license agreements.  See the NOTICE file
    006 * distributed with this work for additional information
    007 * regarding copyright ownership.  The ASF licenses this file
    008 * to you under the Apache License, Version 2.0 (the
    009 * "License"); you may not use this file except in compliance
    010 * with the License.  You may obtain a copy of the License at
    011 *
    012 *   http://www.apache.org/licenses/LICENSE-2.0
    013 *
    014 * Unless required by applicable law or agreed to in writing,
    015 * software distributed under the License is distributed on an
    016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    017 * KIND, either express or implied.  See the License for the
    018 * specific language governing permissions and limitations
    019 * under the License.
    020 */
    021
    022import java.io.BufferedReader;
    023import java.io.File;
    024import java.io.FileInputStream;
    025import java.io.FileNotFoundException;
    026import java.io.FileReader;
    027import java.io.IOException;
    028import java.io.Reader;
    029import java.io.UnsupportedEncodingException;
    030import java.util.HashMap;
    031import java.util.Iterator;
    032import java.util.Map;
    033import java.util.Properties;
    034
    035import javax.servlet.ServletConfig;
    036import javax.servlet.ServletContext;
    037import javax.servlet.ServletException;
    038import javax.servlet.http.HttpServlet;
    039import javax.servlet.http.HttpServletRequest;
    040import javax.servlet.http.HttpServletResponse;
    041import javax.xml.parsers.FactoryConfigurationError;
    042
    043import org.apache.commons.configuration.Configuration;
    044import org.apache.commons.configuration.DefaultConfigurationBuilder;
    045import org.apache.commons.configuration.PropertiesConfiguration;
    046import org.apache.commons.lang.StringUtils;
    047import org.apache.commons.lang.exception.ExceptionUtils;
    048import org.apache.commons.logging.Log;
    049import org.apache.commons.logging.LogFactory;
    050import org.apache.log4j.PropertyConfigurator;
    051import org.apache.log4j.xml.DOMConfigurator;
    052import org.apache.turbine.modules.PageLoader;
    053import org.apache.turbine.pipeline.Pipeline;
    054import org.apache.turbine.pipeline.PipelineData;
    055import org.apache.turbine.pipeline.TurbinePipeline;
    056import org.apache.turbine.services.Initable;
    057import org.apache.turbine.services.InitializationException;
    058import org.apache.turbine.services.ServiceManager;
    059import org.apache.turbine.services.TurbineServices;
    060import org.apache.turbine.services.rundata.RunDataService;
    061import org.apache.turbine.services.template.TemplateService;
    062import org.apache.turbine.services.template.TurbineTemplate;
    063import org.apache.turbine.util.RunData;
    064import org.apache.turbine.util.ServerData;
    065import org.apache.turbine.util.TurbineConfig;
    066import org.apache.turbine.util.TurbineException;
    067import org.apache.turbine.util.uri.URIConstants;
    068
    069import com.thoughtworks.xstream.XStream;
    070import com.thoughtworks.xstream.io.xml.StaxDriver;
    071
    072/**
    073 * Turbine is the main servlet for the entire system. It is <code>final</code>
    074 * because you should <i>not</i> ever need to subclass this servlet.  If you
    075 * need to perform initialization of a service, then you should implement the
    076 * Services API and let your code be initialized by it.
    077 * If you need to override something in the <code>doGet()</code> or
    078 * <code>doPost()</code> methods, edit the TurbineResources.properties file and
    079 * specify your own classes there.
    080 * <p>
    081 * Turbine servlet recognizes the following initialization parameters.
    082 * <ul>
    083 * <li><code>properties</code> the path to TurbineResources.properties file
    084 * used by the default implementation of <code>ResourceService</code>, relative
    085 * to the application root.</li>
    086 * <li><code>basedir</code> this parameter is used <strong>only</strong> if your
    087 * application server does not support web applications, or the or does not
    088 * support <code>ServletContext.getRealPath(String)</code> method correctly.
    089 * You can use this parameter to specify the directory within the server's
    090 * filesystem, that is the base of your web application.</li>
    091 * </ul>
    092 *
    093 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
    094 * @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a>
    095 * @author <a href="mailto:greg@shwoop.com">Greg Ritter</a>
    096 * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
    097 * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
    098 * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
    099 * @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 */
    109public 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}