001 package org.apache.turbine;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements. See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership. The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License. You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied. See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022 import java.io.BufferedReader;
023 import java.io.File;
024 import java.io.FileInputStream;
025 import java.io.FileNotFoundException;
026 import java.io.FileReader;
027 import java.io.IOException;
028 import java.io.Reader;
029 import java.io.UnsupportedEncodingException;
030 import java.util.Iterator;
031 import java.util.Properties;
032
033 import javax.servlet.ServletConfig;
034 import javax.servlet.ServletContext;
035 import javax.servlet.ServletException;
036 import javax.servlet.http.HttpServlet;
037 import javax.servlet.http.HttpServletRequest;
038 import javax.servlet.http.HttpServletResponse;
039
040 import org.apache.commons.configuration.Configuration;
041 import org.apache.commons.configuration.ConfigurationFactory;
042 import org.apache.commons.configuration.PropertiesConfiguration;
043 import org.apache.commons.lang.StringUtils;
044 import org.apache.commons.lang.exception.ExceptionUtils;
045 import org.apache.commons.logging.Log;
046 import org.apache.commons.logging.LogFactory;
047 import org.apache.log4j.PropertyConfigurator;
048 import org.apache.turbine.modules.PageLoader;
049 import org.apache.turbine.pipeline.Pipeline;
050 import org.apache.turbine.pipeline.PipelineData;
051 import org.apache.turbine.pipeline.TurbinePipeline;
052 import org.apache.turbine.services.Initable;
053 import org.apache.turbine.services.InitializationException;
054 import org.apache.turbine.services.ServiceManager;
055 import org.apache.turbine.services.TurbineServices;
056 import org.apache.turbine.services.rundata.RunDataService;
057 import org.apache.turbine.services.template.TemplateService;
058 import org.apache.turbine.services.template.TurbineTemplate;
059 import org.apache.turbine.util.RunData;
060 import org.apache.turbine.util.ServerData;
061 import org.apache.turbine.util.TurbineConfig;
062 import org.apache.turbine.util.TurbineException;
063 import org.apache.turbine.util.uri.URIConstants;
064
065 import com.thoughtworks.xstream.XStream;
066 import com.thoughtworks.xstream.io.xml.DomDriver;
067
068 /**
069 * Turbine is the main servlet for the entire system. It is <code>final</code>
070 * because you should <i>not</i> ever need to subclass this servlet. If you
071 * need to perform initialization of a service, then you should implement the
072 * Services API and let your code be initialized by it.
073 * If you need to override something in the <code>doGet()</code> or
074 * <code>doPost()</code> methods, edit the TurbineResources.properties file and
075 * specify your own classes there.
076 * <p>
077 * Turbine servlet recognizes the following initialization parameters.
078 * <ul>
079 * <li><code>properties</code> the path to TurbineResources.properties file
080 * used by the default implementation of <code>ResourceService</code>, relative
081 * to the application root.</li>
082 * <li><code>basedir</code> this parameter is used <strong>only</strong> if your
083 * application server does not support web applications, or the or does not
084 * support <code>ServletContext.getRealPath(String)</code> method correctly.
085 * You can use this parameter to specify the directory within the server's
086 * filesystem, that is the base of your web application.</li>
087 * </ul>
088 *
089 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
090 * @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a>
091 * @author <a href="mailto:greg@shwoop.com">Greg Ritter</a>
092 * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
093 * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
094 * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
095 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
096 * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
097 * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
098 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
099 * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
100 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
101 * @author <a href="mailto:peter@courcoux.biz">Peter Courcoux</a>
102 * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
103 * @version $Id: Turbine.java 1096586 2011-04-25 20:31:43Z ludwig $
104 */
105 public class Turbine
106 extends HttpServlet
107 {
108 /** Serialversion */
109 private static final long serialVersionUID = -6317118078613623990L;
110
111 /**
112 * Name of path info parameter used to indicate the redirected stage of
113 * a given user's initial Turbine request
114 */
115 public static final String REDIRECTED_PATHINFO_NAME = "redirected";
116
117 /** The base directory key */
118 public static final String BASEDIR_KEY = "basedir";
119
120 /**
121 * In certain situations the init() method is called more than once,
122 * somtimes even concurrently. This causes bad things to happen,
123 * so we use this flag to prevent it.
124 */
125 private static boolean firstInit = true;
126
127 /**
128 * The pipeline to use when processing requests.
129 */
130 private static Pipeline pipeline = null;
131
132 /** Whether init succeeded or not. */
133 private static Throwable initFailure = null;
134
135 /**
136 * Should initialization activities be performed during doGet() execution?
137 */
138 private static boolean firstDoGet = true;
139
140 /**
141 * Keep all the properties of the web server in a convenient data
142 * structure
143 */
144 private static ServerData serverData = null;
145
146 /** The base from which the Turbine application will operate. */
147 private static String applicationRoot;
148
149 /** Servlet config for this Turbine webapp. */
150 private static ServletConfig servletConfig;
151
152 /** Servlet context for this Turbine webapp. */
153 private static ServletContext servletContext;
154
155 /**
156 * The webapp root where the Turbine application
157 * is running in the servlet container.
158 * This might differ from the application root.
159 */
160 private static String webappRoot;
161
162 /** Our internal configuration object */
163 private static Configuration configuration = 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 templateService = TurbineTemplate.getService();
202 if (templateService == null)
203 {
204 throw new TurbineException(
205 "No Template Service configured!");
206 }
207
208 if (getRunDataService() == null)
209 {
210 throw new TurbineException(
211 "No RunData Service configured!");
212 }
213
214 }
215 catch (Exception e)
216 {
217 // save the exception to complain loudly later :-)
218 initFailure = e;
219 log.fatal("Turbine: init() failed: ", e);
220 throw new ServletException("Turbine: init() failed", e);
221 }
222
223 log.info("Turbine: init() Ready to Rumble!");
224 }
225 }
226
227 /**
228 * Read the master configuration file in, configure logging
229 * and start up any early services.
230 *
231 * @param config The Servlet Configuration supplied by the container
232 * @param context The Servlet Context supplied by the container
233 *
234 * @throws Exception A problem occured while reading the configuration or performing early startup
235 */
236
237 private void configure(ServletConfig config, ServletContext context)
238 throws Exception
239 {
240
241 // Set the application root. This defaults to the webapp
242 // context if not otherwise set. This is to allow 2.1 apps
243 // to be developed from CVS. This feature will carry over
244 // into 3.0.
245 applicationRoot = findInitParameter(context, config,
246 TurbineConstants.APPLICATION_ROOT_KEY,
247 TurbineConstants.APPLICATION_ROOT_DEFAULT);
248
249 webappRoot = config.getServletContext().getRealPath("/");
250 // log.info("Web Application root is " + webappRoot);
251 // log.info("Application root is " + applicationRoot);
252
253 if (applicationRoot == null || applicationRoot.equals(TurbineConstants.WEB_CONTEXT))
254 {
255 applicationRoot = webappRoot;
256 // log.info("got empty or 'webContext' Application root. Application root now: " + applicationRoot);
257 }
258
259 // Set the applicationRoot for this webapp.
260 setApplicationRoot(applicationRoot);
261
262 // Create any directories that need to be setup for
263 // a running Turbine application.
264 createRuntimeDirectories(context, config);
265
266 //
267 // Now we run the Turbine configuration code. There are two ways
268 // to configure Turbine:
269 //
270 // a) By supplying an web.xml init parameter called "configuration"
271 //
272 // <init-param>
273 // <param-name>configuration</param-name>
274 // <param-value>/WEB-INF/conf/turbine.xml</param-value>
275 // </init-param>
276 //
277 // This loads an XML based configuration file.
278 //
279 // b) By supplying an web.xml init parameter called "properties"
280 //
281 // <init-param>
282 // <param-name>properties</param-name>
283 // <param-value>/WEB-INF/conf/TurbineResources.properties</param-value>
284 // </init-param>
285 //
286 // This loads a Properties based configuration file. Actually, these are
287 // extended properties as provided by commons-configuration
288 //
289 // If neither a) nor b) is supplied, Turbine will fall back to the
290 // known behaviour of loading a properties file called
291 // /WEB-INF/conf/TurbineResources.properties relative to the
292 // web application root.
293
294 String confFile= findInitParameter(context, config,
295 TurbineConfig.CONFIGURATION_PATH_KEY,
296 null);
297
298 String confPath;
299 String confStyle = "unset";
300
301 if (StringUtils.isNotEmpty(confFile))
302 {
303 confPath = getRealPath(confFile);
304 ConfigurationFactory configurationFactory = new ConfigurationFactory(confPath);
305 configurationFactory.setBasePath(getApplicationRoot());
306 configuration = configurationFactory.getConfiguration();
307 confStyle = "XML";
308 }
309 else
310 {
311 confFile = findInitParameter(context, config,
312 TurbineConfig.PROPERTIES_PATH_KEY,
313 TurbineConfig.PROPERTIES_PATH_DEFAULT);
314
315 confPath = getRealPath(confFile);
316
317 // This should eventually be a Configuration
318 // interface so that service and app configuration
319 // can be stored anywhere.
320 configuration = new PropertiesConfiguration(confPath);
321 confStyle = "Properties";
322 }
323
324
325 //
326 // Set up logging as soon as possible
327 //
328 String log4jFile = configuration.getString(TurbineConstants.LOG4J_CONFIG_FILE,
329 TurbineConstants.LOG4J_CONFIG_FILE_DEFAULT);
330
331 if (StringUtils.isNotEmpty(log4jFile) &&
332 !log4jFile.equalsIgnoreCase("none"))
333 {
334 log4jFile = getRealPath(log4jFile);
335
336 //
337 // Load the config file above into a Properties object and
338 // fix up the Application root
339 //
340 Properties p = new Properties();
341 try
342 {
343 p.load(new FileInputStream(log4jFile));
344 p.setProperty(TurbineConstants.APPLICATION_ROOT_KEY, getApplicationRoot());
345 PropertyConfigurator.configure(p);
346
347 //
348 // Rebuild our log object with a configured commons-logging
349 log = LogFactory.getLog(this.getClass());
350
351 log.info("Configured log4j from " + log4jFile);
352 }
353 catch (FileNotFoundException fnf)
354 {
355 System.err.println("Could not open Log4J configuration file "
356 + log4jFile + ": ");
357 fnf.printStackTrace();
358 }
359 }
360
361 // Now report our successful configuration to the world
362 log.info("Loaded configuration (" + confStyle + ") from " + confFile + " (" + confPath + ")");
363
364 setTurbineServletConfig(config);
365 setTurbineServletContext(context);
366
367 getServiceManager().setApplicationRoot(applicationRoot);
368
369 // We want to set a few values in the configuration so
370 // that ${variable} interpolation will work for
371 //
372 // ${applicationRoot}
373 // ${webappRoot}
374 configuration.setProperty(TurbineConstants.APPLICATION_ROOT_KEY, applicationRoot);
375 configuration.setProperty(TurbineConstants.WEBAPP_ROOT_KEY, webappRoot);
376
377 // Get the default input encoding
378 inputEncoding = configuration.getString(
379 TurbineConstants.PARAMETER_ENCODING_KEY,
380 TurbineConstants.PARAMETER_ENCODING_DEFAULT);
381
382 if (log.isDebugEnabled())
383 {
384 log.debug("Input Encoding has been set to " + inputEncoding);
385 }
386
387 getServiceManager().setConfiguration(configuration);
388
389 // Initialize the service manager. Services
390 // that have its 'earlyInit' property set to
391 // a value of 'true' will be started when
392 // the service manager is initialized.
393 getServiceManager().init();
394
395 // Retrieve the pipeline class and then initialize it. The pipeline
396 // handles the processing of a webrequest/response cycle.
397
398 String descriptorPath =
399 configuration.getString(
400 "pipeline.default.descriptor",
401 TurbinePipeline.CLASSIC_PIPELINE);
402
403 descriptorPath = getRealPath(descriptorPath);
404
405 log.debug("Using descriptor path: " + descriptorPath);
406 Reader reader = new BufferedReader(new FileReader(descriptorPath));
407 XStream pipelineMapper = new XStream(new DomDriver()); // does not require XPP3 library
408 pipeline = (Pipeline) pipelineMapper.fromXML(reader);
409
410 log.debug("Initializing pipeline");
411
412 pipeline.initialize();
413 }
414
415 /**
416 * Create any directories that might be needed during
417 * runtime. Right now this includes:
418 *
419 * <ul>
420 *
421 * <li>The directory to write the log files to (relative to the
422 * web application root), or <code>null</code> for the default of
423 * <code>/logs</code>. The directory is specified via the {@link
424 * TurbineConstants#LOGGING_ROOT} parameter.</li>
425 *
426 * </ul>
427 *
428 * @param context Global initialization parameters.
429 * @param config Initialization parameters specific to the Turbine
430 * servlet.
431 */
432 private static void createRuntimeDirectories(ServletContext context,
433 ServletConfig config)
434 {
435 String path = findInitParameter(context, config,
436 TurbineConstants.LOGGING_ROOT_KEY,
437 TurbineConstants.LOGGING_ROOT_DEFAULT);
438
439 File logDir = new File(getRealPath(path));
440 if (!logDir.exists())
441 {
442 // Create the logging directory
443 if (!logDir.mkdirs())
444 {
445 System.err.println("Cannot create directory for logs!");
446 }
447 }
448 }
449
450 /**
451 * Finds the specified servlet configuration/initialization
452 * parameter, looking first for a servlet-specific parameter, then
453 * for a global parameter, and using the provided default if not
454 * found.
455 */
456 protected static final String findInitParameter(ServletContext context,
457 ServletConfig config, String name, String defaultValue)
458 {
459 String path = null;
460
461 // Try the name as provided first.
462 boolean usingNamespace = name.startsWith(TurbineConstants.CONFIG_NAMESPACE);
463 while (true)
464 {
465 path = config.getInitParameter(name);
466 if (StringUtils.isEmpty(path))
467 {
468 path = context.getInitParameter(name);
469 if (StringUtils.isEmpty(path))
470 {
471 // The named parameter didn't yield a value.
472 if (usingNamespace)
473 {
474 path = defaultValue;
475 }
476 else
477 {
478 // Try again using Turbine's namespace.
479 name = TurbineConstants.CONFIG_NAMESPACE + '.' + name;
480 usingNamespace = true;
481 continue;
482 }
483 }
484 }
485 break;
486 }
487
488 return path;
489 }
490
491 /**
492 * Initializes the services which need <code>PipelineData</code> to
493 * initialize themselves (post startup).
494 *
495 * @param data The first <code>GET</code> request.
496 */
497 public final void init(PipelineData data)
498 {
499 synchronized (Turbine.class)
500 {
501 if (firstDoGet)
502 {
503 // All we want to do here is save some servlet
504 // information so that services and processes
505 // that don't have direct access to a RunData
506 // object can still know something about
507 // the servlet environment.
508 saveServletInfo(data);
509
510 // Initialize services with the PipelineData instance
511 TurbineServices services = (TurbineServices)TurbineServices.getInstance();
512
513 for (Iterator i = services.getServiceNames(); i.hasNext();)
514 {
515 String serviceName = (String)i.next();
516 Object service = services.getService(serviceName);
517
518 if (service instanceof Initable)
519 {
520 try
521 {
522 ((Initable)service).init(data);
523 }
524 catch (InitializationException e)
525 {
526 log.warn("Could not initialize Initable " + serviceName + " with PipelineData", e);
527 }
528 }
529 }
530
531 // Mark that we're done.
532 firstDoGet = false;
533 log.info("Turbine: first Request successful");
534 }
535 }
536 }
537
538 /**
539 * Return the current configuration with all keys included
540 *
541 * @return a Configuration Object
542 */
543 public static Configuration getConfiguration()
544 {
545 return configuration;
546 }
547
548 /**
549 * Return the server name.
550 *
551 * @return String server name
552 */
553 public static String getServerName()
554 {
555 return getDefaultServerData().getServerName();
556 }
557
558 /**
559 * Return the server scheme.
560 *
561 * @return String server scheme
562 */
563 public static String getServerScheme()
564 {
565 return getDefaultServerData().getServerScheme();
566 }
567
568 /**
569 * Return the server port.
570 *
571 * @return String server port
572 */
573 public static String getServerPort()
574 {
575 return Integer.toString(getDefaultServerData().getServerPort());
576 }
577
578 /**
579 * Get the script name. This is the initial script name.
580 * Actually this is probably not needed any more. I'll
581 * check. jvz.
582 *
583 * @return String initial script name.
584 */
585 public static String getScriptName()
586 {
587 return getDefaultServerData().getScriptName();
588 }
589
590 /**
591 * Return the context path.
592 *
593 * @return String context path
594 */
595 public static String getContextPath()
596 {
597 return getDefaultServerData().getContextPath();
598 }
599
600 /**
601 * Return all the Turbine Servlet information (Server Name, Port,
602 * Scheme in a ServerData structure. This is generated from the
603 * values set when initializing the Turbine and may not be correct
604 * if you're running in a clustered structure. You can provide default
605 * values in your configuration for cases where access is requied before
606 * your application is first accessed by a user. This might be used
607 * if you need a DataURI and have no RunData object handy.
608 *
609 * @return An initialized ServerData object
610 */
611 public static ServerData getDefaultServerData()
612 {
613 if (serverData == null)
614 {
615 String serverName
616 = configuration.getString(TurbineConstants.DEFAULT_SERVER_NAME_KEY);
617 if (serverName == null)
618 {
619 log.error("ServerData Information requested from Turbine before first request!");
620 }
621 else
622 {
623 log.info("ServerData Information retrieved from configuration.");
624 }
625 // Will be overwritten once the first request is run;
626 serverData = new ServerData(serverName,
627 configuration.getInt(TurbineConstants.DEFAULT_SERVER_PORT_KEY,
628 URIConstants.HTTP_PORT),
629 configuration.getString(TurbineConstants.DEFAULT_SERVER_SCHEME_KEY,
630 URIConstants.HTTP),
631 configuration.getString(TurbineConstants.DEFAULT_SCRIPT_NAME_KEY),
632 configuration.getString(TurbineConstants.DEFAULT_CONTEXT_PATH_KEY));
633 }
634 return serverData;
635 }
636
637 /**
638 * Set the servlet config for this turbine webapp.
639 *
640 * @param config New servlet config
641 */
642 public static void setTurbineServletConfig(ServletConfig config)
643 {
644 servletConfig = config;
645 }
646
647 /**
648 * Get the servlet config for this turbine webapp.
649 *
650 * @return ServletConfig
651 */
652 public static ServletConfig getTurbineServletConfig()
653 {
654 return servletConfig;
655 }
656
657 /**
658 * Set the servlet context for this turbine webapp.
659 *
660 * @param context New servlet context.
661 */
662 public static void setTurbineServletContext(ServletContext context)
663 {
664 servletContext = context;
665 }
666
667 /**
668 * Get the servlet context for this turbine webapp.
669 *
670 * @return ServletContext
671 */
672 public static ServletContext getTurbineServletContext()
673 {
674 return servletContext;
675 }
676
677 /**
678 * The <code>Servlet</code> destroy method. Invokes
679 * <code>ServiceBroker</code> tear down method.
680 */
681 public final void destroy()
682 {
683 // Shut down all Turbine Services.
684 getServiceManager().shutdownServices();
685 System.gc();
686
687 firstInit = true;
688 firstDoGet = true;
689 log.info("Turbine: Done shutting down!");
690 }
691
692 /**
693 * The primary method invoked when the Turbine servlet is executed.
694 *
695 * @param req Servlet request.
696 * @param res Servlet response.
697 * @exception IOException a servlet exception.
698 * @exception ServletException a servlet exception.
699 */
700 public final void doGet(HttpServletRequest req, HttpServletResponse res)
701 throws IOException, ServletException
702 {
703 PipelineData pipelineData = null;
704
705 try
706 {
707 // Check to make sure that we started up properly.
708 if (initFailure != null)
709 {
710 throw initFailure;
711 }
712
713 //
714 // If the servlet container gives us no clear indication about the
715 // Encoding of the contents, set it to our default value.
716 if (req.getCharacterEncoding() == null)
717 {
718 if (log.isDebugEnabled())
719 {
720 log.debug("Changing Input Encoding to " + inputEncoding);
721 }
722
723 try
724 {
725 req.setCharacterEncoding(inputEncoding);
726 }
727 catch (UnsupportedEncodingException uee)
728 {
729 log.warn("Could not change request encoding to " + inputEncoding, uee);
730 }
731 }
732
733 // Get general RunData here...
734 // Perform turbine specific initialization below.
735 pipelineData = getRunDataService().getRunData(req, res, getServletConfig());
736 // Map runDataMap = new HashMap();
737 //runDataMap.put(RunData.class, data);
738 // put the data into the pipeline
739 // pipelineData.put(RunData.class, runDataMap);
740
741 // If this is the first invocation, perform some
742 // initialization. Certain services need RunData to initialize
743 // themselves.
744 if (firstDoGet)
745 {
746 init(pipelineData);
747 }
748
749 // Stages of Pipeline implementation execution
750 // configurable via attached Valve implementations in a
751 // XML properties file.
752 pipeline.invoke(pipelineData);
753
754 }
755 catch (Exception e)
756 {
757 handleException(pipelineData, res, e);
758 }
759 catch (Throwable t)
760 {
761 handleException(pipelineData, res, t);
762 }
763 finally
764 {
765 // Return the used RunData to the factory for recycling.
766 getRunDataService().putRunData((RunData)pipelineData);
767 }
768 }
769
770 /**
771 * In this application doGet and doPost are the same thing.
772 *
773 * @param req Servlet request.
774 * @param res Servlet response.
775 * @exception IOException a servlet exception.
776 * @exception ServletException a servlet exception.
777 */
778 public final void doPost(HttpServletRequest req, HttpServletResponse res)
779 throws IOException, ServletException
780 {
781 doGet(req, res);
782 }
783
784 /**
785 * Return the servlet info.
786 *
787 * @return a string with the servlet information.
788 */
789 public final String getServletInfo()
790 {
791 return "Turbine Servlet";
792 }
793
794 /**
795 * This method is about making sure that we catch and display
796 * errors to the screen in one fashion or another. What happens is
797 * that it will attempt to show the error using your user defined
798 * Error Screen. If that fails, then it will resort to just
799 * displaying the error and logging it all over the place
800 * including the servlet engine log file, the Turbine log file and
801 * on the screen.
802 *
803 * @param data A Turbine PipelineData object.
804 * @param res Servlet response.
805 * @param t The exception to report.
806 */
807 private final void handleException(PipelineData pipelineData, HttpServletResponse res,
808 Throwable t)
809 {
810 RunData data = getRunData(pipelineData);
811 // make sure that the stack trace makes it the log
812 log.error("Turbine.handleException: ", t);
813
814 String mimeType = "text/plain";
815 try
816 {
817 // This is where we capture all exceptions and show the
818 // Error Screen.
819 data.setStackTrace(ExceptionUtils.getStackTrace(t), t);
820
821 // setup the screen
822 data.setScreen(configuration.getString(
823 TurbineConstants.SCREEN_ERROR_KEY,
824 TurbineConstants.SCREEN_ERROR_DEFAULT));
825
826 // do more screen setup for template execution if needed
827 if (data.getTemplateInfo() != null)
828 {
829 data.getTemplateInfo()
830 .setScreenTemplate(configuration.getString(
831 TurbineConstants.TEMPLATE_ERROR_KEY,
832 TurbineConstants.TEMPLATE_ERROR_VM));
833 }
834
835 // Make sure to not execute an action.
836 data.setAction("");
837
838 PageLoader.getInstance().exec(pipelineData,
839 configuration.getString(TurbineConstants.PAGE_DEFAULT_KEY,
840 TurbineConstants.PAGE_DEFAULT_DEFAULT));
841
842 data.getResponse().setContentType(data.getContentType());
843 data.getResponse().setStatus(data.getStatusCode());
844 }
845 // Catch this one because it occurs if some code hasn't been
846 // completely re-compiled after a change..
847 catch (java.lang.NoSuchFieldError e)
848 {
849 try
850 {
851 data.getResponse().setContentType(mimeType);
852 data.getResponse().setStatus(200);
853 }
854 catch (Exception ignored)
855 {
856 // ignore
857 }
858
859 try
860 {
861 data.getResponse().getWriter().print("java.lang.NoSuchFieldError: "
862 + "Please recompile all of your source code.");
863 }
864 catch (IOException ignored)
865 {
866 // ignore
867 }
868
869 log.error(data.getStackTrace(), e);
870 }
871 // Attempt to do *something* at this point...
872 catch (Throwable reallyScrewedNow)
873 {
874 StringBuffer msg = new StringBuffer();
875 msg.append("Horrible Exception: ");
876 if (data != null)
877 {
878 msg.append(data.getStackTrace());
879 }
880 else
881 {
882 msg.append(t);
883 }
884 try
885 {
886 res.setContentType(mimeType);
887 res.setStatus(200);
888 res.getWriter().print(msg.toString());
889 }
890 catch (Exception ignored)
891 {
892 // ignore
893 }
894
895 log.error(reallyScrewedNow.getMessage(), reallyScrewedNow);
896 }
897 }
898
899 /**
900 * Save some information about this servlet so that
901 * it can be utilized by object instances that do not
902 * have direct access to RunData.
903 *
904 * @param data Turbine request data
905 */
906 public static synchronized void saveServletInfo(PipelineData data)
907 {
908 // Store the context path for tools like ContentURI and
909 // the UIManager that use webapp context path information
910 // for constructing URLs.
911
912 //
913 // Bundle all the information above up into a convenient structure
914 //
915 ServerData requestServerData = (ServerData) data.get(Turbine.class, ServerData.class);
916 serverData = (ServerData) requestServerData.clone();
917 }
918
919 /**
920 * Set the application root for the webapp.
921 *
922 * @param val New app root.
923 */
924 public static void setApplicationRoot(String val)
925 {
926 applicationRoot = val;
927 }
928
929 /**
930 * Get the application root for this Turbine webapp. This
931 * concept was started in 3.0 and will allow an app to be
932 * developed from a standard CVS layout. With a simple
933 * switch the app will work fully within the servlet
934 * container for deployment.
935 *
936 * @return String applicationRoot
937 */
938 public static String getApplicationRoot()
939 {
940 return applicationRoot;
941 }
942
943 /**
944 * Used to get the real path of configuration and resource
945 * information. This can be used by an app being
946 * developed in a standard CVS layout.
947 *
948 * @param path path translated to the application root
949 * @return the real path
950 */
951 public static String getRealPath(String path)
952 {
953 if (path.startsWith("/"))
954 {
955 path = path.substring(1);
956 }
957
958 return new File(getApplicationRoot(), path).getAbsolutePath();
959 }
960
961 /**
962 * Return an instance of the currently configured Service Manager
963 *
964 * @return A service Manager instance
965 */
966 private ServiceManager getServiceManager()
967 {
968 return TurbineServices.getInstance();
969 }
970
971 /**
972 * Get a RunData from the pipelineData. Once RunData is fully replaced
973 * by PipelineData this should not be required.
974 * @param pipelineData
975 * @return
976 */
977 private RunData getRunData(PipelineData pipelineData)
978 {
979 RunData data = null;
980 data = (RunData)pipelineData;
981 return data;
982 }
983
984
985 /**
986 * Returns the default input encoding for the servlet.
987 *
988 * @return the default input encoding.
989 */
990 public String getDefaultInputEncoding() {
991 return inputEncoding;
992 }
993
994 /**
995 * Static Helper method for looking up the RunDataService
996 * @return A RunDataService
997 */
998 private static RunDataService getRunDataService()
999 {
1000 return (RunDataService) TurbineServices
1001 .getInstance().getService(RunDataService.SERVICE_NAME);
1002 }
1003 }