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