blob: 76b1bdd1045d4ce23e552ff8784f09f9210292f7 [file] [log] [blame]
package org.apache.turbine;
/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" and
* "Apache Turbine" must not be used to endorse or promote products
* derived from this software without prior written permission. For
* written permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* "Apache Turbine", nor may "Apache" appear in their name, without
* prior written permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Properties;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationFactory;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.PropertyConfigurator;
import org.apache.turbine.modules.PageLoader;
import org.apache.turbine.pipeline.Pipeline;
import org.apache.turbine.pipeline.PipelineData;
import org.apache.turbine.pipeline.TurbinePipeline;
import org.apache.turbine.services.ServiceManager;
import org.apache.turbine.services.TurbineServices;
import org.apache.turbine.services.avaloncomponent.AvalonComponentService;
import org.apache.turbine.services.component.ComponentService;
import org.apache.turbine.services.rundata.RunDataService;
import org.apache.turbine.services.template.TemplateService;
import org.apache.turbine.services.template.TurbineTemplate;
import org.apache.turbine.util.RunData;
import org.apache.turbine.util.ServerData;
import org.apache.turbine.util.TurbineConfig;
import org.apache.turbine.util.TurbineException;
import org.apache.turbine.util.uri.URIConstants;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
/**
* Turbine is the main servlet for the entire system. It is <code>final</code>
* because you should <i>not</i> ever need to subclass this servlet. If you
* need to perform initialization of a service, then you should implement the
* Services API and let your code be initialized by it.
* If you need to override something in the <code>doGet()</code> or
* <code>doPost()</code> methods, edit the TurbineResources.properties file and
* specify your own classes there.
* <p>
* Turbine servlet recognizes the following initialization parameters.
* <ul>
* <li><code>properties</code> the path to TurbineResources.properties file
* used by the default implementation of <code>ResourceService</code>, relative
* to the application root.</li>
* <li><code>basedir</code> this parameter is used <strong>only</strong> if your
* application server does not support web applications, or the or does not
* support <code>ServletContext.getRealPath(String)</code> method correctly.
* You can use this parameter to specify the directory within the server's
* filesystem, that is the base of your web application.</li>
* </ul>
*
* @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
* @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a>
* @author <a href="mailto:greg@shwoop.com">Greg Ritter</a>
* @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
* @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
* @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
* @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
* @author <a href="mailto:sean@informage.net">Sean Legassick</a>
* @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
* @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
* @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
* @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
* @author <a href="mailto:peter@courcoux.biz">Peter Courcoux</a>
* @version $Id$
*/
public class Turbine
extends HttpServlet
implements TurbineConstants
{
/**
* Name of path info parameter used to indicate the redirected stage of
* a given user's initial Turbine request
*/
public static final String REDIRECTED_PATHINFO_NAME = "redirected";
/** The base directory key */
public static final String BASEDIR_KEY = "basedir";
/**
* In certain situations the init() method is called more than once,
* somtimes even concurrently. This causes bad things to happen,
* so we use this flag to prevent it.
*/
private static boolean firstInit = true;
/**
* The pipeline to use when processing requests.
*/
private static Pipeline pipeline = null;
/** Whether init succeeded or not. */
private static Throwable initFailure = null;
/**
* Should initialization activities be performed during doGet() execution?
*/
private static boolean firstDoGet = true;
/**
* Keep all the properties of the web server in a convenient data
* structure
*/
private static ServerData serverData = null;
/** The base from which the Turbine application will operate. */
private static String applicationRoot;
/** Servlet config for this Turbine webapp. */
private static ServletConfig servletConfig;
/** Servlet context for this Turbine webapp. */
private static ServletContext servletContext;
/**
* The webapp root where the Turbine application
* is running in the servlet container.
* This might differ from the application root.
*/
private static String webappRoot;
/** Our internal configuration object */
private static Configuration configuration = null;
/** Logging class from commons.logging */
private static Log log = LogFactory.getLog(Turbine.class);
/**
* This init method will load the default resources from a
* properties file.
*
* This method is called by init(ServletConfig config)
*
* @exception ServletException a servlet exception.
*/
public final void init() throws ServletException
{
synchronized (this.getClass())
{
super.init();
ServletConfig config = getServletConfig();
if (!firstInit)
{
log.info("Double initialization of Turbine was attempted!");
return;
}
// executing init will trigger some static initializers, so we have
// only one chance.
firstInit = false;
try
{
ServletContext context = config.getServletContext();
configure(config, context);
TemplateService templateService = TurbineTemplate.getService();
if (templateService == null)
{
throw new TurbineException(
"No Template Service configured!");
}
if (getRunDataService() == null)
{
throw new TurbineException(
"No RunData Service configured!");
}
}
catch (Exception e)
{
// save the exception to complain loudly later :-)
initFailure = e;
log.fatal("Turbine: init() failed: ", e);
throw new ServletException("Turbine: init() failed", e);
}
log.info("Turbine: init() Ready to Rumble!");
}
}
/**
* Read the master configuration file in, configure logging
* and start up any early services.
*
* @param config The Servlet Configuration supplied by the container
* @param context The Servlet Context supplied by the container
*
* @throws Exception A problem occured while reading the configuration or performing early startup
*/
private void configure(ServletConfig config, ServletContext context)
throws Exception
{
// Set the application root. This defaults to the webapp
// context if not otherwise set. This is to allow 2.1 apps
// to be developed from CVS. This feature will carry over
// into 3.0.
applicationRoot = findInitParameter(context, config,
APPLICATION_ROOT_KEY,
APPLICATION_ROOT_DEFAULT);
webappRoot = config.getServletContext().getRealPath("/");
// log.info("Web Application root is " + webappRoot);
// log.info("Application root is " + applicationRoot);
if (applicationRoot == null || applicationRoot.equals(WEB_CONTEXT))
{
applicationRoot = webappRoot;
// log.info("got empty or 'webContext' Application root. Application root now: " + applicationRoot);
}
// Set the applicationRoot for this webapp.
setApplicationRoot(applicationRoot);
// Create any directories that need to be setup for
// a running Turbine application.
createRuntimeDirectories(context, config);
//
// Now we run the Turbine configuration code. There are two ways
// to configure Turbine:
//
// a) By supplying an web.xml init parameter called "configuration"
//
// <init-param>
// <param-name>configuration</param-name>
// <param-value>/WEB-INF/conf/turbine.xml</param-value>
// </init-param>
//
// This loads an XML based configuration file.
//
// b) By supplying an web.xml init parameter called "properties"
//
// <init-param>
// <param-name>properties</param-name>
// <param-value>/WEB-INF/conf/TurbineResources.properties</param-value>
// </init-param>
//
// This loads a Properties based configuration file. Actually, these are
// extended properties as provided by commons-configuration
//
// If neither a) nor b) is supplied, Turbine will fall back to the
// known behaviour of loading a properties file called
// /WEB-INF/conf/TurbineResources.properties relative to the
// web application root.
String confFile= findInitParameter(context, config,
TurbineConfig.CONFIGURATION_PATH_KEY,
null);
String confPath;
String confStyle = "unset";
if (StringUtils.isNotEmpty(confFile))
{
confPath = getRealPath(confFile);
ConfigurationFactory configurationFactory = new ConfigurationFactory(confPath);
configurationFactory.setBasePath(getApplicationRoot());
configuration = configurationFactory.getConfiguration();
confStyle = "XML";
}
else
{
confFile = findInitParameter(context, config,
TurbineConfig.PROPERTIES_PATH_KEY,
TurbineConfig.PROPERTIES_PATH_DEFAULT);
confPath = getRealPath(confFile);
// This should eventually be a Configuration
// interface so that service and app configuration
// can be stored anywhere.
configuration = (Configuration) new PropertiesConfiguration(confPath);
confStyle = "Properties";
}
//
// Set up logging as soon as possible
//
String log4jFile = configuration.getString(LOG4J_CONFIG_FILE,
LOG4J_CONFIG_FILE_DEFAULT);
log4jFile = getRealPath(log4jFile);
//
// Load the config file above into a Properties object and
// fix up the Application root
//
Properties p = new Properties();
try
{
p.load(new FileInputStream(log4jFile));
p.setProperty(APPLICATION_ROOT_KEY, getApplicationRoot());
PropertyConfigurator.configure(p);
//
// Rebuild our log object with a configured commons-logging
log = LogFactory.getLog(this.getClass());
log.info("Configured log4j from " + log4jFile);
}
catch (FileNotFoundException fnf)
{
System.err.println("Could not open Log4J configuration file "
+ log4jFile + ": ");
fnf.printStackTrace();
}
// Now report our successful configuration to the world
log.info("Loaded configuration (" + confStyle + ") from " + confFile + " (" + confPath + ")");
setTurbineServletConfig(config);
setTurbineServletContext(context);
getServiceManager().setApplicationRoot(applicationRoot);
// We want to set a few values in the configuration so
// that ${variable} interpolation will work for
//
// ${applicationRoot}
// ${webappRoot}
configuration.setProperty(APPLICATION_ROOT_KEY, applicationRoot);
configuration.setProperty(WEBAPP_ROOT_KEY, webappRoot);
// Retrieve the pipeline class and then initialize it. The pipeline
// handles the processing of a webrequest/response cycle.
String descriptorPath =
configuration.getString(
"pipeline.default.descriptor",
TurbinePipeline.CLASSIC_PIPELINE);
descriptorPath = getRealPath(descriptorPath);
log.debug("Using descriptor path: " + descriptorPath);
Reader reader = new BufferedReader(new FileReader(descriptorPath));
XStream pipelineMapper = new XStream(new DomDriver()); // does not require XPP3 library
pipeline = (Pipeline) pipelineMapper.fromXML(reader);
log.debug("Initializing pipeline");
pipeline.initialize();
//
// Be sure, that our essential services get run early
//
configuration.setProperty(TurbineServices.SERVICE_PREFIX +
ComponentService.SERVICE_NAME + ".earlyInit",
Boolean.TRUE);
configuration.setProperty(TurbineServices.SERVICE_PREFIX +
AvalonComponentService.SERVICE_NAME + ".earlyInit",
Boolean.TRUE);
getServiceManager().setConfiguration(configuration);
// Initialize the service manager. Services
// that have its 'earlyInit' property set to
// a value of 'true' will be started when
// the service manager is initialized.
getServiceManager().init();
}
/**
* Create any directories that might be needed during
* runtime. Right now this includes:
*
* <ul>
*
* <li>The directory to write the log files to (relative to the
* web application root), or <code>null</code> for the default of
* <code>/logs</code>. The directory is specified via the {@link
* TurbineConstants#LOGGING_ROOT} parameter.</li>
*
* </ul>
*
* @param context Global initialization parameters.
* @param config Initialization parameters specific to the Turbine
* servlet.
*/
private static void createRuntimeDirectories(ServletContext context,
ServletConfig config)
{
String path = findInitParameter(context, config,
LOGGING_ROOT_KEY,
LOGGING_ROOT_DEFAULT);
File logDir = new File(getRealPath(path));
if (!logDir.exists())
{
// Create the logging directory
if (!logDir.mkdirs())
{
System.err.println("Cannot create directory for logs!");
}
}
}
/**
* Finds the specified servlet configuration/initialization
* parameter, looking first for a servlet-specific parameter, then
* for a global parameter, and using the provided default if not
* found.
*/
protected static final String findInitParameter(ServletContext context,
ServletConfig config, String name, String defaultValue)
{
String path = null;
// Try the name as provided first.
boolean usingNamespace = name.startsWith(CONFIG_NAMESPACE);
while (true)
{
path = config.getInitParameter(name);
if (StringUtils.isEmpty(path))
{
path = context.getInitParameter(name);
if (StringUtils.isEmpty(path))
{
// The named parameter didn't yield a value.
if (usingNamespace)
{
path = defaultValue;
}
else
{
// Try again using Turbine's namespace.
name = CONFIG_NAMESPACE + '.' + name;
usingNamespace = true;
continue;
}
}
}
break;
}
return path;
}
/**
* Return the current configuration with all keys included
*
* @return a Configuration Object
*/
public static Configuration getConfiguration()
{
return configuration;
}
/**
* Return the server name.
*
* @return String server name
*/
public static String getServerName()
{
return getDefaultServerData().getServerName();
}
/**
* Return the server scheme.
*
* @return String server scheme
*/
public static String getServerScheme()
{
return getDefaultServerData().getServerScheme();
}
/**
* Return the server port.
*
* @return String server port
*/
public static String getServerPort()
{
return Integer.toString(getDefaultServerData().getServerPort());
}
/**
* Get the script name. This is the initial script name.
* Actually this is probably not needed any more. I'll
* check. jvz.
*
* @return String initial script name.
*/
public static String getScriptName()
{
return getDefaultServerData().getScriptName();
}
/**
* Return the context path.
*
* @return String context path
*/
public static String getContextPath()
{
return getDefaultServerData().getContextPath();
}
/**
* Return all the Turbine Servlet information (Server Name, Port,
* Scheme in a ServerData structure. This is generated from the
* values set when initializing the Turbine and may not be correct
* if you're running in a clustered structure. This might be used
* if you need a DataURI and have no RunData object handy-
*
* @return An initialized ServerData object
*/
public static ServerData getDefaultServerData()
{
if(serverData == null)
{
log.error("ServerData Information requested from Turbine before first request!");
// Will be overwritten once the first request is run;
serverData = new ServerData(null, URIConstants.HTTP_PORT,
URIConstants.HTTP, null, null);
}
return serverData;
}
/**
* Set the servlet config for this turbine webapp.
*
* @param config New servlet config
*/
public static void setTurbineServletConfig(ServletConfig config)
{
servletConfig = config;
}
/**
* Get the servlet config for this turbine webapp.
*
* @return ServletConfig
*/
public static ServletConfig getTurbineServletConfig()
{
return servletConfig;
}
/**
* Set the servlet context for this turbine webapp.
*
* @param context New servlet context.
*/
public static void setTurbineServletContext(ServletContext context)
{
servletContext = context;
}
/**
* Get the servlet context for this turbine webapp.
*
* @return ServletContext
*/
public static ServletContext getTurbineServletContext()
{
return servletContext;
}
/**
* The <code>Servlet</code> destroy method. Invokes
* <code>ServiceBroker</code> tear down method.
*/
public final void destroy()
{
// Shut down all Turbine Services.
getServiceManager().shutdownServices();
System.gc();
firstInit = true;
firstDoGet = true;
log.info("Turbine: Done shutting down!");
}
/**
* The primary method invoked when the Turbine servlet is executed.
*
* @param req Servlet request.
* @param res Servlet response.
* @exception IOException a servlet exception.
* @exception ServletException a servlet exception.
*/
public final void doGet(HttpServletRequest req, HttpServletResponse res)
throws IOException, ServletException
{
// set to true if the request is to be redirected by the page
boolean requestRedirected = false;
// Placeholder for the RunData object.
//RunData data = null;
PipelineData pipelineData = null;//new DefaultPipelineData();
try
{
// Check to make sure that we started up properly.
if (initFailure != null)
{
throw initFailure;
}
// If this is the first invocation, perform some
// initialization. Certain services need RunData to initialize
// themselves.
if (firstDoGet)
{
synchronized (Turbine.class)
{
// Store the context path for tools like ContentURI and
// the UIManager that use webapp context path information
// for constructing URLs.
serverData = new ServerData(req);
// Mark that we're done.
firstDoGet = false;
log.info("Turbine: first Request successful");
}
}
// Get general RunData here...
// Perform turbine specific initialization below.
pipelineData = getRunDataService().getRunData(req, res, getServletConfig());
// Map runDataMap = new HashMap();
//runDataMap.put(RunData.class, data);
// put the data into the pipeline
// pipelineData.put(RunData.class, runDataMap);
// Stages of Pipeline implementation execution
// configurable via attached Valve implementations in a
// XML properties file.
pipeline.invoke(pipelineData);
}
catch (Exception e)
{
handleException(pipelineData, res, e);
}
catch (Throwable t)
{
handleException(pipelineData, res, t);
}
finally
{
// Return the used RunData to the factory for recycling.
getRunDataService().putRunData((RunData)pipelineData);
}
}
/**
* In this application doGet and doPost are the same thing.
*
* @param req Servlet request.
* @param res Servlet response.
* @exception IOException a servlet exception.
* @exception ServletException a servlet exception.
*/
public final void doPost(HttpServletRequest req, HttpServletResponse res)
throws IOException, ServletException
{
doGet(req, res);
}
/**
* Return the servlet info.
*
* @return a string with the servlet information.
*/
public final String getServletInfo()
{
return "Turbine Servlet";
}
/**
* This method is about making sure that we catch and display
* errors to the screen in one fashion or another. What happens is
* that it will attempt to show the error using your user defined
* Error Screen. If that fails, then it will resort to just
* displaying the error and logging it all over the place
* including the servlet engine log file, the Turbine log file and
* on the screen.
*
* @param data A Turbine PipelineData object.
* @param res Servlet response.
* @param t The exception to report.
*/
private final void handleException(PipelineData pipelineData, HttpServletResponse res,
Throwable t)
{
RunData data = (RunData)getRunData(pipelineData);
// make sure that the stack trace makes it the log
log.error("Turbine.handleException: ", t);
String mimeType = "text/plain";
try
{
// This is where we capture all exceptions and show the
// Error Screen.
data.setStackTrace(ExceptionUtils.getStackTrace(t), t);
// setup the screen
data.setScreen(configuration.getString(SCREEN_ERROR_KEY,
SCREEN_ERROR_DEFAULT));
// do more screen setup for template execution if needed
if (data.getTemplateInfo() != null)
{
data.getTemplateInfo()
.setScreenTemplate(configuration.getString(
TEMPLATE_ERROR_KEY, TEMPLATE_ERROR_VM));
}
// Make sure to not execute an action.
data.setAction("");
PageLoader.getInstance().exec(pipelineData,
configuration.getString(PAGE_DEFAULT_KEY,
PAGE_DEFAULT_DEFAULT));
data.getResponse().setContentType(data.getContentType());
data.getResponse().setStatus(data.getStatusCode());
}
// Catch this one because it occurs if some code hasn't been
// completely re-compiled after a change..
catch (java.lang.NoSuchFieldError e)
{
try
{
data.getResponse().setContentType(mimeType);
data.getResponse().setStatus(200);
}
catch (Exception ignored)
{
}
try
{
data.getResponse().getWriter().print("java.lang.NoSuchFieldError: "
+ "Please recompile all of your source code.");
}
catch (IOException ignored)
{
}
log.error(data.getStackTrace(), e);
}
// Attempt to do *something* at this point...
catch (Throwable reallyScrewedNow)
{
StringBuffer msg = new StringBuffer();
msg.append("Horrible Exception: ");
if (data != null)
{
msg.append(data.getStackTrace());
}
else
{
msg.append(t);
}
try
{
res.setContentType(mimeType);
res.setStatus(200);
res.getWriter().print(msg.toString());
}
catch (Exception ignored)
{
}
log.error(reallyScrewedNow.getMessage(), reallyScrewedNow);
}
}
/**
* Set the application root for the webapp.
*
* @param val New app root.
*/
public static void setApplicationRoot(String val)
{
applicationRoot = val;
}
/**
* Get the application root for this Turbine webapp. This
* concept was started in 3.0 and will allow an app to be
* developed from a standard CVS layout. With a simple
* switch the app will work fully within the servlet
* container for deployment.
*
* @return String applicationRoot
*/
public static String getApplicationRoot()
{
return applicationRoot;
}
/**
* Used to get the real path of configuration and resource
* information. This can be used by an app being
* developed in a standard CVS layout.
*
* @param path path translated to the application root
* @return the real path
*/
public static String getRealPath(String path)
{
if (path.startsWith("/"))
{
path = path.substring(1);
}
return new File(getApplicationRoot(), path).getAbsolutePath();
}
/**
* Return an instance of the currently configured Service Manager
*
* @return A service Manager instance
*/
private ServiceManager getServiceManager()
{
return TurbineServices.getInstance();
}
/**
* Get a RunData from the pipelineData. Once RunData is fully replaced
* by PipelineData this should not be required.
* @param pipelineData
* @return
*/
private RunData getRunData(PipelineData pipelineData)
{
RunData data = null;
data = (RunData)pipelineData;
return data;
}
/**
* Static Helper method for looking up the RunDataService
* @return A RunDataService
*/
private static RunDataService getRunDataService(){
return (RunDataService) TurbineServices
.getInstance().getService(RunDataService.SERVICE_NAME);
}
}