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