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}