View Javadoc

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