0001: package org.apache.turbine;
0002:
0003: /*
0004: * Licensed to the Apache Software Foundation (ASF) under one
0005: * or more contributor license agreements. See the NOTICE file
0006: * distributed with this work for additional information
0007: * regarding copyright ownership. The ASF licenses this file
0008: * to you under the Apache License, Version 2.0 (the
0009: * "License"); you may not use this file except in compliance
0010: * with the License. You may obtain a copy of the License at
0011: *
0012: * http://www.apache.org/licenses/LICENSE-2.0
0013: *
0014: * Unless required by applicable law or agreed to in writing,
0015: * software distributed under the License is distributed on an
0016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
0017: * KIND, either express or implied. See the License for the
0018: * specific language governing permissions and limitations
0019: * under the License.
0020: */
0021:
0022: import java.io.File;
0023: import java.io.FileInputStream;
0024: import java.io.FileNotFoundException;
0025: import java.io.IOException;
0026: import java.io.UnsupportedEncodingException;
0027: import java.util.Properties;
0028:
0029: import javax.servlet.ServletConfig;
0030: import javax.servlet.ServletContext;
0031: import javax.servlet.ServletException;
0032: import javax.servlet.http.HttpServlet;
0033: import javax.servlet.http.HttpServletRequest;
0034: import javax.servlet.http.HttpServletResponse;
0035:
0036: import org.apache.commons.configuration.Configuration;
0037: import org.apache.commons.configuration.ConfigurationFactory;
0038: import org.apache.commons.configuration.PropertiesConfiguration;
0039: import org.apache.commons.lang.StringUtils;
0040: import org.apache.commons.lang.exception.ExceptionUtils;
0041: import org.apache.commons.logging.Log;
0042: import org.apache.commons.logging.LogFactory;
0043: import org.apache.log4j.PropertyConfigurator;
0044: import org.apache.turbine.modules.ActionLoader;
0045: import org.apache.turbine.modules.PageLoader;
0046: import org.apache.turbine.services.ServiceManager;
0047: import org.apache.turbine.services.TurbineServices;
0048: import org.apache.turbine.services.avaloncomponent.AvalonComponentService;
0049: import org.apache.turbine.services.rundata.RunDataService;
0050: import org.apache.turbine.services.rundata.TurbineRunDataFacade;
0051: import org.apache.turbine.services.template.TemplateService;
0052: import org.apache.turbine.services.template.TurbineTemplate;
0053: import org.apache.turbine.services.velocity.VelocityService;
0054: import org.apache.turbine.util.RunData;
0055: import org.apache.turbine.util.ServerData;
0056: import org.apache.turbine.util.TurbineConfig;
0057: import org.apache.turbine.util.TurbineException;
0058: import org.apache.turbine.util.security.AccessControlList;
0059: import org.apache.turbine.util.template.TemplateInfo;
0060: import org.apache.turbine.util.uri.URIConstants;
0061:
0062: /**
0063: * Turbine is the main servlet for the entire system. It is <code>final</code>
0064: * because you should <i>not</i> ever need to subclass this servlet. If you
0065: * need to perform initialization of a service, then you should implement the
0066: * Services API and let your code be initialized by it.
0067: * If you need to override something in the <code>doGet()</code> or
0068: * <code>doPost()</code> methods, edit the TurbineResources.properties file and
0069: * specify your own classes there.
0070: * <p>
0071: * Turbine servlet recognizes the following initialization parameters.
0072: * <ul>
0073: * <li><code>properties</code> the path to TurbineResources.properties file
0074: * used by the default implementation of <code>ResourceService</code>, relative
0075: * to the application root.</li>
0076: * <li><code>basedir</code> this parameter is used <strong>only</strong> if your
0077: * application server does not support web applications, or the or does not
0078: * support <code>ServletContext.getRealPath(String)</code> method correctly.
0079: * You can use this parameter to specify the directory within the server's
0080: * filesystem, that is the base of your web application.</li>
0081: * </ul>
0082: *
0083: * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
0084: * @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a>
0085: * @author <a href="mailto:greg@shwoop.com">Greg Ritter</a>
0086: * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
0087: * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
0088: * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
0089: * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
0090: * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
0091: * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
0092: * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
0093: * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
0094: * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
0095: * @author <a href="mailto:seade@backstagetech.com.au">Scott Eade</a>
0096: * @version $Id: Turbine.java 543399 2007-06-01 06:15:17Z seade $
0097: */
0098: public class Turbine extends HttpServlet implements TurbineConstants {
0099: /** SerialVersionUID for serialization */
0100: private static final long serialVersionUID = -6895381097045304308L;
0101:
0102: /**
0103: * Name of path info parameter used to indicate the redirected stage of
0104: * a given user's initial Turbine request
0105: */
0106: public static final String REDIRECTED_PATHINFO_NAME = "redirected";
0107:
0108: /** The base directory key */
0109: public static final String BASEDIR_KEY = "basedir";
0110:
0111: /**
0112: * In certain situations the init() method is called more than once,
0113: * somtimes even concurrently. This causes bad things to happen,
0114: * so we use this flag to prevent it.
0115: */
0116: private static boolean firstInit = true;
0117:
0118: /** Whether init succeeded or not. */
0119: private static Throwable initFailure = null;
0120:
0121: /**
0122: * Should initialization activities be performed during doGet() execution?
0123: */
0124: private static boolean firstDoGet = true;
0125:
0126: /**
0127: * Keep all the properties of the web server in a convenient data
0128: * structure
0129: */
0130: private static ServerData serverData = null;
0131:
0132: /** The base from which the Turbine application will operate. */
0133: private static String applicationRoot;
0134:
0135: /** Servlet config for this Turbine webapp. */
0136: private static ServletConfig servletConfig;
0137:
0138: /** Servlet context for this Turbine webapp. */
0139: private static ServletContext servletContext;
0140:
0141: /**
0142: * The webapp root where the Turbine application
0143: * is running in the servlet container.
0144: * This might differ from the application root.
0145: */
0146: private static String webappRoot;
0147:
0148: /** Our internal configuration object */
0149: private static Configuration configuration = null;
0150:
0151: /** A reference to the Template Service */
0152: private TemplateService templateService = null;
0153:
0154: /** A reference to the RunData Service */
0155: private RunDataService rundataService = null;
0156:
0157: /** Default Input encoding if the servlet container does not report an encoding */
0158: private String inputEncoding = null;
0159:
0160: /** Logging class from commons.logging */
0161: private static Log log = LogFactory.getLog(Turbine.class);
0162:
0163: /**
0164: * This init method will load the default resources from a
0165: * properties file.
0166: *
0167: * This method is called by init(ServletConfig config)
0168: *
0169: * @exception ServletException a servlet exception.
0170: */
0171: public final void init() throws ServletException {
0172: synchronized (this .getClass()) {
0173: super .init();
0174: ServletConfig config = getServletConfig();
0175:
0176: if (!firstInit) {
0177: log
0178: .info("Double initialization of Turbine was attempted!");
0179: return;
0180: }
0181: // executing init will trigger some static initializers, so we have
0182: // only one chance.
0183: firstInit = false;
0184:
0185: try {
0186: ServletContext context = config.getServletContext();
0187:
0188: configure(config, context);
0189:
0190: templateService = TurbineTemplate.getService();
0191: rundataService = TurbineRunDataFacade.getService();
0192:
0193: if (rundataService == null) {
0194: throw new TurbineException(
0195: "No RunData Service configured!");
0196: }
0197:
0198: } catch (Exception e) {
0199: // save the exception to complain loudly later :-)
0200: initFailure = e;
0201: log.fatal("Turbine: init() failed: ", e);
0202: throw new ServletException("Turbine: init() failed", e);
0203: }
0204: log.info("Turbine: init() Ready to Rumble!");
0205: }
0206: }
0207:
0208: /**
0209: * Read the master configuration file in, configure logging
0210: * and start up any early services.
0211: *
0212: * @param config The Servlet Configuration supplied by the container
0213: * @param context The Servlet Context supplied by the container
0214: *
0215: * @throws Exception A problem occured while reading the configuration or performing early startup
0216: */
0217:
0218: private void configure(ServletConfig config, ServletContext context)
0219: throws Exception {
0220: // Set the application root. This defaults to the webapp
0221: // context if not otherwise set. This is to allow 2.1 apps
0222: // to be developed from CVS. This feature will carry over
0223: // into 3.0.
0224: applicationRoot = findInitParameter(context, config,
0225: APPLICATION_ROOT_KEY, APPLICATION_ROOT_DEFAULT);
0226:
0227: webappRoot = config.getServletContext().getRealPath("/");
0228: // log.info("Web Application root is " + webappRoot);
0229: // log.info("Application root is " + applicationRoot);
0230:
0231: if (applicationRoot == null
0232: || applicationRoot.equals(WEB_CONTEXT)) {
0233: applicationRoot = webappRoot;
0234: // log.info("got empty or 'webContext' Application root. Application root now: " + applicationRoot);
0235: }
0236:
0237: // Set the applicationRoot for this webapp.
0238: setApplicationRoot(applicationRoot);
0239:
0240: // Create any directories that need to be setup for
0241: // a running Turbine application.
0242: createRuntimeDirectories(context, config);
0243:
0244: //
0245: // Now we run the Turbine configuration code. There are two ways
0246: // to configure Turbine:
0247: //
0248: // a) By supplying an web.xml init parameter called "configuration"
0249: //
0250: // <init-param>
0251: // <param-name>configuration</param-name>
0252: // <param-value>/WEB-INF/conf/turbine.xml</param-value>
0253: // </init-param>
0254: //
0255: // This loads an XML based configuration file.
0256: //
0257: // b) By supplying an web.xml init parameter called "properties"
0258: //
0259: // <init-param>
0260: // <param-name>properties</param-name>
0261: // <param-value>/WEB-INF/conf/TurbineResources.properties</param-value>
0262: // </init-param>
0263: //
0264: // This loads a Properties based configuration file. Actually, these are
0265: // extended properties as provided by commons-configuration
0266: //
0267: // If neither a) nor b) is supplied, Turbine will fall back to the
0268: // known behaviour of loading a properties file called
0269: // /WEB-INF/conf/TurbineResources.properties relative to the
0270: // web application root.
0271:
0272: String confFile = findInitParameter(context, config,
0273: TurbineConfig.CONFIGURATION_PATH_KEY, null);
0274:
0275: String confPath;
0276: String confStyle = "unset";
0277:
0278: if (StringUtils.isNotEmpty(confFile)) {
0279: confPath = getRealPath(confFile);
0280: ConfigurationFactory configurationFactory = new ConfigurationFactory(
0281: confPath);
0282: configurationFactory.setBasePath(getApplicationRoot());
0283: configuration = configurationFactory.getConfiguration();
0284: confStyle = "XML";
0285: } else {
0286: confFile = findInitParameter(context, config,
0287: TurbineConfig.PROPERTIES_PATH_KEY,
0288: TurbineConfig.PROPERTIES_PATH_DEFAULT);
0289:
0290: confPath = getRealPath(confFile);
0291:
0292: // This should eventually be a Configuration
0293: // interface so that service and app configuration
0294: // can be stored anywhere.
0295: configuration = (Configuration) new PropertiesConfiguration(
0296: confPath);
0297: confStyle = "Properties";
0298: }
0299:
0300: //
0301: // Set up logging as soon as possible
0302: //
0303: String log4jFile = configuration.getString(LOG4J_CONFIG_FILE,
0304: LOG4J_CONFIG_FILE_DEFAULT);
0305:
0306: if (StringUtils.isNotEmpty(log4jFile)
0307: && !log4jFile.equalsIgnoreCase("none")) {
0308: log4jFile = getRealPath(log4jFile);
0309:
0310: //
0311: // Load the config file above into a Properties object and
0312: // fix up the Application root
0313: //
0314: Properties p = new Properties();
0315: try {
0316: p.load(new FileInputStream(log4jFile));
0317: p.setProperty(APPLICATION_ROOT_KEY,
0318: getApplicationRoot());
0319: PropertyConfigurator.configure(p);
0320:
0321: log.info("Configured log4j from " + log4jFile);
0322: } catch (FileNotFoundException fnf) {
0323: System.err
0324: .println("Could not open Log4J configuration file "
0325: + log4jFile + ": ");
0326: fnf.printStackTrace();
0327: }
0328: }
0329:
0330: // Now report our successful configuration to the world
0331: log.info("Loaded configuration (" + confStyle + ") from "
0332: + confFile + " (" + confPath + ")");
0333:
0334: setTurbineServletConfig(config);
0335: setTurbineServletContext(context);
0336:
0337: getServiceManager().setApplicationRoot(applicationRoot);
0338:
0339: // We want to set a few values in the configuration so
0340: // that ${variable} interpolation will work for
0341: //
0342: // ${applicationRoot}
0343: // ${webappRoot}
0344: configuration
0345: .setProperty(APPLICATION_ROOT_KEY, applicationRoot);
0346: configuration.setProperty(WEBAPP_ROOT_KEY, webappRoot);
0347:
0348: //
0349: // Be sure, that our essential services get run early
0350: //
0351: configuration.setProperty(TurbineServices.SERVICE_PREFIX
0352: + AvalonComponentService.SERVICE_NAME + ".earlyInit",
0353: Boolean.TRUE);
0354:
0355: getServiceManager().setConfiguration(configuration);
0356:
0357: // Initialize the service manager. Services
0358: // that have its 'earlyInit' property set to
0359: // a value of 'true' will be started when
0360: // the service manager is initialized.
0361: getServiceManager().init();
0362:
0363: // Get the default input encoding
0364: inputEncoding = configuration.getString(
0365: TurbineConstants.PARAMETER_ENCODING_KEY,
0366: TurbineConstants.PARAMETER_ENCODING_DEFAULT);
0367:
0368: if (log.isDebugEnabled()) {
0369: log
0370: .debug("Input Encoding has been set to "
0371: + inputEncoding);
0372: }
0373: }
0374:
0375: /**
0376: * Create any directories that might be needed during
0377: * runtime. Right now this includes:
0378: *
0379: * <ul>
0380: *
0381: * <li>The directory to write the log files to (relative to the
0382: * web application root), or <code>null</code> for the default of
0383: * <code>/logs</code>. The directory is specified via the {@link
0384: * TurbineConstants#LOGGING_ROOT} parameter.</li>
0385: *
0386: * </ul>
0387: *
0388: * @param context Global initialization parameters.
0389: * @param config Initialization parameters specific to the Turbine
0390: * servlet.
0391: */
0392: private static void createRuntimeDirectories(
0393: ServletContext context, ServletConfig config) {
0394: String path = findInitParameter(context, config,
0395: LOGGING_ROOT_KEY, LOGGING_ROOT_DEFAULT);
0396:
0397: File logDir = new File(getRealPath(path));
0398: if (!logDir.exists()) {
0399: // Create the logging directory
0400: if (!logDir.mkdirs()) {
0401: System.err.println("Cannot create directory for logs!");
0402: }
0403: }
0404: }
0405:
0406: /**
0407: * Finds the specified servlet configuration/initialization
0408: * parameter, looking first for a servlet-specific parameter, then
0409: * for a global parameter, and using the provided default if not
0410: * found.
0411: */
0412: protected static final String findInitParameter(
0413: ServletContext context, ServletConfig config, String name,
0414: String defaultValue) {
0415: String path = null;
0416:
0417: // Try the name as provided first.
0418: boolean usingNamespace = name.startsWith(CONFIG_NAMESPACE);
0419: while (true) {
0420: path = config.getInitParameter(name);
0421: if (StringUtils.isEmpty(path)) {
0422: path = context.getInitParameter(name);
0423: if (StringUtils.isEmpty(path)) {
0424: // The named parameter didn't yield a value.
0425: if (usingNamespace) {
0426: path = defaultValue;
0427: } else {
0428: // Try again using Turbine's namespace.
0429: name = CONFIG_NAMESPACE + '.' + name;
0430: usingNamespace = true;
0431: continue;
0432: }
0433: }
0434: }
0435: break;
0436: }
0437:
0438: return path;
0439: }
0440:
0441: /**
0442: * Initializes the services which need <code>RunData</code> to
0443: * initialize themselves (post startup).
0444: *
0445: * @param data The first <code>GET</code> request.
0446: */
0447: public final void init(RunData data) {
0448: synchronized (Turbine.class) {
0449: if (firstDoGet) {
0450: // All we want to do here is save some servlet
0451: // information so that services and processes
0452: // that don't have direct access to a RunData
0453: // object can still know something about
0454: // the servlet environment.
0455: saveServletInfo(data);
0456:
0457: // Mark that we're done.
0458: firstDoGet = false;
0459: log.info("Turbine: first Request successful");
0460: }
0461: }
0462: }
0463:
0464: /**
0465: * Return the current configuration with all keys included
0466: *
0467: * @return a Configuration Object
0468: */
0469: public static Configuration getConfiguration() {
0470: return configuration;
0471: }
0472:
0473: /**
0474: * Return the server name.
0475: *
0476: * @return String server name
0477: */
0478: public static String getServerName() {
0479: return getDefaultServerData().getServerName();
0480: }
0481:
0482: /**
0483: * Return the server scheme.
0484: *
0485: * @return String server scheme
0486: */
0487: public static String getServerScheme() {
0488: return getDefaultServerData().getServerScheme();
0489: }
0490:
0491: /**
0492: * Return the server port.
0493: *
0494: * @return String server port
0495: */
0496: public static String getServerPort() {
0497: return Integer.toString(getDefaultServerData().getServerPort());
0498: }
0499:
0500: /**
0501: * Get the script name. This is the initial script name.
0502: * Actually this is probably not needed any more. I'll
0503: * check. jvz.
0504: *
0505: * @return String initial script name.
0506: */
0507: public static String getScriptName() {
0508: return getDefaultServerData().getScriptName();
0509: }
0510:
0511: /**
0512: * Return the context path.
0513: *
0514: * @return String context path
0515: */
0516: public static String getContextPath() {
0517: return getDefaultServerData().getContextPath();
0518: }
0519:
0520: /**
0521: * Return all the Turbine Servlet information (Server Name, Port,
0522: * Scheme in a ServerData structure. This is generated from the
0523: * values set when initializing the Turbine and may not be correct
0524: * if you're running in a clustered structure. You can provide default
0525: * values in your configuration for cases where access is requied before
0526: * your application is first accessed by a user. This might be used
0527: * if you need a DataURI and have no RunData object handy.
0528: *
0529: * @return An initialized ServerData object
0530: */
0531: public static ServerData getDefaultServerData() {
0532: if (serverData == null) {
0533: String serverName = configuration
0534: .getString(DEFAULT_SERVER_NAME_KEY);
0535: if (serverName == null) {
0536: log
0537: .error("ServerData Information requested from Turbine before first request!");
0538: } else {
0539: log
0540: .info("ServerData Information retrieved from configuration.");
0541: }
0542: // Will be overwritten once the first request is run;
0543: serverData = new ServerData(serverName, configuration
0544: .getInt(DEFAULT_SERVER_PORT_KEY,
0545: URIConstants.HTTP_PORT), configuration
0546: .getString(DEFAULT_SERVER_SCHEME_KEY,
0547: URIConstants.HTTP), configuration
0548: .getString(DEFAULT_SCRIPT_NAME_KEY), configuration
0549: .getString(DEFAULT_CONTEXT_PATH_KEY));
0550: }
0551: return serverData;
0552: }
0553:
0554: /**
0555: * Set the servlet config for this turbine webapp.
0556: *
0557: * @param config New servlet config
0558: */
0559: public static void setTurbineServletConfig(ServletConfig config) {
0560: servletConfig = config;
0561: }
0562:
0563: /**
0564: * Get the servlet config for this turbine webapp.
0565: *
0566: * @return ServletConfig
0567: */
0568: public static ServletConfig getTurbineServletConfig() {
0569: return servletConfig;
0570: }
0571:
0572: /**
0573: * Set the servlet context for this turbine webapp.
0574: *
0575: * @param context New servlet context.
0576: */
0577: public static void setTurbineServletContext(ServletContext context) {
0578: servletContext = context;
0579: }
0580:
0581: /**
0582: * Get the servlet context for this turbine webapp.
0583: *
0584: * @return ServletContext
0585: */
0586: public static ServletContext getTurbineServletContext() {
0587: return servletContext;
0588: }
0589:
0590: /**
0591: * The <code>Servlet</code> destroy method. Invokes
0592: * <code>ServiceBroker</code> tear down method.
0593: */
0594: public final void destroy() {
0595: // Shut down all Turbine Services.
0596: getServiceManager().shutdownServices();
0597: System.gc();
0598:
0599: firstInit = true;
0600: firstDoGet = true;
0601: log.info("Turbine: Done shutting down!");
0602: }
0603:
0604: /**
0605: * The primary method invoked when the Turbine servlet is executed.
0606: *
0607: * @param req Servlet request.
0608: * @param res Servlet response.
0609: * @exception IOException a servlet exception.
0610: * @exception ServletException a servlet exception.
0611: */
0612: public final void doGet(HttpServletRequest req,
0613: HttpServletResponse res) throws IOException,
0614: ServletException {
0615: // set to true if the request is to be redirected by the page
0616: boolean requestRedirected = false;
0617:
0618: // Placeholder for the RunData object.
0619: RunData data = null;
0620: try {
0621: // Check to make sure that we started up properly.
0622: if (initFailure != null) {
0623: throw initFailure;
0624: }
0625:
0626: //
0627: // If the servlet container gives us no clear indication about the
0628: // Encoding of the contents, set it to our default value.
0629: if (req.getCharacterEncoding() == null) {
0630: if (log.isDebugEnabled()) {
0631: log.debug("Changing Input Encoding to "
0632: + inputEncoding);
0633: }
0634:
0635: try {
0636: req.setCharacterEncoding(inputEncoding);
0637: } catch (UnsupportedEncodingException uee) {
0638: log.warn("Could not change request encoding to "
0639: + inputEncoding, uee);
0640: }
0641: }
0642:
0643: // Get general RunData here...
0644: // Perform turbine specific initialization below.
0645: data = rundataService.getRunData(req, res,
0646: getServletConfig());
0647:
0648: // If this is the first invocation, perform some
0649: // initialization. Certain services need RunData to initialize
0650: // themselves.
0651: if (firstDoGet) {
0652: init(data);
0653: }
0654:
0655: // set the session timeout if specified in turbine's properties
0656: // file if this is a new session
0657: if (data.getSession().isNew()) {
0658: int timeout = configuration.getInt(SESSION_TIMEOUT_KEY,
0659: SESSION_TIMEOUT_DEFAULT);
0660:
0661: if (timeout != SESSION_TIMEOUT_DEFAULT) {
0662: data.getSession().setMaxInactiveInterval(timeout);
0663: }
0664: }
0665:
0666: // Fill in the screen and action variables.
0667: data.setScreen(data.getParameters().getString(
0668: URIConstants.CGI_SCREEN_PARAM));
0669: data.setAction(data.getParameters().getString(
0670: URIConstants.CGI_ACTION_PARAM));
0671:
0672: // Special case for login and logout, this must happen before the
0673: // session validator is executed in order either to allow a user to
0674: // even login, or to ensure that the session validator gets to
0675: // mandate its page selection policy for non-logged in users
0676: // after the logout has taken place.
0677: if (data.hasAction()) {
0678: String action = data.getAction();
0679: log.debug("action = " + action);
0680:
0681: if (action.equalsIgnoreCase(configuration.getString(
0682: ACTION_LOGIN_KEY, ACTION_LOGIN_DEFAULT))) {
0683: loginAction(data);
0684: } else if (action.equalsIgnoreCase(configuration
0685: .getString(ACTION_LOGOUT_KEY,
0686: ACTION_LOGOUT_DEFAULT))) {
0687: logoutAction(data);
0688: }
0689: }
0690:
0691: // This is where the validation of the Session information
0692: // is performed if the user has not logged in yet, then
0693: // the screen is set to be Login. This also handles the
0694: // case of not having a screen defined by also setting the
0695: // screen to Login. If you want people to go to another
0696: // screen other than Login, you need to change that within
0697: // TurbineResources.properties...screen.homepage; or, you
0698: // can specify your own SessionValidator action.
0699: ActionLoader.getInstance().exec(
0700: data,
0701: configuration.getString(
0702: ACTION_SESSION_VALIDATOR_KEY,
0703: ACTION_SESSION_VALIDATOR_DEFAULT));
0704:
0705: // Put the Access Control List into the RunData object, so
0706: // it is easily available to modules. It is also placed
0707: // into the session for serialization. Modules can null
0708: // out the ACL to force it to be rebuilt based on more
0709: // information.
0710: ActionLoader.getInstance().exec(
0711: data,
0712: configuration.getString(
0713: ACTION_ACCESS_CONTROLLER_KEY,
0714: ACTION_ACCESS_CONTROLLER_DEFAULT));
0715:
0716: // Start the execution phase. DefaultPage will execute the
0717: // appropriate action as well as get the Layout from the
0718: // Screen and then execute that. The Layout is then
0719: // responsible for executing the Navigation and Screen
0720: // modules.
0721: //
0722: // Note that by default, this cannot be overridden from
0723: // parameters passed in via post/query data. This is for
0724: // security purposes. You should really never need more
0725: // than just the default page. If you do, add logic to
0726: // DefaultPage to do what you want.
0727:
0728: String defaultPage = (templateService == null) ? null
0729: : templateService.getDefaultPageName(data);
0730:
0731: if (defaultPage == null) {
0732: /*
0733: * In this case none of the template services are running.
0734: * The application may be using ECS for views, or a
0735: * decendent of RawScreen is trying to produce output.
0736: * If there is a 'page.default' property in the TR.props
0737: * then use that, otherwise return DefaultPage which will
0738: * handle ECS view scenerios and RawScreen scenerios. The
0739: * app developer can still specify the 'page.default'
0740: * if they wish but the DefaultPage should work in
0741: * most cases.
0742: */
0743: defaultPage = configuration.getString(PAGE_DEFAULT_KEY,
0744: PAGE_DEFAULT_DEFAULT);
0745: }
0746:
0747: PageLoader.getInstance().exec(data, defaultPage);
0748:
0749: // If a module has set data.acl = null, remove acl from
0750: // the session.
0751: if (data.getACL() == null) {
0752: try {
0753: data.getSession().removeAttribute(
0754: AccessControlList.SESSION_KEY);
0755: } catch (IllegalStateException ignored) {
0756: }
0757: }
0758:
0759: // handle a redirect request
0760: requestRedirected = ((data.getRedirectURI() != null) && (data
0761: .getRedirectURI().length() > 0));
0762: if (requestRedirected) {
0763: if (data.getResponse().isCommitted()) {
0764: requestRedirected = false;
0765: log
0766: .warn("redirect requested, response already committed: "
0767: + data.getRedirectURI());
0768: } else {
0769: data.getResponse().sendRedirect(
0770: data.getRedirectURI());
0771: }
0772: }
0773:
0774: if (!requestRedirected) {
0775: try {
0776: if (data.isPageSet() == false
0777: && data.isOutSet() == false) {
0778: throw new Exception("Nothing to output");
0779: }
0780:
0781: // We are all done! if isPageSet() output that way
0782: // otherwise, data.getOut() has already been written
0783: // to the data.getOut().close() happens below in the
0784: // finally.
0785: if (data.isPageSet() && data.isOutSet() == false) {
0786: // Modules can override these.
0787: data.getResponse().setLocale(data.getLocale());
0788: data.getResponse().setContentType(
0789: data.getContentType());
0790:
0791: // Set the status code.
0792: data.getResponse().setStatus(
0793: data.getStatusCode());
0794: // Output the Page.
0795: data.getPage().output(data.getOut());
0796: }
0797: } catch (Exception e) {
0798: // The output stream was probably closed by the client
0799: // end of things ie: the client clicked the Stop
0800: // button on the browser, so ignore any errors that
0801: // result.
0802: log.debug("Output stream closed? ", e);
0803: }
0804: }
0805: } catch (Exception e) {
0806: handleException(data, res, e);
0807: } catch (Throwable t) {
0808: handleException(data, res, t);
0809: } finally {
0810: // Return the used RunData to the factory for recycling.
0811: rundataService.putRunData(data);
0812: }
0813: }
0814:
0815: /**
0816: * In this application doGet and doPost are the same thing.
0817: *
0818: * @param req Servlet request.
0819: * @param res Servlet response.
0820: * @exception IOException a servlet exception.
0821: * @exception ServletException a servlet exception.
0822: */
0823: public final void doPost(HttpServletRequest req,
0824: HttpServletResponse res) throws IOException,
0825: ServletException {
0826: doGet(req, res);
0827: }
0828:
0829: /**
0830: * This method is executed if the configured Login action should be
0831: * executed by Turbine.
0832: * <p>
0833: * This Action must be performed before the Session validation or we
0834: * get sent in an endless loop back to the Login screen before
0835: * the action can be performed
0836: *
0837: * @param data a RunData object
0838: *
0839: * @throws Exception A problem while logging in occured.
0840: */
0841: private void loginAction(RunData data) throws Exception {
0842: ActionLoader.getInstance().exec(data, data.getAction());
0843: cleanupTemplateContext(data);
0844: data.setAction(null);
0845: }
0846:
0847: /**
0848: * This method is executed if the configured Logout action should be
0849: * executed by Turbine.
0850: * <p>
0851: * This Action must be performed before the Session validation for the
0852: * session validator to send us back to the Login screen.
0853: * <p>
0854: * The existing session is invalidated before the logout action is
0855: * executed.
0856: *
0857: * @param data a RunData object
0858: *
0859: * @throws Exception A problem while logging out occured.
0860: */
0861: private void logoutAction(RunData data) throws Exception {
0862: ActionLoader.getInstance().exec(data, data.getAction());
0863: cleanupTemplateContext(data);
0864: data.setAction(null);
0865: data.getSession().invalidate();
0866: }
0867:
0868: /**
0869: * cleans the Velocity Context if available.
0870: *
0871: * @param data A RunData Object
0872: *
0873: * @throws Exception A problem while cleaning out the Template Context occured.
0874: */
0875: private void cleanupTemplateContext(RunData data) throws Exception {
0876: // This is Velocity specific and shouldn't be done here.
0877: // But this is a band aid until we get real listeners
0878: // here.
0879: TemplateInfo ti = data.getTemplateInfo();
0880: if (ti != null) {
0881: ti.removeTemp(VelocityService.CONTEXT);
0882: }
0883: }
0884:
0885: /**
0886: * Return the servlet info.
0887: *
0888: * @return a string with the servlet information.
0889: */
0890: public final String getServletInfo() {
0891: return "Turbine Servlet";
0892: }
0893:
0894: /**
0895: * This method is about making sure that we catch and display
0896: * errors to the screen in one fashion or another. What happens is
0897: * that it will attempt to show the error using your user defined
0898: * Error Screen. If that fails, then it will resort to just
0899: * displaying the error and logging it all over the place
0900: * including the servlet engine log file, the Turbine log file and
0901: * on the screen.
0902: *
0903: * @param data A Turbine RunData object.
0904: * @param res Servlet response.
0905: * @param t The exception to report.
0906: */
0907: private void handleException(RunData data, HttpServletResponse res,
0908: Throwable t) {
0909: // make sure that the stack trace makes it the log
0910: log.error("Turbine.handleException: ", t);
0911:
0912: String mimeType = "text/plain";
0913: try {
0914: // This is where we capture all exceptions and show the
0915: // Error Screen.
0916: data.setStackTrace(ExceptionUtils.getStackTrace(t), t);
0917:
0918: // setup the screen
0919: data.setScreen(configuration.getString(SCREEN_ERROR_KEY,
0920: SCREEN_ERROR_DEFAULT));
0921:
0922: // do more screen setup for template execution if needed
0923: if (data.getTemplateInfo() != null) {
0924: data.getTemplateInfo().setScreenTemplate(
0925: configuration.getString(TEMPLATE_ERROR_KEY,
0926: TEMPLATE_ERROR_VM));
0927: }
0928:
0929: // Make sure to not execute an action.
0930: data.setAction("");
0931:
0932: PageLoader.getInstance().exec(
0933: data,
0934: configuration.getString(PAGE_DEFAULT_KEY,
0935: PAGE_DEFAULT_DEFAULT));
0936:
0937: data.getResponse().setContentType(data.getContentType());
0938: data.getResponse().setStatus(data.getStatusCode());
0939: if (data.isPageSet()) {
0940: data.getOut().print(data.getPage().toString());
0941: }
0942: }
0943: // Catch this one because it occurs if some code hasn't been
0944: // completely re-compiled after a change..
0945: catch (java.lang.NoSuchFieldError e) {
0946: try {
0947: data.getResponse().setContentType(mimeType);
0948: data.getResponse().setStatus(200);
0949: } catch (Exception ignored) {
0950: // Ignored
0951: }
0952:
0953: try {
0954: data
0955: .getOut()
0956: .print(
0957: "java.lang.NoSuchFieldError: "
0958: + "Please recompile all of your source code.");
0959: } catch (IOException ignored) {
0960: }
0961:
0962: log.error(data.getStackTrace(), e);
0963: }
0964: // Attempt to do *something* at this point...
0965: catch (Throwable reallyScrewedNow) {
0966: StringBuffer msg = new StringBuffer();
0967: msg.append("Horrible Exception: ");
0968: if (data != null) {
0969: msg.append(data.getStackTrace());
0970: } else {
0971: msg.append(t);
0972: }
0973: try {
0974: res.setContentType(mimeType);
0975: res.setStatus(200);
0976: res.getWriter().print(msg.toString());
0977: } catch (Exception ignored) {
0978: }
0979:
0980: log.error(reallyScrewedNow.getMessage(), reallyScrewedNow);
0981: }
0982: }
0983:
0984: /**
0985: * Save some information about this servlet so that
0986: * it can be utilized by object instances that do not
0987: * have direct access to RunData.
0988: *
0989: * @param data
0990: */
0991: public static synchronized void saveServletInfo(RunData data) {
0992: // Store the context path for tools like ContentURI and
0993: // the UIManager that use webapp context path information
0994: // for constructing URLs.
0995:
0996: //
0997: // Bundle all the information above up into a convenient structure
0998: //
0999: serverData = (ServerData) data.getServerData().clone();
1000: }
1001:
1002: /**
1003: * Set the application root for the webapp.
1004: *
1005: * @param val New app root.
1006: */
1007: public static void setApplicationRoot(String val) {
1008: applicationRoot = val;
1009: }
1010:
1011: /**
1012: * Get the application root for this Turbine webapp. This
1013: * concept was started in 3.0 and will allow an app to be
1014: * developed from a standard CVS layout. With a simple
1015: * switch the app will work fully within the servlet
1016: * container for deployment.
1017: *
1018: * @return String applicationRoot
1019: */
1020: public static String getApplicationRoot() {
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: if (path.startsWith("/")) {
1034: path = path.substring(1);
1035: }
1036:
1037: return new File(getApplicationRoot(), path).getAbsolutePath();
1038: }
1039:
1040: /**
1041: * Return an instance of the currently configured Service Manager
1042: *
1043: * @return A service Manager instance
1044: */
1045: private ServiceManager getServiceManager() {
1046: return TurbineServices.getInstance();
1047: }
1048: }
|