Clover coverage report - Code Coverage for tapestry release 4.0-beta-12
Coverage timestamp: Sun Oct 30 2005 16:22:01 EST
file stats: LOC: 714   Methods: 38
NCLOC: 374   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
RequestCycle.java 76.5% 90.2% 94.7% 88.9%
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.util.HashMap;
 18    import java.util.Iterator;
 19    import java.util.Map;
 20   
 21    import org.apache.commons.logging.Log;
 22    import org.apache.commons.logging.LogFactory;
 23    import org.apache.hivemind.ApplicationRuntimeException;
 24    import org.apache.hivemind.ErrorLog;
 25    import org.apache.hivemind.impl.ErrorLogImpl;
 26    import org.apache.hivemind.util.Defense;
 27    import org.apache.hivemind.util.ToStringBuilder;
 28    import org.apache.tapestry.IComponent;
 29    import org.apache.tapestry.IEngine;
 30    import org.apache.tapestry.IForm;
 31    import org.apache.tapestry.IMarkupWriter;
 32    import org.apache.tapestry.IPage;
 33    import org.apache.tapestry.IRequestCycle;
 34    import org.apache.tapestry.RedirectException;
 35    import org.apache.tapestry.RenderRewoundException;
 36    import org.apache.tapestry.StaleLinkException;
 37    import org.apache.tapestry.Tapestry;
 38    import org.apache.tapestry.record.PageRecorderImpl;
 39    import org.apache.tapestry.record.PropertyPersistenceStrategySource;
 40    import org.apache.tapestry.request.RequestContext;
 41    import org.apache.tapestry.services.AbsoluteURLBuilder;
 42    import org.apache.tapestry.services.Infrastructure;
 43    import org.apache.tapestry.util.IdAllocator;
 44    import org.apache.tapestry.util.QueryParameterMap;
 45   
 46    /**
 47    * Provides the logic for processing a single request cycle. Provides access to the
 48    * {@link IEngine engine} and the {@link RequestContext}.
 49    *
 50    * @author Howard Lewis Ship
 51    */
 52   
 53    public class RequestCycle implements IRequestCycle
 54    {
 55    private static final Log LOG = LogFactory.getLog(RequestCycle.class);
 56   
 57    private IPage _page;
 58   
 59    private IEngine _engine;
 60   
 61    private String _serviceName;
 62   
 63    private IMonitor _monitor;
 64   
 65    /** @since 4.0 */
 66   
 67    private PropertyPersistenceStrategySource _strategySource;
 68   
 69    /** @since 4.0 */
 70   
 71    private IPageSource _pageSource;
 72   
 73    /** @since 4.0 */
 74   
 75    private Infrastructure _infrastructure;
 76   
 77    /**
 78    * Contains parameters extracted from the request context, plus any decoded by any
 79    * {@link ServiceEncoder}s.
 80    *
 81    * @since 4.0
 82    */
 83   
 84    private QueryParameterMap _parameters;
 85   
 86    /** @since 4.0 */
 87   
 88    private AbsoluteURLBuilder _absoluteURLBuilder;
 89   
 90    /**
 91    * A mapping of pages loaded during the current request cycle. Key is the page name, value is
 92    * the {@link IPage}instance.
 93    */
 94   
 95    private Map _loadedPages;
 96   
 97    /**
 98    * A mapping of page recorders for the current request cycle. Key is the page name, value is the
 99    * {@link IPageRecorder}instance.
 100    */
 101   
 102    private Map _pageRecorders;
 103   
 104    private boolean _rewinding = false;
 105   
 106    private Map _attributes = new HashMap();
 107   
 108    private int _actionId;
 109   
 110    private int _targetActionId;
 111   
 112    private IComponent _targetComponent;
 113   
 114    /** @since 2.0.3 * */
 115   
 116    private Object[] _listenerParameters;
 117   
 118    /** @since 4.0 */
 119   
 120    private ErrorLog _log;
 121   
 122    private RequestContext _requestContext;
 123   
 124    /** @since 4.0 */
 125   
 126    private IdAllocator _idAllocator = new IdAllocator();
 127   
 128    /**
 129    * Standard constructor used to render a response page.
 130    *
 131    * @param engine
 132    * the current request's engine
 133    * @param parameters
 134    * query parameters (possibly the result of {@link ServiceEncoder}s decoding path
 135    * information)
 136    * @param serviceName
 137    * the name of engine service
 138    * @param monitor
 139    * informed of various events during the processing of the request
 140    * @param environment
 141    * additional invariant services and objects needed by each RequestCycle instance
 142    */
 143   
 144  125 public RequestCycle(IEngine engine, QueryParameterMap parameters, String serviceName,
 145    IMonitor monitor, RequestCycleEnvironment environment)
 146    {
 147    // Variant from instance to instance
 148   
 149  125 _engine = engine;
 150  125 _parameters = parameters;
 151  125 _serviceName = serviceName;
 152  125 _monitor = monitor;
 153   
 154    // Invariant from instance to instance
 155   
 156  125 _infrastructure = environment.getInfrastructure();
 157  125 _pageSource = _infrastructure.getPageSource();
 158  125 _strategySource = environment.getStrategySource();
 159  125 _absoluteURLBuilder = environment.getAbsoluteURLBuilder();
 160  125 _requestContext = environment.getRequestContext();
 161  125 _log = new ErrorLogImpl(environment.getErrorHandler(), LOG);
 162   
 163    }
 164   
 165    /**
 166    * Alternate constructor used <strong>only for testing purposes</strong>.
 167    *
 168    * @since 4.0
 169    */
 170  2 public RequestCycle()
 171    {
 172    }
 173   
 174    /**
 175    * Called at the end of the request cycle (i.e., after all responses have been sent back to the
 176    * client), to release all pages loaded during the request cycle.
 177    */
 178   
 179  123 public void cleanup()
 180    {
 181  123 if (_loadedPages == null)
 182  1 return;
 183   
 184  122 Iterator i = _loadedPages.values().iterator();
 185   
 186  122 while (i.hasNext())
 187    {
 188  175 IPage page = (IPage) i.next();
 189   
 190  175 _pageSource.releasePage(page);
 191    }
 192   
 193  122 _loadedPages = null;
 194  122 _pageRecorders = null;
 195   
 196    }
 197   
 198  124 public IEngineService getService()
 199    {
 200  124 return _infrastructure.getServiceMap().getService(_serviceName);
 201    }
 202   
 203  109 public String encodeURL(String URL)
 204    {
 205  109 return _infrastructure.getResponse().encodeURL(URL);
 206    }
 207   
 208  645 public IEngine getEngine()
 209    {
 210  645 return _engine;
 211    }
 212   
 213  640 public Object getAttribute(String name)
 214    {
 215  640 return _attributes.get(name);
 216    }
 217   
 218  124 public IMonitor getMonitor()
 219    {
 220  124 return _monitor;
 221    }
 222   
 223    /** @deprecated */
 224  74 public String getNextActionId()
 225    {
 226  74 return Integer.toHexString(++_actionId);
 227    }
 228   
 229  311 public IPage getPage()
 230    {
 231  311 return _page;
 232    }
 233   
 234    /**
 235    * Gets the page from the engines's {@link IPageSource}.
 236    */
 237   
 238  189 public IPage getPage(String name)
 239    {
 240  189 Defense.notNull(name, "name");
 241   
 242  189 IPage result = null;
 243   
 244  189 if (_loadedPages != null)
 245  57 result = (IPage) _loadedPages.get(name);
 246   
 247  189 if (result == null)
 248    {
 249  185 result = loadPage(name);
 250   
 251  175 if (_loadedPages == null)
 252  122 _loadedPages = new HashMap();
 253   
 254  175 _loadedPages.put(name, result);
 255    }
 256   
 257  179 return result;
 258    }
 259   
 260  185 private IPage loadPage(String name)
 261    {
 262  185 try
 263    {
 264  185 _monitor.pageLoadBegin(name);
 265   
 266  185 IPage result = _pageSource.getPage(this, name, _monitor);
 267   
 268    // Get the recorder that will eventually observe and record
 269    // changes to persistent properties of the page.
 270   
 271  175 IPageRecorder recorder = getPageRecorder(name);
 272   
 273    // Have it rollback the page to the prior state. Note that
 274    // the page has a null observer at this time (which keeps
 275    // these changes from being sent to the page recorder).
 276   
 277  175 recorder.rollback(result);
 278   
 279    // Now, have the page use the recorder for any future
 280    // property changes.
 281   
 282  175 result.setChangeObserver(recorder);
 283   
 284    // Now that persistent properties have been restored, we can
 285    // attach the page to this request.
 286   
 287  175 result.attach(_engine, this);
 288   
 289  175 return result;
 290    }
 291    finally
 292    {
 293  185 _monitor.pageLoadEnd(name);
 294    }
 295   
 296    }
 297   
 298    /**
 299    * Returns the page recorder for the named page. Starting with Tapestry 4.0, page recorders are
 300    * shortlived objects managed exclusively by the request cycle.
 301    */
 302   
 303  175 protected IPageRecorder getPageRecorder(String name)
 304    {
 305  175 if (_pageRecorders == null)
 306  122 _pageRecorders = new HashMap();
 307   
 308  175 IPageRecorder result = (IPageRecorder) _pageRecorders.get(name);
 309   
 310  175 if (result == null)
 311    {
 312  175 result = new PageRecorderImpl(name, this, _strategySource, _log);
 313  175 _pageRecorders.put(name, result);
 314    }
 315   
 316  175 return result;
 317    }
 318   
 319  1954 public boolean isRewinding()
 320    {
 321  1954 return _rewinding;
 322    }
 323   
 324  74 public boolean isRewound(IComponent component) throws StaleLinkException
 325    {
 326    // If not rewinding ...
 327   
 328  74 if (!_rewinding)
 329  45 return false;
 330   
 331  29 if (_actionId != _targetActionId)
 332  3 return false;
 333   
 334    // OK, we're there, is the page is good order?
 335   
 336  26 if (component == _targetComponent)
 337  26 return true;
 338   
 339    // Woops. Mismatch.
 340   
 341  0 throw new StaleLinkException(component, Integer.toHexString(_targetActionId),
 342    _targetComponent.getExtendedId());
 343    }
 344   
 345  266 public void removeAttribute(String name)
 346    {
 347  266 if (LOG.isDebugEnabled())
 348  0 LOG.debug("Removing attribute " + name);
 349   
 350  266 _attributes.remove(name);
 351    }
 352   
 353    /**
 354    * Renders the page by invoking {@link IPage#renderPage(IMarkupWriter, IRequestCycle)}. This
 355    * clears all attributes.
 356    */
 357   
 358  130 public void renderPage(IMarkupWriter writer)
 359    {
 360  130 String pageName = _page.getPageName();
 361  130 _monitor.pageRenderBegin(pageName);
 362   
 363  130 _rewinding = false;
 364  130 _actionId = -1;
 365  130 _targetActionId = 0;
 366   
 367  130 try
 368    {
 369  130 _page.renderPage(writer, this);
 370   
 371    }
 372    catch (ApplicationRuntimeException ex)
 373    {
 374    // Nothing much to add here.
 375   
 376  8 throw ex;
 377    }
 378    catch (Throwable ex)
 379    {
 380    // But wrap other exceptions in a RequestCycleException ... this
 381    // will ensure that some of the context is available.
 382   
 383  0 throw new ApplicationRuntimeException(ex.getMessage(), _page, null, ex);
 384    }
 385    finally
 386    {
 387  130 reset();
 388    }
 389   
 390  122 _monitor.pageRenderEnd(pageName);
 391   
 392    }
 393   
 394    /**
 395    * Resets all internal state after a render or a rewind.
 396    */
 397   
 398  156 private void reset()
 399    {
 400  156 _actionId = 0;
 401  156 _targetActionId = 0;
 402  156 _attributes.clear();
 403  156 _idAllocator.clear();
 404    }
 405   
 406    /**
 407    * Rewinds an individual form by invoking {@link IForm#rewind(IMarkupWriter, IRequestCycle)}.
 408    * <p>
 409    * The process is expected to end with a {@link RenderRewoundException}. If the entire page is
 410    * renderred without this exception being thrown, it means that the target action id was not
 411    * valid, and a {@link ApplicationRuntimeException}&nbsp;is thrown.
 412    * <p>
 413    * This clears all attributes.
 414    *
 415    * @since 1.0.2
 416    */
 417   
 418  21 public void rewindForm(IForm form)
 419    {
 420  21 IPage page = form.getPage();
 421  21 String pageName = page.getPageName();
 422   
 423  21 _rewinding = true;
 424   
 425  21 _monitor.pageRewindBegin(pageName);
 426   
 427    // Fake things a little for getNextActionId() / isRewound()
 428    // This used to be more involved (and include service parameters, and a parameter
 429    // to this method), when the actionId was part of the Form name. That's not longer
 430    // necessary (no service parameters), and we can fake things here easily enough with
 431    // fixed actionId of 0.
 432   
 433  21 _targetActionId = 0;
 434  21 _actionId = -1;
 435   
 436  21 _targetComponent = form;
 437   
 438  21 try
 439    {
 440  21 page.beginPageRender();
 441   
 442  21 form.rewind(NullWriter.getSharedInstance(), this);
 443   
 444    // Shouldn't get this far, because the form should
 445    // throw the RenderRewoundException.
 446   
 447  0 throw new StaleLinkException(Tapestry.format("RequestCycle.form-rewind-failure", form
 448    .getExtendedId()), form);
 449    }
 450    catch (RenderRewoundException ex)
 451    {
 452    // This is acceptible and expected.
 453    }
 454    catch (ApplicationRuntimeException ex)
 455    {
 456    // RequestCycleExceptions don't need to be wrapped.
 457  2 throw ex;
 458    }
 459    catch (Throwable ex)
 460    {
 461    // But wrap other exceptions in a ApplicationRuntimeException ... this
 462    // will ensure that some of the context is available.
 463   
 464  0 throw new ApplicationRuntimeException(ex.getMessage(), page, null, ex);
 465    }
 466    finally
 467    {
 468  21 page.endPageRender();
 469   
 470  21 _monitor.pageRewindEnd(pageName);
 471   
 472  21 reset();
 473  21 _rewinding = false;
 474    }
 475    }
 476   
 477    /**
 478    * Rewinds the page by invoking {@link IPage#renderPage(IMarkupWriter, IRequestCycle)}.
 479    * <p>
 480    * The process is expected to end with a {@link RenderRewoundException}. If the entire page is
 481    * renderred without this exception being thrown, it means that the target action id was not
 482    * valid, and a {@link ApplicationRuntimeException}is thrown.
 483    * <p>
 484    * This clears all attributes.
 485    *
 486    * @deprecated To be removed in 4.1 with no replacement.
 487    */
 488   
 489  5 public void rewindPage(String targetActionId, IComponent targetComponent)
 490    {
 491  5 String pageName = _page.getPageName();
 492   
 493  5 _rewinding = true;
 494   
 495  5 _monitor.pageRewindBegin(pageName);
 496   
 497  5 _actionId = -1;
 498   
 499    // Parse the action Id as hex since that's whats generated
 500    // by getNextActionId()
 501  5 _targetActionId = Integer.parseInt(targetActionId, 16);
 502  5 _targetComponent = targetComponent;
 503   
 504  5 try
 505    {
 506  5 _page.renderPage(NullWriter.getSharedInstance(), this);
 507   
 508    // Shouldn't get this far, because the target component should
 509    // throw the RenderRewoundException.
 510   
 511  0 throw new StaleLinkException(_page, targetActionId, targetComponent.getExtendedId());
 512    }
 513    catch (RenderRewoundException ex)
 514    {
 515    // This is acceptible and expected.
 516    }
 517    catch (ApplicationRuntimeException ex)
 518    {
 519    // ApplicationRuntimeExceptions don't need to be wrapped.
 520  0 throw ex;
 521    }
 522    catch (Throwable ex)
 523    {
 524    // But wrap other exceptions in a RequestCycleException ... this
 525    // will ensure that some of the context is available.
 526   
 527  0 throw new ApplicationRuntimeException(ex.getMessage(), _page, null, ex);
 528    }
 529    finally
 530    {
 531  5 _monitor.pageRewindEnd(pageName);
 532   
 533  5 _rewinding = false;
 534   
 535  5 reset();
 536    }
 537   
 538    }
 539   
 540  353 public void setAttribute(String name, Object value)
 541    {
 542  353 if (LOG.isDebugEnabled())
 543  0 LOG.debug("Set attribute " + name + " to " + value);
 544   
 545  353 _attributes.put(name, value);
 546    }
 547   
 548    /**
 549    * Invokes {@link IPageRecorder#commit()}on each page recorder loaded during the request cycle
 550    * (even recorders marked for discard).
 551    */
 552   
 553  130 public void commitPageChanges()
 554    {
 555  130 if (LOG.isDebugEnabled())
 556  0 LOG.debug("Committing page changes");
 557   
 558  130 if (_pageRecorders == null || _pageRecorders.isEmpty())
 559  0 return;
 560   
 561  130 Iterator i = _pageRecorders.values().iterator();
 562   
 563  130 while (i.hasNext())
 564    {
 565  183 IPageRecorder recorder = (IPageRecorder) i.next();
 566   
 567  183 recorder.commit();
 568    }
 569    }
 570   
 571    /**
 572    * As of 4.0, just a synonym for {@link #forgetPage(String)}.
 573    *
 574    * @since 2.0.2
 575    */
 576   
 577  1 public void discardPage(String name)
 578    {
 579  1 forgetPage(name);
 580    }
 581   
 582    /** @since 2.0.3 * */
 583   
 584  0 public Object[] getServiceParameters()
 585    {
 586  0 return getListenerParameters();
 587    }
 588   
 589    /** @since 2.0.3 * */
 590   
 591  0 public void setServiceParameters(Object[] serviceParameters)
 592    {
 593  0 setListenerParameters(serviceParameters);
 594    }
 595   
 596    /** @since 4.0 */
 597  45 public Object[] getListenerParameters()
 598    {
 599  45 return _listenerParameters;
 600    }
 601   
 602    /** @since 4.0 */
 603  44 public void setListenerParameters(Object[] parameters)
 604    {
 605  44 _listenerParameters = parameters;
 606    }
 607   
 608    /** @since 3.0 * */
 609   
 610  91 public void activate(String name)
 611    {
 612  91 IPage page = getPage(name);
 613   
 614  81 activate(page);
 615    }
 616   
 617    /** @since 3.0 */
 618   
 619  173 public void activate(IPage page)
 620    {
 621  173 Defense.notNull(page, "page");
 622   
 623  173 if (LOG.isDebugEnabled())
 624  0 LOG.debug("Activating page " + page);
 625   
 626  173 Tapestry.clearMethodInvocations();
 627   
 628  173 page.validate(this);
 629   
 630  168 Tapestry
 631    .checkMethodInvocation(Tapestry.ABSTRACTPAGE_VALIDATE_METHOD_ID, "validate()", page);
 632   
 633  168 _page = page;
 634    }
 635   
 636    /** @since 4.0 */
 637  361 public String getParameter(String name)
 638    {
 639  361 return _parameters.getParameterValue(name);
 640    }
 641   
 642    /** @since 4.0 */
 643  54 public String[] getParameters(String name)
 644    {
 645  54 return _parameters.getParameterValues(name);
 646    }
 647   
 648    /**
 649    * @since 3.0
 650    */
 651  2 public String toString()
 652    {
 653  2 ToStringBuilder b = new ToStringBuilder(this);
 654   
 655  2 b.append("rewinding", _rewinding);
 656   
 657  2 b.append("serviceName", _serviceName);
 658   
 659  2 b.append("serviceParameters", _listenerParameters);
 660   
 661  2 if (_loadedPages != null)
 662  0 b.append("loadedPages", _loadedPages.keySet());
 663   
 664  2 b.append("attributes", _attributes);
 665  2 b.append("targetActionId", _targetActionId);
 666  2 b.append("targetComponent", _targetComponent);
 667   
 668  2 return b.toString();
 669    }
 670   
 671    /** @since 4.0 */
 672   
 673  106 public String getAbsoluteURL(String partialURL)
 674    {
 675  106 String contextPath = _infrastructure.getRequest().getContextPath();
 676   
 677  106 return _absoluteURLBuilder.constructURL(contextPath + partialURL);
 678    }
 679   
 680    /** @since 4.0 */
 681   
 682  2 public void forgetPage(String pageName)
 683    {
 684  2 Defense.notNull(pageName, "pageName");
 685   
 686  2 _strategySource.discardAllStoredChanged(pageName, this);
 687    }
 688   
 689    /** @since 4.0 */
 690   
 691  43 public Infrastructure getInfrastructure()
 692    {
 693  43 return _infrastructure;
 694    }
 695   
 696  1 public RequestContext getRequestContext()
 697    {
 698  1 return _requestContext;
 699    }
 700   
 701    /** @since 4.0 */
 702   
 703  49 public String getUniqueId(String baseId)
 704    {
 705  49 return _idAllocator.allocateId(baseId);
 706    }
 707   
 708    /** @since 4.0 */
 709  1 public void sendRedirect(String URL)
 710    {
 711  1 throw new RedirectException(URL);
 712    }
 713   
 714    }