| package org.apache.turbine; |
| |
| /* ==================================================================== |
| * The Apache Software License, Version 1.1 |
| * |
| * Copyright (c) 2001 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/>. |
| */ |
| |
| // Java Core Classes |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.util.Enumeration; |
| |
| // Java Servlet Classes |
| import javax.servlet.ServletConfig; |
| import javax.servlet.ServletException; |
| import javax.servlet.http.HttpServlet; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| // Turbine Modules |
| import org.apache.turbine.modules.ActionLoader; |
| import org.apache.turbine.modules.PageLoader; |
| import org.apache.turbine.modules.actions.sessionvalidator.SessionValidator; |
| |
| // Turbine Utility Classes |
| import org.apache.turbine.util.DynamicURI; |
| import org.apache.turbine.util.Log; |
| import org.apache.turbine.util.RunData; |
| import org.apache.turbine.util.RunDataFactory; |
| import org.apache.turbine.util.StringUtils; |
| import org.apache.turbine.util.security.AccessControlList; |
| |
| //Turbine Services |
| import org.apache.turbine.services.TurbineServices; |
| import org.apache.turbine.services.resources.TurbineResources; |
| import org.apache.turbine.services.logging.LoggingService; |
| import org.apache.turbine.services.template.TurbineTemplate; |
| |
| /** |
| * 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>resources</code> the implementation of |
| * {@link org.apache.turbine.services.resources.ResourceService} to be used</li> |
| * <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><br> |
| * |
| * @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> |
| * @version $Id$ |
| */ |
| public class Turbine extends HttpServlet |
| { |
| /** |
| * 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; |
| |
| /** |
| * Whether init succeeded or not. |
| */ |
| private static Throwable initFailure = null; |
| |
| /** |
| * Should initialization activities be performed during doGet() |
| * execution? |
| */ |
| private static boolean firstDoGet = true; |
| |
| /** |
| * This init method will load the default resources from a |
| * properties file. |
| * |
| * @param config typical Servlet initialization parameter. |
| * @exception ServletException a servlet exception. |
| */ |
| public final void init(ServletConfig config) |
| throws ServletException |
| { |
| super.init(config); |
| |
| synchronized ( this.getClass() ) |
| { |
| if(!firstInit) |
| { |
| log ("Double initializaton of Turbine was attempted!"); |
| return; |
| } |
| // executing init will trigger some static initializers, so we have |
| // only one chance. |
| firstInit = false; |
| |
| try |
| { |
| // Initalize TurbineServices and init bootstrap services |
| TurbineServices services = |
| (TurbineServices) TurbineServices.getInstance(); |
| |
| // Initialize essential services (Resources & Logging) |
| services.initPrimaryServices(config); |
| |
| // Initialize other services that require early init |
| services.initServices(config, false); |
| } |
| catch ( Exception e ) |
| { |
| // save the exception to complain loudly later :-) |
| initFailure = e; |
| log ("Turbine: init() failed: " + StringUtils.stackTrace(e)); |
| return; |
| } |
| log ("Turbine: init() Ready to Rumble!"); |
| } |
| } |
| |
| /** |
| * Initializes the services which need <code>RunData</code> to |
| * initialize themselves (post startup). |
| * |
| * @param data The first <code>GET</code> request. |
| */ |
| public final void init(RunData data) |
| { |
| if (firstDoGet) |
| { |
| synchronized (Turbine.class) |
| { |
| if (firstDoGet) |
| { |
| log("Turbine: Starting HTTP initialization of services"); |
| TurbineServices.getInstance().initServices(data); |
| log("Turbine: Completed HTTP initialization of services"); |
| |
| // Mark that we're done. |
| firstDoGet = false; |
| } |
| } |
| } |
| } |
| |
| /** |
| * The <code>Servlet</code> destroy method. Invokes |
| * <code>ServiceBroker</code> tear down method. |
| */ |
| public final void destroy() |
| { |
| // Shut down all Turbine Services. |
| TurbineServices.getInstance().shutdownServices(); |
| System.gc(); |
| |
| log("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 |
| { |
| // Placeholder for the RunData object. |
| RunData data = null; |
| try |
| { |
| // Check to make sure that we started up properly. |
| if (initFailure != null) |
| { |
| throw initFailure; |
| } |
| |
| // Get general RunData here... |
| // Perform turbine specific initialization below. |
| data = RunDataFactory.getRunData( req, res, |
| getServletConfig() ); |
| |
| // If this is the first invocation, perform some |
| // initialization. Certain services need RunData to initialize |
| // themselves. |
| init(data); |
| |
| // Get the instance of the Session Validator. |
| SessionValidator sessionValidator = (SessionValidator)ActionLoader |
| .getInstance().getInstance(TurbineResources.getString( |
| "action.sessionvalidator")); |
| |
| // if this is the redirected stage of the initial request, |
| // check that the session is now not new. |
| // If it is not, then redirect back to the |
| // original URL (i.e. remove the "redirected" pathinfo) |
| if (data.getParameters() |
| .getString(REDIRECTED_PATHINFO_NAME, "false").equals("true")) |
| { |
| if (data.getSession().isNew()) |
| { |
| String message = "Infinite redirect detected..."; |
| log(message); |
| Log.error(message); |
| throw new Exception(message); |
| } |
| else |
| { |
| DynamicURI duri = new DynamicURI (data, true); |
| |
| // Pass on the sent data in pathinfo. |
| for (Enumeration e = data.getParameters().keys() ; |
| e.hasMoreElements() ;) |
| { |
| String key = (String) e.nextElement(); |
| if (!key.equals(REDIRECTED_PATHINFO_NAME)) |
| { |
| String value = |
| (String) data.getParameters().getString ( key ); |
| duri.addPathInfo((String)key, (String)value ); |
| } |
| } |
| |
| data.getResponse().sendRedirect( duri.toString() ); |
| return; |
| } |
| } |
| else |
| { |
| // Insist that the client starts a session before access |
| // to data is allowed. this is done by redirecting them to |
| // the "screen.homepage" page but you could have them go |
| // to any page as a starter (ie: the homepage) |
| // "data.getResponse()" represents the HTTP servlet |
| // response. |
| if ( sessionValidator.requiresNewSession(data) && |
| data.getSession().isNew() ) |
| { |
| DynamicURI duri = new DynamicURI (data, true); |
| |
| // Pass on the sent data in pathinfo. |
| for (Enumeration e = data.getParameters().keys() ; |
| e.hasMoreElements() ;) |
| { |
| String key = (String) e.nextElement(); |
| String value = |
| (String) data.getParameters().getString ( key ); |
| duri.addPathInfo((String)key, (String)value ); |
| } |
| |
| // add a dummy bit of path info to fool browser into |
| // thinking this is a new URL |
| if (!data.getParameters() |
| .containsKey(REDIRECTED_PATHINFO_NAME)) |
| { |
| duri.addPathInfo(REDIRECTED_PATHINFO_NAME, "true"); |
| } |
| |
| // as the session is new take this opportunity to |
| // set the session timeout if specified in TR.properties |
| int timeout = |
| TurbineResources.getInt("session.timeout", -1); |
| |
| if (timeout != -1) |
| { |
| data.getSession().setMaxInactiveInterval(timeout); |
| } |
| |
| data.getResponse().sendRedirect( duri.toString() ); |
| return; |
| } |
| } |
| |
| // Fill in the screen and action variables. |
| data.setScreen ( data.getParameters().getString("screen") ); |
| data.setAction ( data.getParameters().getString("action") ); |
| |
| // Special case for login and logout, this must happen before the |
| // session validator is executed in order either to allow a user to |
| // even login, or to ensure that the session validator gets to |
| // mandate its page selection policy for non-logged in users |
| // after the logout has taken place. |
| if ( data.hasAction() |
| && data.getAction().equalsIgnoreCase(TurbineResources |
| .getString("action.login")) |
| || data.getAction().equalsIgnoreCase(TurbineResources |
| .getString("action.logout"))) |
| { |
| // If a User is logging in, we should refresh the |
| // session here. Invalidating session and starting a |
| // new session would seem to be a good method, but I |
| // (JDM) could not get this to work well (it always |
| // required the user to login twice). Maybe related |
| // to JServ? If we do not clear out the session, it |
| // is possible a new User may accidently (if they |
| // login incorrectly) continue on with information |
| // associated with the previous User. Currently the |
| // only keys stored in the session are "turbine.user" |
| // and "turbine.acl". |
| if (data.getAction().equalsIgnoreCase(TurbineResources |
| .getString("action.login"))) |
| { |
| String[] names = data.getSession().getValueNames(); |
| if (names != null) |
| { |
| for (int i=0; i< names.length; i++) |
| { |
| data.getSession().removeValue(names[i]); |
| } |
| } |
| } |
| ActionLoader.getInstance().exec ( data, data.getAction() ); |
| data.setAction(null); |
| } |
| |
| // This is where the validation of the Session information |
| // is performed if the user has not logged in yet, then |
| // the screen is set to be Login. This also handles the |
| // case of not having a screen defined by also setting the |
| // screen to Login. If you want people to go to another |
| // screen other than Login, you need to change that within |
| // TurbineResources.properties...screen.homepage; or, you |
| // can specify your own SessionValidator action. |
| ActionLoader.getInstance().exec( |
| data,TurbineResources.getString("action.sessionvalidator") ); |
| |
| // Put the Access Control List into the RunData object, so |
| // it is easily available to modules. It is also placed |
| // into the session for serialization. Modules can null |
| // out the ACL to force it to be rebuilt based on more |
| // information. |
| ActionLoader.getInstance().exec( |
| data,TurbineResources.getString("action.accesscontroller")); |
| |
| // Start the execution phase. DefaultPage will execute the |
| // appropriate action as well as get the Layout from the |
| // Screen and then execute that. The Layout is then |
| // responsible for executing the Navigation and Screen |
| // modules. |
| // |
| // Note that by default, this cannot be overridden from |
| // parameters passed in via post/query data. This is for |
| // security purposes. You should really never need more |
| // than just the default page. If you do, add logic to |
| // DefaultPage to do what you want. |
| |
| String defaultPage = TurbineTemplate.getDefaultPageName(data); |
| |
| if (defaultPage == null) |
| { |
| /* |
| * In this case none of the template services are running. |
| * The application may be using ECS for views, or a |
| * decendent of RawScreen is trying to produce output. |
| * If there is a 'page.default' property in the TR.props |
| * then use that, otherwise return DefaultPage which will |
| * handle ECS view scenerios and RawScreen scenerios. The |
| * app developer can still specify the 'page.default' |
| * if they wish but the DefaultPage should work in |
| * most cases. |
| */ |
| defaultPage = TurbineResources.getString( |
| "page.default", "DefaultPage"); |
| } |
| |
| PageLoader.getInstance().exec(data, defaultPage); |
| |
| // If a module has set data.acl = null, remove acl from |
| // the session. |
| if ( data.getACL() == null ) |
| { |
| data.getSession().removeValue( |
| AccessControlList.SESSION_KEY); |
| } |
| |
| try |
| { |
| if ( data.isPageSet() == false && |
| data.isOutSet() == false ) |
| throw new Exception ( "Nothing to output" ); |
| |
| // We are all done! if isPageSet() output that way |
| // otherwise, data.getOut() has already been written |
| // to the data.getOut().close() happens below in the |
| // finally. |
| if ( data.isPageSet() && data.isOutSet() == false ) |
| { |
| // Modules can override these. |
| data.getResponse() |
| .setLocale( data.getLocale() ); |
| data.getResponse() |
| .setContentType( data.getContentType() ); |
| |
| // Handle the case where a module may want to send |
| // a redirect. |
| if ( ( data.getStatusCode() == 301 || |
| data.getStatusCode() == 302 ) && |
| data.getRedirectURI() != null ) |
| { |
| data.getResponse() |
| .sendRedirect ( data.getRedirectURI() ); |
| } |
| |
| // Set the status code. |
| data.getResponse().setStatus ( data.getStatusCode() ); |
| // Output the Page. |
| data.getPage().output (data.getOut()); |
| } |
| } |
| catch ( Exception e ) |
| { |
| // The output stream was probably closed by the client |
| // end of things ie: the client clicked the Stop |
| // button on the browser, so ignore any errors that |
| // result. |
| } |
| } |
| catch ( Exception e ) |
| { |
| handleException(data, res, e); |
| } |
| catch (Throwable t) |
| { |
| handleException(data, res, t); |
| } |
| finally |
| { |
| // Make sure to close the outputstream when we are done. |
| try |
| { |
| data.getOut().close(); |
| } |
| catch (Exception e) |
| { |
| // Ignore. |
| } |
| |
| // Return the used RunData to the factory for recycling. |
| RunDataFactory.putRunData(data); |
| } |
| } |
| |
| /** |
| * 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 RunData object. |
| * @param res Servlet response. |
| * @param e The exception to report. |
| */ |
| private final void handleException(RunData data, |
| HttpServletResponse res, |
| Throwable t) |
| { |
| // make sure that the stack trace makes it the log |
| Log.error("Turbine.handleException: "+t.getMessage()); |
| Log.error(t); |
| |
| String mimeType = "text/plain"; |
| try |
| { |
| // This is where we capture all exceptions and show the |
| // Error Screen. |
| data.setStackTrace(StringUtils.stackTrace(t),t); |
| |
| // setup the screen |
| data.setScreen(TurbineResources |
| .getString("screen.error")); |
| |
| // do more screen setup for template execution if needed |
| if (data.getTemplateInfo() != null) |
| data.getTemplateInfo() |
| .setScreenTemplate(TurbineResources |
| .getString("template.error")); |
| |
| // Make sure to not execute an action. |
| data.setAction (""); |
| |
| PageLoader.getInstance() |
| .exec(data, |
| TurbineResources.getString("page.default", |
| "DefaultPage")); |
| |
| data.getResponse().setContentType( data.getContentType() ); |
| data.getResponse().setStatus ( data.getStatusCode() ); |
| if( data.isPageSet() ) |
| data.getOut().print ( data.getPage().toString() ); |
| } |
| // 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.getOut().print ("java.lang.NoSuchFieldError: " + |
| "Please recompile all of your " + |
| "source code."); |
| } |
| catch (IOException ignored) {} |
| |
| log ( data.getStackTrace() ); |
| org.apache.turbine.util.Log.error ( e.getMessage(), 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) {} |
| org.apache.turbine.util.Log.error ( |
| reallyScrewedNow.getMessage(), reallyScrewedNow ); |
| } |
| } |
| } |