1 package org.apache.turbine;
2
3 /* ====================================================================
4 * The Apache Software License, Version 1.1
5 *
6 * Copyright (c) 2001-2002 The Apache Software Foundation. All rights
7 * reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in
18 * the documentation and/or other materials provided with the
19 * distribution.
20 *
21 * 3. The end-user documentation included with the redistribution,
22 * if any, must include the following acknowledgment:
23 * "This product includes software developed by the
24 * Apache Software Foundation (http://www.apache.org/)."
25 * Alternately, this acknowledgment may appear in the software itself,
26 * if and wherever such third-party acknowledgments normally appear.
27 *
28 * 4. The names "Apache" and "Apache Software Foundation" and
29 * "Apache Turbine" must not be used to endorse or promote products
30 * derived from this software without prior written permission. For
31 * written permission, please contact apache@apache.org.
32 *
33 * 5. Products derived from this software may not be called "Apache",
34 * "Apache Turbine", nor may "Apache" appear in their name, without
35 * prior written permission of the Apache Software Foundation.
36 *
37 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48 * SUCH DAMAGE.
49 * ====================================================================
50 *
51 * This software consists of voluntary contributions made by many
52 * individuals on behalf of the Apache Software Foundation. For more
53 * information on the Apache Software Foundation, please see
54 * <http://www.apache.org/>.
55 */
56
57 import java.io.File;
58 import java.io.IOException;
59 import javax.servlet.ServletConfig;
60 import javax.servlet.ServletException;
61 import javax.servlet.http.HttpServlet;
62 import javax.servlet.http.HttpServletRequest;
63 import javax.servlet.http.HttpServletResponse;
64 import org.apache.stratum.component.ComponentLoader;
65 import org.apache.turbine.modules.ActionLoader;
66 import org.apache.turbine.modules.PageLoader;
67 import org.apache.turbine.services.TurbineServices;
68 import org.apache.turbine.services.resources.TurbineResources;
69 import org.apache.turbine.services.template.TurbineTemplate;
70 import org.apache.turbine.util.Log;
71 import org.apache.turbine.util.RunData;
72 import org.apache.turbine.util.RunDataFactory;
73 import org.apache.turbine.util.StringUtils;
74 import org.apache.turbine.util.security.AccessControlList;
75
76 /***
77 * Turbine is the main servlet for the entire system. It is <code>final</code>
78 * because you should <i>not</i> ever need to subclass this servlet. If you
79 * need to perform initialization of a service, then you should implement the
80 * Services API and let your code be initialized by it.
81 * If you need to override something in the <code>doGet()</code> or
82 * <code>doPost()</code> methods, edit the TurbineResources.properties file and
83 * specify your own classes there.
84 *
85 * <p> Turbine servlet recognizes the following initialization parameters.
86 *
87 * <ul>
88 * <li><code>resources</code> the implementation of
89 * {@link org.apache.turbine.services.resources.ResourceService} to be used</li>
90 * <li><code>properties</code> the path to TurbineResources.properties file
91 * used by the default implementation of <code>ResourceService</code>, relative
92 * to the application root.</li>
93 * <li><code>basedir</code> this parameter is used <strong>only</strong> if your
94 * application server does not support web applications, or the or does not
95 * support <code>ServletContext.getRealPath(String)</code> method correctly.
96 * You can use this parameter to specify the directory within the server's
97 * filesystem, that is the base of your web application.</li>
98 * </ul><br>
99 *
100 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
101 * @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a>
102 * @author <a href="mailto:greg@shwoop.com">Greg Ritter</a>
103 * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
104 * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
105 * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
106 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
107 * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
108 * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
109 * @version $Id: Turbine.java,v 1.19 2002/07/11 14:28:29 mpoeschl Exp $
110 */
111 public class Turbine
112 extends HttpServlet
113 implements TurbineConstants
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 /***
122 * The base directory key
123 */
124 public static final String BASEDIR_KEY = "basedir";
125
126 /***
127 * In certain situations the init() method is called more than once,
128 * somtimes even concurrently. This causes bad things to happen,
129 * so we use this flag to prevent it.
130 */
131 private static boolean firstInit = true;
132
133 /***
134 * Whether init succeeded or not.
135 */
136 private static Throwable initFailure = null;
137
138 /***
139 * Should initialization activities be performed during doGet()
140 * execution?
141 */
142 private static boolean firstDoGet = true;
143
144 /***
145 * The base from which the Turbine application
146 * will operate.
147 */
148 private static String applicationRoot;
149
150 /***
151 * The webapp root where the Turbine application
152 * is running.
153 */
154 private static String webappRoot;
155
156 /***
157 * instance of turbine services
158 */
159 private TurbineServices services =
160 (TurbineServices) TurbineServices.getInstance();
161
162 /***
163 * Server information. This information needs to
164 * be made available to processes that do not have
165 * access to RunData and the ServletService doesn't
166 * seem to be working in all cases.
167 */
168 private static String serverName;
169 private static String serverScheme;
170 private static String serverPort;
171 private static String contextPath;
172
173 /***
174 * This init method will load the default resources from a
175 * properties file.
176 *
177 * @param config typical Servlet initialization parameter.
178 * @exception ServletException a servlet exception.
179 */
180 public final void init(ServletConfig config)
181 throws ServletException
182 {
183 super.init(config);
184
185 synchronized (this.getClass())
186 {
187 if (!firstInit)
188 {
189 log("Double initializaton of Turbine was attempted!");
190 return;
191 }
192 // executing init will trigger some static initializers, so we have
193 // only one chance.
194 firstInit = false;
195
196 try
197 {
198 // Set the application root. This defaults to the webapp
199 // context if not otherwise set. This is to allow 2.1 apps
200 // to be developed from CVS. This feature will carry over
201 // into 3.0.
202 applicationRoot = config.getInitParameter(APPLICATION_ROOT);
203
204 if (applicationRoot == null
205 || applicationRoot.equals("webContext"))
206 {
207 applicationRoot = config.getServletContext()
208 .getRealPath("");
209 }
210
211 // Set the webapp root. The applicationRoot and the
212 // webappRoot will be the same when the application is
213 // deployed, but during development they may have
214 // different values.
215 webappRoot = config.getServletContext().getRealPath("");
216
217 // Create any directories that need to be setup for
218 // a running Turbine application.
219 createRuntimeDirectories();
220
221 // Initialize essential services (Resources & Logging)
222 services.initPrimaryServices(config);
223
224 // Now that TurbineResources is setup, we want to insert
225 // the applicationRoot and webappRoot into the resources
226 // so that ${applicationRoot} and ${webappRoot} can be
227 // use in the TRP.
228 TurbineResources.setProperty(APPLICATION_ROOT, applicationRoot);
229 TurbineResources.setProperty(WEBAPP_ROOT, webappRoot);
230
231 // Initialize other services that require early init
232 services.initServices(config, false);
233
234 // Initialize components like torque and fulcrum
235 ComponentLoader loader = new ComponentLoader(
236 TurbineResources.getConfiguration());
237 loader.load();
238
239 log ("Turbine: init() Ready to Rumble!");
240 }
241 catch (Exception e)
242 {
243 // save the exception to complain loudly later :-)
244 initFailure = e;
245 log ("Turbine: init() failed: " + StringUtils.stackTrace(e));
246 }
247 }
248 }
249
250 /***
251 * Create any directories that might be needed during
252 * runtime. Right now this includes:
253 *
254 * i) directories for logging
255 */
256 private static void createRuntimeDirectories()
257 {
258 // Create the logging directory
259 File logDir = new File(webappRoot + "/logs");
260
261 if (logDir.exists() == false)
262 {
263 if (logDir.mkdirs() == false)
264 {
265 System.err.println("Cannot create directory for logs!");
266 }
267 }
268 }
269
270 /***
271 * Initializes the services which need <code>RunData</code> to
272 * initialize themselves (post startup).
273 *
274 * @param data The first <code>GET</code> request.
275 */
276 public final void init(RunData data)
277 {
278 synchronized (Turbine.class)
279 {
280 if (firstDoGet)
281 {
282 serverName = data.getRequest().getServerName();
283 serverPort = Integer.toString(data.getRequest().getServerPort());
284 serverScheme = data.getRequest().getScheme();
285
286 // Store the context path for tools like ContentURI and
287 // the UIManager that use webapp context path information
288 // for constructing URLs.
289 contextPath = data.getRequest().getContextPath();
290
291 log("Turbine: Starting HTTP initialization of services");
292 TurbineServices.getInstance().initServices(data);
293 log("Turbine: Completed HTTP initialization of services");
294
295 // Mark that we're done.
296 firstDoGet = false;
297 }
298 }
299 }
300
301 /***
302 * Return the server name.
303 *
304 * @return String server name
305 */
306 public static String getServerName()
307 {
308 return serverName;
309 }
310
311 /***
312 * Return the server scheme.
313 *
314 * @return String server scheme
315 */
316 public static String getServerScheme()
317 {
318 return serverScheme;
319 }
320
321 /***
322 * Return the server port.
323 *
324 * @return String server port
325 */
326 public static String getServerPort()
327 {
328 return serverPort;
329 }
330
331 /***
332 * Return the context path.
333 *
334 * @return String context path
335 */
336 public static String getContextPath()
337 {
338 return contextPath;
339 }
340
341 /***
342 * The <code>Servlet</code> destroy method. Invokes
343 * <code>ServiceBroker</code> tear down method.
344 */
345 public final void destroy()
346 {
347 // Shut down all Turbine Services.
348 TurbineServices.getInstance().shutdownServices();
349 System.gc();
350
351 log("Turbine: Done shutting down!");
352 }
353
354 /***
355 * The primary method invoked when the Turbine servlet is executed.
356 *
357 * @param req Servlet request.
358 * @param res Servlet response.
359 * @exception IOException a servlet exception.
360 * @exception ServletException a servlet exception.
361 */
362 public final void doGet(HttpServletRequest req, HttpServletResponse res)
363 throws IOException, ServletException
364 {
365 // set to true if the request is to be redirected by the page
366 boolean requestRedirected = false;
367
368 // Placeholder for the RunData object.
369 RunData data = null;
370 try
371 {
372 // Check to make sure that we started up properly.
373 if (initFailure != null)
374 {
375 throw initFailure;
376 }
377
378 // Get general RunData here...
379 // Perform turbine specific initialization below.
380 data = RunDataFactory.getRunData(req, res, getServletConfig());
381
382 // If this is the first invocation, perform some
383 // initialization. Certain services need RunData to initialize
384 // themselves.
385 if (firstDoGet)
386 {
387 init(data);
388 }
389
390 // set the session timeout if specified in turbine's properties
391 // file if this is a new session
392 if (data.getSession().isNew())
393 {
394 int timeout = TurbineResources.getInt("session.timeout", -1);
395 if (timeout != -1)
396 {
397 data.getSession().setMaxInactiveInterval(timeout);
398 }
399 }
400
401 // Fill in the screen and action variables.
402 data.setScreen(data.getParameters().getString("screen"));
403 data.setAction(data.getParameters().getString("action"));
404
405 // Special case for login and logout, this must happen before the
406 // session validator is executed in order either to allow a user to
407 // even login, or to ensure that the session validator gets to
408 // mandate its page selection policy for non-logged in users
409 // after the logout has taken place.
410 if (data.hasAction()
411 && data.getAction().equalsIgnoreCase(TurbineResources
412 .getString("action.login"))
413 || data.getAction().equalsIgnoreCase(TurbineResources
414 .getString("action.logout")))
415 {
416 // If a User is logging in, we should refresh the
417 // session here. Invalidating session and starting a
418 // new session would seem to be a good method, but I
419 // (JDM) could not get this to work well (it always
420 // required the user to login twice). Maybe related
421 // to JServ? If we do not clear out the session, it
422 // is possible a new User may accidently (if they
423 // login incorrectly) continue on with information
424 // associated with the previous User. Currently the
425 // only keys stored in the session are "turbine.user"
426 // and "turbine.acl".
427 if (data.getAction().equalsIgnoreCase(TurbineResources
428 .getString("action.login")))
429 {
430 String[] names = data.getSession().getValueNames();
431 if (names != null)
432 {
433 for (int i = 0; i < names.length; i++)
434 {
435 data.getSession().removeValue(names[i]);
436 }
437 }
438 }
439 ActionLoader.getInstance().exec(data, data.getAction());
440 data.setAction(null);
441 }
442
443 // This is where the validation of the Session information
444 // is performed if the user has not logged in yet, then
445 // the screen is set to be Login. This also handles the
446 // case of not having a screen defined by also setting the
447 // screen to Login. If you want people to go to another
448 // screen other than Login, you need to change that within
449 // TurbineResources.properties...screen.homepage; or, you
450 // can specify your own SessionValidator action.
451 ActionLoader.getInstance().exec(
452 data, TurbineResources.getString("action.sessionvalidator"));
453
454 // Put the Access Control List into the RunData object, so
455 // it is easily available to modules. It is also placed
456 // into the session for serialization. Modules can null
457 // out the ACL to force it to be rebuilt based on more
458 // information.
459 ActionLoader.getInstance().exec(
460 data, TurbineResources.getString("action.accesscontroller"));
461
462 // Start the execution phase. DefaultPage will execute the
463 // appropriate action as well as get the Layout from the
464 // Screen and then execute that. The Layout is then
465 // responsible for executing the Navigation and Screen
466 // modules.
467 //
468 // Note that by default, this cannot be overridden from
469 // parameters passed in via post/query data. This is for
470 // security purposes. You should really never need more
471 // than just the default page. If you do, add logic to
472 // DefaultPage to do what you want.
473
474 String defaultPage = TurbineTemplate.getDefaultPageName(data);
475
476 if (defaultPage == null)
477 {
478 /*
479 * In this case none of the template services are running.
480 * The application may be using ECS for views, or a
481 * decendent of RawScreen is trying to produce output.
482 * If there is a 'page.default' property in the TR.props
483 * then use that, otherwise return DefaultPage which will
484 * handle ECS view scenerios and RawScreen scenerios. The
485 * app developer can still specify the 'page.default'
486 * if they wish but the DefaultPage should work in
487 * most cases.
488 */
489 defaultPage = TurbineResources.getString(
490 "page.default", "DefaultPage");
491 }
492
493 PageLoader.getInstance().exec(data, defaultPage);
494
495 // If a module has set data.acl = null, remove acl from
496 // the session.
497 if (data.getACL() == null)
498 {
499 try
500 {
501 data.getSession().removeValue(
502 AccessControlList.SESSION_KEY);
503 }
504 catch (IllegalStateException ignored)
505 {
506 }
507 }
508
509 // handle a redirect request
510 requestRedirected = ((data.getRedirectURI() != null)
511 && (data.getRedirectURI().length() > 0));
512 if (requestRedirected)
513 {
514 if (data.getResponse().isCommitted())
515 {
516 requestRedirected = false;
517 log("redirect requested, response already committed: " +
518 data.getRedirectURI());
519 }
520 else
521 {
522 data.getResponse().sendRedirect(data.getRedirectURI());
523 }
524 }
525
526 if (!requestRedirected)
527 {
528 try
529 {
530 if (data.isPageSet() == false && data.isOutSet() == false)
531 {
532 throw new Exception("Nothing to output");
533 }
534
535 // We are all done! if isPageSet() output that way
536 // otherwise, data.getOut() has already been written
537 // to the data.getOut().close() happens below in the
538 // finally.
539 if (data.isPageSet() && data.isOutSet() == false)
540 {
541 // Modules can override these.
542 data.getResponse().setLocale(data.getLocale());
543 data.getResponse()
544 .setContentType(data.getContentType());
545
546 // Set the status code.
547 data.getResponse().setStatus(data.getStatusCode());
548 // Output the Page.
549 data.getPage().output(data.getOut());
550 }
551 }
552 catch (Exception e)
553 {
554 // The output stream was probably closed by the client
555 // end of things ie: the client clicked the Stop
556 // button on the browser, so ignore any errors that
557 // result.
558 Log.debug("Output stream closed? ", e);
559 }
560 }
561 }
562 catch (Exception e)
563 {
564 handleException(data, res, e);
565 }
566 catch (Throwable t)
567 {
568 handleException(data, res, t);
569 }
570 finally
571 {
572 // Return the used RunData to the factory for recycling.
573 RunDataFactory.putRunData(data);
574 }
575 }
576
577 /***
578 * In this application doGet and doPost are the same thing.
579 *
580 * @param req Servlet request.
581 * @param res Servlet response.
582 * @exception IOException a servlet exception.
583 * @exception ServletException a servlet exception.
584 */
585 public final void doPost(HttpServletRequest req, HttpServletResponse res)
586 throws IOException, ServletException
587 {
588 doGet(req, res);
589 }
590
591 /***
592 * Return the servlet info.
593 *
594 * @return a string with the servlet information.
595 */
596 public final String getServletInfo()
597 {
598 return "Turbine Servlet";
599 }
600
601 /***
602 * This method is about making sure that we catch and display
603 * errors to the screen in one fashion or another. What happens is
604 * that it will attempt to show the error using your user defined
605 * Error Screen. If that fails, then it will resort to just
606 * displaying the error and logging it all over the place
607 * including the servlet engine log file, the Turbine log file and
608 * on the screen.
609 *
610 * @param data A Turbine RunData object.
611 * @param res Servlet response.
612 * @param t The exception to report.
613 */
614 private final void handleException(RunData data,
615 HttpServletResponse res,
616 Throwable t)
617 {
618 // make sure that the stack trace makes it the log
619 Log.error("Turbine.handleException: " + t.getMessage());
620 Log.error(t);
621
622 String mimeType = "text/plain";
623 try
624 {
625 // This is where we capture all exceptions and show the
626 // Error Screen.
627 data.setStackTrace(StringUtils.stackTrace(t), t);
628
629 // setup the screen
630 data.setScreen(TurbineResources.getString("screen.error"));
631
632 // do more screen setup for template execution if needed
633 if (data.getTemplateInfo() != null)
634 {
635 data.getTemplateInfo().setScreenTemplate(TurbineResources
636 .getString("template.error"));
637 }
638
639 // Make sure to not execute an action.
640 data.setAction("");
641
642 PageLoader.getInstance().exec(data,
643 TurbineResources.getString("page.default",
644 "DefaultPage"));
645
646 data.getResponse().setContentType(data.getContentType());
647 data.getResponse().setStatus(data.getStatusCode());
648 if (data.isPageSet())
649 {
650 data.getOut().print(data.getPage().toString());
651 }
652 }
653 // Catch this one because it occurs if some code hasn't been
654 // completely re-compiled after a change..
655 catch (java.lang.NoSuchFieldError e)
656 {
657 try
658 {
659 data.getResponse().setContentType(mimeType);
660 data.getResponse().setStatus(200);
661 }
662 catch (Exception ignored)
663 {
664 }
665
666 try
667 {
668 data.getOut().print("java.lang.NoSuchFieldError: "
669 + "Please recompile all of your source code.");
670 }
671 catch (IOException ignored)
672 {
673 }
674
675 log(data.getStackTrace());
676 org.apache.turbine.util.Log.error(e.getMessage(), e);
677 }
678 // Attempt to do *something* at this point...
679 catch (Throwable reallyScrewedNow)
680 {
681 StringBuffer msg = new StringBuffer();
682 msg.append("Horrible Exception: ");
683 if (data != null)
684 {
685 msg.append(data.getStackTrace());
686 }
687 else
688 {
689 msg.append(t);
690 }
691 try
692 {
693 res.setContentType(mimeType);
694 res.setStatus(200);
695 res.getWriter().print(msg.toString());
696 }
697 catch (Exception ignored)
698 {
699 }
700 org.apache.turbine.util.Log.error(
701 reallyScrewedNow.getMessage(), reallyScrewedNow);
702 }
703 }
704
705 /***
706 * Get the application root for this Turbine webapp. This
707 * concept was started in 3.0 and will allow an app to be
708 * developed from a standard CVS layout. With a simple
709 * switch the app will work fully within the servlet
710 * container for deployment.
711 *
712 * @return String applicationRoot
713 */
714 public static String getApplicationRoot()
715 {
716 return applicationRoot;
717 }
718
719 /***
720 * Used to get the real path of configuration and resource
721 * information. This can be used by an app being
722 * developed in a standard CVS layout.
723 *
724 * @param path path translated to the application root
725 * @return the real path
726 */
727 public static String getRealPath(String path)
728 {
729 if (path.startsWith("/"))
730 {
731 path = path.substring(1);
732 }
733
734 return applicationRoot + "/" + path;
735 }
736
737 /***
738 * logs message using turbine's logging facility
739 *
740 * @param msg message to be logged
741 */
742 public void log(String msg)
743 {
744 services.notice(msg);
745 }
746
747 /***
748 * Writes an explanatory message and a stack trace
749 * for a given <code>Throwable</code> exception
750 *
751 * @param message the message
752 * @param t the error
753 */
754
755 public void log(String message, Throwable t)
756 {
757 services.notice(message);
758 services.error(t);
759 }
760 }
This page was automatically generated by Maven