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