Coverage Report - org.apache.tapestry.engine.AbstractEngine
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractEngine
0%
0/94
0%
0/12
1.704
 
 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  0
 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  0
     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  0
         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)
 184  
       throws IOException
 185  
     {
 186  0
         IPage page = cycle.getPage(pageName);
 187  
 
 188  0
         cycle.activate(page);
 189  
 
 190  0
         renderResponse(cycle);
 191  0
     }
 192  
 
 193  
     /**
 194  
      * Delegates to
 195  
      * {@link org.apache.tapestry.services.ResponseRenderer#renderResponse(IRequestCycle)}.
 196  
      */
 197  
 
 198  
     public void renderResponse(IRequestCycle cycle)
 199  
       throws IOException
 200  
     {
 201  0
         _infrastructure.getResponseRenderer().renderResponse(cycle);
 202  0
     }
 203  
 
 204  
     /**
 205  
      * Delegate method for the servlet. Services the request.
 206  
      */
 207  
 
 208  
     public void service(WebRequest request, WebResponse response)
 209  
       throws IOException
 210  
     {
 211  0
         IRequestCycle cycle = null;
 212  0
         IEngineService service = null;
 213  
 
 214  0
         if (_infrastructure == null)
 215  0
             _infrastructure = (Infrastructure) request.getAttribute(Constants.INFRASTRUCTURE_KEY);
 216  
 
 217  
         // Create the request cycle; if this fails, there's not much that can be done ... everything
 218  
         // else in Tapestry relies on the RequestCycle.
 219  
 
 220  
         try
 221  
         {
 222  0
             cycle = _infrastructure.getRequestCycleFactory().newRequestCycle(this);
 223  
         }
 224  0
         catch (RuntimeException ex)
 225  
         {
 226  0
             throw ex;
 227  
         }
 228  0
         catch (Exception ex)
 229  
         {
 230  0
             throw new IOException(ex.getMessage());
 231  0
         }
 232  
 
 233  
         try
 234  
         {
 235  
             try
 236  
             {
 237  0
                 service = cycle.getService();
 238  
 
 239  
                 // Let the service handle the rest of the request.
 240  
 
 241  0
                 service.service(cycle);
 242  
             }
 243  0
             catch (PageRedirectException ex)
 244  
             {
 245  0
                 handlePageRedirectException(cycle, ex);
 246  
             }
 247  0
             catch (RedirectException ex)
 248  
             {
 249  0
                 handleRedirectException(cycle, ex);
 250  
             }
 251  0
             catch (StaleLinkException ex)
 252  
             {
 253  0
                 handleStaleLinkException(cycle, ex);
 254  
             }
 255  0
             catch (StaleSessionException ex)
 256  
             {
 257  0
                 handleStaleSessionException(cycle, ex);
 258  0
             }
 259  
         }
 260  0
         catch (Exception ex)
 261  
         {
 262  
             // Attempt to switch to the exception page. However, this may itself
 263  
             // fail for a number of reasons, in which case an ApplicationRuntimeException is
 264  
             // thrown.
 265  
 
 266  0
             if (LOG.isDebugEnabled())
 267  0
                 LOG.debug("Uncaught exception", ex);
 268  
 
 269  0
             activateExceptionPage(cycle, ex);
 270  
         }
 271  
         finally
 272  
         {
 273  0
             try
 274  
             {
 275  0
                 cycle.cleanup();
 276  0
                 _infrastructure.getApplicationStateManager().flush();
 277  
             }
 278  0
             catch (Exception ex)
 279  
             {
 280  0
                 reportException(EngineMessages.exceptionDuringCleanup(ex), ex);
 281  0
             }
 282  0
         }
 283  0
     }
 284  
 
 285  
     /**
 286  
      * Handles {@link PageRedirectException} which involves executing
 287  
      * {@link IRequestCycle#activate(IPage)} on the target page (of the exception), until either a
 288  
      * loop is found, or a page succesfully activates.
 289  
      * <p>
 290  
      * This should generally not be overriden in subclasses.
 291  
      *
 292  
      * @since 3.0
 293  
      */
 294  
 
 295  
     protected void handlePageRedirectException(IRequestCycle cycle, PageRedirectException exception)
 296  
       throws IOException
 297  
     {
 298  0
         List pageNames = new ArrayList();
 299  
 
 300  0
         String pageName = exception.getTargetPageName();
 301  
 
 302  
         while (true)
 303  
         {
 304  0
             if (pageNames.contains(pageName))
 305  
             {
 306  0
                 pageNames.add(pageName);
 307  
 
 308  0
                 throw new ApplicationRuntimeException(EngineMessages.validateCycle(pageNames));
 309  
             }
 310  
 
 311  
             // Record that this page has been a target.
 312  
 
 313  0
             pageNames.add(pageName);
 314  
 
 315  
             try
 316  
             {
 317  
                 // Attempt to activate the new page.
 318  
 
 319  0
                 cycle.activate(pageName);
 320  
 
 321  0
                 break;
 322  
             }
 323  0
             catch (PageRedirectException secondRedirectException)
 324  
             {
 325  0
                 pageName = secondRedirectException.getTargetPageName();
 326  0
             }
 327  
         }
 328  
 
 329  0
         renderResponse(cycle);
 330  0
     }
 331  
 
 332  
     /**
 333  
      * Invoked by {@link #service(WebRequest, WebResponse)} if a {@link StaleLinkException} is
 334  
      * thrown by the {@link IEngineService service}. This implementation sets the message property
 335  
      * of the StaleLink page to the message provided in the exception, then invokes
 336  
      * {@link #redirect(String, IRequestCycle, ApplicationRuntimeException)} to render the StaleLink
 337  
      * page.
 338  
      * <p>
 339  
      * Subclasses may overide this method (without invoking this implementation). A better practice
 340  
      * is to contribute an alternative implementation of
 341  
      * {@link org.apache.tapestry.error.StaleLinkExceptionPresenter} to the
 342  
      * tapestry.InfrastructureOverrides configuration point.
 343  
      * <p>
 344  
      * A common practice is to present an error message on the application's Home page. Alternately,
 345  
      * the application may provide its own version of the StaleLink page, overriding the framework's
 346  
      * implementation (probably a good idea, because the default page hints at "application errors"
 347  
      * and isn't localized). The overriding StaleLink implementation must implement a message
 348  
      * property of type String.
 349  
      *
 350  
      * @since 0.2.10
 351  
      */
 352  
 
 353  
     protected void handleStaleLinkException(IRequestCycle cycle, StaleLinkException exception)
 354  
       throws IOException
 355  
     {
 356  0
         _infrastructure.getStaleLinkExceptionPresenter().presentStaleLinkException(cycle, exception);
 357  0
     }
 358  
 
 359  
     /**
 360  
      * Invoked by {@link #service(WebRequest, WebResponse)} if a {@link StaleSessionException} is
 361  
      * thrown by the {@link IEngineService service}. This implementation uses the
 362  
      * {@link org.apache.tapestry.error.StaleSessionExceptionPresenter} to render the StaleSession
 363  
      * page.
 364  
      * <p>
 365  
      * Subclasses may overide this method (without invoking this implementation), but it is better
 366  
      * to override the tapestry.error.StaleSessionExceptionReporter service instead (or contribute a
 367  
      * replacement to the tapestry.InfrastructureOverrides configuration point).
 368  
      *
 369  
      * @since 0.2.10
 370  
      */
 371  
 
 372  
     protected void handleStaleSessionException(IRequestCycle cycle, StaleSessionException exception)
 373  
       throws IOException
 374  
     {
 375  0
         _infrastructure.getStaleSessionExceptionPresenter().presentStaleSessionException(cycle, exception);
 376  0
     }
 377  
 
 378  
     /**
 379  
      * Changes the locale for the engine.
 380  
      */
 381  
 
 382  
     public void setLocale(Locale value)
 383  
     {
 384  0
         Defense.notNull(value, "locale");
 385  
 
 386  0
         _locale = value;
 387  
 
 388  
         // The locale may be set before the engine is initialized with the Infrastructure.
 389  
 
 390  0
         if (_infrastructure != null)
 391  0
             _infrastructure.setLocale(value);
 392  0
     }
 393  
 
 394  
     /**
 395  
      * @see Infrastructure#getClassResolver()
 396  
      */
 397  
 
 398  
     public ClassResolver getClassResolver()
 399  
     {
 400  0
         return _infrastructure.getClassResolver();
 401  
     }
 402  
 
 403  
     /**
 404  
      * {@inheritDoc}
 405  
      */
 406  
 
 407  
     public String toString()
 408  
     {
 409  0
         ToStringBuilder builder = new ToStringBuilder(this);
 410  
 
 411  0
         builder.append("locale", _locale);
 412  
 
 413  0
         return builder.toString();
 414  
     }
 415  
 
 416  
     /**
 417  
      * Gets the visit object from the
 418  
      * {@link org.apache.tapestry.engine.state.ApplicationStateManager}, creating it if it does not
 419  
      * already exist.
 420  
      * <p>
 421  
      * As of Tapestry 4.0, this will always create the visit object, possibly creating a new session
 422  
      * in the process.
 423  
      */
 424  
 
 425  
     public Object getVisit()
 426  
     {
 427  0
         return _infrastructure.getApplicationStateManager().get("visit");
 428  
     }
 429  
 
 430  
     public void setVisit(Object visit)
 431  
     {
 432  0
         _infrastructure.getApplicationStateManager().store("visit", visit);
 433  0
     }
 434  
 
 435  
     /**
 436  
      * Gets the visit object from the
 437  
      * {@link org.apache.tapestry.engine.state.ApplicationStateManager}, which will create it as
 438  
      * necessary.
 439  
      */
 440  
 
 441  
     public Object getVisit(IRequestCycle cycle)
 442  
     {
 443  0
         return getVisit();
 444  
     }
 445  
 
 446  
     public boolean getHasVisit()
 447  
     {
 448  0
         return _infrastructure.getApplicationStateManager().exists("visit");
 449  
     }
 450  
 
 451  
     public IScriptSource getScriptSource()
 452  
     {
 453  0
         return _infrastructure.getScriptSource();
 454  
     }
 455  
 
 456  
     /**
 457  
      * Allows subclasses to include listener methods easily.
 458  
      *
 459  
      * @since 1.0.2
 460  
      */
 461  
 
 462  
     public ListenerMap getListeners()
 463  
     {
 464  0
         if (_listeners == null)
 465  0
             _listeners = _infrastructure.getListenerMapSource().getListenerMapForObject(this);
 466  
 
 467  0
         return _listeners;
 468  
     }
 469  
 
 470  
     /**
 471  
      * Invoked when a {@link RedirectException} is thrown during the processing of a request.
 472  
      *
 473  
      * @throws ApplicationRuntimeException
 474  
      *             if an {@link IOException},{@link ServletException}is thrown by the redirect,
 475  
      *             or if no {@link javax.servlet.RequestDispatcher} can be found for local resource.
 476  
      * @since 2.2
 477  
      */
 478  
 
 479  
     protected void handleRedirectException(IRequestCycle cycle, RedirectException redirectException)
 480  
     {
 481  0
         String location = redirectException.getRedirectLocation();
 482  
 
 483  0
         if (LOG.isDebugEnabled())
 484  0
             LOG.debug("Redirecting to: " + location);
 485  
 
 486  0
         _infrastructure.getRequest().forward(location);
 487  0
     }
 488  
 
 489  
     /**
 490  
      * @see Infrastructure#getDataSqueezer()
 491  
      */
 492  
 
 493  
     public DataSqueezer getDataSqueezer()
 494  
     {
 495  0
         return _infrastructure.getDataSqueezer();
 496  
     }
 497  
 
 498  
     /** @since 2.3 */
 499  
 
 500  
     public IPropertySource getPropertySource()
 501  
     {
 502  0
         return _infrastructure.getApplicationPropertySource();
 503  
     }
 504  
 
 505  
     /** @since 4.0 */
 506  
     public Infrastructure getInfrastructure()
 507  
     {
 508  0
         return _infrastructure;
 509  
     }
 510  
 
 511  
     public String getOutputEncoding()
 512  
     {
 513  0
         return _infrastructure.getOutputEncoding();
 514  
     }
 515  
 }