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