1 package org.apache.turbine;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.io.BufferedReader;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileNotFoundException;
26 import java.io.FileReader;
27 import java.io.IOException;
28 import java.io.Reader;
29 import java.io.UnsupportedEncodingException;
30 import java.util.HashMap;
31 import java.util.Iterator;
32 import java.util.Map;
33 import java.util.Properties;
34
35 import javax.servlet.ServletConfig;
36 import javax.servlet.ServletContext;
37 import javax.servlet.ServletException;
38 import javax.servlet.http.HttpServlet;
39 import javax.servlet.http.HttpServletRequest;
40 import javax.servlet.http.HttpServletResponse;
41 import javax.xml.parsers.FactoryConfigurationError;
42
43 import org.apache.commons.configuration.Configuration;
44 import org.apache.commons.configuration.DefaultConfigurationBuilder;
45 import org.apache.commons.configuration.PropertiesConfiguration;
46 import org.apache.commons.lang.StringUtils;
47 import org.apache.commons.lang.exception.ExceptionUtils;
48 import org.apache.commons.logging.Log;
49 import org.apache.commons.logging.LogFactory;
50 import org.apache.log4j.PropertyConfigurator;
51 import org.apache.log4j.xml.DOMConfigurator;
52 import org.apache.turbine.modules.PageLoader;
53 import org.apache.turbine.pipeline.Pipeline;
54 import org.apache.turbine.pipeline.PipelineData;
55 import org.apache.turbine.pipeline.TurbinePipeline;
56 import org.apache.turbine.services.Initable;
57 import org.apache.turbine.services.InitializationException;
58 import org.apache.turbine.services.ServiceManager;
59 import org.apache.turbine.services.TurbineServices;
60 import org.apache.turbine.services.rundata.RunDataService;
61 import org.apache.turbine.services.template.TemplateService;
62 import org.apache.turbine.services.template.TurbineTemplate;
63 import org.apache.turbine.util.RunData;
64 import org.apache.turbine.util.ServerData;
65 import org.apache.turbine.util.TurbineConfig;
66 import org.apache.turbine.util.TurbineException;
67 import org.apache.turbine.util.uri.URIConstants;
68
69 import com.thoughtworks.xstream.XStream;
70 import com.thoughtworks.xstream.io.xml.StaxDriver;
71
72 /**
73 * Turbine is the main servlet for the entire system. It is <code>final</code>
74 * because you should <i>not</i> ever need to subclass this servlet. If you
75 * need to perform initialization of a service, then you should implement the
76 * Services API and let your code be initialized by it.
77 * If you need to override something in the <code>doGet()</code> or
78 * <code>doPost()</code> methods, edit the TurbineResources.properties file and
79 * specify your own classes there.
80 * <p>
81 * Turbine servlet recognizes the following initialization parameters.
82 * <ul>
83 * <li><code>properties</code> the path to TurbineResources.properties file
84 * used by the default implementation of <code>ResourceService</code>, relative
85 * to the application root.</li>
86 * <li><code>basedir</code> this parameter is used <strong>only</strong> if your
87 * application server does not support web applications, or the or does not
88 * support <code>ServletContext.getRealPath(String)</code> method correctly.
89 * You can use this parameter to specify the directory within the server's
90 * filesystem, that is the base of your web application.</li>
91 * </ul>
92 *
93 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
94 * @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a>
95 * @author <a href="mailto:greg@shwoop.com">Greg Ritter</a>
96 * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
97 * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
98 * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
99 * @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 1709648 2015-10-20 17:08:10Z 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 * sometimes 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 @Override
184 public void init() throws ServletException
185 {
186 synchronized (Turbine.class)
187 {
188 super.init();
189 ServletConfig config = getServletConfig();
190
191 if (!firstInit)
192 {
193 log.info("Double initialization of Turbine was attempted!");
194 return;
195 }
196 // executing init will trigger some static initializers, so we have
197 // only one chance.
198 firstInit = false;
199
200 try
201 {
202 ServletContext context = config.getServletContext();
203
204 configure(config, context);
205
206 TemplateService templateService = TurbineTemplate.getService();
207 if (templateService == null)
208 {
209 throw new TurbineException(
210 "No Template Service configured!");
211 }
212
213 if (getRunDataService() == null)
214 {
215 throw new TurbineException(
216 "No RunData Service configured!");
217 }
218
219 }
220 catch (Exception e)
221 {
222 // save the exception to complain loudly later :-)
223 initFailure = e;
224 log.fatal("Turbine: init() failed: ", e);
225 throw new ServletException("Turbine: init() failed", e);
226 }
227
228 log.info("Turbine: init() Ready to Rumble!");
229 }
230 }
231
232 /**
233 * Read the master configuration file in, configure logging
234 * and start up any early services.
235 *
236 * @param config The Servlet Configuration supplied by the container
237 * @param context The Servlet Context supplied by the container
238 *
239 * @throws Exception A problem occurred while reading the configuration or performing early startup
240 */
241
242 protected void configure(ServletConfig config, ServletContext context)
243 throws Exception
244 {
245
246 // Set the application root. This defaults to the webapp
247 // context if not otherwise set. This is to allow 2.1 apps
248 // to be developed from CVS. This feature will carry over
249 // into 3.0.
250 applicationRoot = findInitParameter(context, config,
251 TurbineConstants.APPLICATION_ROOT_KEY,
252 TurbineConstants.APPLICATION_ROOT_DEFAULT);
253
254 webappRoot = config.getServletContext().getRealPath("/");
255 // log.info("Web Application root is " + webappRoot);
256 // log.info("Application root is " + applicationRoot);
257
258 if (applicationRoot == null || applicationRoot.equals(TurbineConstants.WEB_CONTEXT))
259 {
260 applicationRoot = webappRoot;
261 // log.info("got empty or 'webContext' Application root. Application root now: " + applicationRoot);
262 }
263
264 // Set the applicationRoot for this webapp.
265 setApplicationRoot(applicationRoot);
266
267 // Create any directories that need to be setup for
268 // a running Turbine application.
269 createRuntimeDirectories(context, config);
270
271 //
272 // Now we run the Turbine configuration code. There are two ways
273 // to configure Turbine:
274 //
275 // a) By supplying an web.xml init parameter called "configuration"
276 //
277 // <init-param>
278 // <param-name>configuration</param-name>
279 // <param-value>/WEB-INF/conf/turbine.xml</param-value>
280 // </init-param>
281 //
282 // This loads an XML based configuration file.
283 //
284 // b) By supplying an web.xml init parameter called "properties"
285 //
286 // <init-param>
287 // <param-name>properties</param-name>
288 // <param-value>/WEB-INF/conf/TurbineResources.properties</param-value>
289 // </init-param>
290 //
291 // This loads a Properties based configuration file. Actually, these are
292 // extended properties as provided by commons-configuration
293 //
294 // If neither a) nor b) is supplied, Turbine will fall back to the
295 // known behaviour of loading a properties file called
296 // /WEB-INF/conf/TurbineResources.properties relative to the
297 // web application root.
298
299 String confStyle = "unset";
300 String confPath= null;
301 // first test
302 String confFile= findInitParameter(context, config,
303 TurbineConfig.CONFIGURATION_PATH_KEY,
304 null);
305 if (StringUtils.isNotEmpty(confFile))
306 {
307 confStyle = "XML";
308 } else // // second test
309 {
310 confFile = findInitParameter(context, config,
311 TurbineConfig.PROPERTIES_PATH_KEY,
312 null);
313 if (StringUtils.isNotEmpty((confFile)) )
314 {
315 confStyle = "Properties";
316 }
317 }
318 // more tests ..
319 // last test
320 if (confStyle.equals( "unset" ))
321 { // last resort
322 confFile = findInitParameter(context, config,
323 TurbineConfig.PROPERTIES_PATH_KEY,
324 TurbineConfig.PROPERTIES_PATH_DEFAULT);
325 confStyle = "Properties";
326 }
327 // now begin loading
328 if (!confStyle.equals( "unset" ))
329 {
330 if (confStyle.equals( "XML" )) {
331 if (confFile.startsWith( "/" ))
332 {
333 confFile = confFile.substring( 1 ); // cft. RFC2396 should not start with a slash, if not absolute path
334 }
335 DefaultConfigurationBuilder configurationBuilder = new DefaultConfigurationBuilder(confFile);
336 confPath = new File(applicationRoot).toURI().toString();// relative base path used for this and child configuration files
337 configurationBuilder.setBasePath(confPath);
338 configuration = configurationBuilder.getConfiguration();
339 } else {
340 confPath = getRealPath(confFile);
341 //configurationBuilder.setBasePath(getRealPath(getApplicationRoot()));
342 configuration = new PropertiesConfiguration(confPath);
343 }
344 }
345 //
346 // Set up logging as soon as possible
347 //
348 configureLogging();
349
350 // Now report our successful configuration to the world
351 log.info("Loaded configuration (" + confStyle + ") from " + confFile + " (" + confPath + ") style: "+ configuration.toString());
352
353 setTurbineServletConfig(config);
354 setTurbineServletContext(context);
355
356 getServiceManager().setApplicationRoot(applicationRoot);
357
358 // We want to set a few values in the configuration so
359 // that ${variable} interpolation will work for
360 //
361 // ${applicationRoot}
362 // ${webappRoot}
363 configuration.setProperty(TurbineConstants.APPLICATION_ROOT_KEY, applicationRoot);
364 configuration.setProperty(TurbineConstants.WEBAPP_ROOT_KEY, webappRoot);
365
366 // Get the default input encoding
367 inputEncoding = configuration.getString(
368 TurbineConstants.PARAMETER_ENCODING_KEY,
369 TurbineConstants.PARAMETER_ENCODING_DEFAULT);
370
371 if (log.isDebugEnabled())
372 {
373 log.debug("Input Encoding has been set to " + inputEncoding);
374 }
375
376 getServiceManager().setConfiguration(configuration);
377
378 // Initialize the service manager. Services
379 // that have its 'earlyInit' property set to
380 // a value of 'true' will be started when
381 // the service manager is initialized.
382 getServiceManager().init();
383
384 // Retrieve the pipeline class and then initialize it. The pipeline
385 // handles the processing of a webrequest/response cycle.
386
387 String descriptorPath =
388 configuration.getString(
389 "pipeline.default.descriptor",
390 TurbinePipeline.CLASSIC_PIPELINE);
391
392 descriptorPath = getRealPath(descriptorPath);
393
394 log.debug("Using descriptor path: " + descriptorPath);
395 Reader reader = new BufferedReader(new FileReader(descriptorPath));
396 XStream pipelineMapper = new XStream(new StaxDriver()); // does not require XPP3 library
397 pipeline = (Pipeline) pipelineMapper.fromXML(reader);
398
399 log.debug("Initializing pipeline");
400
401 pipeline.initialize();
402 }
403
404 /**
405 * Configure the logging facilities of Turbine
406 *
407 * @throws IOException if the configuration file handling fails.
408 */
409 protected void configureLogging() throws IOException
410 {
411 String log4jFile = configuration.getString(TurbineConstants.LOG4J_CONFIG_FILE,
412 TurbineConstants.LOG4J_CONFIG_FILE_DEFAULT);
413
414 if (StringUtils.isNotEmpty(log4jFile) &&
415 !log4jFile.equalsIgnoreCase("none"))
416 {
417 log4jFile = getRealPath(log4jFile);
418 boolean success = false;
419
420 if (log4jFile.endsWith(".xml"))
421 {
422 // load XML type configuration
423 // NOTE: Only system property expansion available
424 try
425 {
426 DOMConfigurator.configure(log4jFile);
427 success = true;
428 }
429 catch (FactoryConfigurationError e)
430 {
431 System.err.println("Could not configure Log4J from configuration file "
432 + log4jFile + ": ");
433 e.printStackTrace();
434 }
435 }
436 else
437 {
438 //
439 // Load the config file above into a Properties object and
440 // fix up the Application root
441 //
442 Properties p = new Properties();
443 FileInputStream fis = null;
444
445 try
446 {
447 fis = new FileInputStream(log4jFile);
448 p.load(fis);
449 p.setProperty(TurbineConstants.APPLICATION_ROOT_KEY, getApplicationRoot());
450 PropertyConfigurator.configure(p);
451 success = true;
452 }
453 catch (FileNotFoundException fnf)
454 {
455 System.err.println("Could not open Log4J configuration file "
456 + log4jFile + ": ");
457 fnf.printStackTrace();
458 }
459 finally
460 {
461 if (fis != null)
462 {
463 fis.close();
464 }
465 }
466 }
467
468 if (success)
469 {
470 // Rebuild our log object with a configured commons-logging
471 log = LogFactory.getLog(this.getClass());
472 log.info("Configured log4j from " + log4jFile);
473 }
474 }
475 }
476 /**
477 * Create any directories that might be needed during
478 * runtime. Right now this includes:
479 *
480 * <ul>
481 *
482 * <li>The directory to write the log files to (relative to the
483 * web application root), or <code>null</code> for the default of
484 * <code>/logs</code>. The directory is specified via the {@link
485 * TurbineConstants#LOGGING_ROOT_KEY} parameter.</li>
486 *
487 * </ul>
488 *
489 * @param context Global initialization parameters.
490 * @param config Initialization parameters specific to the Turbine
491 * servlet.
492 */
493 protected void createRuntimeDirectories(ServletContext context,
494 ServletConfig config)
495 {
496 String path = findInitParameter(context, config,
497 TurbineConstants.LOGGING_ROOT_KEY,
498 TurbineConstants.LOGGING_ROOT_DEFAULT);
499
500 File logDir = new File(getRealPath(path));
501 if (!logDir.exists())
502 {
503 // Create the logging directory
504 if (!logDir.mkdirs())
505 {
506 System.err.println("Cannot create directory for logs!");
507 }
508 }
509 }
510
511 /**
512 * Finds the specified servlet configuration/initialization
513 * parameter, looking first for a servlet-specific parameter, then
514 * for a global parameter, and using the provided default if not
515 * found.
516 */
517 protected String findInitParameter(ServletContext context,
518 ServletConfig config, String name, String defaultValue)
519 {
520 String path = null;
521
522 // Try the name as provided first.
523 boolean usingNamespace = name.startsWith(TurbineConstants.CONFIG_NAMESPACE);
524 while (true)
525 {
526 path = config.getInitParameter(name);
527 if (StringUtils.isEmpty(path))
528 {
529 path = context.getInitParameter(name);
530 if (StringUtils.isEmpty(path))
531 {
532 // The named parameter didn't yield a value.
533 if (usingNamespace)
534 {
535 path = defaultValue;
536 }
537 else
538 {
539 // Try again using Turbine's namespace.
540 name = TurbineConstants.CONFIG_NAMESPACE + '.' + name;
541 usingNamespace = true;
542 continue;
543 }
544 }
545 }
546 break;
547 }
548
549 return path;
550 }
551
552 /**
553 * Initializes the services which need <code>PipelineData</code> to
554 * initialize themselves (post startup).
555 *
556 * @param data The first <code>GET</code> request.
557 */
558 public void init(PipelineData data)
559 {
560 synchronized (Turbine.class)
561 {
562 if (firstDoGet)
563 {
564 // All we want to do here is save some servlet
565 // information so that services and processes
566 // that don't have direct access to a RunData
567 // object can still know something about
568 // the servlet environment.
569 saveServletInfo(data);
570
571 // Initialize services with the PipelineData instance
572 TurbineServices services = (TurbineServices)TurbineServices.getInstance();
573
574 for (Iterator<String> i = services.getServiceNames(); i.hasNext();)
575 {
576 String serviceName = i.next();
577 Object service = services.getService(serviceName);
578
579 if (service instanceof Initable)
580 {
581 try
582 {
583 ((Initable)service).init(data);
584 }
585 catch (InitializationException e)
586 {
587 log.warn("Could not initialize Initable " + serviceName + " with PipelineData", e);
588 }
589 }
590 }
591
592 // Mark that we're done.
593 firstDoGet = false;
594 log.info("Turbine: first Request successful");
595 }
596 }
597 }
598
599 /**
600 * Return the current configuration with all keys included
601 *
602 * @return a Configuration Object
603 */
604 public static Configuration getConfiguration()
605 {
606 return configuration;
607 }
608
609 /**
610 * Return the server name.
611 *
612 * @return String server name
613 */
614 public static String getServerName()
615 {
616 return getDefaultServerData().getServerName();
617 }
618
619 /**
620 * Return the server scheme.
621 *
622 * @return String server scheme
623 */
624 public static String getServerScheme()
625 {
626 return getDefaultServerData().getServerScheme();
627 }
628
629 /**
630 * Return the server port.
631 *
632 * @return String server port
633 */
634 public static String getServerPort()
635 {
636 return Integer.toString(getDefaultServerData().getServerPort());
637 }
638
639 /**
640 * Get the script name. This is the initial script name.
641 * Actually this is probably not needed any more. I'll
642 * check. jvz.
643 *
644 * @return String initial script name.
645 */
646 public static String getScriptName()
647 {
648 return getDefaultServerData().getScriptName();
649 }
650
651 /**
652 * Return the context path.
653 *
654 * @return String context path
655 */
656 public static String getContextPath()
657 {
658 return getDefaultServerData().getContextPath();
659 }
660
661 /**
662 * Return all the Turbine Servlet information (Server Name, Port,
663 * Scheme in a ServerData structure. This is generated from the
664 * values set when initializing the Turbine and may not be correct
665 * if you're running in a clustered structure. You can provide default
666 * values in your configuration for cases where access is requied before
667 * your application is first accessed by a user. This might be used
668 * if you need a DataURI and have no RunData object handy.
669 *
670 * @return An initialized ServerData object
671 */
672 public static ServerData getDefaultServerData()
673 {
674 if (serverData == null)
675 {
676 String serverName
677 = configuration.getString(TurbineConstants.DEFAULT_SERVER_NAME_KEY);
678 if (serverName == null)
679 {
680 log.error("ServerData Information requested from Turbine before first request!");
681 }
682 else
683 {
684 log.info("ServerData Information retrieved from configuration.");
685 }
686 // Will be overwritten once the first request is run;
687 serverData = new ServerData(serverName,
688 configuration.getInt(TurbineConstants.DEFAULT_SERVER_PORT_KEY,
689 URIConstants.HTTP_PORT),
690 configuration.getString(TurbineConstants.DEFAULT_SERVER_SCHEME_KEY,
691 URIConstants.HTTP),
692 configuration.getString(TurbineConstants.DEFAULT_SCRIPT_NAME_KEY),
693 configuration.getString(TurbineConstants.DEFAULT_CONTEXT_PATH_KEY));
694 }
695 return serverData;
696 }
697
698 /**
699 * Set the servlet config for this turbine webapp.
700 *
701 * @param config New servlet config
702 */
703 public static void setTurbineServletConfig(ServletConfig config)
704 {
705 servletConfig = config;
706 }
707
708 /**
709 * Get the servlet config for this turbine webapp.
710 *
711 * @return ServletConfig
712 */
713 public static ServletConfig getTurbineServletConfig()
714 {
715 return servletConfig;
716 }
717
718 /**
719 * Set the servlet context for this turbine webapp.
720 *
721 * @param context New servlet context.
722 */
723 public static void setTurbineServletContext(ServletContext context)
724 {
725 servletContext = context;
726 }
727
728 /**
729 * Get the servlet context for this turbine webapp.
730 *
731 * @return ServletContext
732 */
733 public static ServletContext getTurbineServletContext()
734 {
735 return servletContext;
736 }
737
738 /**
739 * The <code>Servlet</code> destroy method. Invokes
740 * <code>ServiceBroker</code> tear down method.
741 */
742 @Override
743 public void destroy()
744 {
745 // Shut down all Turbine Services.
746 getServiceManager().shutdownServices();
747
748 firstInit = true;
749 firstDoGet = true;
750 log.info("Turbine: Done shutting down!");
751 }
752
753 /**
754 * The primary method invoked when the Turbine servlet is executed.
755 *
756 * @param req Servlet request.
757 * @param res Servlet response.
758 * @exception IOException a servlet exception.
759 * @exception ServletException a servlet exception.
760 */
761 @Override
762 public void doGet(HttpServletRequest req, HttpServletResponse res)
763 throws IOException, ServletException
764 {
765 PipelineData pipelineData = null;
766
767 try
768 {
769 // Check to make sure that we started up properly.
770 if (initFailure != null)
771 {
772 throw initFailure;
773 }
774
775 //
776 // If the servlet container gives us no clear indication about the
777 // Encoding of the contents, set it to our default value.
778 if (req.getCharacterEncoding() == null)
779 {
780 if (log.isDebugEnabled())
781 {
782 log.debug("Changing Input Encoding to " + inputEncoding);
783 }
784
785 try
786 {
787 req.setCharacterEncoding(inputEncoding);
788 }
789 catch (UnsupportedEncodingException uee)
790 {
791 log.warn("Could not change request encoding to " + inputEncoding, uee);
792 }
793 }
794
795 // Get general RunData here...
796 // Perform turbine specific initialization below.
797 pipelineData = getRunDataService().getRunData(req, res, getServletConfig());
798 Map<Class<?>, Object> runDataMap = new HashMap<Class<?>, Object>();
799 runDataMap.put(RunData.class, pipelineData);
800 // put the data into the pipeline
801 pipelineData.put(RunData.class, runDataMap);
802
803 // If this is the first invocation, perform some
804 // initialization. Certain services need RunData to initialize
805 // themselves.
806 if (firstDoGet)
807 {
808 init(pipelineData);
809 }
810
811 // Stages of Pipeline implementation execution
812 // configurable via attached Valve implementations in a
813 // XML properties file.
814 pipeline.invoke(pipelineData);
815
816 }
817 catch (Exception e)
818 {
819 handleException(pipelineData, res, e);
820 }
821 catch (Throwable t)
822 {
823 handleException(pipelineData, res, t);
824 }
825 finally
826 {
827 // Return the used RunData to the factory for recycling.
828 getRunDataService().putRunData((RunData)pipelineData);
829 }
830 }
831
832 /**
833 * In this application doGet and doPost are the same thing.
834 *
835 * @param req Servlet request.
836 * @param res Servlet response.
837 * @exception IOException a servlet exception.
838 * @exception ServletException a servlet exception.
839 */
840 @Override
841 public void doPost(HttpServletRequest req, HttpServletResponse res)
842 throws IOException, ServletException
843 {
844 doGet(req, res);
845 }
846
847 /**
848 * Return the servlet info.
849 *
850 * @return a string with the servlet information.
851 */
852 @Override
853 public String getServletInfo()
854 {
855 return "Turbine Servlet";
856 }
857
858 /**
859 * This method is about making sure that we catch and display
860 * errors to the screen in one fashion or another. What happens is
861 * that it will attempt to show the error using your user defined
862 * Error Screen. If that fails, then it will resort to just
863 * displaying the error and logging it all over the place
864 * including the servlet engine log file, the Turbine log file and
865 * on the screen.
866 *
867 * @param pipelineData A Turbine PipelineData object.
868 * @param res Servlet response.
869 * @param t The exception to report.
870 */
871 protected void handleException(PipelineData pipelineData, HttpServletResponse res,
872 Throwable t)
873 {
874 RunData data = getRunData(pipelineData);
875 // make sure that the stack trace makes it the log
876 log.error("Turbine.handleException: ", t);
877
878 String mimeType = "text/plain";
879 try
880 {
881 // This is where we capture all exceptions and show the
882 // Error Screen.
883 data.setStackTrace(ExceptionUtils.getStackTrace(t), t);
884
885 // setup the screen
886 data.setScreen(configuration.getString(
887 TurbineConstants.SCREEN_ERROR_KEY,
888 TurbineConstants.SCREEN_ERROR_DEFAULT));
889
890 // do more screen setup for template execution if needed
891 if (data.getTemplateInfo() != null)
892 {
893 data.getTemplateInfo()
894 .setScreenTemplate(configuration.getString(
895 TurbineConstants.TEMPLATE_ERROR_KEY,
896 TurbineConstants.TEMPLATE_ERROR_VM));
897 }
898
899 // Make sure to not execute an action.
900 data.setAction("");
901
902 PageLoader.getInstance().exec(pipelineData,
903 configuration.getString(TurbineConstants.PAGE_DEFAULT_KEY,
904 TurbineConstants.PAGE_DEFAULT_DEFAULT));
905
906 data.getResponse().setContentType(data.getContentType());
907 data.getResponse().setStatus(data.getStatusCode());
908 }
909 // Catch this one because it occurs if some code hasn't been
910 // completely re-compiled after a change..
911 catch (java.lang.NoSuchFieldError e)
912 {
913 try
914 {
915 data.getResponse().setContentType(mimeType);
916 data.getResponse().setStatus(200);
917 }
918 catch (Exception ignored)
919 {
920 // ignore
921 }
922
923 try
924 {
925 data.getResponse().getWriter().print("java.lang.NoSuchFieldError: "
926 + "Please recompile all of your source code.");
927 }
928 catch (IOException ignored)
929 {
930 // ignore
931 }
932
933 log.error(data.getStackTrace(), e);
934 }
935 // Attempt to do *something* at this point...
936 catch (Throwable reallyScrewedNow)
937 {
938 StringBuilder msg = new StringBuilder();
939 msg.append("Horrible Exception: ");
940 if (data != null)
941 {
942 msg.append(data.getStackTrace());
943 }
944 else
945 {
946 msg.append(t);
947 }
948 try
949 {
950 res.setContentType(mimeType);
951 res.setStatus(200);
952 res.getWriter().print(msg.toString());
953 }
954 catch (Exception ignored)
955 {
956 // ignore
957 }
958
959 log.error(reallyScrewedNow.getMessage(), reallyScrewedNow);
960 }
961 }
962
963 /**
964 * Save some information about this servlet so that
965 * it can be utilized by object instances that do not
966 * have direct access to PipelineData.
967 *
968 * @param data Turbine request data
969 */
970 public static synchronized void saveServletInfo(PipelineData data)
971 {
972 // Store the context path for tools like ContentURI and
973 // the UIManager that use webapp context path information
974 // for constructing URLs.
975
976 //
977 // Bundle all the information above up into a convenient structure
978 //
979 ServerData requestServerData = data.get(Turbine.class, ServerData.class);
980 serverData = (ServerData) requestServerData.clone();
981 }
982
983 /**
984 * Set the application root for the webapp.
985 *
986 * @param val New app root.
987 */
988 public static void setApplicationRoot(String val)
989 {
990 applicationRoot = val;
991 }
992
993 /**
994 * Get the application root for this Turbine webapp. This
995 * concept was started in 3.0 and will allow an app to be
996 * developed from a standard CVS layout. With a simple
997 * switch the app will work fully within the servlet
998 * container for deployment.
999 *
1000 * @return String applicationRoot
1001 */
1002 public static String getApplicationRoot()
1003 {
1004 return applicationRoot;
1005 }
1006
1007 /**
1008 * Used to get the real path of configuration and resource
1009 * information. This can be used by an app being
1010 * developed in a standard CVS layout.
1011 *
1012 * @param path path translated to the application root
1013 * @return the real path
1014 */
1015 public static String getRealPath(String path)
1016 {
1017 if (path.startsWith("/"))
1018 {
1019 return new File(getApplicationRoot(), path.substring(1)).getAbsolutePath();
1020 }
1021
1022 return new File(getApplicationRoot(), path).getAbsolutePath();
1023 }
1024
1025 /**
1026 * Return an instance of the currently configured Service Manager
1027 *
1028 * @return A service Manager instance
1029 */
1030 private ServiceManager getServiceManager()
1031 {
1032 return TurbineServices.getInstance();
1033 }
1034
1035 /**
1036 * Get a RunData from the pipelineData. Once RunData is fully replaced
1037 * by PipelineData this should not be required.
1038 * @param pipelineData
1039 * @return
1040 */
1041 private RunData getRunData(PipelineData pipelineData)
1042 {
1043 RunData data = null;
1044 data = (RunData)pipelineData;
1045 return data;
1046 }
1047
1048
1049 /**
1050 * Returns the default input encoding for the servlet.
1051 *
1052 * @return the default input encoding.
1053 */
1054 public String getDefaultInputEncoding()
1055 {
1056 return inputEncoding;
1057 }
1058
1059 /**
1060 * Static Helper method for looking up the RunDataService
1061 * @return A RunDataService
1062 */
1063 private RunDataService getRunDataService()
1064 {
1065 return (RunDataService) TurbineServices
1066 .getInstance().getService(RunDataService.SERVICE_NAME);
1067 }
1068 }