001 // Copyright 2004, 2005 The Apache Software Foundation 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package org.apache.tapestry.engine; 016 017 import org.apache.commons.logging.Log; 018 import org.apache.commons.logging.LogFactory; 019 import org.apache.hivemind.ApplicationRuntimeException; 020 import org.apache.hivemind.ClassResolver; 021 import org.apache.hivemind.util.Defense; 022 import org.apache.hivemind.util.ToStringBuilder; 023 import org.apache.tapestry.*; 024 import org.apache.tapestry.listener.ListenerMap; 025 import org.apache.tapestry.services.ComponentMessagesSource; 026 import org.apache.tapestry.services.DataSqueezer; 027 import org.apache.tapestry.services.Infrastructure; 028 import org.apache.tapestry.services.TemplateSource; 029 import org.apache.tapestry.spec.IApplicationSpecification; 030 import org.apache.tapestry.web.WebRequest; 031 import org.apache.tapestry.web.WebResponse; 032 033 import javax.servlet.ServletContext; 034 import javax.servlet.ServletException; 035 import java.io.IOException; 036 import java.util.ArrayList; 037 import java.util.List; 038 import java.util.Locale; 039 040 /** 041 * Basis for building real Tapestry applications. Immediate subclasses provide different strategies 042 * for managing page state and other resources between request cycles. 043 * <p> 044 * Note: much of this description is <em>in transition</em> as part of Tapestry 4.0. All ad-hoc 045 * singletons and such are being replaced with HiveMind services. 046 * <p> 047 * Uses a shared instance of {@link TemplateSource},{@link ISpecificationSource}, 048 * {@link IScriptSource}and {@link ComponentMessagesSource}stored as attributes of the 049 * {@link ServletContext}(they will be shared by all sessions). 050 * <p> 051 * An engine is designed to be very lightweight. Particularily, it should <b>never </b> hold 052 * references to any {@link IPage}or {@link org.apache.tapestry.IComponent}objects. The entire 053 * system is based upon being able to quickly rebuild the state of any page(s). 054 * <p> 055 * Where possible, instance variables should be transient. 056 * <p> 057 * In practice, a subclass (usually {@link BaseEngine}) is used without subclassing. Instead, a 058 * visit object is specified. To facilitate this, the application specification may include a 059 * property, <code>org.apache.tapestry.visit-class</code> which is the class name to instantiate 060 * when a visit object is first needed. 061 * <p> 062 * Some of the classes' behavior is controlled by JVM system properties (typically only used during 063 * development): <table border=1> 064 * <tr> 065 * <th>Property</th> 066 * <th>Description</th> 067 * </tr> 068 * <tr> 069 * <td>org.apache.tapestry.enable-reset-service</td> 070 * <td>If true, enabled an additional service, reset, that allow page, specification and template 071 * caches to be cleared on demand.</td> 072 * </tr> 073 * <tr> 074 * <td>org.apache.tapestry.disable-caching</td> 075 * <td>If true, then the page, specification, template and script caches will be cleared after each 076 * request. This slows things down, but ensures that the latest versions of such files are used. 077 * Care should be taken that the source directories for the files preceeds any versions of the files 078 * available in JARs or WARs.</td> 079 * </tr> 080 * </table> 081 * 082 * @author Howard Lewis Ship 083 */ 084 085 public abstract class AbstractEngine implements IEngine 086 { 087 /** 088 * The name of the application specification property used to specify the class of the visit 089 * object. 090 */ 091 092 public static final String VISIT_CLASS_PROPERTY_NAME = "org.apache.tapestry.visit-class"; 093 094 private static final Log LOG = LogFactory.getLog(AbstractEngine.class); 095 096 /** 097 * The link to the world of HiveMind services. 098 * 099 * @since 4.0 100 */ 101 private Infrastructure _infrastructure; 102 103 private ListenerMap _listeners; 104 105 /** 106 * The curent locale for the engine, which may be changed at any time. 107 */ 108 109 private Locale _locale; 110 111 /** 112 * @see org.apache.tapestry.error.ExceptionPresenter 113 */ 114 115 protected void activateExceptionPage(IRequestCycle cycle, Throwable cause) 116 { 117 _infrastructure.getExceptionPresenter().presentException(cycle, cause); 118 } 119 120 /** 121 * Writes a detailed report of the exception to <code>System.err</code>. 122 * 123 * @see org.apache.tapestry.error.RequestExceptionReporter 124 */ 125 126 public void reportException(String reportTitle, Throwable ex) 127 { 128 _infrastructure.getRequestExceptionReporter().reportRequestException(reportTitle, ex); 129 } 130 131 /** 132 * Invoked at the end of the request cycle to release any resources specific to the request 133 * cycle. This implementation does nothing and may be overriden freely. 134 */ 135 136 protected void cleanupAfterRequest(IRequestCycle cycle) 137 { 138 139 } 140 141 /** 142 * Returns the locale for the engine. This is initially set by the {@link ApplicationServlet} 143 * but may be updated by the application. 144 */ 145 146 public Locale getLocale() 147 { 148 return _locale; 149 } 150 151 /** 152 * Returns a service with the given name. 153 * 154 * @see Infrastructure#getServiceMap() 155 * @see org.apache.tapestry.services.ServiceMap 156 */ 157 158 public IEngineService getService(String name) 159 { 160 return _infrastructure.getServiceMap().getService(name); 161 } 162 163 /** @see Infrastructure#getApplicationSpecification() */ 164 165 public IApplicationSpecification getSpecification() 166 { 167 return _infrastructure.getApplicationSpecification(); 168 } 169 170 /** @see Infrastructure#getSpecificationSource() */ 171 172 public ISpecificationSource getSpecificationSource() 173 { 174 return _infrastructure.getSpecificationSource(); 175 } 176 177 /** 178 * Invoked, typically, when an exception occurs while servicing the request. This method resets 179 * the output, sets the new page and renders it. 180 */ 181 182 protected void redirect(String pageName, IRequestCycle cycle, 183 ApplicationRuntimeException exception) 184 throws IOException 185 { 186 IPage page = cycle.getPage(pageName); 187 188 cycle.activate(page); 189 190 renderResponse(cycle); 191 } 192 193 /** 194 * Delegates to 195 * {@link org.apache.tapestry.services.ResponseRenderer#renderResponse(IRequestCycle)}. 196 */ 197 198 public void renderResponse(IRequestCycle cycle) 199 throws IOException 200 { 201 _infrastructure.getResponseRenderer().renderResponse(cycle); 202 } 203 204 /** 205 * Delegate method for the servlet. Services the request. 206 */ 207 208 public void service(WebRequest request, WebResponse response) 209 throws IOException 210 { 211 IRequestCycle cycle = null; 212 IEngineService service = null; 213 214 if (_infrastructure == null) 215 _infrastructure = (Infrastructure) request.getAttribute(Constants.INFRASTRUCTURE_KEY); 216 217 // Create the request cycle; if this fails, there's not much that can be done ... everything 218 // else in Tapestry relies on the RequestCycle. 219 220 try 221 { 222 cycle = _infrastructure.getRequestCycleFactory().newRequestCycle(this); 223 } 224 catch (RuntimeException ex) 225 { 226 throw ex; 227 } 228 catch (Exception ex) 229 { 230 throw new IOException(ex.getMessage()); 231 } 232 233 try 234 { 235 try 236 { 237 service = cycle.getService(); 238 239 // Let the service handle the rest of the request. 240 241 service.service(cycle); 242 } 243 catch (PageRedirectException ex) 244 { 245 handlePageRedirectException(cycle, ex); 246 } 247 catch (RedirectException ex) 248 { 249 handleRedirectException(cycle, ex); 250 } 251 catch (StaleLinkException ex) 252 { 253 handleStaleLinkException(cycle, ex); 254 } 255 catch (StaleSessionException ex) 256 { 257 handleStaleSessionException(cycle, ex); 258 } 259 } 260 catch (Exception ex) 261 { 262 // Attempt to switch to the exception page. However, this may itself 263 // fail for a number of reasons, in which case an ApplicationRuntimeException is 264 // thrown. 265 266 if (LOG.isDebugEnabled()) 267 LOG.debug("Uncaught exception", ex); 268 269 activateExceptionPage(cycle, ex); 270 } 271 finally 272 { 273 try 274 { 275 cycle.cleanup(); 276 _infrastructure.getApplicationStateManager().flush(); 277 } 278 catch (Exception ex) 279 { 280 reportException(EngineMessages.exceptionDuringCleanup(ex), ex); 281 } 282 } 283 } 284 285 /** 286 * Handles {@link PageRedirectException} which involves executing 287 * {@link IRequestCycle#activate(IPage)} on the target page (of the exception), until either a 288 * loop is found, or a page succesfully activates. 289 * <p> 290 * This should generally not be overriden in subclasses. 291 * 292 * @since 3.0 293 */ 294 295 protected void handlePageRedirectException(IRequestCycle cycle, PageRedirectException exception) 296 throws IOException 297 { 298 List pageNames = new ArrayList(); 299 300 String pageName = exception.getTargetPageName(); 301 302 while (true) 303 { 304 if (pageNames.contains(pageName)) 305 { 306 pageNames.add(pageName); 307 308 throw new ApplicationRuntimeException(EngineMessages.validateCycle(pageNames)); 309 } 310 311 // Record that this page has been a target. 312 313 pageNames.add(pageName); 314 315 try 316 { 317 // Attempt to activate the new page. 318 319 cycle.activate(pageName); 320 321 break; 322 } 323 catch (PageRedirectException secondRedirectException) 324 { 325 pageName = secondRedirectException.getTargetPageName(); 326 } 327 } 328 329 renderResponse(cycle); 330 } 331 332 /** 333 * Invoked by {@link #service(WebRequest, WebResponse)} if a {@link StaleLinkException} is 334 * thrown by the {@link IEngineService service}. This implementation sets the message property 335 * of the StaleLink page to the message provided in the exception, then invokes 336 * {@link #redirect(String, IRequestCycle, ApplicationRuntimeException)} to render the StaleLink 337 * page. 338 * <p> 339 * Subclasses may overide this method (without invoking this implementation). A better practice 340 * is to contribute an alternative implementation of 341 * {@link org.apache.tapestry.error.StaleLinkExceptionPresenter} to the 342 * tapestry.InfrastructureOverrides configuration point. 343 * <p> 344 * A common practice is to present an error message on the application's Home page. Alternately, 345 * the application may provide its own version of the StaleLink page, overriding the framework's 346 * implementation (probably a good idea, because the default page hints at "application errors" 347 * and isn't localized). The overriding StaleLink implementation must implement a message 348 * property of type String. 349 * 350 * @since 0.2.10 351 */ 352 353 protected void handleStaleLinkException(IRequestCycle cycle, StaleLinkException exception) 354 throws IOException 355 { 356 _infrastructure.getStaleLinkExceptionPresenter().presentStaleLinkException(cycle, exception); 357 } 358 359 /** 360 * Invoked by {@link #service(WebRequest, WebResponse)} if a {@link StaleSessionException} is 361 * thrown by the {@link IEngineService service}. This implementation uses the 362 * {@link org.apache.tapestry.error.StaleSessionExceptionPresenter} to render the StaleSession 363 * page. 364 * <p> 365 * Subclasses may overide this method (without invoking this implementation), but it is better 366 * to override the tapestry.error.StaleSessionExceptionReporter service instead (or contribute a 367 * replacement to the tapestry.InfrastructureOverrides configuration point). 368 * 369 * @since 0.2.10 370 */ 371 372 protected void handleStaleSessionException(IRequestCycle cycle, StaleSessionException exception) 373 throws IOException 374 { 375 _infrastructure.getStaleSessionExceptionPresenter().presentStaleSessionException(cycle, exception); 376 } 377 378 /** 379 * Changes the locale for the engine. 380 */ 381 382 public void setLocale(Locale value) 383 { 384 Defense.notNull(value, "locale"); 385 386 _locale = value; 387 388 // The locale may be set before the engine is initialized with the Infrastructure. 389 390 if (_infrastructure != null) 391 _infrastructure.setLocale(value); 392 } 393 394 /** 395 * @see Infrastructure#getClassResolver() 396 */ 397 398 public ClassResolver getClassResolver() 399 { 400 return _infrastructure.getClassResolver(); 401 } 402 403 /** 404 * {@inheritDoc} 405 */ 406 407 public String toString() 408 { 409 ToStringBuilder builder = new ToStringBuilder(this); 410 411 builder.append("locale", _locale); 412 413 return builder.toString(); 414 } 415 416 /** 417 * Gets the visit object from the 418 * {@link org.apache.tapestry.engine.state.ApplicationStateManager}, creating it if it does not 419 * already exist. 420 * <p> 421 * As of Tapestry 4.0, this will always create the visit object, possibly creating a new session 422 * in the process. 423 */ 424 425 public Object getVisit() 426 { 427 return _infrastructure.getApplicationStateManager().get("visit"); 428 } 429 430 public void setVisit(Object visit) 431 { 432 _infrastructure.getApplicationStateManager().store("visit", visit); 433 } 434 435 /** 436 * Gets the visit object from the 437 * {@link org.apache.tapestry.engine.state.ApplicationStateManager}, which will create it as 438 * necessary. 439 */ 440 441 public Object getVisit(IRequestCycle cycle) 442 { 443 return getVisit(); 444 } 445 446 public boolean getHasVisit() 447 { 448 return _infrastructure.getApplicationStateManager().exists("visit"); 449 } 450 451 public IScriptSource getScriptSource() 452 { 453 return _infrastructure.getScriptSource(); 454 } 455 456 /** 457 * Allows subclasses to include listener methods easily. 458 * 459 * @since 1.0.2 460 */ 461 462 public ListenerMap getListeners() 463 { 464 if (_listeners == null) 465 _listeners = _infrastructure.getListenerMapSource().getListenerMapForObject(this); 466 467 return _listeners; 468 } 469 470 /** 471 * Invoked when a {@link RedirectException} is thrown during the processing of a request. 472 * 473 * @throws ApplicationRuntimeException 474 * if an {@link IOException},{@link ServletException}is thrown by the redirect, 475 * or if no {@link javax.servlet.RequestDispatcher} can be found for local resource. 476 * @since 2.2 477 */ 478 479 protected void handleRedirectException(IRequestCycle cycle, RedirectException redirectException) 480 { 481 String location = redirectException.getRedirectLocation(); 482 483 if (LOG.isDebugEnabled()) 484 LOG.debug("Redirecting to: " + location); 485 486 _infrastructure.getRequest().forward(location); 487 } 488 489 /** 490 * @see Infrastructure#getDataSqueezer() 491 */ 492 493 public DataSqueezer getDataSqueezer() 494 { 495 return _infrastructure.getDataSqueezer(); 496 } 497 498 /** @since 2.3 */ 499 500 public IPropertySource getPropertySource() 501 { 502 return _infrastructure.getApplicationPropertySource(); 503 } 504 505 /** @since 4.0 */ 506 public Infrastructure getInfrastructure() 507 { 508 return _infrastructure; 509 } 510 511 public String getOutputEncoding() 512 { 513 return _infrastructure.getOutputEncoding(); 514 } 515 }