View Javadoc

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