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}