View Javadoc

1   package org.apache.turbine;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileNotFoundException;
25  import java.io.IOException;
26  import java.io.UnsupportedEncodingException;
27  import java.util.Properties;
28  
29  import javax.servlet.ServletConfig;
30  import javax.servlet.ServletContext;
31  import javax.servlet.ServletException;
32  import javax.servlet.http.HttpServlet;
33  import javax.servlet.http.HttpServletRequest;
34  import javax.servlet.http.HttpServletResponse;
35  
36  import org.apache.commons.configuration.Configuration;
37  import org.apache.commons.configuration.ConfigurationFactory;
38  import org.apache.commons.configuration.PropertiesConfiguration;
39  import org.apache.commons.lang.StringUtils;
40  import org.apache.commons.lang.exception.ExceptionUtils;
41  import org.apache.commons.logging.Log;
42  import org.apache.commons.logging.LogFactory;
43  import org.apache.log4j.PropertyConfigurator;
44  import org.apache.turbine.modules.ActionLoader;
45  import org.apache.turbine.modules.PageLoader;
46  import org.apache.turbine.services.ServiceManager;
47  import org.apache.turbine.services.TurbineServices;
48  import org.apache.turbine.services.avaloncomponent.AvalonComponentService;
49  import org.apache.turbine.services.rundata.RunDataService;
50  import org.apache.turbine.services.rundata.TurbineRunDataFacade;
51  import org.apache.turbine.services.template.TemplateService;
52  import org.apache.turbine.services.template.TurbineTemplate;
53  import org.apache.turbine.services.velocity.VelocityService;
54  import org.apache.turbine.util.RunData;
55  import org.apache.turbine.util.ServerData;
56  import org.apache.turbine.util.TurbineConfig;
57  import org.apache.turbine.util.TurbineException;
58  import org.apache.turbine.util.security.AccessControlList;
59  import org.apache.turbine.util.template.TemplateInfo;
60  import org.apache.turbine.util.uri.URIConstants;
61  
62  /***
63   * Turbine is the main servlet for the entire system. It is <code>final</code>
64   * because you should <i>not</i> ever need to subclass this servlet.  If you
65   * need to perform initialization of a service, then you should implement the
66   * Services API and let your code be initialized by it.
67   * If you need to override something in the <code>doGet()</code> or
68   * <code>doPost()</code> methods, edit the TurbineResources.properties file and
69   * specify your own classes there.
70   * <p>
71   * Turbine servlet recognizes the following initialization parameters.
72   * <ul>
73   * <li><code>properties</code> the path to TurbineResources.properties file
74   * used by the default implementation of <code>ResourceService</code>, relative
75   * to the application root.</li>
76   * <li><code>basedir</code> this parameter is used <strong>only</strong> if your
77   * application server does not support web applications, or the or does not
78   * support <code>ServletContext.getRealPath(String)</code> method correctly.
79   * You can use this parameter to specify the directory within the server's
80   * filesystem, that is the base of your web application.</li>
81   * </ul>
82   *
83   * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
84   * @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a>
85   * @author <a href="mailto:greg@shwoop.com">Greg Ritter</a>
86   * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
87   * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
88   * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
89   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
90   * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
91   * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
92   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
93   * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
94   * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
95   * @author <a href="mailto:seade@backstagetech.com.au">Scott Eade</a>
96   * @version $Id: Turbine.java 543399 2007-06-01 06:15:17Z seade $
97   */
98  public class Turbine
99          extends HttpServlet
100         implements TurbineConstants
101 {
102     /*** SerialVersionUID for serialization */
103     private static final long serialVersionUID = -6895381097045304308L;
104 
105     /***
106      * Name of path info parameter used to indicate the redirected stage of
107      * a given user's initial Turbine request
108      */
109     public static final String REDIRECTED_PATHINFO_NAME = "redirected";
110 
111     /*** The base directory key */
112     public static final String BASEDIR_KEY = "basedir";
113 
114     /***
115      * In certain situations the init() method is called more than once,
116      * somtimes even concurrently. This causes bad things to happen,
117      * so we use this flag to prevent it.
118      */
119     private static boolean firstInit = true;
120 
121     /*** Whether init succeeded or not. */
122     private static Throwable initFailure = null;
123 
124     /***
125      * Should initialization activities be performed during doGet() execution?
126      */
127     private static boolean firstDoGet = true;
128 
129     /***
130      * Keep all the properties of the web server in a convenient data
131      * structure
132      */
133     private static ServerData serverData = null;
134 
135     /*** The base from which the Turbine application will operate. */
136     private static String applicationRoot;
137 
138     /*** Servlet config for this Turbine webapp. */
139     private static ServletConfig servletConfig;
140 
141     /*** Servlet context for this Turbine webapp. */
142     private static ServletContext servletContext;
143 
144     /***
145      * The webapp root where the Turbine application
146      * is running in the servlet container.
147      * This might differ from the application root.
148      */
149     private static String webappRoot;
150 
151     /*** Our internal configuration object */
152     private static Configuration configuration = null;
153 
154     /*** A reference to the Template Service */
155     private TemplateService templateService = null;
156 
157     /*** A reference to the RunData Service */
158     private RunDataService rundataService = null;
159 
160     /*** Default Input encoding if the servlet container does not report an encoding */
161     private String inputEncoding = null;
162 
163     /*** Logging class from commons.logging */
164     private static Log log = LogFactory.getLog(Turbine.class);
165 
166     /***
167      * This init method will load the default resources from a
168      * properties file.
169      *
170      * This method is called by init(ServletConfig config)
171      *
172      * @exception ServletException a servlet exception.
173      */
174     public final void init() throws ServletException
175     {
176         synchronized (this.getClass())
177         {
178             super.init();
179             ServletConfig config = getServletConfig();
180 
181             if (!firstInit)
182             {
183                 log.info("Double initialization of Turbine was attempted!");
184                 return;
185             }
186             // executing init will trigger some static initializers, so we have
187             // only one chance.
188             firstInit = false;
189 
190             try
191             {
192                 ServletContext context = config.getServletContext();
193 
194                 configure(config, context);
195 
196                 templateService = TurbineTemplate.getService();
197                 rundataService = TurbineRunDataFacade.getService();
198 
199                 if (rundataService == null)
200                 {
201                     throw new TurbineException(
202                             "No RunData Service configured!");
203                 }
204 
205             }
206             catch (Exception e)
207             {
208                 // save the exception to complain loudly later :-)
209                 initFailure = e;
210                 log.fatal("Turbine: init() failed: ", e);
211                 throw new ServletException("Turbine: init() failed", e);
212             }
213             log.info("Turbine: init() Ready to Rumble!");
214         }
215     }
216 
217     /***
218      * Read the master configuration file in, configure logging
219      * and start up any early services.
220      *
221      * @param config The Servlet Configuration supplied by the container
222      * @param context The Servlet Context supplied by the container
223      *
224      * @throws Exception A problem occured while reading the configuration or performing early startup
225      */
226 
227     private void configure(ServletConfig config, ServletContext context)
228             throws Exception
229     {
230         // Set the application root. This defaults to the webapp
231         // context if not otherwise set. This is to allow 2.1 apps
232         // to be developed from CVS. This feature will carry over
233         // into 3.0.
234         applicationRoot = findInitParameter(context, config,
235                 APPLICATION_ROOT_KEY,
236                 APPLICATION_ROOT_DEFAULT);
237 
238         webappRoot = config.getServletContext().getRealPath("/");
239         // log.info("Web Application root is " + webappRoot);
240         // log.info("Application root is "     + applicationRoot);
241 
242         if (applicationRoot == null || applicationRoot.equals(WEB_CONTEXT))
243         {
244             applicationRoot = webappRoot;
245             // log.info("got empty or 'webContext' Application root. Application root now: " + applicationRoot);
246         }
247 
248         // Set the applicationRoot for this webapp.
249         setApplicationRoot(applicationRoot);
250 
251         // Create any directories that need to be setup for
252         // a running Turbine application.
253         createRuntimeDirectories(context, config);
254 
255         //
256         // Now we run the Turbine configuration code. There are two ways
257         // to configure Turbine:
258         //
259         // a) By supplying an web.xml init parameter called "configuration"
260         //
261         // <init-param>
262         //   <param-name>configuration</param-name>
263         //   <param-value>/WEB-INF/conf/turbine.xml</param-value>
264         // </init-param>
265         //
266         // This loads an XML based configuration file.
267         //
268         // b) By supplying an web.xml init parameter called "properties"
269         //
270         // <init-param>
271         //   <param-name>properties</param-name>
272         //   <param-value>/WEB-INF/conf/TurbineResources.properties</param-value>
273         // </init-param>
274         //
275         // This loads a Properties based configuration file. Actually, these are
276         // extended properties as provided by commons-configuration
277         //
278         // If neither a) nor b) is supplied, Turbine will fall back to the
279         // known behaviour of loading a properties file called
280         // /WEB-INF/conf/TurbineResources.properties relative to the
281         // web application root.
282 
283         String confFile= findInitParameter(context, config,
284                 TurbineConfig.CONFIGURATION_PATH_KEY,
285                 null);
286 
287         String confPath;
288         String confStyle = "unset";
289 
290         if (StringUtils.isNotEmpty(confFile))
291         {
292             confPath = getRealPath(confFile);
293             ConfigurationFactory configurationFactory = new ConfigurationFactory(confPath);
294             configurationFactory.setBasePath(getApplicationRoot());
295             configuration = configurationFactory.getConfiguration();
296             confStyle = "XML";
297         }
298         else
299         {
300             confFile = findInitParameter(context, config,
301                     TurbineConfig.PROPERTIES_PATH_KEY,
302                     TurbineConfig.PROPERTIES_PATH_DEFAULT);
303 
304             confPath = getRealPath(confFile);
305 
306             // This should eventually be a Configuration
307             // interface so that service and app configuration
308             // can be stored anywhere.
309             configuration = (Configuration) new PropertiesConfiguration(confPath);
310             confStyle = "Properties";
311         }
312 
313 
314         //
315         // Set up logging as soon as possible
316         //
317         String log4jFile = configuration.getString(LOG4J_CONFIG_FILE,
318                                                    LOG4J_CONFIG_FILE_DEFAULT);
319 
320         if (StringUtils.isNotEmpty(log4jFile) &&
321                 !log4jFile.equalsIgnoreCase("none"))
322         {
323             log4jFile = getRealPath(log4jFile);
324 
325             //
326             // Load the config file above into a Properties object and
327             // fix up the Application root
328             //
329             Properties p = new Properties();
330             try
331             {
332                 p.load(new FileInputStream(log4jFile));
333                 p.setProperty(APPLICATION_ROOT_KEY, getApplicationRoot());
334                 PropertyConfigurator.configure(p);
335 
336                 log.info("Configured log4j from " + log4jFile);
337             }
338             catch (FileNotFoundException fnf)
339             {
340                 System.err.println("Could not open Log4J configuration file "
341                         + log4jFile + ": ");
342                 fnf.printStackTrace();
343             }
344         }
345 
346         // Now report our successful configuration to the world
347         log.info("Loaded configuration  (" + confStyle + ") from " + confFile + " (" + confPath + ")");
348 
349 
350         setTurbineServletConfig(config);
351         setTurbineServletContext(context);
352 
353         getServiceManager().setApplicationRoot(applicationRoot);
354 
355         // We want to set a few values in the configuration so
356         // that ${variable} interpolation will work for
357         //
358         // ${applicationRoot}
359         // ${webappRoot}
360         configuration.setProperty(APPLICATION_ROOT_KEY, applicationRoot);
361         configuration.setProperty(WEBAPP_ROOT_KEY, webappRoot);
362 
363 
364         //
365         // Be sure, that our essential services get run early
366         //
367         configuration.setProperty(TurbineServices.SERVICE_PREFIX +
368                                   AvalonComponentService.SERVICE_NAME + ".earlyInit",
369                                   Boolean.TRUE);
370 
371         getServiceManager().setConfiguration(configuration);
372 
373         // Initialize the service manager. Services
374         // that have its 'earlyInit' property set to
375         // a value of 'true' will be started when
376         // the service manager is initialized.
377         getServiceManager().init();
378 
379         // Get the default input encoding
380         inputEncoding = configuration.getString(
381                 TurbineConstants.PARAMETER_ENCODING_KEY,
382                 TurbineConstants.PARAMETER_ENCODING_DEFAULT);
383 
384         if (log.isDebugEnabled())
385         {
386             log.debug("Input Encoding has been set to " + inputEncoding);
387         }
388     }
389 
390     /***
391      * Create any directories that might be needed during
392      * runtime. Right now this includes:
393      *
394      * <ul>
395      *
396      * <li>The directory to write the log files to (relative to the
397      * web application root), or <code>null</code> for the default of
398      * <code>/logs</code>.  The directory is specified via the {@link
399      * TurbineConstants#LOGGING_ROOT} parameter.</li>
400      *
401      * </ul>
402      *
403      * @param context Global initialization parameters.
404      * @param config Initialization parameters specific to the Turbine
405      * servlet.
406      */
407     private static void createRuntimeDirectories(ServletContext context,
408                                                  ServletConfig config)
409     {
410         String path = findInitParameter(context, config,
411                                         LOGGING_ROOT_KEY,
412                                         LOGGING_ROOT_DEFAULT);
413 
414         File logDir = new File(getRealPath(path));
415         if (!logDir.exists())
416         {
417             // Create the logging directory
418             if (!logDir.mkdirs())
419             {
420                 System.err.println("Cannot create directory for logs!");
421             }
422         }
423     }
424 
425     /***
426      * Finds the specified servlet configuration/initialization
427      * parameter, looking first for a servlet-specific parameter, then
428      * for a global parameter, and using the provided default if not
429      * found.
430      */
431     protected static final String findInitParameter(ServletContext context,
432             ServletConfig config, String name, String defaultValue)
433     {
434         String path = null;
435 
436         // Try the name as provided first.
437         boolean usingNamespace = name.startsWith(CONFIG_NAMESPACE);
438         while (true)
439         {
440             path = config.getInitParameter(name);
441             if (StringUtils.isEmpty(path))
442             {
443                 path = context.getInitParameter(name);
444                 if (StringUtils.isEmpty(path))
445                 {
446                     // The named parameter didn't yield a value.
447                     if (usingNamespace)
448                     {
449                         path = defaultValue;
450                     }
451                     else
452                     {
453                         // Try again using Turbine's namespace.
454                         name = CONFIG_NAMESPACE + '.' + name;
455                         usingNamespace = true;
456                         continue;
457                     }
458                 }
459             }
460             break;
461         }
462 
463         return path;
464     }
465 
466     /***
467      * Initializes the services which need <code>RunData</code> to
468      * initialize themselves (post startup).
469      *
470      * @param data The first <code>GET</code> request.
471      */
472     public final void init(RunData data)
473     {
474         synchronized (Turbine.class)
475         {
476             if (firstDoGet)
477             {
478                 // All we want to do here is save some servlet
479                 // information so that services and processes
480                 // that don't have direct access to a RunData
481                 // object can still know something about
482                 // the servlet environment.
483                 saveServletInfo(data);
484 
485                 // Mark that we're done.
486                 firstDoGet = false;
487                 log.info("Turbine: first Request successful");
488             }
489         }
490     }
491 
492     /***
493      * Return the current configuration with all keys included
494      *
495      * @return a Configuration Object
496      */
497     public static Configuration getConfiguration()
498     {
499         return configuration;
500     }
501 
502     /***
503      * Return the server name.
504      *
505      * @return String server name
506      */
507     public static String getServerName()
508     {
509         return getDefaultServerData().getServerName();
510     }
511 
512     /***
513      * Return the server scheme.
514      *
515      * @return String server scheme
516      */
517     public static String getServerScheme()
518     {
519         return getDefaultServerData().getServerScheme();
520     }
521 
522     /***
523      * Return the server port.
524      *
525      * @return String server port
526      */
527     public static String getServerPort()
528     {
529         return Integer.toString(getDefaultServerData().getServerPort());
530     }
531 
532     /***
533      * Get the script name. This is the initial script name.
534      * Actually this is probably not needed any more. I'll
535      * check. jvz.
536      *
537      * @return String initial script name.
538      */
539     public static String getScriptName()
540     {
541         return getDefaultServerData().getScriptName();
542     }
543 
544     /***
545      * Return the context path.
546      *
547      * @return String context path
548      */
549     public static String getContextPath()
550     {
551         return getDefaultServerData().getContextPath();
552     }
553 
554     /***
555      * Return all the Turbine Servlet information (Server Name, Port,
556      * Scheme in a ServerData structure. This is generated from the
557      * values set when initializing the Turbine and may not be correct
558      * if you're running in a clustered structure. You can provide default
559      * values in your configuration for cases where access is requied before
560      * your application is first accessed by a user.  This might be used
561      * if you need a DataURI and have no RunData object handy.
562      *
563      * @return An initialized ServerData object
564      */
565     public static ServerData getDefaultServerData()
566     {
567         if (serverData == null)
568         {
569             String serverName
570                     = configuration.getString(DEFAULT_SERVER_NAME_KEY);
571             if (serverName == null)
572             {
573                 log.error("ServerData Information requested from Turbine before first request!");
574             }
575             else
576             {
577                 log.info("ServerData Information retrieved from configuration.");
578             }
579             // Will be overwritten once the first request is run;
580             serverData = new ServerData(serverName,
581                     configuration.getInt(DEFAULT_SERVER_PORT_KEY,
582                             URIConstants.HTTP_PORT),
583                     configuration.getString(DEFAULT_SERVER_SCHEME_KEY,
584                             URIConstants.HTTP),
585                     configuration.getString(DEFAULT_SCRIPT_NAME_KEY),
586                     configuration.getString(DEFAULT_CONTEXT_PATH_KEY));
587         }
588         return serverData;
589     }
590 
591     /***
592      * Set the servlet config for this turbine webapp.
593      *
594      * @param config New servlet config
595      */
596     public static void setTurbineServletConfig(ServletConfig config)
597     {
598         servletConfig = config;
599     }
600 
601     /***
602      * Get the servlet config for this turbine webapp.
603      *
604      * @return ServletConfig
605      */
606     public static ServletConfig getTurbineServletConfig()
607     {
608         return servletConfig;
609     }
610 
611     /***
612      * Set the servlet context for this turbine webapp.
613      *
614      * @param context New servlet context.
615      */
616     public static void setTurbineServletContext(ServletContext context)
617     {
618         servletContext = context;
619     }
620 
621     /***
622      * Get the servlet context for this turbine webapp.
623      *
624      * @return ServletContext
625      */
626     public static ServletContext getTurbineServletContext()
627     {
628         return servletContext;
629     }
630 
631     /***
632      * The <code>Servlet</code> destroy method.  Invokes
633      * <code>ServiceBroker</code> tear down method.
634      */
635     public final void destroy()
636     {
637         // Shut down all Turbine Services.
638         getServiceManager().shutdownServices();
639         System.gc();
640 
641         firstInit = true;
642         firstDoGet = true;
643         log.info("Turbine: Done shutting down!");
644     }
645 
646     /***
647      * The primary method invoked when the Turbine servlet is executed.
648      *
649      * @param req Servlet request.
650      * @param res Servlet response.
651      * @exception IOException a servlet exception.
652      * @exception ServletException a servlet exception.
653      */
654     public final void doGet(HttpServletRequest req, HttpServletResponse res)
655             throws IOException, ServletException
656     {
657         // set to true if the request is to be redirected by the page
658         boolean requestRedirected = false;
659 
660         // Placeholder for the RunData object.
661         RunData data = null;
662         try
663         {
664             // Check to make sure that we started up properly.
665             if (initFailure != null)
666             {
667                 throw initFailure;
668             }
669 
670             //
671             // If the servlet container gives us no clear indication about the
672             // Encoding of the contents, set it to our default value.
673             if (req.getCharacterEncoding() == null)
674             {
675                 if (log.isDebugEnabled())
676                 {
677                     log.debug("Changing Input Encoding to " + inputEncoding);
678                 }
679 
680                 try
681                 {
682                     req.setCharacterEncoding(inputEncoding);
683                 }
684                 catch (UnsupportedEncodingException uee)
685                 {
686                     log.warn("Could not change request encoding to " + inputEncoding, uee);
687                 }
688             }
689 
690             // Get general RunData here...
691             // Perform turbine specific initialization below.
692             data = rundataService.getRunData(req, res, getServletConfig());
693 
694             // If this is the first invocation, perform some
695             // initialization.  Certain services need RunData to initialize
696             // themselves.
697             if (firstDoGet)
698             {
699                 init(data);
700             }
701 
702             // set the session timeout if specified in turbine's properties
703             // file if this is a new session
704             if (data.getSession().isNew())
705             {
706                 int timeout = configuration.getInt(SESSION_TIMEOUT_KEY,
707                                                    SESSION_TIMEOUT_DEFAULT);
708 
709                 if (timeout != SESSION_TIMEOUT_DEFAULT)
710                 {
711                     data.getSession().setMaxInactiveInterval(timeout);
712                 }
713             }
714 
715             // Fill in the screen and action variables.
716             data.setScreen(data.getParameters().getString(URIConstants.CGI_SCREEN_PARAM));
717             data.setAction(data.getParameters().getString(URIConstants.CGI_ACTION_PARAM));
718 
719             // Special case for login and logout, this must happen before the
720             // session validator is executed in order either to allow a user to
721             // even login, or to ensure that the session validator gets to
722             // mandate its page selection policy for non-logged in users
723             // after the logout has taken place.
724             if (data.hasAction())
725             {
726                 String action = data.getAction();
727                 log.debug("action = " + action);
728 
729                 if (action.equalsIgnoreCase(
730                         configuration.getString(ACTION_LOGIN_KEY,
731                                                 ACTION_LOGIN_DEFAULT)))
732                 {
733                     loginAction(data);
734                 }
735                 else if (action.equalsIgnoreCase(
736                         configuration.getString(ACTION_LOGOUT_KEY,
737                                                 ACTION_LOGOUT_DEFAULT)))
738                 {
739                    logoutAction(data);
740                 }
741             }
742 
743             // This is where the validation of the Session information
744             // is performed if the user has not logged in yet, then
745             // the screen is set to be Login. This also handles the
746             // case of not having a screen defined by also setting the
747             // screen to Login. If you want people to go to another
748             // screen other than Login, you need to change that within
749             // TurbineResources.properties...screen.homepage; or, you
750             // can specify your own SessionValidator action.
751             ActionLoader.getInstance().exec(
752                     data, configuration.getString(ACTION_SESSION_VALIDATOR_KEY,
753                         ACTION_SESSION_VALIDATOR_DEFAULT));
754 
755             // Put the Access Control List into the RunData object, so
756             // it is easily available to modules.  It is also placed
757             // into the session for serialization.  Modules can null
758             // out the ACL to force it to be rebuilt based on more
759             // information.
760             ActionLoader.getInstance().exec(
761                     data, configuration.getString(ACTION_ACCESS_CONTROLLER_KEY,
762                         ACTION_ACCESS_CONTROLLER_DEFAULT));
763 
764             // Start the execution phase. DefaultPage will execute the
765             // appropriate action as well as get the Layout from the
766             // Screen and then execute that. The Layout is then
767             // responsible for executing the Navigation and Screen
768             // modules.
769             //
770             // Note that by default, this cannot be overridden from
771             // parameters passed in via post/query data. This is for
772             // security purposes.  You should really never need more
773             // than just the default page.  If you do, add logic to
774             // DefaultPage to do what you want.
775 
776             String defaultPage = (templateService == null)
777                     ? null :templateService.getDefaultPageName(data);
778 
779             if (defaultPage == null)
780             {
781                 /*
782                  * In this case none of the template services are running.
783                  * The application may be using ECS for views, or a
784                  * decendent of RawScreen is trying to produce output.
785                  * If there is a 'page.default' property in the TR.props
786                  * then use that, otherwise return DefaultPage which will
787                  * handle ECS view scenerios and RawScreen scenerios. The
788                  * app developer can still specify the 'page.default'
789                  * if they wish but the DefaultPage should work in
790                  * most cases.
791                  */
792                 defaultPage = configuration.getString(PAGE_DEFAULT_KEY,
793                         PAGE_DEFAULT_DEFAULT);
794             }
795 
796             PageLoader.getInstance().exec(data, defaultPage);
797 
798             // If a module has set data.acl = null, remove acl from
799             // the session.
800             if (data.getACL() == null)
801             {
802                 try
803                 {
804                     data.getSession().removeAttribute(
805                             AccessControlList.SESSION_KEY);
806                 }
807                 catch (IllegalStateException ignored)
808                 {
809                 }
810             }
811 
812             // handle a redirect request
813             requestRedirected = ((data.getRedirectURI() != null)
814                                  && (data.getRedirectURI().length() > 0));
815             if (requestRedirected)
816             {
817                 if (data.getResponse().isCommitted())
818                 {
819                     requestRedirected = false;
820                     log.warn("redirect requested, response already committed: " +
821                              data.getRedirectURI());
822                 }
823                 else
824                 {
825                     data.getResponse().sendRedirect(data.getRedirectURI());
826                 }
827             }
828 
829             if (!requestRedirected)
830             {
831                 try
832                 {
833                     if (data.isPageSet() == false && data.isOutSet() == false)
834                     {
835                         throw new Exception("Nothing to output");
836                     }
837 
838                     // We are all done! if isPageSet() output that way
839                     // otherwise, data.getOut() has already been written
840                     // to the data.getOut().close() happens below in the
841                     // finally.
842                     if (data.isPageSet() && data.isOutSet() == false)
843                     {
844                         // Modules can override these.
845                         data.getResponse().setLocale(data.getLocale());
846                         data.getResponse().setContentType(
847                                 data.getContentType());
848 
849                         // Set the status code.
850                         data.getResponse().setStatus(data.getStatusCode());
851                         // Output the Page.
852                         data.getPage().output(data.getOut());
853                     }
854                 }
855                 catch (Exception e)
856                 {
857                     // The output stream was probably closed by the client
858                     // end of things ie: the client clicked the Stop
859                     // button on the browser, so ignore any errors that
860                     // result.
861                     log.debug("Output stream closed? ", e);
862                 }
863             }
864         }
865         catch (Exception e)
866         {
867             handleException(data, res, e);
868         }
869         catch (Throwable t)
870         {
871             handleException(data, res, t);
872         }
873         finally
874         {
875             // Return the used RunData to the factory for recycling.
876             rundataService.putRunData(data);
877         }
878     }
879 
880     /***
881      * In this application doGet and doPost are the same thing.
882      *
883      * @param req Servlet request.
884      * @param res Servlet response.
885      * @exception IOException a servlet exception.
886      * @exception ServletException a servlet exception.
887      */
888     public final void doPost(HttpServletRequest req, HttpServletResponse res)
889             throws IOException, ServletException
890     {
891         doGet(req, res);
892     }
893 
894     /***
895      * This method is executed if the configured Login action should be
896      * executed by Turbine.
897      * <p>
898      * This Action must be performed before the Session validation or we
899      * get sent in an endless loop back to the Login screen before
900      * the action can be performed
901      *
902      * @param data a RunData object
903      *
904      * @throws Exception A problem while logging in occured.
905      */
906     private void loginAction(RunData data)
907             throws Exception
908     {
909         ActionLoader.getInstance().exec(data, data.getAction());
910         cleanupTemplateContext(data);
911         data.setAction(null);
912     }
913 
914     /***
915      * This method is executed if the configured Logout action should be
916      * executed by Turbine.
917      * <p>
918      * This Action must be performed before the Session validation for the
919      * session validator to send us back to the Login screen.
920      * <p>
921      * The existing session is invalidated before the logout action is
922      * executed.
923      *
924      * @param data a RunData object
925      *
926      * @throws Exception A problem while logging out occured.
927      */
928     private void logoutAction(RunData data)
929             throws Exception
930     {
931         ActionLoader.getInstance().exec(data, data.getAction());
932         cleanupTemplateContext(data);
933         data.setAction(null);
934         data.getSession().invalidate();
935     }
936 
937     /***
938      * cleans the Velocity Context if available.
939      *
940      * @param data A RunData Object
941      *
942      * @throws Exception A problem while cleaning out the Template Context occured.
943      */
944     private void cleanupTemplateContext(RunData data)
945             throws Exception
946     {
947         // This is Velocity specific and shouldn't be done here.
948         // But this is a band aid until we get real listeners
949         // here.
950         TemplateInfo ti = data.getTemplateInfo();
951         if (ti != null)
952         {
953             ti.removeTemp(VelocityService.CONTEXT);
954         }
955     }
956 
957     /***
958      * Return the servlet info.
959      *
960      * @return a string with the servlet information.
961      */
962     public final String getServletInfo()
963     {
964         return "Turbine Servlet";
965     }
966 
967     /***
968      * This method is about making sure that we catch and display
969      * errors to the screen in one fashion or another. What happens is
970      * that it will attempt to show the error using your user defined
971      * Error Screen. If that fails, then it will resort to just
972      * displaying the error and logging it all over the place
973      * including the servlet engine log file, the Turbine log file and
974      * on the screen.
975      *
976      * @param data A Turbine RunData object.
977      * @param res Servlet response.
978      * @param t The exception to report.
979      */
980     private void handleException(RunData data, HttpServletResponse res,
981                                        Throwable t)
982     {
983         // make sure that the stack trace makes it the log
984         log.error("Turbine.handleException: ", t);
985 
986         String mimeType = "text/plain";
987         try
988         {
989             // This is where we capture all exceptions and show the
990             // Error Screen.
991             data.setStackTrace(ExceptionUtils.getStackTrace(t), t);
992 
993             // setup the screen
994             data.setScreen(configuration.getString(SCREEN_ERROR_KEY,
995                     SCREEN_ERROR_DEFAULT));
996 
997             // do more screen setup for template execution if needed
998             if (data.getTemplateInfo() != null)
999             {
1000                 data.getTemplateInfo()
1001                     .setScreenTemplate(configuration.getString(
1002                             TEMPLATE_ERROR_KEY, TEMPLATE_ERROR_VM));
1003             }
1004 
1005             // Make sure to not execute an action.
1006             data.setAction("");
1007 
1008             PageLoader.getInstance().exec(data,
1009                     configuration.getString(PAGE_DEFAULT_KEY,
1010                             PAGE_DEFAULT_DEFAULT));
1011 
1012             data.getResponse().setContentType(data.getContentType());
1013             data.getResponse().setStatus(data.getStatusCode());
1014             if (data.isPageSet())
1015             {
1016                 data.getOut().print(data.getPage().toString());
1017             }
1018         }
1019         // Catch this one because it occurs if some code hasn't been
1020         // completely re-compiled after a change..
1021         catch (java.lang.NoSuchFieldError e)
1022         {
1023             try
1024             {
1025                 data.getResponse().setContentType(mimeType);
1026                 data.getResponse().setStatus(200);
1027             }
1028             catch (Exception ignored)
1029             {
1030                 // Ignored
1031             }
1032 
1033             try
1034             {
1035                 data.getOut().print("java.lang.NoSuchFieldError: "
1036                         + "Please recompile all of your source code.");
1037             }
1038             catch (IOException ignored)
1039             {
1040             }
1041 
1042             log.error(data.getStackTrace(), e);
1043         }
1044         // Attempt to do *something* at this point...
1045         catch (Throwable reallyScrewedNow)
1046         {
1047             StringBuffer msg = new StringBuffer();
1048             msg.append("Horrible Exception: ");
1049             if (data != null)
1050             {
1051                 msg.append(data.getStackTrace());
1052             }
1053             else
1054             {
1055                 msg.append(t);
1056             }
1057             try
1058             {
1059                 res.setContentType(mimeType);
1060                 res.setStatus(200);
1061                 res.getWriter().print(msg.toString());
1062             }
1063             catch (Exception ignored)
1064             {
1065             }
1066 
1067             log.error(reallyScrewedNow.getMessage(), reallyScrewedNow);
1068         }
1069     }
1070 
1071     /***
1072      * Save some information about this servlet so that
1073      * it can be utilized by object instances that do not
1074      * have direct access to RunData.
1075      *
1076      * @param data
1077      */
1078     public static synchronized void saveServletInfo(RunData data)
1079     {
1080         // Store the context path for tools like ContentURI and
1081         // the UIManager that use webapp context path information
1082         // for constructing URLs.
1083 
1084         //
1085         // Bundle all the information above up into a convenient structure
1086         //
1087         serverData = (ServerData) data.getServerData().clone();
1088     }
1089 
1090     /***
1091      * Set the application root for the webapp.
1092      *
1093      * @param val New app root.
1094      */
1095     public static void setApplicationRoot(String val)
1096     {
1097         applicationRoot = val;
1098     }
1099 
1100     /***
1101      * Get the application root for this Turbine webapp. This
1102      * concept was started in 3.0 and will allow an app to be
1103      * developed from a standard CVS layout. With a simple
1104      * switch the app will work fully within the servlet
1105      * container for deployment.
1106      *
1107      * @return String applicationRoot
1108      */
1109     public static String getApplicationRoot()
1110     {
1111         return applicationRoot;
1112     }
1113 
1114     /***
1115      * Used to get the real path of configuration and resource
1116      * information. This can be used by an app being
1117      * developed in a standard CVS layout.
1118      *
1119      * @param path path translated to the application root
1120      * @return the real path
1121      */
1122     public static String getRealPath(String path)
1123     {
1124         if (path.startsWith("/"))
1125         {
1126             path = path.substring(1);
1127         }
1128 
1129         return new File(getApplicationRoot(), path).getAbsolutePath();
1130     }
1131 
1132     /***
1133      * Return an instance of the currently configured Service Manager
1134      *
1135      * @return A service Manager instance
1136      */
1137     private ServiceManager getServiceManager()
1138     {
1139         return TurbineServices.getInstance();
1140     }
1141 }