Source code: org/apache/turbine/Turbine.java
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}