001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket; 018 019import java.util.ArrayList; 020import java.util.HashSet; 021import java.util.Iterator; 022import java.util.List; 023import java.util.Set; 024 025import org.apache.wicket.authorization.UnauthorizedActionException; 026import org.apache.wicket.core.util.lang.WicketObjects; 027import org.apache.wicket.feedback.FeedbackDelay; 028import org.apache.wicket.markup.MarkupException; 029import org.apache.wicket.markup.MarkupStream; 030import org.apache.wicket.markup.MarkupType; 031import org.apache.wicket.markup.html.WebPage; 032import org.apache.wicket.markup.resolver.IComponentResolver; 033import org.apache.wicket.model.IModel; 034import org.apache.wicket.page.IPageManager; 035import org.apache.wicket.pageStore.IPageStore; 036import org.apache.wicket.request.component.IRequestablePage; 037import org.apache.wicket.request.cycle.RequestCycle; 038import org.apache.wicket.request.mapper.parameter.PageParameters; 039import org.apache.wicket.settings.DebugSettings; 040import org.apache.wicket.util.lang.Classes; 041import org.apache.wicket.util.lang.Generics; 042import org.apache.wicket.util.string.StringValue; 043import org.apache.wicket.util.visit.IVisit; 044import org.apache.wicket.util.visit.IVisitor; 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047 048 049/** 050 * Abstract base class for pages. As a {@link MarkupContainer} subclass, a Page can contain a 051 * component hierarchy and markup in some markup language such as HTML. Users of the framework 052 * should not attempt to subclass Page directly. Instead they should subclass a subclass of Page 053 * that is appropriate to the markup type they are using, such as {@link WebPage} (for HTML markup). 054 * <p> 055 * Page has the following differences to {@link Component}s main concepts: 056 * <ul> 057 * <li><b>Identity </b>- Page numerical identifiers start at 0 for each {@link Session} and 058 * increment for each new page. This numerical identifier is used as the component identifier 059 * accessible via {@link #getId()}.</li> 060 * <li><b>Construction </b>- When a page is constructed, it is automatically registerd with the 061 * application's {@link IPageManager}. <br> 062 * Pages can be constructed with any constructor like any other component, but if you wish to link 063 * to a Page using a URL that is "bookmarkable" (which implies that the URL will not have any 064 * session information encoded in it, and that you can call this page directly without having a 065 * session first directly from your browser), you need to implement your Page with a no-arg 066 * constructor or with a constructor that accepts a {@link PageParameters} argument (which wraps any 067 * query string parameters for a request). In case the page has both constructors, the constructor 068 * with PageParameters will be used.</li> 069 * <li><b>Versioning </b>- Pages support the browser's back button when versioning is enabled via 070 * {@link #setVersioned(boolean)}. By default all pages are versioned if not configured differently 071 * in {@link org.apache.wicket.settings.PageSettings#setVersionPagesByDefault(boolean)}</li> 072 * </ul> 073 * 074 * @see org.apache.wicket.markup.html.WebPage 075 * @see org.apache.wicket.MarkupContainer 076 * @see org.apache.wicket.model.CompoundPropertyModel 077 * @see org.apache.wicket.Component 078 * 079 * @author Jonathan Locke 080 * @author Chris Turner 081 * @author Eelco Hillenius 082 * @author Johan Compagner 083 * 084 */ 085public abstract class Page extends MarkupContainer 086 implements 087 IRequestablePage, 088 IQueueRegion 089{ 090 /** True if the page hierarchy has been modified in the current request. */ 091 private static final int FLAG_IS_DIRTY = FLAG_RESERVED3; 092 093 /** Set to prevent marking page as dirty under certain circumstances. */ 094 private static final int FLAG_PREVENT_DIRTY = FLAG_RESERVED4; 095 096 /** True if the page should try to be stateless */ 097 private static final int FLAG_STATELESS_HINT = FLAG_RESERVED5; 098 099 /** Flag that indicates if the page was created using one of its bookmarkable constructors */ 100 private static final int FLAG_WAS_CREATED_BOOKMARKABLE = FLAG_RESERVED8; 101 102 /** Log. */ 103 private static final Logger log = LoggerFactory.getLogger(Page.class); 104 105 private static final long serialVersionUID = 1L; 106 107 /** Used to create page-unique numbers */ 108 private int autoIndex; 109 110 /** Numeric version of this page's id */ 111 private int numericId; 112 113 /** Set of components that rendered if component use checking is enabled */ 114 private transient Set<Component> renderedComponents; 115 116 /** 117 * Boolean if the page is stateless, so it doesn't have to be in the page map, will be set in 118 * urlFor 119 */ 120 private transient Boolean stateless = null; 121 122 /** Page parameters used to construct this page */ 123 private final PageParameters pageParameters; 124 125 /** 126 * @see IRequestablePage#getRenderCount() 127 */ 128 private int renderCount = 0; 129 130 /** 131 * Constructor. 132 */ 133 protected Page() 134 { 135 this(null, null); 136 } 137 138 /** 139 * Constructor. 140 * 141 * @param model 142 * See Component 143 * @see Component#Component(String, IModel) 144 */ 145 protected Page(final IModel<?> model) 146 { 147 this(null, model); 148 } 149 150 /** 151 * The {@link PageParameters} parameter will be stored in this page and then those parameters 152 * will be used to create stateless links to this bookmarkable page. 153 * 154 * @param parameters 155 * externally passed parameters 156 * @see PageParameters 157 */ 158 protected Page(final PageParameters parameters) 159 { 160 this(parameters, null); 161 } 162 163 /** 164 * Construct. 165 * 166 * @param parameters 167 * @param model 168 */ 169 private Page(final PageParameters parameters, IModel<?> model) 170 { 171 super(null, model); 172 173 if (parameters == null) 174 { 175 pageParameters = new PageParameters(); 176 } 177 else 178 { 179 pageParameters = parameters; 180 } 181 } 182 183 /** 184 * The {@link PageParameters} object that was used to construct this page. This will be used in 185 * creating stateless/bookmarkable links to this page 186 * 187 * @return {@link PageParameters} The construction page parameter 188 */ 189 @Override 190 public PageParameters getPageParameters() 191 { 192 return pageParameters; 193 } 194 195 /** 196 * Adds a component to the set of rendered components. 197 * 198 * @param component 199 * The component that was rendered 200 */ 201 public final void componentRendered(final Component component) 202 { 203 // Inform the page that this component rendered 204 if (getApplication().getDebugSettings().getComponentUseCheck()) 205 { 206 if (renderedComponents == null) 207 { 208 renderedComponents = new HashSet<Component>(); 209 } 210 if (renderedComponents.add(component) == false) 211 { 212 throw new MarkupException( 213 "The component " + 214 component + 215 " was rendered already. You can render it only once during a render phase. Class relative path: " + 216 component.getClassRelativePath()); 217 } 218 log.debug("Rendered {}", component); 219 220 } 221 } 222 223 /** 224 * Detaches any attached models referenced by this page. 225 */ 226 @Override 227 public void detachModels() 228 { 229 super.detachModels(); 230 } 231 232 @Override 233 protected void onConfigure() 234 { 235 if (!isInitialized()) 236 { 237 // initialize the page if not yet initialized 238 internalInitialize(); 239 } 240 241 super.onConfigure(); 242 } 243 244 /** 245 * @see #dirty(boolean) 246 */ 247 public final void dirty() 248 { 249 dirty(false); 250 } 251 252 /** {@inheritDoc} */ 253 @Override 254 public boolean setFreezePageId(boolean freeze) 255 { 256 boolean frozen = getFlag(FLAG_PREVENT_DIRTY); 257 setFlag(FLAG_PREVENT_DIRTY, freeze); 258 return frozen; 259 } 260 261 /** 262 * Mark this page as modified in the session. If versioning is supported then a new version of 263 * the page will be stored in {@link IPageStore page store} 264 * 265 * @param isInitialization 266 * a flag whether this is a page instantiation 267 */ 268 public void dirty(final boolean isInitialization) 269 { 270 checkHierarchyChange(this); 271 272 if (getFlag(FLAG_PREVENT_DIRTY)) 273 { 274 return; 275 } 276 277 final IPageManager pageManager = getSession().getPageManager(); 278 if (!getFlag(FLAG_IS_DIRTY) && (isVersioned() && pageManager.supportsVersioning() || 279 280 // we need to get pageId for new page instances even when the page doesn't need 281 // versioning, otherwise pages override each other in the page store and back button 282 // support is broken 283 isInitialization)) 284 { 285 setFlag(FLAG_IS_DIRTY, true); 286 setNextAvailableId(); 287 288 if (isInitialization == false) 289 { 290 pageManager.touchPage(this); 291 } 292 } 293 } 294 295 @Override 296 protected void onInitialize() 297 { 298 super.onInitialize(); 299 300 final IPageManager pageManager = getSession().getPageManager(); 301 pageManager.touchPage(this); 302 } 303 304 /** 305 * This method is called when a component was rendered as a part. If it is a <code> 306 * MarkupContainer</code> then the rendering for that container is checked. 307 * 308 * @param component 309 * 310 * @see Component#renderPart() 311 */ 312 final void endComponentRender(Component component) 313 { 314 if (component instanceof MarkupContainer) 315 { 316 checkRendering((MarkupContainer)component); 317 } 318 else 319 { 320 renderedComponents = null; 321 } 322 } 323 324 /** 325 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. 326 * 327 * Get a page unique number, which will be increased with each call. 328 * 329 * @return A page unique number 330 */ 331 public final int getAutoIndex() 332 { 333 return autoIndex++; 334 } 335 336 @Override 337 public final String getId() 338 { 339 return Integer.toString(numericId); 340 } 341 342 /** 343 * 344 * @return page class 345 */ 346 public final Class<? extends Page> getPageClass() 347 { 348 return getClass(); 349 } 350 351 /** 352 * @return Size of this page in bytes 353 */ 354 @Override 355 public final long getSizeInBytes() 356 { 357 return WicketObjects.sizeof(this); 358 } 359 360 /** 361 * Returns whether the page should try to be stateless. To be stateless, getStatelessHint() of 362 * every component on page (and it's behavior) must return true and the page must be 363 * bookmarkable. 364 * 365 * @see org.apache.wicket.Component#getStatelessHint() 366 */ 367 @Override 368 public final boolean getStatelessHint() 369 { 370 return getFlag(FLAG_STATELESS_HINT); 371 } 372 373 /** 374 * @return This page's component hierarchy as a string 375 */ 376 public final String hierarchyAsString() 377 { 378 final StringBuilder buffer = new StringBuilder(); 379 buffer.append("Page ").append(getId()); 380 visitChildren(new IVisitor<Component, Void>() 381 { 382 @Override 383 public void component(final Component component, final IVisit<Void> visit) 384 { 385 int levels = 0; 386 for (Component current = component; current != null; current = current.getParent()) 387 { 388 levels++; 389 } 390 buffer.append(StringValue.repeat(levels, " ")) 391 .append(component.getPageRelativePath()) 392 .append(':') 393 .append(Classes.simpleName(component.getClass())); 394 } 395 }); 396 return buffer.toString(); 397 } 398 399 /** 400 * Bookmarkable page can be instantiated using a bookmarkable URL. 401 * 402 * @return Returns true if the page is bookmarkable. 403 */ 404 @Override 405 public boolean isBookmarkable() 406 { 407 return getApplication().getPageFactory().isBookmarkable(getClass()); 408 } 409 410 /** 411 * Override this method and return true if your page is used to display Wicket errors. This can 412 * help the framework prevent infinite failure loops. 413 * 414 * @return True if this page is intended to display an error to the end user. 415 */ 416 public boolean isErrorPage() 417 { 418 return false; 419 } 420 421 /** 422 * Determine the "statelessness" of the page while not changing the cached value. 423 * 424 * @return boolean value 425 */ 426 private boolean peekPageStateless() 427 { 428 Boolean old = stateless; 429 Boolean res = isPageStateless(); 430 stateless = old; 431 return res; 432 } 433 434 /** 435 * Gets whether the page is stateless. Components on stateless page must not render any stateful 436 * urls, and components on stateful page must not render any stateless urls. Stateful urls are 437 * urls, which refer to a certain (current) page instance. 438 * 439 * @return Whether this page is stateless 440 */ 441 @Override 442 public final boolean isPageStateless() 443 { 444 if (isBookmarkable() == false) 445 { 446 stateless = Boolean.FALSE; 447 if (getStatelessHint()) 448 { 449 log.warn("Page '" + this + "' is not stateless because it is not bookmarkable, " + 450 "but the stateless hint is set to true!"); 451 } 452 } 453 454 if (getStatelessHint() == false) 455 { 456 return false; 457 } 458 459 if (stateless == null) 460 { 461 internalInitialize(); 462 463 if (isStateless() == false) 464 { 465 stateless = Boolean.FALSE; 466 } 467 } 468 469 if (stateless == null) 470 { 471 Component statefulComponent = visitChildren(Component.class, 472 new IVisitor<Component, Component>() 473 { 474 @Override 475 public void component(final Component component, final IVisit<Component> visit) 476 { 477 if (!component.isStateless()) 478 { 479 visit.stop(component); 480 } 481 } 482 }); 483 484 stateless = statefulComponent == null; 485 486 if (log.isDebugEnabled() && !stateless.booleanValue() && getStatelessHint()) 487 { 488 log.debug("Page '{}' is not stateless because of component with path '{}'.", this, 489 statefulComponent.getPageRelativePath()); 490 } 491 492 } 493 494 return stateless; 495 } 496 497 /** 498 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL. 499 * 500 * Set the id for this Page. This method is called by PageMap when a Page is added because the 501 * id, which is assigned by PageMap, is not known until this time. 502 * 503 * @param id 504 * The id 505 */ 506 public final void setNumericId(final int id) 507 { 508 numericId = id; 509 } 510 511 /** 512 * Sets whether the page should try to be stateless. To be stateless, getStatelessHint() of 513 * every component on page (and it's behavior) must return true and the page must be 514 * bookmarkable. 515 * 516 * @param value 517 * whether the page should try to be stateless 518 */ 519 public final Page setStatelessHint(boolean value) 520 { 521 if (value && !isBookmarkable()) 522 { 523 throw new WicketRuntimeException( 524 "Can't set stateless hint to true on a page when the page is not bookmarkable, page: " + 525 this); 526 } 527 setFlag(FLAG_STATELESS_HINT, value); 528 return this; 529 } 530 531 /** 532 * This method is called when a component will be rendered as a part. 533 * 534 * @param component 535 * 536 * @see Component#renderPart() 537 */ 538 final void startComponentRender(Component component) 539 { 540 renderedComponents = null; 541 } 542 543 /** 544 * Get the string representation of this container. 545 * 546 * @return String representation of this container 547 */ 548 @Override 549 public String toString() 550 { 551 return "[Page class = " + getClass().getName() + ", id = " + getId() + ", render count = " + 552 getRenderCount() + "]"; 553 } 554 555 /** 556 * Throw an exception if not all components rendered. 557 * 558 * @param renderedContainer 559 * The page itself if it was a full page render or the container that was rendered 560 * standalone 561 */ 562 private void checkRendering(final MarkupContainer renderedContainer) 563 { 564 // If the application wants component uses checked and 565 // the response is not a redirect 566 final DebugSettings debugSettings = getApplication().getDebugSettings(); 567 if (debugSettings.getComponentUseCheck()) 568 { 569 final List<Component> unrenderedComponents = new ArrayList<Component>(); 570 final StringBuilder buffer = new StringBuilder(); 571 renderedContainer.visitChildren(new IVisitor<Component, Void>() 572 { 573 @Override 574 public void component(final Component component, final IVisit<Void> visit) 575 { 576 // If component never rendered 577 if (renderedComponents == null || !renderedComponents.contains(component)) 578 { 579 // If not an auto component ... 580 if (!component.isAuto() && component.isVisibleInHierarchy()) 581 { 582 // Increase number of unrendered components 583 unrenderedComponents.add(component); 584 585 // Add to explanatory string to buffer 586 buffer.append(Integer.toString(unrenderedComponents.size())) 587 .append(". ") 588 .append(component.toString(true)) 589 .append('\n'); 590 String metadata = component.getMetaData(Component.CONSTRUCTED_AT_KEY); 591 if (metadata != null) 592 { 593 buffer.append(metadata).append('\n'); 594 } 595 metadata = component.getMetaData(Component.ADDED_AT_KEY); 596 if (metadata != null) 597 { 598 buffer.append(metadata).append('\n'); 599 } 600 } 601 else 602 { 603 // if the component is not visible in hierarchy we 604 // should not visit its children since they are also 605 // not visible 606 visit.dontGoDeeper(); 607 } 608 } 609 } 610 }); 611 612 // Throw exception if any errors were found 613 if (unrenderedComponents.size() > 0) 614 { 615 renderedComponents = null; 616 617 List<Component> transparentContainerChildren = Generics.newArrayList(); 618 619 Iterator<Component> iterator = unrenderedComponents.iterator(); 620 outerWhile : while (iterator.hasNext()) 621 { 622 Component component = iterator.next(); 623 624 // If any of the transparentContainerChildren is a parent to component, then 625 // ignore it. 626 for (Component transparentContainerChild : transparentContainerChildren) 627 { 628 MarkupContainer parent = component.getParent(); 629 while (parent != null) 630 { 631 if (parent == transparentContainerChild) 632 { 633 iterator.remove(); 634 continue outerWhile; 635 } 636 parent = parent.getParent(); 637 } 638 } 639 640 if (hasInvisibleTransparentChild(component.getParent(), component)) 641 { 642 // If we found a transparent container that isn't visible then ignore this 643 // component and only do a debug statement here. 644 if (log.isDebugEnabled()) 645 { 646 log.debug( 647 "Component {} wasn't rendered but might have a transparent parent.", 648 component); 649 } 650 651 transparentContainerChildren.add(component); 652 iterator.remove(); 653 continue outerWhile; 654 } 655 } 656 657 // if still > 0 658 if (unrenderedComponents.size() > 0) 659 { 660 // Throw exception 661 throw new WicketRuntimeException( 662 "The component(s) below failed to render. Possible reasons could be that:\n\t1) you have added a component in code but forgot to reference it in the markup (thus the component will never be rendered),\n\t2) if your components were added in a parent container then make sure the markup for the child container includes them in <wicket:extend>.\n\n" + 663 buffer.toString()); 664 } 665 } 666 } 667 668 // Get rid of set 669 renderedComponents = null; 670 } 671 672 private boolean hasInvisibleTransparentChild(final MarkupContainer root, final Component self) 673 { 674 for (Component sibling : root) 675 { 676 if ((sibling != self) && (sibling instanceof IComponentResolver) && 677 (sibling instanceof MarkupContainer)) 678 { 679 if (!sibling.isVisible()) 680 { 681 return true; 682 } 683 else 684 { 685 boolean rtn = hasInvisibleTransparentChild((MarkupContainer)sibling, self); 686 if (rtn == true) 687 { 688 return true; 689 } 690 } 691 } 692 } 693 694 return false; 695 } 696 697 /** 698 * Initializes Page by adding it to the Session and initializing it. 699 */ 700 @Override 701 void init() 702 { 703 if (isBookmarkable() == false) 704 { 705 setStatelessHint(false); 706 } 707 708 // Set versioning of page based on default 709 setVersioned(getApplication().getPageSettings().getVersionPagesByDefault()); 710 711 // All Pages are born dirty so they get clustered right away 712 dirty(true); 713 714 // this is a bit of a dirty hack, but calling dirty(true) results in isStateless called 715 // which is bound to set the stateless cache to true as there are no components yet 716 stateless = null; 717 } 718 719 private void setNextAvailableId() 720 { 721 setNumericId(getSession().nextPageId()); 722 } 723 724 /** 725 * This method will be called for all components that are changed on the page So also auto 726 * components or components that are not versioned. 727 * 728 * If the parent is given that it was a remove or add from that parent of the given component. 729 * else it was just a internal property change of that component. 730 * 731 * @param component 732 * @param parent 733 */ 734 protected void componentChanged(Component component, MarkupContainer parent) 735 { 736 if (!component.isAuto()) 737 { 738 dirty(); 739 } 740 } 741 742 /** 743 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL OR OVERRIDE. 744 * 745 * @see org.apache.wicket.Component#internalOnModelChanged() 746 */ 747 @Override 748 protected final void internalOnModelChanged() 749 { 750 visitChildren(new IVisitor<Component, Void>() 751 { 752 @Override 753 public void component(final Component component, final IVisit<Void> visit) 754 { 755 // If form component is using form model 756 if (component.sameInnermostModel(Page.this)) 757 { 758 component.modelChanged(); 759 } 760 } 761 }); 762 } 763 764 @Override 765 void internalOnAfterConfigure() 766 { 767 super.internalOnAfterConfigure(); 768 769 // first try to check if the page can be rendered: 770 if (!isRenderAllowed()) 771 { 772 if (log.isDebugEnabled()) 773 { 774 log.debug("Page not allowed to render: " + this); 775 } 776 throw new UnauthorizedActionException(this, Component.RENDER); 777 } 778 } 779 780 @Override 781 protected void onBeforeRender() 782 { 783 // Make sure it is really empty 784 renderedComponents = null; 785 786 // rendering might remove or add stateful components, so clear flag to force reevaluation 787 stateless = null; 788 789 super.onBeforeRender(); 790 791 // If any of the components on page is not stateless, we need to bind the session 792 // before we start rendering components, as then jsessionid won't be appended 793 // for links rendered before first stateful component 794 if (getSession().isTemporary() && !peekPageStateless()) 795 { 796 getSession().bind(); 797 } 798 } 799 800 @Override 801 protected void onAfterRender() 802 { 803 super.onAfterRender(); 804 805 // Check rendering if it happened fully 806 checkRendering(this); 807 808 // clean up debug meta data if component check is on 809 if (getApplication().getDebugSettings().getComponentUseCheck()) 810 { 811 visitChildren(new IVisitor<Component, Void>() 812 { 813 @Override 814 public void component(final Component component, final IVisit<Void> visit) 815 { 816 component.setMetaData(Component.CONSTRUCTED_AT_KEY, null); 817 component.setMetaData(Component.ADDED_AT_KEY, null); 818 } 819 }); 820 } 821 822 if (!isPageStateless()) 823 { 824 // trigger creation of the actual session in case it was deferred 825 getSession().getSessionStore().getSessionId(RequestCycle.get().getRequest(), true); 826 827 // Add/touch the response page in the session. 828 getSession().getPageManager().touchPage(this); 829 } 830 831 if (getApplication().getDebugSettings().isOutputMarkupContainerClassName()) 832 { 833 String className = Classes.name(getClass()); 834 getResponse().write("<!-- Page Class "); 835 getResponse().write(className); 836 getResponse().write(" END -->\n"); 837 } 838 } 839 840 @Override 841 protected void onDetach() 842 { 843 if (log.isDebugEnabled()) 844 { 845 log.debug("ending request for page " + this + ", request " + getRequest()); 846 } 847 848 setFlag(FLAG_IS_DIRTY, false); 849 850 super.onDetach(); 851 } 852 853 @Override 854 protected void onRender() 855 { 856 // Loop through the markup in this container 857 MarkupStream markupStream = new MarkupStream(getMarkup()); 858 renderAll(markupStream, null); 859 } 860 861 /** 862 * A component was added. 863 * 864 * @param component 865 * The component that was added 866 */ 867 final void componentAdded(final Component component) 868 { 869 if (!component.isAuto()) 870 { 871 dirty(); 872 } 873 } 874 875 /** 876 * A component's model changed. 877 * 878 * @param component 879 * The component whose model is about to change 880 */ 881 final void componentModelChanging(final Component component) 882 { 883 dirty(); 884 } 885 886 /** 887 * A component was removed. 888 * 889 * @param component 890 * The component that was removed 891 */ 892 final void componentRemoved(final Component component) 893 { 894 if (!component.isAuto()) 895 { 896 dirty(); 897 } 898 } 899 900 /** 901 * 902 * @param component 903 */ 904 final void componentStateChanging(final Component component) 905 { 906 if (!component.isAuto()) 907 { 908 dirty(); 909 } 910 } 911 912 /** 913 * Set page stateless 914 * 915 * @param stateless 916 */ 917 void setPageStateless(Boolean stateless) 918 { 919 this.stateless = stateless; 920 } 921 922 @Override 923 public MarkupType getMarkupType() 924 { 925 throw new UnsupportedOperationException( 926 "Page does not support markup. This error can happen if you have extended Page directly, instead extend WebPage"); 927 } 928 929 /** 930 * Gets page instance's unique identifier 931 * 932 * @return instance unique identifier 933 */ 934 public PageReference getPageReference() 935 { 936 setStatelessHint(false); 937 938 // make sure the page will be available on following request 939 getSession().getPageManager().touchPage(this); 940 941 return new PageReference(numericId); 942 } 943 944 @Override 945 public int getPageId() 946 { 947 return numericId; 948 } 949 950 @Override 951 public int getRenderCount() 952 { 953 return renderCount; 954 } 955 956 /** 957 * THIS METHOD IS NOT PART OF WICKET API. DO NOT USE! 958 * 959 * Sets the flag that determines whether or not this page was created using one of its 960 * bookmarkable constructors 961 * 962 * @param wasCreatedBookmarkable 963 */ 964 public final void setWasCreatedBookmarkable(boolean wasCreatedBookmarkable) 965 { 966 setFlag(FLAG_WAS_CREATED_BOOKMARKABLE, wasCreatedBookmarkable); 967 } 968 969 /** 970 * Checks if this page was created using one of its bookmarkable constructors 971 * 972 * @see org.apache.wicket.request.component.IRequestablePage#wasCreatedBookmarkable() 973 */ 974 @Override 975 public final boolean wasCreatedBookmarkable() 976 { 977 return getFlag(FLAG_WAS_CREATED_BOOKMARKABLE); 978 } 979 980 @Override 981 public void renderPage() 982 { 983 // page id is frozen during the render 984 final boolean frozen = setFreezePageId(true); 985 try 986 { 987 ++renderCount; 988 989 // delay rendering of feedbacks after all other components 990 try (FeedbackDelay delay = new FeedbackDelay(getRequestCycle())) { 991 beforeRender(); 992 993 delay.beforeRender(); 994 } 995 996 markRendering(true); 997 998 render(); 999 } 1000 finally 1001 { 1002 setFreezePageId(frozen); 1003 } 1004 } 1005 1006 /** 1007 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL. 1008 * 1009 * @param component 1010 * @return if this component was render in this page 1011 */ 1012 public final boolean wasRendered(Component component) 1013 { 1014 return renderedComponents != null && renderedComponents.contains(component); 1015 } 1016}