All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.wicket.Page Maven / Gradle / Ivy

Go to download

Pax Wicket Service is an OSGi extension of the Wicket framework, allowing for dynamic loading and unloading of Wicket components and pageSources.

There is a newer version: 5.0.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.wicket;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.wicket.authorization.IAuthorizationStrategy;
import org.apache.wicket.authorization.UnauthorizedActionException;
import org.apache.wicket.authorization.strategies.page.SimplePageAuthorizationStrategy;
import org.apache.wicket.markup.MarkupException;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.MarkupType;
import org.apache.wicket.markup.resolver.IComponentResolver;
import org.apache.wicket.model.IModel;
import org.apache.wicket.page.IPageManager;
import org.apache.wicket.pageStore.IPageStore;
import org.apache.wicket.request.component.IRequestablePage;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.session.ISessionStore;
import org.apache.wicket.settings.IDebugSettings;
import org.apache.wicket.util.lang.Classes;
import org.apache.wicket.util.lang.Generics;
import org.apache.wicket.util.lang.WicketObjects;
import org.apache.wicket.util.string.StringValue;
import org.apache.wicket.util.visit.IVisit;
import org.apache.wicket.util.visit.IVisitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Abstract base class for pages. As a MarkupContainer subclass, a Page can contain a component
 * hierarchy and markup in some markup language such as HTML. Users of the framework should not
 * attempt to subclass Page directly. Instead they should subclass a subclass of Page that is
 * appropriate to the markup type they are using, such as WebPage (for HTML markup).
 * 
    *
  • Construction - When a page is constructed, it is automatically added to the current * PageMap in the Session. When a Page is added to the Session's PageMap, the PageMap assigns the * Page an id. A PageMap is roughly equivalent to a browser window and encapsulates a set of pages * accessible through that window. When a popup window is created, a new PageMap is created for the * popup. * *
  • Identity - The Session that a Page is contained in can be retrieved by calling * Page.getSession(). Page identifiers start at 0 for each PageMap in the Session and increment as * new pages are added to the map. The PageMap-(and Session)-unique identifier assigned to a given * Page can be retrieved by calling getId(). So, the first Page added to a new user Session will * always be named "0". * *
  • LifeCycle - Subclasses of Page which are interested in lifecycle events can override * onBeginRequest, onEndRequest() and onModelChanged(). The onBeginRequest() method is inherited * from Component. A call to onBeginRequest() is made for every Component on a Page before page * rendering begins. At the end of a request (when rendering has completed) to a Page, the * onEndRequest() method is called for every Component on the Page. * *
  • Nested Component Hierarchy - The Page class is a subclass of MarkupContainer. All * MarkupContainers can have "associated markup", which resides alongside the Java code by default. * All MarkupContainers are also Component containers. Through nesting, of containers, a Page can * contain any arbitrary tree of Components. For more details on MarkupContainers, see * {@link org.apache.wicket.MarkupContainer}. * *
  • Bookmarkable Pages - Pages can be constructed with any constructor when they are being * used in a Wicket session, but if you wish to link to a Page using a URL that is "bookmarkable" * (which implies that the URL will not have any session information encoded in it, and that you can * call this page directly without having a session first directly from your browser), you need to * implement your Page with a no-arg constructor or with a constructor that accepts a PageParameters * argument (which wraps any query string parameters for a request). In case the page has both * constructors, the constructor with PageParameters will be used. * *
  • Models - Pages, like other Components, can have models (see {@link IModel}). A Page * can be assigned a model by passing one to the Page's constructor, by overriding initModel() or * with an explicit invocation of setModel(). If the model is a * {@link org.apache.wicket.model.CompoundPropertyModel}, Components on the Page can use the Page's * model implicitly via container inheritance. If a Component is not assigned a model, the * initModel() override in Component will cause that Component to use the nearest CompoundModel in * the parent chain, in this case, the Page's model. For basic CompoundModels, the name of the * Component determines which property of the implicit page model the component is bound to. If more * control is desired over the binding of Components to the page model (for example, if you want to * specify some property expression other than the component's name for retrieving the model * object), BoundCompoundPropertyModel can be used. * *
  • Back Button - Pages can support the back button by enabling versioning with a call to * setVersioned(boolean). If a Page is versioned and changes occur to it which need to be tracked, a * version manager will be installed using the {@link ISessionStore}'s factory method * newVersionManager(). * *
  • Security - See {@link IAuthorizationStrategy}, {@link SimplePageAuthorizationStrategy} * * @see org.apache.wicket.markup.html.WebPage * @see org.apache.wicket.MarkupContainer * @see org.apache.wicket.model.CompoundPropertyModel * @see org.apache.wicket.Component * * @author Jonathan Locke * @author Chris Turner * @author Eelco Hillenius * @author Johan Compagner * */ public abstract class Page extends MarkupContainer implements IRedirectListener, IRequestablePage { /** True if the page hierarchy has been modified in the current request. */ private static final int FLAG_IS_DIRTY = FLAG_RESERVED3; /** Set to prevent marking page as dirty under certain circumstances. */ private static final int FLAG_PREVENT_DIRTY = FLAG_RESERVED4; /** True if the page should try to be stateless */ private static final int FLAG_STATELESS_HINT = FLAG_RESERVED5; /** TODO WICKET-NG JAVADOC */ private static final int FLAG_WAS_CREATED_BOOKMARKABLE = FLAG_RESERVED8; /** Log. */ private static final Logger log = LoggerFactory.getLogger(Page.class); private static final long serialVersionUID = 1L; /** Used to create page-unique numbers */ private int autoIndex; /** Numeric version of this page's id */ private int numericId; /** Set of components that rendered if component use checking is enabled */ private transient Set renderedComponents; /** * Boolean if the page is stateless, so it doesn't have to be in the page map, will be set in * urlFor */ private transient Boolean stateless = null; /** Page parameters used to construct this page */ private final PageParameters pageParameters; /** * The purpose of render count is to detect stale listener interface links. For example: there * is a page A rendered in tab 1. Then page A is opened also in tab 2. During render page state * changes (i.e. some repeater gets rebuilt). This makes all links on tab 1 stale - because they * no longer match the actual page state. This is done by incrementing render count. When link * is clicked Wicket checks if it's render count matches the render count value of page */ private int renderCount = 0; /** * Constructor. */ protected Page() { this(null, null); } /** * Constructor. * * @param model * See Component * @see Component#Component(String, IModel) */ protected Page(final IModel model) { this(null, model); } /** * The {@link PageParameters} parameter will be stored in this page and then those parameters * will be used to create stateless links to this bookmarkable page. * * @param parameters * externally passed parameters * @see PageParameters */ protected Page(final PageParameters parameters) { this(parameters, null); } /** * Construct. * * @param parameters * @param model */ private Page(final PageParameters parameters, IModel model) { super(null, model); if (parameters == null) { pageParameters = new PageParameters(); } else { pageParameters = parameters; } init(); } /** * The {@link PageParameters} object that was used to construct this page. This will be used in * creating stateless/bookmarkable links to this page * * @return {@link PageParameters} The construction page parameter */ public PageParameters getPageParameters() { return pageParameters; } /** * Adds a component to the set of rendered components. * * @param component * The component that was rendered */ public final void componentRendered(final Component component) { // Inform the page that this component rendered if (getApplication().getDebugSettings().getComponentUseCheck()) { if (renderedComponents == null) { renderedComponents = new HashSet(); } if (renderedComponents.add(component) == false) { throw new MarkupException( "The component " + component + " was rendered already. You can render it only once during a render phase. Class relative path: " + component.getClassRelativePath()); } if (log.isDebugEnabled()) { log.debug("Rendered " + component); } } } /** * Detaches any attached models referenced by this page. */ @Override public void detachModels() { super.detachModels(); } /** * @see org.apache.wicket.Component#internalPrepareForRender(boolean) */ @Override public void internalPrepareForRender(boolean setRenderingFlag) { if (!isInitialized()) { // initialize the page if not yet initialized internalInitialize(); } super.internalPrepareForRender(setRenderingFlag); } /** * @see #dirty(boolean) */ public final void dirty() { dirty(false); } /** {@inheritDoc} */ public boolean setFreezePageId(boolean freeze) { boolean frozen = getFlag(FLAG_PREVENT_DIRTY); setFlag(FLAG_PREVENT_DIRTY, freeze); return frozen; } /** * Mark this page as modified in the session. If versioning is supported then a new version of * the page will be stored in {@link IPageStore page store} * * @param isInitialization * a flag whether this is a page instantiation */ public void dirty(final boolean isInitialization) { checkHierarchyChange(this); if (getFlag(FLAG_PREVENT_DIRTY)) { return; } final IPageManager pageManager = getSession().getPageManager(); if (!getFlag(FLAG_IS_DIRTY) && (isVersioned() && pageManager.supportsVersioning() || // we need to get pageId for new page instances even when the page doesn't need // versioning, otherwise pages override each other in the page store and back button // support is broken isInitialization)) { setFlag(FLAG_IS_DIRTY, true); setNextAvailableId(); pageManager.touchPage(this); } } /** * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL. * * This method is called when a component was rendered standalone. If it is a * MarkupContainer then the rendering for that container is checked. * * @param component */ public final void endComponentRender(Component component) { if (component instanceof MarkupContainer) { checkRendering((MarkupContainer)component); } else { renderedComponents = null; } } /** * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. * * Get a page unique number, which will be increased with each call. * * @return A page unique number */ public final int getAutoIndex() { return autoIndex++; } /** * @see org.apache.wicket.Component#getId() */ @Override public final String getId() { return Integer.toString(numericId); } /** * * @return page class */ public final Class getPageClass() { return getClass(); } /** * @return Size of this page in bytes */ @Override public final long getSizeInBytes() { return WicketObjects.sizeof(this); } /** * Returns whether the page should try to be stateless. To be stateless, getStatelessHint() of * every component on page (and it's behavior) must return true and the page must be * bookmarkable. * * @see org.apache.wicket.Component#getStatelessHint() */ @Override public final boolean getStatelessHint() { return getFlag(FLAG_STATELESS_HINT); } /** * @return This page's component hierarchy as a string */ public final String hierarchyAsString() { final StringBuilder buffer = new StringBuilder(); buffer.append("Page ").append(getId()); visitChildren(new IVisitor() { public void component(final Component component, final IVisit visit) { int levels = 0; for (Component current = component; current != null; current = current.getParent()) { levels++; } buffer.append(StringValue.repeat(levels, " ")) .append(component.getPageRelativePath()) .append(":") .append(Classes.simpleName(component.getClass())); } }); return buffer.toString(); } /** * Bookmarkable page can be instantiated using a bookmarkable URL. * * @return Returns true if the page is bookmarkable. */ public boolean isBookmarkable() { return getApplication().getPageFactory().isBookmarkable(getClass()); } /** * Override this method and return true if your page is used to display Wicket errors. This can * help the framework prevent infinite failure loops. * * @return True if this page is intended to display an error to the end user. */ public boolean isErrorPage() { return false; } /** * Determine the "statelessness" of the page while not changing the cached value. * * @return boolean value */ private boolean peekPageStateless() { Boolean old = stateless; Boolean res = isPageStateless(); stateless = old; return res; } /** * Gets whether the page is stateless. Components on stateless page must not render any stateful * urls, and components on stateful page must not render any stateless urls. Stateful urls are * urls, which refer to a certain (current) page instance. * * @return Whether this page is stateless */ public final boolean isPageStateless() { if (isBookmarkable() == false) { stateless = Boolean.FALSE; if (getStatelessHint()) { log.warn("Page '" + this + "' is not stateless because it is not bookmarkable, " + "but the stateless hint is set to true!"); } } if (getStatelessHint() == false) { return false; } if (stateless == null) { if (isStateless() == false) { stateless = Boolean.FALSE; } } if (stateless == null) { Component statefulComponent = visitChildren(Component.class, new IVisitor() { public void component(final Component component, final IVisit visit) { if (!component.isStateless()) { visit.stop(component); } } }); stateless = statefulComponent == null; if (log.isDebugEnabled() && !stateless.booleanValue() && getStatelessHint()) { log.debug("Page '{}' is not stateless because of component with path '{}'.", this, statefulComponent.getPageRelativePath()); } } return stateless; } /** * Redirect to this page. * * @see org.apache.wicket.IRedirectListener#onRedirect() */ public final void onRedirect() { } /** * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL. * * Set the id for this Page. This method is called by PageMap when a Page is added because the * id, which is assigned by PageMap, is not known until this time. * * @param id * The id */ public final void setNumericId(final int id) { numericId = id; } /** * Sets whether the page should try to be stateless. To be stateless, getStatelessHint() of * every component on page (and it's behavior) must return true and the page must be * bookmarkable. * * @param value * whether the page should try to be stateless */ public final void setStatelessHint(boolean value) { if (value && !isBookmarkable()) { throw new WicketRuntimeException( "Can't set stateless hint to true on a page when the page is not bookmarkable, page: " + this); } setFlag(FLAG_STATELESS_HINT, value); } /** * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL. * * This method is called when a component will be rendered standalone. * * @param component * */ public final void startComponentRender(Component component) { renderedComponents = null; } /** * Get the string representation of this container. * * @return String representation of this container */ @Override public String toString() { return "[Page class = " + getClass().getName() + ", id = " + getId() + ", render count = " + getRenderCount() + "]"; } /** * Throw an exception if not all components rendered. * * @param renderedContainer * The page itself if it was a full page render or the container that was rendered * standalone */ private void checkRendering(final MarkupContainer renderedContainer) { // If the application wants component uses checked and // the response is not a redirect final IDebugSettings debugSettings = getApplication().getDebugSettings(); if (debugSettings.getComponentUseCheck()) { final List unrenderedComponents = new ArrayList(); final StringBuilder buffer = new StringBuilder(); renderedContainer.visitChildren(new IVisitor() { public void component(final Component component, final IVisit visit) { // If component never rendered if (renderedComponents == null || !renderedComponents.contains(component)) { // If not an auto component ... if (!component.isAuto() && component.isVisibleInHierarchy()) { // Increase number of unrendered components unrenderedComponents.add(component); // Add to explanatory string to buffer buffer.append(Integer.toString(unrenderedComponents.size())) .append(". ") .append(component) .append("\n"); String metadata = component.getMetaData(Component.CONSTRUCTED_AT_KEY); if (metadata != null) { buffer.append(metadata); } metadata = component.getMetaData(Component.ADDED_AT_KEY); if (metadata != null) { buffer.append(metadata); } } else { // if the component is not visible in hierarchy we // should not visit its children since they are also // not visible visit.dontGoDeeper(); } } } }); // Throw exception if any errors were found if (unrenderedComponents.size() > 0) { renderedComponents = null; List transparentContainerChildren = Generics.newArrayList(); Iterator iterator = unrenderedComponents.iterator(); outerWhile : while (iterator.hasNext()) { Component component = iterator.next(); // If any of the transparentContainerChildren is a parent to component, then // ignore it. for (Component transparentContainerChild : transparentContainerChildren) { MarkupContainer parent = component.getParent(); while (parent != null) { if (parent == transparentContainerChild) { iterator.remove(); continue outerWhile; } parent = parent.getParent(); } } if (hasInvisibleTransparentChild(component.getParent(), component)) { // If we found a transparent container that isn't visible then ignore this // component and only do a debug statement here. if (log.isDebugEnabled()) { log.debug( "Component {} wasn't rendered but might have a transparent parent.", component); } transparentContainerChildren.add(component); iterator.remove(); continue outerWhile; } } // if still > 0 if (unrenderedComponents.size() > 0) { // Throw exception throw new WicketRuntimeException( "The component(s) below failed to render. A common problem is that you have added a component in code but forgot to reference it in the markup (thus the component will never be rendered).\n\n" + buffer.toString()); } } } // Get rid of set renderedComponents = null; } private boolean hasInvisibleTransparentChild(final MarkupContainer root, final Component self) { for (Component sibling : root) { if ((sibling != self) && (sibling instanceof IComponentResolver) && (sibling instanceof MarkupContainer)) { if (!sibling.isVisible()) { return true; } else { boolean rtn = hasInvisibleTransparentChild((MarkupContainer)sibling, self); if (rtn == true) { return true; } } } } return false; } /** * Initializes Page by adding it to the Session and initializing it. */ private void init() { if (isBookmarkable()) { setStatelessHint(true); } // Set versioning of page based on default setVersioned(getApplication().getPageSettings().getVersionPagesByDefault()); // All Pages are born dirty so they get clustered right away dirty(true); // this is a bit of a dirty hack, but calling dirty(true) results in isStateless called // which is bound to set the stateless cache to true as there are no components yet stateless = null; } /** * */ private void setNextAvailableId() { setNumericId(getSession().nextPageId()); } /** * This method will be called for all components that are changed on the page So also auto * components or components that are not versioned. * * If the parent is given that it was a remove or add from that parent of the given component. * else it was just a internal property change of that component. * * @param component * @param parent */ protected void componentChanged(Component component, MarkupContainer parent) { if (!component.isAuto()) { dirty(); } } /** * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL OR OVERRIDE. * * @see org.apache.wicket.Component#internalOnModelChanged() */ @Override protected final void internalOnModelChanged() { visitChildren(new IVisitor() { public void component(final Component component, final IVisit visit) { // If form component is using form model if (component.sameInnermostModel(Page.this)) { component.modelChanged(); } } }); } @Override void internalOnAfterConfigure() { super.internalOnAfterConfigure(); // check if the page can be rendered if (!isActionAuthorized(RENDER)) { if (log.isDebugEnabled()) { log.debug("Page not allowed to render: " + this); } throw new UnauthorizedActionException(this, Component.RENDER); } } /** * * @see org.apache.wicket.Component#onBeforeRender() */ @Override protected void onBeforeRender() { // Make sure it is really empty renderedComponents = null; // if the page is stateless, reset the flag so that it is tested again if (Boolean.TRUE.equals(stateless)) { stateless = null; } super.onBeforeRender(); // If any of the components on page is not stateless, we need to bind the session // before we start rendering components, as then jsessionid won't be appended // for links rendered before first stateful component if (getSession().isTemporary() && !peekPageStateless()) { getSession().bind(); } } /** * @see org.apache.wicket.Component#onAfterRender() */ @Override protected void onAfterRender() { super.onAfterRender(); // Check rendering if it happened fully checkRendering(this); // clean up debug meta data if component check is on if (getApplication().getDebugSettings().getComponentUseCheck()) { visitChildren(new IVisitor() { public void component(final Component component, final IVisit visit) { component.setMetaData(Component.CONSTRUCTED_AT_KEY, null); component.setMetaData(Component.ADDED_AT_KEY, null); } }); } if (!isPageStateless()) { // trigger creation of the actual session in case it was deferred getSession().getSessionStore().getSessionId(RequestCycle.get().getRequest(), true); // Add/touch the response page in the session. getSession().getPageManager().touchPage(this); } if (getApplication().getDebugSettings().isOutputMarkupContainerClassName()) { Class klass = getClass(); while (klass.isAnonymousClass()) { klass = klass.getSuperclass(); } getResponse().write("\n"); } } /** * @see org.apache.wicket.Component#onDetach() */ @Override protected void onDetach() { if (log.isDebugEnabled()) { log.debug("ending request for page " + this + ", request " + getRequest()); } setFlag(FLAG_IS_DIRTY, false); super.onDetach(); } /** * @see org.apache.wicket.MarkupContainer#onRender() */ @Override protected void onRender() { // Loop through the markup in this container MarkupStream markupStream = new MarkupStream(getMarkup()); renderAll(markupStream, null); } /** * A component was added. * * @param component * The component that was added */ final void componentAdded(final Component component) { if (!component.isAuto()) { dirty(); } } /** * A component's model changed. * * @param component * The component whose model is about to change */ final void componentModelChanging(final Component component) { dirty(); } /** * A component was removed. * * @param component * The component that was removed */ final void componentRemoved(final Component component) { if (!component.isAuto()) { dirty(); } } /** * * @param component */ final void componentStateChanging(final Component component) { if (!component.isAuto()) { dirty(); } } /** * Set page stateless * * @param stateless */ void setPageStateless(Boolean stateless) { this.stateless = stateless; } /** * @see org.apache.wicket.MarkupContainer#getMarkupType() */ @Override public MarkupType getMarkupType() { throw new UnsupportedOperationException( "Page does not support markup. This error can happen if you have extended Page directly, instead extend WebPage"); } /** * Gets page instance's unique identifier * * @return instance unique identifier */ public PageReference getPageReference() { setStatelessHint(false); return new PageReference(numericId); } /** * @see org.apache.wicket.page.IManageablePage#getPageId() */ public int getPageId() { return numericId; } /** * @see org.apache.wicket.request.component.IRequestablePage#getRenderCount() */ public int getRenderCount() { return renderCount; } /** * TODO WICKET-NG javadoc * * @param wasCreatedBookmarkable */ public final void setWasCreatedBookmarkable(boolean wasCreatedBookmarkable) { setFlag(FLAG_WAS_CREATED_BOOKMARKABLE, wasCreatedBookmarkable); } /** TODO WICKET-NG javadoc */ public final boolean wasCreatedBookmarkable() { return getFlag(FLAG_WAS_CREATED_BOOKMARKABLE); } /** * @see org.apache.wicket.request.component.IRequestablePage#renderPage() */ public void renderPage() { // page id is frozen during the render final boolean frozen = setFreezePageId(true); try { ++renderCount; render(); } finally { setFreezePageId(frozen); } } /** * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL. * * @param component * @return if this component was render in this page */ public final boolean wasRendered(Component component) { return renderedComponents != null && renderedComponents.contains(component); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy