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