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