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