View Javadoc
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