1 package org.apache.turbine;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 import java.io.File;
58 import java.io.FileInputStream;
59 import java.io.FileNotFoundException;
60 import java.io.IOException;
61 import java.util.HashMap;
62 import java.util.Map;
63 import java.util.Properties;
64
65 import javax.servlet.ServletConfig;
66 import javax.servlet.ServletContext;
67 import javax.servlet.ServletException;
68 import javax.servlet.http.HttpServlet;
69 import javax.servlet.http.HttpServletRequest;
70 import javax.servlet.http.HttpServletResponse;
71
72 import org.apache.commons.configuration.Configuration;
73 import org.apache.commons.configuration.ConfigurationFactory;
74 import org.apache.commons.configuration.PropertiesConfiguration;
75
76 import org.apache.commons.lang.StringUtils;
77
78 import org.apache.commons.lang.exception.ExceptionUtils;
79
80 import org.apache.commons.logging.Log;
81 import org.apache.commons.logging.LogFactory;
82
83 import org.apache.commons.xo.Mapper;
84
85 import org.apache.log4j.PropertyConfigurator;
86
87 import org.apache.turbine.modules.PageLoader;
88 import org.apache.turbine.pipeline.DefaultPipelineData;
89 import org.apache.turbine.pipeline.Pipeline;
90 import org.apache.turbine.pipeline.PipelineData;
91 import org.apache.turbine.pipeline.TurbinePipeline;
92
93 import org.apache.turbine.services.ServiceManager;
94 import org.apache.turbine.services.TurbineServices;
95 import org.apache.turbine.services.avaloncomponent.AvalonComponentService;
96 import org.apache.turbine.services.component.ComponentService;
97 import org.apache.turbine.services.template.TemplateService;
98 import org.apache.turbine.services.template.TurbineTemplate;
99 import org.apache.turbine.services.rundata.RunDataService;
100 import org.apache.turbine.services.rundata.TurbineRunDataFacade;
101
102 import org.apache.turbine.util.RunData;
103 import org.apache.turbine.util.ServerData;
104 import org.apache.turbine.util.TurbineConfig;
105 import org.apache.turbine.util.TurbineException;
106 import org.apache.turbine.util.uri.URIConstants;
107
108 /***
109 * Turbine is the main servlet for the entire system. It is <code>final</code>
110 * because you should <i>not</i> ever need to subclass this servlet. If you
111 * need to perform initialization of a service, then you should implement the
112 * Services API and let your code be initialized by it.
113 * If you need to override something in the <code>doGet()</code> or
114 * <code>doPost()</code> methods, edit the TurbineResources.properties file and
115 * specify your own classes there.
116 * <p>
117 * Turbine servlet recognizes the following initialization parameters.
118 * <ul>
119 * <li><code>properties</code> the path to TurbineResources.properties file
120 * used by the default implementation of <code>ResourceService</code>, relative
121 * to the application root.</li>
122 * <li><code>basedir</code> this parameter is used <strong>only</strong> if your
123 * application server does not support web applications, or the or does not
124 * support <code>ServletContext.getRealPath(String)</code> method correctly.
125 * You can use this parameter to specify the directory within the server's
126 * filesystem, that is the base of your web application.</li>
127 * </ul>
128 *
129 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
130 * @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a>
131 * @author <a href="mailto:greg@shwoop.com">Greg Ritter</a>
132 * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
133 * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
134 * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
135 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
136 * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
137 * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
138 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
139 * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
140 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
141 * @author <a href="mailto:peter@courcoux.biz">Peter Courcoux</a>
142 * @version $Id: Turbine.java,v 1.51 2004/08/02 08:57:36 epugh Exp $
143 */
144 public class Turbine
145 extends HttpServlet
146 implements TurbineConstants
147 {
148 /***
149 * Name of path info parameter used to indicate the redirected stage of
150 * a given user's initial Turbine request
151 */
152 public static final String REDIRECTED_PATHINFO_NAME = "redirected";
153
154 /*** The base directory key */
155 public static final String BASEDIR_KEY = "basedir";
156
157 /***
158 * In certain situations the init() method is called more than once,
159 * somtimes even concurrently. This causes bad things to happen,
160 * so we use this flag to prevent it.
161 */
162 private static boolean firstInit = true;
163
164 /***
165 * The pipeline to use when processing requests.
166 */
167 private static Pipeline pipeline = null;
168
169 /*** Whether init succeeded or not. */
170 private static Throwable initFailure = null;
171
172 /***
173 * Should initialization activities be performed during doGet() execution?
174 */
175 private static boolean firstDoGet = true;
176
177 /***
178 * Keep all the properties of the web server in a convenient data
179 * structure
180 */
181 private static ServerData serverData = null;
182
183 /*** The base from which the Turbine application will operate. */
184 private static String applicationRoot;
185
186 /*** Servlet config for this Turbine webapp. */
187 private static ServletConfig servletConfig;
188
189 /*** Servlet context for this Turbine webapp. */
190 private static ServletContext servletContext;
191
192 /***
193 * The webapp root where the Turbine application
194 * is running in the servlet container.
195 * This might differ from the application root.
196 */
197 private static String webappRoot;
198
199 /*** Our internal configuration object */
200 private static Configuration configuration = null;
201
202 /*** A reference to the Template Service */
203 private TemplateService templateService = null;
204
205 /*** A reference to the RunData Service */
206 private RunDataService rundataService = null;
207
208 /*** Logging class from commons.logging */
209 private static Log log = LogFactory.getLog(Turbine.class);
210
211 /***
212 * This init method will load the default resources from a
213 * properties file.
214 *
215 * This method is called by init(ServletConfig config)
216 *
217 * @exception ServletException a servlet exception.
218 */
219 public final void init() throws ServletException
220 {
221 synchronized (this.getClass())
222 {
223 super.init();
224 ServletConfig config = getServletConfig();
225
226 if (!firstInit)
227 {
228 log.info("Double initialization of Turbine was attempted!");
229 return;
230 }
231
232
233 firstInit = false;
234
235 try
236 {
237 ServletContext context = config.getServletContext();
238
239 configure(config, context);
240
241 templateService = TurbineTemplate.getService();
242 rundataService = TurbineRunDataFacade.getService();
243
244 if (rundataService == null)
245 {
246 throw new TurbineException(
247 "No RunData Service configured!");
248 }
249
250 }
251 catch (Exception e)
252 {
253
254 initFailure = e;
255 log.fatal("Turbine: init() failed: ", e);
256 throw new ServletException("Turbine: init() failed", e);
257 }
258 log.info("Turbine: init() Ready to Rumble!");
259 }
260 }
261
262 /***
263 * Read the master configuration file in, configure logging
264 * and start up any early services.
265 *
266 * @param config The Servlet Configuration supplied by the container
267 * @param context The Servlet Context supplied by the container
268 *
269 * @throws Exception A problem occured while reading the configuration or performing early startup
270 */
271
272 private void configure(ServletConfig config, ServletContext context)
273 throws Exception
274 {
275
276
277
278
279
280 applicationRoot = findInitParameter(context, config,
281 APPLICATION_ROOT_KEY,
282 APPLICATION_ROOT_DEFAULT);
283
284 webappRoot = config.getServletContext().getRealPath("/");
285
286
287
288 if (applicationRoot == null || applicationRoot.equals(WEB_CONTEXT))
289 {
290 applicationRoot = webappRoot;
291
292 }
293
294
295 setApplicationRoot(applicationRoot);
296
297
298
299 createRuntimeDirectories(context, config);
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329 String confFile= findInitParameter(context, config,
330 TurbineConfig.CONFIGURATION_PATH_KEY,
331 null);
332
333 String confPath;
334 String confStyle = "unset";
335
336 if (StringUtils.isNotEmpty(confFile))
337 {
338 confPath = getRealPath(confFile);
339 ConfigurationFactory configurationFactory = new ConfigurationFactory(confPath);
340 configurationFactory.setBasePath(getApplicationRoot());
341 configuration = configurationFactory.getConfiguration();
342 confStyle = "XML";
343 }
344 else
345 {
346 confFile = findInitParameter(context, config,
347 TurbineConfig.PROPERTIES_PATH_KEY,
348 TurbineConfig.PROPERTIES_PATH_DEFAULT);
349
350 confPath = getRealPath(confFile);
351
352
353
354
355 configuration = (Configuration) new PropertiesConfiguration(confPath);
356 confStyle = "Properties";
357 }
358
359
360
361
362
363 String log4jFile = configuration.getString(LOG4J_CONFIG_FILE,
364 LOG4J_CONFIG_FILE_DEFAULT);
365
366 log4jFile = getRealPath(log4jFile);
367
368
369
370
371
372 Properties p = new Properties();
373 try
374 {
375 p.load(new FileInputStream(log4jFile));
376 p.setProperty(APPLICATION_ROOT_KEY, getApplicationRoot());
377 PropertyConfigurator.configure(p);
378
379
380
381 log = LogFactory.getLog(this.getClass());
382
383 log.info("Configured log4j from " + log4jFile);
384 }
385 catch (FileNotFoundException fnf)
386 {
387 System.err.println("Could not open Log4J configuration file "
388 + log4jFile + ": ");
389 fnf.printStackTrace();
390 }
391
392
393 log.info("Loaded configuration (" + confStyle + ") from " + confFile + " (" + confPath + ")");
394
395
396 setTurbineServletConfig(config);
397 setTurbineServletContext(context);
398
399 getServiceManager().setApplicationRoot(applicationRoot);
400
401
402
403
404
405
406 configuration.setProperty(APPLICATION_ROOT_KEY, applicationRoot);
407 configuration.setProperty(WEBAPP_ROOT_KEY, webappRoot);
408
409
410
411
412
413 Class pipelineClass =
414 Class.forName(
415 configuration.getString("pipeline.default", STANDARD_PIPELINE));
416
417 log.debug("Using Pipeline: " + pipelineClass.getName());
418
419
420 String descriptorPath =
421 configuration.getString(
422 "pipeline.default.descriptor",
423 TurbinePipeline.CLASSIC_PIPELINE);
424 descriptorPath = getRealPath(descriptorPath);
425
426 log.debug("Using descriptor path: " + descriptorPath);
427 Mapper m = new Mapper();
428 pipeline = (Pipeline) m.map(descriptorPath, pipelineClass.getName());
429 log.debug("Initializing pipeline");
430
431 pipeline.initialize();
432
433
434
435
436
437 configuration.setProperty(TurbineServices.SERVICE_PREFIX +
438 ComponentService.SERVICE_NAME + ".earlyInit",
439 Boolean.TRUE);
440
441 configuration.setProperty(TurbineServices.SERVICE_PREFIX +
442 AvalonComponentService.SERVICE_NAME + ".earlyInit",
443 Boolean.TRUE);
444
445 getServiceManager().setConfiguration(configuration);
446
447
448
449
450
451 getServiceManager().init();
452 }
453
454 /***
455 * Create any directories that might be needed during
456 * runtime. Right now this includes:
457 *
458 * <ul>
459 *
460 * <li>The directory to write the log files to (relative to the
461 * web application root), or <code>null</code> for the default of
462 * <code>/logs</code>. The directory is specified via the {@link
463 * TurbineConstants#LOGGING_ROOT} parameter.</li>
464 *
465 * </ul>
466 *
467 * @param context Global initialization parameters.
468 * @param config Initialization parameters specific to the Turbine
469 * servlet.
470 */
471 private static void createRuntimeDirectories(ServletContext context,
472 ServletConfig config)
473 {
474 String path = findInitParameter(context, config,
475 LOGGING_ROOT_KEY,
476 LOGGING_ROOT_DEFAULT);
477
478 File logDir = new File(getRealPath(path));
479 if (!logDir.exists())
480 {
481
482 if (!logDir.mkdirs())
483 {
484 System.err.println("Cannot create directory for logs!");
485 }
486 }
487 }
488
489 /***
490 * Finds the specified servlet configuration/initialization
491 * parameter, looking first for a servlet-specific parameter, then
492 * for a global parameter, and using the provided default if not
493 * found.
494 */
495 protected static final String findInitParameter(ServletContext context,
496 ServletConfig config, String name, String defaultValue)
497 {
498 String path = null;
499
500
501 boolean usingNamespace = name.startsWith(CONFIG_NAMESPACE);
502 while (true)
503 {
504 path = config.getInitParameter(name);
505 if (StringUtils.isEmpty(path))
506 {
507 path = context.getInitParameter(name);
508 if (StringUtils.isEmpty(path))
509 {
510
511 if (usingNamespace)
512 {
513 path = defaultValue;
514 }
515 else
516 {
517
518 name = CONFIG_NAMESPACE + '.' + name;
519 usingNamespace = true;
520 continue;
521 }
522 }
523 }
524 break;
525 }
526
527 return path;
528 }
529
530 /***
531 * Return the current configuration with all keys included
532 *
533 * @return a Configuration Object
534 */
535 public static Configuration getConfiguration()
536 {
537 return configuration;
538 }
539
540 /***
541 * Return the server name.
542 *
543 * @return String server name
544 */
545 public static String getServerName()
546 {
547 return getDefaultServerData().getServerName();
548 }
549
550 /***
551 * Return the server scheme.
552 *
553 * @return String server scheme
554 */
555 public static String getServerScheme()
556 {
557 return getDefaultServerData().getServerScheme();
558 }
559
560 /***
561 * Return the server port.
562 *
563 * @return String server port
564 */
565 public static String getServerPort()
566 {
567 return Integer.toString(getDefaultServerData().getServerPort());
568 }
569
570 /***
571 * Get the script name. This is the initial script name.
572 * Actually this is probably not needed any more. I'll
573 * check. jvz.
574 *
575 * @return String initial script name.
576 */
577 public static String getScriptName()
578 {
579 return getDefaultServerData().getScriptName();
580 }
581
582 /***
583 * Return the context path.
584 *
585 * @return String context path
586 */
587 public static String getContextPath()
588 {
589 return getDefaultServerData().getContextPath();
590 }
591
592 /***
593 * Return all the Turbine Servlet information (Server Name, Port,
594 * Scheme in a ServerData structure. This is generated from the
595 * values set when initializing the Turbine and may not be correct
596 * if you're running in a clustered structure. This might be used
597 * if you need a DataURI and have no RunData object handy-
598 *
599 * @return An initialized ServerData object
600 */
601 public static ServerData getDefaultServerData()
602 {
603 if(serverData == null)
604 {
605 log.error("ServerData Information requested from Turbine before first request!");
606
607 serverData = new ServerData(null, URIConstants.HTTP_PORT,
608 URIConstants.HTTP, null, null);
609 }
610 return serverData;
611 }
612
613 /***
614 * Set the servlet config for this turbine webapp.
615 *
616 * @param config New servlet config
617 */
618 public static void setTurbineServletConfig(ServletConfig config)
619 {
620 servletConfig = config;
621 }
622
623 /***
624 * Get the servlet config for this turbine webapp.
625 *
626 * @return ServletConfig
627 */
628 public static ServletConfig getTurbineServletConfig()
629 {
630 return servletConfig;
631 }
632
633 /***
634 * Set the servlet context for this turbine webapp.
635 *
636 * @param context New servlet context.
637 */
638 public static void setTurbineServletContext(ServletContext context)
639 {
640 servletContext = context;
641 }
642
643 /***
644 * Get the servlet context for this turbine webapp.
645 *
646 * @return ServletContext
647 */
648 public static ServletContext getTurbineServletContext()
649 {
650 return servletContext;
651 }
652
653 /***
654 * The <code>Servlet</code> destroy method. Invokes
655 * <code>ServiceBroker</code> tear down method.
656 */
657 public final void destroy()
658 {
659
660 getServiceManager().shutdownServices();
661 System.gc();
662
663 firstInit = true;
664 firstDoGet = true;
665 log.info("Turbine: Done shutting down!");
666 }
667
668 /***
669 * The primary method invoked when the Turbine servlet is executed.
670 *
671 * @param req Servlet request.
672 * @param res Servlet response.
673 * @exception IOException a servlet exception.
674 * @exception ServletException a servlet exception.
675 */
676 public final void doGet(HttpServletRequest req, HttpServletResponse res)
677 throws IOException, ServletException
678 {
679
680 boolean requestRedirected = false;
681
682
683 RunData data = null;
684
685 PipelineData pipelineData = new DefaultPipelineData();
686 try
687 {
688
689 if (initFailure != null)
690 {
691 throw initFailure;
692 }
693
694
695
696 if (firstDoGet)
697 {
698 synchronized (Turbine.class)
699 {
700
701
702
703 serverData = new ServerData(req);
704
705
706 firstDoGet = false;
707 log.info("Turbine: first Request successful");
708 }
709
710 }
711
712
713
714 data = rundataService.getRunData(req, res, getServletConfig());
715 Map runDataMap = new HashMap();
716 runDataMap.put(RunData.class, data);
717
718 pipelineData.put(RunData.class, runDataMap);
719
720
721
722
723 pipeline.invoke(pipelineData);
724
725 }
726 catch (Exception e)
727 {
728 handleException(pipelineData, res, e);
729 }
730 catch (Throwable t)
731 {
732 handleException(pipelineData, res, t);
733 }
734 finally
735 {
736
737 rundataService.putRunData(data);
738 }
739 }
740
741 /***
742 * In this application doGet and doPost are the same thing.
743 *
744 * @param req Servlet request.
745 * @param res Servlet response.
746 * @exception IOException a servlet exception.
747 * @exception ServletException a servlet exception.
748 */
749 public final void doPost(HttpServletRequest req, HttpServletResponse res)
750 throws IOException, ServletException
751 {
752 doGet(req, res);
753 }
754
755 /***
756 * Return the servlet info.
757 *
758 * @return a string with the servlet information.
759 */
760 public final String getServletInfo()
761 {
762 return "Turbine Servlet";
763 }
764
765 /***
766 * This method is about making sure that we catch and display
767 * errors to the screen in one fashion or another. What happens is
768 * that it will attempt to show the error using your user defined
769 * Error Screen. If that fails, then it will resort to just
770 * displaying the error and logging it all over the place
771 * including the servlet engine log file, the Turbine log file and
772 * on the screen.
773 *
774 * @param data A Turbine PipelineData object.
775 * @param res Servlet response.
776 * @param t The exception to report.
777 */
778 private final void handleException(PipelineData pipelineData, HttpServletResponse res,
779 Throwable t)
780 {
781 RunData data = (RunData)getRunData(pipelineData);
782
783 log.error("Turbine.handleException: ", t);
784
785 String mimeType = "text/plain";
786 try
787 {
788
789
790 data.setStackTrace(ExceptionUtils.getStackTrace(t), t);
791
792
793 data.setScreen(configuration.getString(SCREEN_ERROR_KEY,
794 SCREEN_ERROR_DEFAULT));
795
796
797 if (data.getTemplateInfo() != null)
798 {
799 data.getTemplateInfo()
800 .setScreenTemplate(configuration.getString(
801 TEMPLATE_ERROR_KEY, TEMPLATE_ERROR_VM));
802 }
803
804
805 data.setAction("");
806
807 PageLoader.getInstance().exec(pipelineData,
808 configuration.getString(PAGE_DEFAULT_KEY,
809 PAGE_DEFAULT_DEFAULT));
810
811 data.getResponse().setContentType(data.getContentType());
812 data.getResponse().setStatus(data.getStatusCode());
813 }
814
815
816 catch (java.lang.NoSuchFieldError e)
817 {
818 try
819 {
820 data.getResponse().setContentType(mimeType);
821 data.getResponse().setStatus(200);
822 }
823 catch (Exception ignored)
824 {
825 }
826
827 try
828 {
829 data.getResponse().getWriter().print("java.lang.NoSuchFieldError: "
830 + "Please recompile all of your source code.");
831 }
832 catch (IOException ignored)
833 {
834 }
835
836 log.error(data.getStackTrace(), e);
837 }
838
839 catch (Throwable reallyScrewedNow)
840 {
841 StringBuffer msg = new StringBuffer();
842 msg.append("Horrible Exception: ");
843 if (data != null)
844 {
845 msg.append(data.getStackTrace());
846 }
847 else
848 {
849 msg.append(t);
850 }
851 try
852 {
853 res.setContentType(mimeType);
854 res.setStatus(200);
855 res.getWriter().print(msg.toString());
856 }
857 catch (Exception ignored)
858 {
859 }
860
861 log.error(reallyScrewedNow.getMessage(), reallyScrewedNow);
862 }
863 }
864
865 /***
866 * Set the application root for the webapp.
867 *
868 * @param val New app root.
869 */
870 public static void setApplicationRoot(String val)
871 {
872 applicationRoot = val;
873 }
874
875 /***
876 * Get the application root for this Turbine webapp. This
877 * concept was started in 3.0 and will allow an app to be
878 * developed from a standard CVS layout. With a simple
879 * switch the app will work fully within the servlet
880 * container for deployment.
881 *
882 * @return String applicationRoot
883 */
884 public static String getApplicationRoot()
885 {
886 return applicationRoot;
887 }
888
889 /***
890 * Used to get the real path of configuration and resource
891 * information. This can be used by an app being
892 * developed in a standard CVS layout.
893 *
894 * @param path path translated to the application root
895 * @return the real path
896 */
897 public static String getRealPath(String path)
898 {
899 if (path.startsWith("/"))
900 {
901 path = path.substring(1);
902 }
903
904 return new File(getApplicationRoot(), path).getAbsolutePath();
905 }
906
907 /***
908 * Return an instance of the currently configured Service Manager
909 *
910 * @return A service Manager instance
911 */
912 private ServiceManager getServiceManager()
913 {
914 return TurbineServices.getInstance();
915 }
916
917 /***
918 * Get a RunData from the pipelineData. Once RunData is replaced
919 * by PipelineData this should not be required.
920 * @param pipelineData
921 * @return
922 */
923 private RunData getRunData(PipelineData pipelineData)
924 {
925 RunData data = null;
926 Map runDataMap = (Map) pipelineData.get(RunData.class);
927 data = (RunData)runDataMap.get(RunData.class);
928 return data;
929 }
930 }