Clover coverage report - Code Coverage for tapestry release 4.0.1
Coverage timestamp: Fri Mar 31 2006 09:12:14 EST
file stats: LOC: 551   Methods: 28
NCLOC: 247   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
AbstractEngine.java 57.1% 68.4% 57.1% 64.5%
coverage coverage
 1    // Copyright 2004, 2005 The Apache Software Foundation
 2    //
 3    // Licensed under the Apache License, Version 2.0 (the "License");
 4    // you may not use this file except in compliance with the License.
 5    // You may obtain a copy of the License at
 6    //
 7    // http://www.apache.org/licenses/LICENSE-2.0
 8    //
 9    // Unless required by applicable law or agreed to in writing, software
 10    // distributed under the License is distributed on an "AS IS" BASIS,
 11    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12    // See the License for the specific language governing permissions and
 13    // limitations under the License.
 14   
 15    package org.apache.tapestry.engine;
 16   
 17    import java.io.IOException;
 18    import java.util.ArrayList;
 19    import java.util.List;
 20    import java.util.Locale;
 21   
 22    import org.apache.commons.logging.Log;
 23    import org.apache.commons.logging.LogFactory;
 24    import org.apache.hivemind.ApplicationRuntimeException;
 25    import org.apache.hivemind.ClassResolver;
 26    import org.apache.hivemind.util.Defense;
 27    import org.apache.hivemind.util.ToStringBuilder;
 28    import org.apache.tapestry.Constants;
 29    import org.apache.tapestry.IEngine;
 30    import org.apache.tapestry.IPage;
 31    import org.apache.tapestry.IRequestCycle;
 32    import org.apache.tapestry.PageRedirectException;
 33    import org.apache.tapestry.RedirectException;
 34    import org.apache.tapestry.StaleLinkException;
 35    import org.apache.tapestry.StaleSessionException;
 36    import org.apache.tapestry.listener.ListenerMap;
 37    import org.apache.tapestry.services.DataSqueezer;
 38    import org.apache.tapestry.services.Infrastructure;
 39    import org.apache.tapestry.spec.IApplicationSpecification;
 40    import org.apache.tapestry.web.WebRequest;
 41    import org.apache.tapestry.web.WebResponse;
 42   
 43    /**
 44    * Basis for building real Tapestry applications. Immediate subclasses provide different strategies
 45    * for managing page state and other resources between request cycles.
 46    * <p>
 47    * Note: much of this description is <em>in transition</em> as part of Tapestry 4.0. All ad-hoc
 48    * singletons and such are being replaced with HiveMind services.
 49    * <p>
 50    * Uses a shared instance of {@link ITemplateSource},{@link ISpecificationSource},
 51    * {@link IScriptSource}and {@link IComponentMessagesSource}stored as attributes of the
 52    * {@link ServletContext}(they will be shared by all sessions).
 53    * <p>
 54    * An engine is designed to be very lightweight. Particularily, it should <b>never </b> hold
 55    * references to any {@link IPage}or {@link org.apache.tapestry.IComponent}objects. The entire
 56    * system is based upon being able to quickly rebuild the state of any page(s).
 57    * <p>
 58    * Where possible, instance variables should be transient. They can be restored inside
 59    * {@link #setupForRequest(RequestContext)}.
 60    * <p>
 61    * In practice, a subclass (usually {@link BaseEngine}) is used without subclassing. Instead, a
 62    * visit object is specified. To facilitate this, the application specification may include a
 63    * property, <code>org.apache.tapestry.visit-class</code> which is the class name to instantiate
 64    * when a visit object is first needed. See {@link #createVisit(IRequestCycle)}for more details.
 65    * <p>
 66    * Some of the classes' behavior is controlled by JVM system properties (typically only used during
 67    * development): <table border=1>
 68    * <tr>
 69    * <th>Property</th>
 70    * <th>Description</th>
 71    * </tr>
 72    * <tr>
 73    * <td>org.apache.tapestry.enable-reset-service</td>
 74    * <td>If true, enabled an additional service, reset, that allow page, specification and template
 75    * caches to be cleared on demand. See {@link #isResetServiceEnabled()}.</td>
 76    * </tr>
 77    * <tr>
 78    * <td>org.apache.tapestry.disable-caching</td>
 79    * <td>If true, then the page, specification, template and script caches will be cleared after each
 80    * request. This slows things down, but ensures that the latest versions of such files are used.
 81    * Care should be taken that the source directories for the files preceeds any versions of the files
 82    * available in JARs or WARs.</td>
 83    * </tr>
 84    * </table>
 85    *
 86    * @author Howard Lewis Ship
 87    */
 88   
 89    public abstract class AbstractEngine implements IEngine
 90    {
 91    private static final Log LOG = LogFactory.getLog(AbstractEngine.class);
 92   
 93    /**
 94    * The link to the world of HiveMind services.
 95    *
 96    * @since 4.0
 97    */
 98    private Infrastructure _infrastructure;
 99   
 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  40 protected void activateExceptionPage(IRequestCycle cycle, Throwable cause)
 120    {
 121  40 _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  0 public void reportException(String reportTitle, Throwable ex)
 131    {
 132  0 _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  0 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  620 public Locale getLocale()
 151    {
 152  620 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  112 public IEngineService getService(String name)
 163    {
 164  112 return _infrastructure.getServiceMap().getService(name);
 165    }
 166   
 167    /** @see Infrastructure#getApplicationSpecification() */
 168   
 169  12 public IApplicationSpecification getSpecification()
 170    {
 171  12 return _infrastructure.getApplicationSpecification();
 172    }
 173   
 174    /** @see Infrastructure#getSpecificationSource() */
 175   
 176  0 public ISpecificationSource getSpecificationSource()
 177    {
 178  0 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  0 protected void redirect(String pageName, IRequestCycle cycle,
 187    ApplicationRuntimeException exception) throws IOException
 188    {
 189  0 IPage page = cycle.getPage(pageName);
 190   
 191  0 cycle.activate(page);
 192   
 193  0 renderResponse(cycle);
 194    }
 195   
 196    /**
 197    * Delegates to
 198    * {@link org.apache.tapestry.services.ResponseRenderer#renderResponse(IRequestCycle)}.
 199    */
 200   
 201  4 public void renderResponse(IRequestCycle cycle) throws IOException
 202    {
 203  4 _infrastructure.getResponseRenderer().renderResponse(cycle);
 204    }
 205   
 206    /**
 207    * Delegate method for the servlet. Services the request.
 208    */
 209   
 210  246 public void service(WebRequest request, WebResponse response) throws IOException
 211    {
 212  246 IRequestCycle cycle = null;
 213  246 IMonitor monitor = null;
 214  246 IEngineService service = null;
 215   
 216  246 if (_infrastructure == null)
 217  94 _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  246 try
 223    {
 224  246 cycle = _infrastructure.getRequestCycleFactory().newRequestCycle(this);
 225    }
 226    catch (RuntimeException ex)
 227    {
 228  0 throw ex;
 229    }
 230    catch (Exception ex)
 231    {
 232  0 throw new IOException(ex.getMessage());
 233    }
 234   
 235  246 try
 236    {
 237  246 try
 238    {
 239  246 monitor = cycle.getMonitor();
 240   
 241  246 service = cycle.getService();
 242   
 243  246 monitor.serviceBegin(service.getName(), _infrastructure.getRequest()
 244    .getRequestURI());
 245   
 246    // Let the service handle the rest of the request.
 247   
 248  246 service.service(cycle);
 249   
 250  194 return;
 251    }
 252    catch (PageRedirectException ex)
 253    {
 254  6 handlePageRedirectException(cycle, ex);
 255    }
 256    catch (RedirectException ex)
 257    {
 258  0 handleRedirectException(cycle, ex);
 259    }
 260    catch (StaleLinkException ex)
 261    {
 262  2 handleStaleLinkException(cycle, ex);
 263    }
 264    catch (StaleSessionException ex)
 265    {
 266  6 handleStaleSessionException(cycle, ex);
 267    }
 268    }
 269    catch (Exception ex)
 270    {
 271  40 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  40 if (LOG.isDebugEnabled())
 278  0 LOG.debug("Uncaught exception", ex);
 279   
 280  40 activateExceptionPage(cycle, ex);
 281    }
 282    finally
 283    {
 284  246 if (service != null)
 285  246 monitor.serviceEnd(service.getName());
 286   
 287  246 try
 288    {
 289  246 cycle.cleanup();
 290  246 _infrastructure.getApplicationStateManager().flush();
 291    }
 292    catch (Exception ex)
 293    {
 294  0 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  6 protected void handlePageRedirectException(IRequestCycle cycle, PageRedirectException exception)
 310    throws IOException
 311    {
 312  6 List pageNames = new ArrayList();
 313   
 314  6 String pageName = exception.getTargetPageName();
 315   
 316  6 while (true)
 317    {
 318  10 if (pageNames.contains(pageName))
 319    {
 320  2 pageNames.add(pageName);
 321   
 322  2 throw new ApplicationRuntimeException(EngineMessages.validateCycle(pageNames));
 323    }
 324   
 325    // Record that this page has been a target.
 326   
 327  8 pageNames.add(pageName);
 328   
 329  8 try
 330    {
 331    // Attempt to activate the new page.
 332   
 333  8 cycle.activate(pageName);
 334   
 335  4 break;
 336    }
 337    catch (PageRedirectException secondRedirectException)
 338    {
 339  4 pageName = secondRedirectException.getTargetPageName();
 340    }
 341    }
 342   
 343  4 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  2 protected void handleStaleLinkException(IRequestCycle cycle, StaleLinkException exception)
 368    throws IOException
 369    {
 370  2 _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  6 protected void handleStaleSessionException(IRequestCycle cycle, StaleSessionException exception)
 388    throws IOException
 389    {
 390  6 _infrastructure.getStaleSessionExceptionPresenter().presentStaleSessionException(
 391    cycle,
 392    exception);
 393    }
 394   
 395    /**
 396    * Changes the locale for the engine.
 397    */
 398   
 399  124 public void setLocale(Locale value)
 400    {
 401  124 Defense.notNull(value, "locale");
 402   
 403  124 _locale = value;
 404   
 405    // The locale may be set before the engine is initialized with the Infrastructure.
 406   
 407  124 if (_infrastructure != null)
 408  24 _infrastructure.setLocale(value);
 409    }
 410   
 411    /**
 412    * @see Infrastructure#getClassResolver()
 413    */
 414   
 415  146 public ClassResolver getClassResolver()
 416    {
 417  146 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  0 public String toString()
 428    {
 429  0 ToStringBuilder builder = new ToStringBuilder(this);
 430   
 431  0 builder.append("locale", _locale);
 432   
 433  0 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  4 public Object getVisit()
 446    {
 447  4 return _infrastructure.getApplicationStateManager().get("visit");
 448    }
 449   
 450  0 public void setVisit(Object visit)
 451    {
 452  0 _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  2 public Object getVisit(IRequestCycle cycle)
 462    {
 463  2 return getVisit();
 464    }
 465   
 466  0 public boolean getHasVisit()
 467    {
 468  0 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  2 public Object getGlobal()
 483    {
 484  2 return _infrastructure.getApplicationStateManager().get("global");
 485    }
 486   
 487  0 public IScriptSource getScriptSource()
 488    {
 489  0 return _infrastructure.getScriptSource();
 490    }
 491   
 492    /**
 493    * Allows subclasses to include listener methods easily.
 494    *
 495    * @since 1.0.2
 496    */
 497   
 498  0 public ListenerMap getListeners()
 499    {
 500  0 if (_listeners == null)
 501  0 _listeners = _infrastructure.getListenerMapSource().getListenerMapForObject(this);
 502   
 503  0 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  0 protected void handleRedirectException(IRequestCycle cycle, RedirectException redirectException)
 516    {
 517  0 String location = redirectException.getRedirectLocation();
 518   
 519  0 if (LOG.isDebugEnabled())
 520  0 LOG.debug("Redirecting to: " + location);
 521   
 522  0 _infrastructure.getRequest().forward(location);
 523    }
 524   
 525    /**
 526    * @see Infrastructure#getDataSqueezer()
 527    */
 528   
 529  0 public DataSqueezer getDataSqueezer()
 530    {
 531  0 return _infrastructure.getDataSqueezer();
 532    }
 533   
 534    /** @since 2.3 */
 535   
 536  0 public IPropertySource getPropertySource()
 537    {
 538  0 return _infrastructure.getApplicationPropertySource();
 539    }
 540   
 541    /** @since 4.0 */
 542  48 public Infrastructure getInfrastructure()
 543    {
 544  48 return _infrastructure;
 545    }
 546   
 547  630 public String getOutputEncoding()
 548    {
 549  630 return _infrastructure.getOutputEncoding();
 550    }
 551    }