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 @Override 253 public boolean setFreezePageId(boolean freeze) 254 { 255 boolean frozen = getFlag(FLAG_PREVENT_DIRTY); 256 setFlag(FLAG_PREVENT_DIRTY, freeze); 257 return frozen; 258 } 259 260 /** 261 * Mark this page as modified in the session. If versioning is supported then a new version of 262 * the page will be stored in {@link IPageStore page store} 263 * 264 * @param isInitialization 265 * a flag whether this is a page instantiation 266 */ 267 public void dirty(final boolean isInitialization) 268 { 269 checkHierarchyChange(this); 270 271 if (getFlag(FLAG_PREVENT_DIRTY)) 272 { 273 return; 274 } 275 276 final IPageManager pageManager = getSession().getPageManager(); 277 if (!getFlag(FLAG_IS_DIRTY) && (isVersioned() && pageManager.supportsVersioning() || 278 279 // we need to get pageId for new page instances even when the page doesn't need 280 // versioning, otherwise pages override each other in the page store and back button 281 // support is broken 282 isInitialization)) 283 { 284 setFlag(FLAG_IS_DIRTY, true); 285 setNextAvailableId(); 286 287 if (isInitialization == false) 288 { 289 pageManager.touchPage(this); 290 } 291 } 292 } 293 294 @Override 295 protected void onInitialize() 296 { 297 super.onInitialize(); 298 299 final IPageManager pageManager = getSession().getPageManager(); 300 pageManager.touchPage(this); 301 } 302 303 /** 304 * This method is called when a component was rendered as a part. If it is a <code> 305 * MarkupContainer</code> then the rendering for that container is checked. 306 * 307 * @param component 308 * 309 * @see Component#renderPart() 310 */ 311 final void endComponentRender(Component component) 312 { 313 if (component instanceof MarkupContainer) 314 { 315 checkRendering((MarkupContainer)component); 316 } 317 else 318 { 319 renderedComponents = null; 320 } 321 } 322 323 /** 324 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. 325 * 326 * Get a page unique number, which will be increased with each call. 327 * 328 * @return A page unique number 329 */ 330 public final int getAutoIndex() 331 { 332 return autoIndex++; 333 } 334 335 @Override 336 public final String getId() 337 { 338 return Integer.toString(numericId); 339 } 340 341 /** 342 * 343 * @return page class 344 */ 345 public final Class<? extends Page> getPageClass() 346 { 347 return getClass(); 348 } 349 350 /** 351 * @return Size of this page in bytes 352 */ 353 @Override 354 public final long getSizeInBytes() 355 { 356 return WicketObjects.sizeof(this); 357 } 358 359 /** 360 * Returns whether the page should try to be stateless. To be stateless, getStatelessHint() of 361 * every component on page (and it's behavior) must return true and the page must be 362 * bookmarkable. 363 * 364 * @see org.apache.wicket.Component#getStatelessHint() 365 */ 366 @Override 367 public final boolean getStatelessHint() 368 { 369 return getFlag(FLAG_STATELESS_HINT); 370 } 371 372 /** 373 * @return This page's component hierarchy as a string 374 */ 375 public final String hierarchyAsString() 376 { 377 final StringBuilder buffer = new StringBuilder(); 378 buffer.append("Page ").append(getId()); 379 visitChildren(new IVisitor<Component, Void>() 380 { 381 @Override 382 public void component(final Component component, final IVisit<Void> visit) 383 { 384 int levels = 0; 385 for (Component current = component; current != null; current = current.getParent()) 386 { 387 levels++; 388 } 389 buffer.append(StringValue.repeat(levels, " ")) 390 .append(component.getPageRelativePath()) 391 .append(':') 392 .append(Classes.simpleName(component.getClass())); 393 } 394 }); 395 return buffer.toString(); 396 } 397 398 /** 399 * Bookmarkable page can be instantiated using a bookmarkable URL. 400 * 401 * @return Returns true if the page is bookmarkable. 402 */ 403 @Override 404 public boolean isBookmarkable() 405 { 406 return getApplication().getPageFactory().isBookmarkable(getClass()); 407 } 408 409 /** 410 * Override this method and return true if your page is used to display Wicket errors. This can 411 * help the framework prevent infinite failure loops. 412 * 413 * @return True if this page is intended to display an error to the end user. 414 */ 415 public boolean isErrorPage() 416 { 417 return false; 418 } 419 420 /** 421 * Determine the "statelessness" of the page while not changing the cached value. 422 * 423 * @return boolean value 424 */ 425 private boolean peekPageStateless() 426 { 427 Boolean old = stateless; 428 Boolean res = isPageStateless(); 429 stateless = old; 430 return res; 431 } 432 433 /** 434 * Gets whether the page is stateless. Components on stateless page must not render any stateful 435 * urls, and components on stateful page must not render any stateless urls. Stateful urls are 436 * urls, which refer to a certain (current) page instance. 437 * 438 * @return Whether this page is stateless 439 */ 440 @Override 441 public final boolean isPageStateless() 442 { 443 if (isBookmarkable() == false) 444 { 445 stateless = Boolean.FALSE; 446 if (getStatelessHint()) 447 { 448 log.warn("Page '" + this + "' is not stateless because it is not bookmarkable, " + 449 "but the stateless hint is set to true!"); 450 } 451 } 452 453 if (getStatelessHint() == false) 454 { 455 return false; 456 } 457 458 if (stateless == null) 459 { 460 internalInitialize(); 461 462 if (isStateless() == false) 463 { 464 stateless = Boolean.FALSE; 465 } 466 } 467 468 if (stateless == null) 469 { 470 Component statefulComponent = visitChildren(Component.class, 471 new IVisitor<Component, Component>() 472 { 473 @Override 474 public void component(final Component component, final IVisit<Component> visit) 475 { 476 if (!component.isStateless()) 477 { 478 visit.stop(component); 479 } 480 } 481 }); 482 483 stateless = statefulComponent == null; 484 485 if (log.isDebugEnabled() && !stateless.booleanValue() && getStatelessHint()) 486 { 487 log.debug("Page '{}' is not stateless because of component with path '{}'.", this, 488 statefulComponent.getPageRelativePath()); 489 } 490 491 } 492 493 return stateless; 494 } 495 496 /** 497 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL. 498 * 499 * Set the id for this Page. This method is called by PageMap when a Page is added because the 500 * id, which is assigned by PageMap, is not known until this time. 501 * 502 * @param id 503 * The id 504 */ 505 public final void setNumericId(final int id) 506 { 507 numericId = id; 508 } 509 510 /** 511 * Sets whether the page should try to be stateless. To be stateless, getStatelessHint() of 512 * every component on page (and it's behavior) must return true and the page must be 513 * bookmarkable. 514 * 515 * @param value 516 * whether the page should try to be stateless 517 */ 518 public final Page setStatelessHint(boolean value) 519 { 520 if (value && !isBookmarkable()) 521 { 522 throw new WicketRuntimeException( 523 "Can't set stateless hint to true on a page when the page is not bookmarkable, page: " + 524 this); 525 } 526 setFlag(FLAG_STATELESS_HINT, value); 527 return this; 528 } 529 530 /** 531 * This method is called when a component will be rendered as a part. 532 * 533 * @param component 534 * 535 * @see Component#renderPart() 536 */ 537 final void startComponentRender(Component component) 538 { 539 renderedComponents = null; 540 } 541 542 /** 543 * Get the string representation of this container. 544 * 545 * @return String representation of this container 546 */ 547 @Override 548 public String toString() 549 { 550 return "[Page class = " + getClass().getName() + ", id = " + getId() + ", render count = " + 551 getRenderCount() + "]"; 552 } 553 554 /** 555 * Throw an exception if not all components rendered. 556 * 557 * @param renderedContainer 558 * The page itself if it was a full page render or the container that was rendered 559 * standalone 560 */ 561 private void checkRendering(final MarkupContainer renderedContainer) 562 { 563 // If the application wants component uses checked and 564 // the response is not a redirect 565 final DebugSettings debugSettings = getApplication().getDebugSettings(); 566 if (debugSettings.getComponentUseCheck()) 567 { 568 final List<Component> unrenderedComponents = new ArrayList<Component>(); 569 final StringBuilder buffer = new StringBuilder(); 570 renderedContainer.visitChildren(new IVisitor<Component, Void>() 571 { 572 @Override 573 public void component(final Component component, final IVisit<Void> visit) 574 { 575 // If component never rendered 576 if (renderedComponents == null || !renderedComponents.contains(component)) 577 { 578 // If not an auto component ... 579 if (!component.isAuto() && component.isVisibleInHierarchy()) 580 { 581 // Increase number of unrendered components 582 unrenderedComponents.add(component); 583 584 // Add to explanatory string to buffer 585 buffer.append(Integer.toString(unrenderedComponents.size())) 586 .append(". ") 587 .append(component.toString(true)) 588 .append('\n'); 589 String metadata = component.getMetaData(Component.CONSTRUCTED_AT_KEY); 590 if (metadata != null) 591 { 592 buffer.append(metadata).append('\n'); 593 } 594 metadata = component.getMetaData(Component.ADDED_AT_KEY); 595 if (metadata != null) 596 { 597 buffer.append(metadata).append('\n'); 598 } 599 } 600 else 601 { 602 // if the component is not visible in hierarchy we 603 // should not visit its children since they are also 604 // not visible 605 visit.dontGoDeeper(); 606 } 607 } 608 } 609 }); 610 611 // Throw exception if any errors were found 612 if (unrenderedComponents.size() > 0) 613 { 614 renderedComponents = null; 615 616 List<Component> transparentContainerChildren = Generics.newArrayList(); 617 618 Iterator<Component> iterator = unrenderedComponents.iterator(); 619 outerWhile : while (iterator.hasNext()) 620 { 621 Component component = iterator.next(); 622 623 // If any of the transparentContainerChildren is a parent to component, then 624 // ignore it. 625 for (Component transparentContainerChild : transparentContainerChildren) 626 { 627 MarkupContainer parent = component.getParent(); 628 while (parent != null) 629 { 630 if (parent == transparentContainerChild) 631 { 632 iterator.remove(); 633 continue outerWhile; 634 } 635 parent = parent.getParent(); 636 } 637 } 638 639 if (hasInvisibleTransparentChild(component.getParent(), component)) 640 { 641 // If we found a transparent container that isn't visible then ignore this 642 // component and only do a debug statement here. 643 if (log.isDebugEnabled()) 644 { 645 log.debug( 646 "Component {} wasn't rendered but might have a transparent parent.", 647 component); 648 } 649 650 transparentContainerChildren.add(component); 651 iterator.remove(); 652 continue outerWhile; 653 } 654 } 655 656 // if still > 0 657 if (unrenderedComponents.size() > 0) 658 { 659 // Throw exception 660 throw new WicketRuntimeException( 661 "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" + 662 buffer.toString()); 663 } 664 } 665 } 666 667 // Get rid of set 668 renderedComponents = null; 669 } 670 671 private boolean hasInvisibleTransparentChild(final MarkupContainer root, final Component self) 672 { 673 for (Component sibling : root) 674 { 675 if ((sibling != self) && (sibling instanceof IComponentResolver) && 676 (sibling instanceof MarkupContainer)) 677 { 678 if (!sibling.isVisible()) 679 { 680 return true; 681 } 682 else 683 { 684 boolean rtn = hasInvisibleTransparentChild((MarkupContainer)sibling, self); 685 if (rtn == true) 686 { 687 return true; 688 } 689 } 690 } 691 } 692 693 return false; 694 } 695 696 /** 697 * Initializes Page by adding it to the Session and initializing it. 698 */ 699 @Override 700 void init() 701 { 702 if (isBookmarkable() == false) 703 { 704 setStatelessHint(false); 705 } 706 707 // Set versioning of page based on default 708 setVersioned(getApplication().getPageSettings().getVersionPagesByDefault()); 709 710 // All Pages are born dirty, so they get clustered right away 711 dirty(true); 712 713 // this is a bit of a dirty hack, but calling dirty(true) results in isStateless called 714 // which is bound to set the stateless cache to true as there are no components yet 715 stateless = null; 716 } 717 718 private void setNextAvailableId() 719 { 720 setNumericId(getSession().nextPageId()); 721 } 722 723 /** 724 * This method will be called for all components that are changed on the page So also auto 725 * components or components that are not versioned. 726 * 727 * If the parent is given that it was a remove or add from that parent of the given component. 728 * else it was just a internal property change of that component. 729 * 730 * @param component 731 * @param parent 732 */ 733 protected void componentChanged(Component component, MarkupContainer parent) 734 { 735 if (!component.isAuto()) 736 { 737 dirty(); 738 } 739 } 740 741 /** 742 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL OR OVERRIDE. 743 * 744 * @see org.apache.wicket.Component#internalOnModelChanged() 745 */ 746 @Override 747 protected final void internalOnModelChanged() 748 { 749 visitChildren(new IVisitor<Component, Void>() 750 { 751 @Override 752 public void component(final Component component, final IVisit<Void> visit) 753 { 754 // If form component is using form model 755 if (component.sameInnermostModel(Page.this)) 756 { 757 component.modelChanged(); 758 } 759 } 760 }); 761 } 762 763 @Override 764 void internalOnAfterConfigure() 765 { 766 super.internalOnAfterConfigure(); 767 768 // first try to check if the page can be rendered: 769 if (!isRenderAllowed()) 770 { 771 if (log.isDebugEnabled()) 772 { 773 log.debug("Page not allowed to render: " + this); 774 } 775 throw new UnauthorizedActionException(this, Component.RENDER); 776 } 777 } 778 779 @Override 780 protected void onBeforeRender() 781 { 782 // Make sure it is really empty 783 renderedComponents = null; 784 785 // rendering might remove or add stateful components, so clear flag to force reevaluation 786 stateless = null; 787 788 super.onBeforeRender(); 789 790 // If any of the components on page is not stateless, we need to bind the session 791 // before we start rendering components, as then jsessionid won't be appended 792 // for links rendered before first stateful component 793 if (getSession().isTemporary() && !peekPageStateless()) 794 { 795 getSession().bind(); 796 } 797 } 798 799 @Override 800 protected void onAfterRender() 801 { 802 super.onAfterRender(); 803 804 // Check rendering if it happened fully 805 checkRendering(this); 806 807 // clean up debug meta data if component check is on 808 if (getApplication().getDebugSettings().getComponentUseCheck()) 809 { 810 visitChildren(new IVisitor<Component, Void>() 811 { 812 @Override 813 public void component(final Component component, final IVisit<Void> visit) 814 { 815 component.setMetaData(Component.CONSTRUCTED_AT_KEY, null); 816 component.setMetaData(Component.ADDED_AT_KEY, null); 817 } 818 }); 819 } 820 821 if (!isPageStateless()) 822 { 823 // trigger creation of the actual session in case it was deferred 824 getSession().getSessionStore().getSessionId(RequestCycle.get().getRequest(), true); 825 826 // Add/touch the response page in the session. 827 getSession().getPageManager().touchPage(this); 828 } 829 830 if (getApplication().getDebugSettings().isOutputMarkupContainerClassName()) 831 { 832 final String className = Classes.name(getClass()); 833 getResponse().write("<!-- Page Class "); 834 getResponse().write(className); 835 getResponse().write(" END -->\n"); 836 } 837 } 838 839 @Override 840 protected void onDetach() 841 { 842 if (log.isDebugEnabled()) 843 { 844 log.debug("ending request for page " + this + ", request " + getRequest()); 845 } 846 847 setFlag(FLAG_IS_DIRTY, false); 848 849 super.onDetach(); 850 } 851 852 @Override 853 protected void onRender() 854 { 855 // Loop through the markup in this container 856 MarkupStream markupStream = new MarkupStream(getMarkup()); 857 renderAll(markupStream, null); 858 } 859 860 /** 861 * A component was added. 862 * 863 * @param component 864 * The component that was added 865 */ 866 final void componentAdded(final Component component) 867 { 868 if (!component.isAuto()) 869 { 870 dirty(); 871 } 872 } 873 874 /** 875 * A component's model changed. 876 * 877 * @param component 878 * The component whose model is about to change 879 */ 880 final void componentModelChanging(final Component component) 881 { 882 dirty(); 883 } 884 885 /** 886 * A component was removed. 887 * 888 * @param component 889 * The component that was removed 890 */ 891 final void componentRemoved(final Component component) 892 { 893 if (!component.isAuto()) 894 { 895 dirty(); 896 } 897 } 898 899 /** 900 * 901 * @param component 902 */ 903 final void componentStateChanging(final Component component) 904 { 905 if (!component.isAuto()) 906 { 907 dirty(); 908 } 909 } 910 911 /** 912 * Set page stateless 913 * 914 * @param stateless 915 */ 916 void setPageStateless(Boolean stateless) 917 { 918 this.stateless = stateless; 919 } 920 921 @Override 922 public MarkupType getMarkupType() 923 { 924 throw new UnsupportedOperationException( 925 "Page does not support markup. This error can happen if you have extended Page directly, instead extend WebPage"); 926 } 927 928 /** 929 * Gets page instance's unique identifier 930 * 931 * @return instance unique identifier 932 */ 933 public PageReference getPageReference() 934 { 935 setStatelessHint(false); 936 937 // make sure the page will be available on following request 938 getSession().getPageManager().touchPage(this); 939 940 return new PageReference(numericId); 941 } 942 943 @Override 944 public int getPageId() 945 { 946 return numericId; 947 } 948 949 @Override 950 public int getRenderCount() 951 { 952 return renderCount; 953 } 954 955 /** 956 * THIS METHOD IS NOT PART OF WICKET API. DO NOT USE! 957 * 958 * Sets the flag that determines whether or not this page was created using one of its 959 * bookmarkable constructors 960 * 961 * @param wasCreatedBookmarkable 962 */ 963 public final void setWasCreatedBookmarkable(boolean wasCreatedBookmarkable) 964 { 965 setFlag(FLAG_WAS_CREATED_BOOKMARKABLE, wasCreatedBookmarkable); 966 } 967 968 /** 969 * Checks if this page was created using one of its bookmarkable constructors 970 * 971 * @see org.apache.wicket.request.component.IRequestablePage#wasCreatedBookmarkable() 972 */ 973 @Override 974 public final boolean wasCreatedBookmarkable() 975 { 976 return getFlag(FLAG_WAS_CREATED_BOOKMARKABLE); 977 } 978 979 @Override 980 public void renderPage() 981 { 982 // page id is frozen during the render 983 final boolean frozen = setFreezePageId(true); 984 try 985 { 986 ++renderCount; 987 988 // delay rendering of feedbacks after all other components 989 try (FeedbackDelay delay = new FeedbackDelay(getRequestCycle())) { 990 beforeRender(); 991 992 delay.beforeRender(); 993 } 994 995 markRendering(true); 996 997 render(); 998 } 999 finally 1000 { 1001 setFreezePageId(frozen); 1002 } 1003 } 1004 1005 /** 1006 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL. 1007 * 1008 * @param component 1009 * @return if this component was render in this page 1010 */ 1011 public final boolean wasRendered(Component component) 1012 { 1013 return renderedComponents != null && renderedComponents.contains(component); 1014 } 1015}