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