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