0001: /*
0002: * $Id: Page.java 508111 2007-02-15 19:50:42Z ivaynberg $ $Revision: 508111 $ $Date:
0003: * 2006-03-16 11:34:01 -0800 (Thu, 16 Mar 2006) $
0004: *
0005: * ==============================================================================
0006: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
0007: * use this file except in compliance with the License. You may obtain a copy of
0008: * the License at
0009: *
0010: * http://www.apache.org/licenses/LICENSE-2.0
0011: *
0012: * Unless required by applicable law or agreed to in writing, software
0013: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
0014: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
0015: * License for the specific language governing permissions and limitations under
0016: * the License.
0017: */
0018: package wicket;
0019:
0020: import java.util.ArrayList;
0021: import java.util.HashSet;
0022: import java.util.List;
0023: import java.util.Set;
0024:
0025: import org.apache.commons.logging.Log;
0026: import org.apache.commons.logging.LogFactory;
0027:
0028: import wicket.authorization.UnauthorizedActionException;
0029: import wicket.feedback.FeedbackMessages;
0030: import wicket.feedback.IFeedback;
0031: import wicket.markup.MarkupException;
0032: import wicket.markup.MarkupStream;
0033: import wicket.markup.html.WebPage;
0034: import wicket.markup.html.form.Form;
0035: import wicket.model.IModel;
0036: import wicket.request.RequestParameters;
0037: import wicket.session.pagemap.IPageMapEntry;
0038: import wicket.settings.IDebugSettings;
0039: import wicket.settings.IPageSettings;
0040: import wicket.util.lang.Classes;
0041: import wicket.util.lang.Objects;
0042: import wicket.util.string.StringValue;
0043: import wicket.util.value.Count;
0044: import wicket.version.IPageVersionManager;
0045: import wicket.version.undo.Change;
0046: import wicket.version.undo.UndoPageVersionManager;
0047:
0048: /**
0049: * Abstract base class for pages. As a MarkupContainer subclass, a Page can
0050: * contain a component hierarchy and markup in some markup language such as
0051: * HTML. Users of the framework should not attempt to subclass Page directly.
0052: * Instead they should subclass a subclass of Page that is appropriate to the
0053: * markup type they are using, such as WebPage (for HTML markup).
0054: * <ul>
0055: * <li><b>Construction </b>- When a page is constructed, it is automatically
0056: * added to the current PageMap in the Session. When a Page is added to the
0057: * Session's PageMap, the PageMap assigns the Page an id. A PageMap is roughly
0058: * equivalent to a browser window and encapsulates a set of pages accessible
0059: * through that window. When a popup window is created, a new PageMap is created
0060: * for the popup.
0061: *
0062: * <li><b>Identity </b>- The Session that a Page is contained in can be
0063: * retrieved by calling Page.getSession(). Page identifiers start at 0 for each
0064: * PageMap in the Session and increment as new pages are added to the map. The
0065: * PageMap-(and Session)-unique identifier assigned to a given Page can be
0066: * retrieved by calling getId(). So, the first Page added to a new user Session
0067: * will always be named "0".
0068: *
0069: * <li><b>LifeCycle </b>- Subclasses of Page which are interested in lifecycle
0070: * events can override onBeginRequest, onEndRequest() and onModelChanged(). The
0071: * onBeginRequest() method is inherited from Component. A call to
0072: * onBeginRequest() is made for every Component on a Page before page rendering
0073: * begins. At the end of a request (when rendering has completed) to a Page, the
0074: * onEndRequest() method is called for every Component on the Page.
0075: *
0076: * <li><b>Nested Component Hierarchy </b>- The Page class is a subclass of
0077: * MarkupContainer. All MarkupContainers can have "associated markup", which
0078: * resides alongside the Java code by default. All MarkupContainers are also
0079: * Component containers. Through nesting, of containers, a Page can contain any
0080: * arbitrary tree of Components. For more details on MarkupContainers, see
0081: * {@link wicket.MarkupContainer}.
0082: *
0083: * <li><b>Bookmarkable Pages </b>- Pages can be constructed with any
0084: * constructor when they are being used in a Wicket session, but if you wish to
0085: * link to a Page using a URL that is "bookmarkable" (which implies that the URL
0086: * will not have any session information encoded in it, and that you can call
0087: * this page directly without having a session first directly from your
0088: * browser), you need to implement your Page with a no-arg constructor or with a
0089: * constructor that accepts a PageParameters argument (which wraps any query
0090: * string parameters for a request). In case the page has both constructors, the
0091: * constructor with PageParameters will be used.
0092: *
0093: * <li><b>Models </b>- Pages, like other Components, can have models (see
0094: * {@link IModel}). A Page can be assigned a model by passing one to the Page's
0095: * constructor, by overriding initModel() or with an explicit invocation of
0096: * setModel(). If the model is a {@link wicket.model.CompoundPropertyModel},
0097: * Components on the Page can use the Page's model implicitly via container
0098: * inheritance. If a Component is not assigned a model, the initModel() override
0099: * in Component will cause that Component to use the nearest CompoundModel in
0100: * the parent chain, in this case, the Page's model. For basic CompoundModels,
0101: * the name of the Component determines which property of the implicit page
0102: * model the component is bound to. If more control is desired over the binding
0103: * of Components to the page model (for example, if you want to specify some
0104: * property expression other than the component's name for retrieving the model
0105: * object), BoundCompoundPropertyModel can be used.
0106: *
0107: * <li><b>Back Button </b>- Pages can support the back button by enabling
0108: * versioning with a call to setVersioned(boolean). If a Page is versioned and
0109: * changes occur to it which need to be tracked, a verison manager will be
0110: * installed using the overridable factory method newVersionManager(). The
0111: * default version manager returned by the base implementation of this method is
0112: * an instance of UndoPageVersionManager, which manages versions of a page by
0113: * keeping change records that can be reversed at a later time.
0114: *
0115: * <li><b>Security </b>- Pages can be secured by overriding checkAccess(). If
0116: * checkAccess() returns ACCESS_ALLOWED (true), then onRender() will render the
0117: * page. If it returns false (ACCESS_DENIED), then onRender() will not render
0118: * the page. Besides returning true or false, an implementation of checkAccess()
0119: * may also choose to send the user to another page with
0120: * Component.setResponsePage() or Component.redirectToInterceptPage(). This can
0121: * be used to allow a user to authenticate themselves if they were denied
0122: * access.
0123: *
0124: * @see wicket.markup.html.WebPage
0125: * @see wicket.MarkupContainer
0126: * @see wicket.model.CompoundPropertyModel
0127: * @see wicket.model.BoundCompoundPropertyModel
0128: * @see wicket.Component
0129: * @see wicket.version.IPageVersionManager
0130: * @see wicket.version.undo.UndoPageVersionManager
0131: *
0132: * @author Jonathan Locke
0133: * @author Chris Turner
0134: * @author Eelco Hillenius
0135: * @author Johan Compagner
0136: */
0137: public abstract class Page extends MarkupContainer implements
0138: IRedirectListener, IPageMapEntry {
0139: private static final long serialVersionUID = 1L;
0140:
0141: /**
0142: * When passed to {@link Page#getVersion(int)} the latest page version is
0143: * returned.
0144: */
0145: public static final int LATEST_VERSION = -1;
0146:
0147: /** True if this page is currently rendering. */
0148: private static final short FLAG_IS_RENDERING = FLAG_RESERVED2;
0149:
0150: /** True if a new version was created for this request. */
0151: private static final short FLAG_NEW_VERSION = FLAG_RESERVED3;
0152:
0153: /** True if component changes are being tracked. */
0154: private static final short FLAG_TRACK_CHANGES = FLAG_RESERVED4;
0155:
0156: /** Log. */
0157: private static final Log log = LogFactory.getLog(Page.class);
0158:
0159: /** Used to create page-unique numbers */
0160: private short autoIndex;
0161:
0162: /** Feedback messages for this page */
0163: private FeedbackMessages feedbackMessages;
0164:
0165: /** Numeric version of this page's id */
0166: private short numericId;
0167:
0168: /** The PageMap within the session that this page is stored in */
0169: private transient PageMap pageMap;
0170:
0171: /** Name of PageMap that this page is stored in */
0172: private String pageMapName;
0173:
0174: /** Set of components that rendered if component use checking is enabled */
0175: private transient Set renderedComponents;
0176:
0177: /**
0178: * Boolean if the page is stateless, so it doesn't have to be in the page
0179: * map, will be set in urlFor
0180: */
0181: private transient boolean stateless = true;
0182:
0183: /** Version manager for this page */
0184: private IPageVersionManager versionManager;
0185:
0186: /**
0187: * Constructor.
0188: */
0189: protected Page() {
0190: // A Page's id is not determined until setId is called when the Page is
0191: // added to a PageMap in the Session.
0192: super (null);
0193: init();
0194: }
0195:
0196: /**
0197: * Constructor.
0198: *
0199: * @param model
0200: * See Component
0201: * @see Component#Component(String, IModel)
0202: */
0203: protected Page(final IModel model) {
0204: // A Page's id is not determined until setId is called when the Page is
0205: // added to a PageMap in the Session.
0206: super (null, model);
0207: init();
0208: }
0209:
0210: /**
0211: * Constructor.
0212: *
0213: * @param pageMap
0214: * The page map to put this page in
0215: */
0216: protected Page(final PageMap pageMap) {
0217: // A Page's id is not determined until setId is called when the Page is
0218: // added to a PageMap in the Session.
0219: super (null);
0220: init(pageMap);
0221: }
0222:
0223: /**
0224: * Constructor.
0225: *
0226: * @param pageMap
0227: * the name of the page map to put this page in
0228: * @param model
0229: * See Component
0230: * @see Component#Component(String, IModel)
0231: */
0232: protected Page(final PageMap pageMap, final IModel model) {
0233: // A Page's id is not determined until setId is called when the Page is
0234: // added to a PageMap in the Session.
0235: super (null, model);
0236: init(pageMap);
0237: }
0238:
0239: /**
0240: * Called right after a component's listener method (the provided method
0241: * argument) was called. This method may be used to clean up dependencies,
0242: * do logging, etc. NOTE: this method will also be called when
0243: * {@link WebPage#beforeCallComponent(Component, RequestListenerInterface)}
0244: * or the method invocation itself failed.
0245: *
0246: * @param component
0247: * the component that is to be called
0248: * @param listener
0249: * the listener of that component that is to be called
0250: */
0251: public void afterCallComponent(final Component component,
0252: final RequestListenerInterface listener) {
0253: }
0254:
0255: /**
0256: * Called just before a component's listener method (the provided method
0257: * argument) is called. This method may be used to set up dependencies,
0258: * enforce authorization, etc. NOTE: if this method fails, the method will
0259: * not be excuted. Method
0260: * {@link WebPage#afterCallComponent(Component, RequestListenerInterface)}
0261: * will always be called.
0262: *
0263: * @param component
0264: * the component that is to be called
0265: * @param listener
0266: * the listener of that component that is to be called
0267: */
0268: public void beforeCallComponent(final Component component,
0269: final RequestListenerInterface listener) {
0270: }
0271:
0272: /**
0273: * @return fixed true;
0274: *
0275: * @deprecated this method is to be removed in future version in favor of
0276: * instances of
0277: * {@link wicket.authorization.IAuthorizationStrategy} such as
0278: * {@link wicket.authorization.strategies.page.AbstractPageAuthorizationStrategy}.
0279: * It isn't called anymore and made final so that people see
0280: * what must be changed.
0281: */
0282: public final boolean checkAccess() {
0283: return true;
0284: }
0285:
0286: /**
0287: * @see wicket.MarkupContainer#internalDetach()
0288: */
0289: public void internalDetach() {
0290: super .internalDetach();
0291: }
0292:
0293: /**
0294: * Detaches any attached models referenced by this page.
0295: */
0296: public void detachModels() {
0297: // visit all this page's children to detach the models
0298: visitChildren(new IVisitor() {
0299: public Object component(Component component) {
0300: try {
0301: // detach any models of the component
0302: component.detachModels();
0303: } catch (Exception e) // catch anything; we MUST detach all models
0304: {
0305: log.error("detaching models of component "
0306: + component + " failed:", e);
0307: }
0308: return IVisitor.CONTINUE_TRAVERSAL;
0309: }
0310: });
0311:
0312: super .detachModels();
0313: }
0314:
0315: /**
0316: * Mark this page as dirty in the session
0317: */
0318: public final void dirty() {
0319: Session.get().dirtyPage(this );
0320: }
0321:
0322: /**
0323: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
0324: */
0325: public final void renderPage() {
0326: // first try to check if the page can be rendered:
0327: if (!isActionAuthorized(RENDER)) {
0328: if (log.isDebugEnabled()) {
0329: log.debug("Page not allowed to render: " + this );
0330: }
0331: throw new UnauthorizedActionException(this ,
0332: Component.RENDER);
0333: }
0334:
0335: // Make sure it is really empty
0336: renderedComponents = null;
0337:
0338: // Reset it to stateless so that it can be tested again
0339: this .stateless = true;
0340:
0341: // Set form component values from cookies
0342: setFormComponentValuesFromCookies();
0343:
0344: // First, give priority to IFeedback instances, as they have to
0345: // collect their messages before components like ListViews
0346: // remove any child components
0347: visitChildren(IFeedback.class, new IVisitor() {
0348: public Object component(Component component) {
0349: ((IFeedback) component).updateFeedback();
0350: component.internalAttach();
0351: return IVisitor.CONTINUE_TRAVERSAL;
0352: }
0353: });
0354:
0355: if (this instanceof IFeedback) {
0356: ((IFeedback) this ).updateFeedback();
0357: }
0358:
0359: // Now, do the initialization for the other components
0360: internalAttach();
0361:
0362: // Call reset head rendered on the page
0363: resetHeadRendered();
0364:
0365: // Visit all this page's children to reset markup streams and check
0366: // rendering authorization, as appropriate. We set any result; positive
0367: // or negative as a temporary boolean in the components, and when a
0368: // authorization exception is thrown it will block the rendering of this
0369: // page
0370:
0371: // first the page itself
0372: setRenderAllowed(isActionAuthorized(RENDER));
0373: // children of the page
0374: visitChildren(new IVisitor() {
0375: public Object component(final Component component) {
0376: component.resetHeadRendered();
0377:
0378: // Find out if this component can be rendered
0379: final boolean renderAllowed = component
0380: .isActionAuthorized(RENDER);
0381:
0382: // Authorize rendering
0383: component.setRenderAllowed(renderAllowed);
0384: return IVisitor.CONTINUE_TRAVERSAL;
0385: }
0386: });
0387:
0388: // Handle request by rendering page
0389: render(null);
0390:
0391: // Check rendering if it happened fully
0392: checkRendering(this );
0393:
0394: // Add/touch the response page in the session (its pagemap).
0395: getSession().touch(this );
0396: }
0397:
0398: /**
0399: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL.
0400: *
0401: * This method is called when a component was rendered standalone. If it is
0402: * a markupcontainer then the rendering for that container is checked.
0403: *
0404: * @param component
0405: *
0406: */
0407: public final void endComponentRender(Component component) {
0408: if (component instanceof MarkupContainer) {
0409: checkRendering((MarkupContainer) component);
0410: } else {
0411: renderedComponents = null;
0412: }
0413: }
0414:
0415: /**
0416: * Expire the oldest version of this page
0417: */
0418: public final void expireOldestVersion() {
0419: if (versionManager != null) {
0420: versionManager.expireOldestVersion();
0421: }
0422: }
0423:
0424: /**
0425: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
0426: *
0427: * Get a page unique number, which will be increased with each call.
0428: *
0429: * @return A page unique number
0430: */
0431: public final short getAutoIndex() {
0432: return this .autoIndex++;
0433: }
0434:
0435: /**
0436: * @return The current version number of this page. If the page has been
0437: * changed once, the return value will be 1. If the page has not yet
0438: * been revised, the version returned will be 0, indicating that the
0439: * page is still in its original state.
0440: */
0441: public final int getCurrentVersionNumber() {
0442: return versionManager == null ? 0 : versionManager
0443: .getCurrentVersionNumber();
0444: }
0445:
0446: /**
0447: * @return Returns feedback messages from all components in this page
0448: * (including the page itself).
0449: */
0450: public final FeedbackMessages getFeedbackMessages() {
0451: if (feedbackMessages == null) {
0452: feedbackMessages = new FeedbackMessages();
0453: }
0454: return feedbackMessages;
0455: }
0456:
0457: /**
0458: * @see wicket.Component#getId()
0459: */
0460: public final String getId() {
0461: return Integer.toString(numericId);
0462: }
0463:
0464: /**
0465: * @see wicket.session.pagemap.IPageMapEntry#getNumericId()
0466: */
0467: public int getNumericId() {
0468: return numericId;
0469: }
0470:
0471: /**
0472: * @see wicket.session.pagemap.IPageMapEntry#getPageClass()
0473: */
0474: public final Class getPageClass() {
0475: return getClass();
0476: }
0477:
0478: /**
0479: * @return Returns the PageMap that this Page is stored in.
0480: */
0481: public final PageMap getPageMap() {
0482: // If the transient needs to be restored
0483: if (pageMap == null) {
0484: // Look the page map up in the session
0485: pageMap = PageMap.forName(pageMapName);
0486: }
0487: return pageMap;
0488: }
0489:
0490: /**
0491: * @return Get a page map entry for this page. By default, this is the page
0492: * itself. But if you know of some way to compress the state for the
0493: * page, you can return a custom implementation that produces the
0494: * page on-the-fly.
0495: */
0496: public IPageMapEntry getPageMapEntry() {
0497: return this ;
0498: }
0499:
0500: /**
0501: * @return Size of this page in bytes
0502: */
0503: public final long getSizeInBytes() {
0504: this .pageMap = null;
0505: return Objects.sizeof(this );
0506: }
0507:
0508: /**
0509: * Override this method to implement a custom way of producing a version of
0510: * a Page when it cannot be found in the Session.
0511: *
0512: * @param versionNumber
0513: * The version desired
0514: * @return A Page object with the component/model hierarchy that was
0515: * attached to this page at the time represented by the requested
0516: * version.
0517: */
0518: public Page getVersion(final int versionNumber) {
0519: // If we're still the original Page and that's what's desired
0520: if (versionManager == null) {
0521: if (versionNumber == 0 || versionNumber == LATEST_VERSION) {
0522: return this ;
0523: } else {
0524: log
0525: .info("No version manager available to retrieve requested versionNumber "
0526: + versionNumber);
0527: return null;
0528: }
0529: } else {
0530: // Save original change tracking state
0531: final boolean originalTrackChanges = getFlag(FLAG_TRACK_CHANGES);
0532:
0533: try {
0534: // While the version manager is potentially playing around with
0535: // the Page, it may change the page in order to undo changes and
0536: // we don't want change tracking going on while its doing this.
0537: setFlag(FLAG_TRACK_CHANGES, false);
0538:
0539: // Get page of desired version
0540: final Page page;
0541: if (versionNumber != LATEST_VERSION) {
0542: page = versionManager.getVersion(versionNumber);
0543: } else {
0544: page = versionManager
0545: .getVersion(getCurrentVersionNumber());
0546: }
0547:
0548: // If we went all the way back to the original page
0549: if (page != null && page.getCurrentVersionNumber() == 0) {
0550: // remove version info
0551: page.versionManager = null;
0552: }
0553:
0554: return page;
0555: } finally {
0556: // Restore change tracking state
0557: setFlag(FLAG_TRACK_CHANGES, originalTrackChanges);
0558: }
0559: }
0560: }
0561:
0562: /**
0563: * @return Number of versions of this page
0564: */
0565: public final int getVersions() {
0566: return versionManager == null ? 1 : versionManager
0567: .getVersions() + 1;
0568: }
0569:
0570: /**
0571: * @return This page's component hierarchy as a string
0572: */
0573: public final String hierarchyAsString() {
0574: final StringBuffer buffer = new StringBuffer();
0575: buffer.append("Page " + getId() + " (version "
0576: + getCurrentVersionNumber() + ")");
0577: visitChildren(new IVisitor() {
0578: public Object component(Component component) {
0579: int levels = 0;
0580: for (Component current = component; current != null; current = current
0581: .getParent()) {
0582: levels++;
0583: }
0584: buffer.append(StringValue.repeat(levels, " ")
0585: + component.getPageRelativePath() + ":"
0586: + Classes.simpleName(component.getClass()));
0587: return null;
0588: }
0589: });
0590: return buffer.toString();
0591: }
0592:
0593: /**
0594: * Override this method and return true if your page is used to display
0595: * Wicket errors. This can help the framework prevent infinite failure
0596: * loops.
0597: *
0598: * @return True if this page is intended to display an error to the end
0599: * user.
0600: */
0601: public boolean isErrorPage() {
0602: return false;
0603: }
0604:
0605: /**
0606: * Redirect to this page.
0607: *
0608: * @see wicket.IRedirectListener#onRedirect()
0609: */
0610: public final void onRedirect() {
0611: }
0612:
0613: /**
0614: * Convenience method. Search for children of type fromClass and invoke
0615: * their respective removePersistedFormData() methods.
0616: *
0617: * @see Form#removePersistentFormComponentValues(boolean)
0618: *
0619: * @param formClass
0620: * Form to be selected. Pages may have more than one Form.
0621: * @param disablePersistence
0622: * if true, disable persistence for all FormComponents on that
0623: * page. If false, it will remain unchanged.
0624: */
0625: public final void removePersistedFormData(final Class formClass,
0626: final boolean disablePersistence) {
0627: // Check that formClass is an instanceof Form
0628: if (!Form.class.isAssignableFrom(formClass)) {
0629: throw new WicketRuntimeException("Form class "
0630: + formClass.getName()
0631: + " is not a subclass of Form");
0632: }
0633:
0634: // Visit all children which are an instance of formClass
0635: visitChildren(formClass, new IVisitor() {
0636: public Object component(final Component component) {
0637: // They must be of type Form as well
0638: if (component instanceof Form) {
0639: // Delete persistet FormComponent data and disable
0640: // persistence
0641: ((Form) component)
0642: .removePersistentFormComponentValues(disablePersistence);
0643: }
0644: return CONTINUE_TRAVERSAL;
0645: }
0646: });
0647: }
0648:
0649: /**
0650: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL.
0651: *
0652: * Set the id for this Page. This method is called by PageMap when a Page is
0653: * added because the id, which is assigned by PageMap, is not known until
0654: * this time.
0655: *
0656: * @param id
0657: * The id
0658: */
0659: public final void setNumericId(final int id) {
0660: this .numericId = (short) id;
0661: }
0662:
0663: /**
0664: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL.
0665: *
0666: * This method is called when a component will be rendered standalone.
0667: *
0668: * @param component
0669: *
0670: */
0671: public final void startComponentRender(Component component) {
0672: renderedComponents = null;
0673: }
0674:
0675: /**
0676: * Get the string representation of this container.
0677: *
0678: * @return String representation of this container
0679: */
0680: public String toString() {
0681: return "[Page class = " + getClass().getName() + ", id = "
0682: + getId() + "]";
0683: }
0684:
0685: /**
0686: * Set-up response with appropriate content type, locale and encoding. The
0687: * locale is set equal to the session's locale. The content type header
0688: * contains information about the markup type (@see #getMarkupType()) and
0689: * the encoding. The response (and request) encoding is determined by an
0690: * application setting (@see
0691: * ApplicationSettings#getResponseRequestEncoding()). In addition, if the
0692: * page's markup contains a xml declaration like <?xml ... ?> an xml
0693: * declaration with proper encoding information is written to the output as
0694: * well, provided it is not disabled by an applicaton setting (@see
0695: * ApplicationSettings#getStripXmlDeclarationFromOutput()).
0696: * <p>
0697: * Note: Prior to Wicket 1.1 the output encoding was determined by the
0698: * page's markup encoding. Because this caused uncertainties about the
0699: * /request/ encoding, it has been changed in favour of the new, much safer,
0700: * approach. Please see the Wiki for more details.
0701: */
0702: protected void configureResponse() {
0703: // Get the response and application
0704: final RequestCycle cycle = getRequestCycle();
0705: final Application application = cycle.getApplication();
0706: final Response response = cycle.getResponse();
0707:
0708: // Determine encoding
0709: final String encoding = application.getRequestCycleSettings()
0710: .getResponseRequestEncoding();
0711:
0712: // Set content type based on markup type for page
0713: response.setContentType("text/" + getMarkupType()
0714: + "; charset=" + encoding);
0715:
0716: // Write out an xml declaration if the markup stream and settings allow
0717: final MarkupStream markupStream = findMarkupStream();
0718: if ((markupStream != null)
0719: && (markupStream.getXmlDeclaration() != null)
0720: && (application.getMarkupSettings()
0721: .getStripXmlDeclarationFromOutput() == false)) {
0722: response.write("<?xml version='1.0' encoding='");
0723: response.write(encoding);
0724: response.write("'?>");
0725: }
0726:
0727: // Set response locale from session locale
0728: response.setLocale(getSession().getLocale());
0729: }
0730:
0731: /**
0732: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL OR
0733: * OVERRIDE.
0734: *
0735: * @see wicket.Component#internalOnDetach()
0736: */
0737: protected final void internalOnDetach() {
0738: if (log.isDebugEnabled()) {
0739: log.debug("ending request for page " + this + ", request "
0740: + getRequest());
0741: }
0742:
0743: detachModels();
0744:
0745: if (isVersioned()) {
0746: // Any changes to the page after this point will be tracked by the
0747: // page's version manager. Since trackChanges is never set to false,
0748: // this effectively means that change tracking begins after the
0749: // first request to a page completes.
0750: setFlag(FLAG_TRACK_CHANGES, true);
0751:
0752: endVersion();
0753: }
0754: }
0755:
0756: /**
0757: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL OR
0758: * OVERRIDE.
0759: *
0760: * @see wicket.Component#internalOnModelChanged()
0761: */
0762: protected final void internalOnModelChanged() {
0763: visitChildren(new Component.IVisitor() {
0764: public Object component(final Component component) {
0765: // If form component is using form model
0766: if (component.sameRootModel(Page.this )) {
0767: component.modelChanged();
0768: }
0769: return IVisitor.CONTINUE_TRAVERSAL;
0770: }
0771: });
0772: }
0773:
0774: /**
0775: * @return Factory method that creates a version manager for this Page
0776: */
0777: protected IPageVersionManager newVersionManager() {
0778: final IPageSettings settings = getSession().getApplication()
0779: .getPageSettings();
0780: return new UndoPageVersionManager(this , settings
0781: .getMaxPageVersions());
0782: }
0783:
0784: /**
0785: * Renders this container to the given response object.
0786: *
0787: * @param markupStream
0788: */
0789: protected void onRender(final MarkupStream markupStream) {
0790: // Set page's associated markup stream
0791: final MarkupStream associatedMarkupStream = getAssociatedMarkupStream(true);
0792: setMarkupStream(associatedMarkupStream);
0793:
0794: // Configure response object with locale and content type
0795: configureResponse();
0796:
0797: // Render all the page's markup
0798: setFlag(FLAG_IS_RENDERING, true);
0799: try {
0800: renderAll(associatedMarkupStream);
0801: } finally {
0802: setFlag(FLAG_IS_RENDERING, false);
0803: }
0804: }
0805:
0806: /**
0807: * A component was added.
0808: *
0809: * @param component
0810: * The component that was added
0811: */
0812: final void componentAdded(final Component component) {
0813: checkHierarchyChange(component);
0814:
0815: dirty();
0816: if (mayTrackChangesFor(component, component.getParent())) {
0817: versionManager.componentAdded(component);
0818: }
0819: }
0820:
0821: /**
0822: * A component's model changed.
0823: *
0824: * @param component
0825: * The component whose model is about to change
0826: */
0827: final void componentModelChanging(final Component component) {
0828: checkHierarchyChange(component);
0829:
0830: dirty();
0831: if (mayTrackChangesFor(component, null)) {
0832: versionManager.componentModelChanging(component);
0833: }
0834: }
0835:
0836: /**
0837: * A component was removed.
0838: *
0839: * @param component
0840: * The component that was removed
0841: */
0842: final void componentRemoved(final Component component) {
0843: checkHierarchyChange(component);
0844:
0845: dirty();
0846: if (mayTrackChangesFor(component, component.getParent())) {
0847: versionManager.componentRemoved(component);
0848: }
0849: }
0850:
0851: /**
0852: * Adds a component to the set of rendered components.
0853: *
0854: * @param component
0855: * The component that was rendered
0856: */
0857: final void componentRendered(final Component component) {
0858: // Inform the page that this component rendered
0859: if (Application.get().getDebugSettings().getComponentUseCheck()) {
0860: if (renderedComponents == null) {
0861: renderedComponents = new HashSet();
0862: }
0863: if (renderedComponents.add(component) == false) {
0864: throw new MarkupException(
0865: "The component "
0866: + component
0867: + " has the same wicket:id as another component already added at the same level");
0868: }
0869: if (log.isDebugEnabled()) {
0870: log.debug("Rendered " + component);
0871: }
0872: }
0873: }
0874:
0875: final void componentStateChanging(final Component component,
0876: Change change) {
0877: checkHierarchyChange(component);
0878:
0879: dirty();
0880: if (mayTrackChangesFor(component, null)) {
0881: versionManager.componentStateChanging(change);
0882: }
0883: }
0884:
0885: /**
0886: * @return Return true from this method if you want to keep a page out of
0887: * the session.
0888: */
0889: final boolean isStateless() {
0890: return stateless;
0891: }
0892:
0893: final void setStateless(boolean stateless) {
0894: this .stateless = stateless;
0895: }
0896:
0897: /**
0898: * Sets values for form components based on cookie values in the request.
0899: *
0900: */
0901: final void setFormComponentValuesFromCookies() {
0902: // Visit all Forms contained in the page
0903: visitChildren(Form.class, new Component.IVisitor() {
0904: // For each FormComponent found on the Page (not Form)
0905: public Object component(final Component component) {
0906: ((Form) component).loadPersistentFormComponentValues();
0907: return CONTINUE_TRAVERSAL;
0908: }
0909: });
0910: }
0911:
0912: /**
0913: * @param pageMap
0914: * Sets this page into the page map with the given name. If the
0915: * page map does not yet exist, it is automatically created.
0916: */
0917: final void setPageMap(final PageMap pageMap) {
0918: // Save transient reference to pagemap
0919: this .pageMap = pageMap;
0920:
0921: // Save name for restoring transient
0922: this .pageMapName = pageMap.getName();
0923: }
0924:
0925: /**
0926: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL OR
0927: * OVERRIDE.
0928: *
0929: * @param map
0930: */
0931: protected final void moveToPageMap(PageMap map) {
0932: // TODO post 1.2 shouldn't we remove this page from the pagemap/session
0933: // if it would be in there?
0934: // This should be done if the page was not cloned first, but shouldn't
0935: // be done if it was cloned..
0936: setPageMap(map);
0937: numericId = (short) map.nextId();
0938: }
0939:
0940: /**
0941: * Checks whether the hierarchy may be changed at all, and throws an
0942: * exception if this is not the case.
0943: *
0944: * @param component
0945: * the component which is about to be added or removed
0946: */
0947: private void checkHierarchyChange(Component component) {
0948: // Throw exception if modification is attempted during rendering
0949: if ((!component.isAuto()) && getFlag(FLAG_IS_RENDERING)) {
0950: throw new WicketRuntimeException(
0951: "Cannot modify component hierarchy during render phase");
0952: }
0953: }
0954:
0955: /**
0956: * Throw an exception if not all components rendered.
0957: *
0958: * @param renderedContainer
0959: * The page itself if it was a full page render or the container
0960: * that was rendered standalone
0961: */
0962: private final void checkRendering(
0963: final MarkupContainer renderedContainer) {
0964: // If the application wants component uses checked and
0965: // the response is not a redirect
0966: final IDebugSettings debugSettings = Application.get()
0967: .getDebugSettings();
0968: if (debugSettings.getComponentUseCheck()
0969: && !getResponse().isRedirect()) {
0970: final Count unrenderedComponents = new Count();
0971: final List unrenderedAutoComponents = new ArrayList();
0972: final StringBuffer buffer = new StringBuffer();
0973: renderedContainer.visitChildren(new IVisitor() {
0974: public Object component(final Component component) {
0975: // If component never rendered
0976: if (renderedComponents == null
0977: || !renderedComponents.contains(component)) {
0978: // If auto component ...
0979: if (component.isAuto()) {
0980: // Add to list of unrendered auto components to
0981: // delete below
0982: unrenderedAutoComponents.add(component);
0983: } else if (component.isVisibleInHierarchy()) {
0984: // Increase number of unrendered components
0985: unrenderedComponents.increment();
0986:
0987: // Add to explanatory string to buffer
0988: buffer.append(Integer
0989: .toString(unrenderedComponents
0990: .getCount())
0991: + ". " + component + "\n");
0992: } else {
0993: // if the component is not visible in hierarchy we
0994: // should not visit its children since they are also
0995: // not visible
0996: return CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
0997: }
0998: }
0999: return CONTINUE_TRAVERSAL;
1000: }
1001: });
1002:
1003: // Remove any unrendered auto components since versioning couldn't
1004: // do it. We can't remove the component in the above visitChildren
1005: // callback because we're traversing the list at that time.
1006: for (int i = 0; i < unrenderedAutoComponents.size(); i++) {
1007: ((Component) unrenderedAutoComponents.get(i)).remove();
1008: }
1009:
1010: // Throw exception if any errors were found
1011: if (unrenderedComponents.getCount() > 0) {
1012: // Get rid of set
1013: renderedComponents = null;
1014:
1015: // Throw exception
1016: throw new WicketRuntimeException(
1017: "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"
1018: + buffer.toString());
1019: }
1020: }
1021:
1022: // Get rid of set
1023: renderedComponents = null;
1024: }
1025:
1026: /**
1027: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL OR
1028: * OVERRIDE.
1029: *
1030: */
1031: private final void endVersion() {
1032: // If a new version was created
1033: if (getFlag(FLAG_NEW_VERSION)) {
1034: // We're done with this version
1035: if (versionManager != null) {
1036: versionManager.endVersion();
1037: }
1038:
1039: // Evict any page version(s) as need be
1040: getApplication().getSessionSettings()
1041: .getPageMapEvictionStrategy().evict(getPageMap());
1042:
1043: // Reset boolean for next request
1044: setFlag(FLAG_NEW_VERSION, false);
1045: }
1046: }
1047:
1048: /**
1049: * Initializes Page by adding it to the Session and initializing it.
1050: */
1051: private final void init() {
1052: final RequestCycle cycle = getRequestCycle();
1053: String pageMapName = null;
1054: if (cycle != null) {
1055: RequestParameters parameters = getRequest()
1056: .getRequestParameters();
1057: pageMapName = parameters.getPageMapName();
1058: }
1059: final PageMap pageMap = PageMap.forName(pageMapName);
1060: init(pageMap);
1061: }
1062:
1063: /**
1064: * Initializes Page by adding it to the Session and initializing it.
1065: *
1066: * @param pageMap
1067: * The page map to put this page in.
1068: */
1069: private final void init(final PageMap pageMap) {
1070: // Set the page map
1071: if (pageMap != null) {
1072: setPageMap(pageMap);
1073: } else {
1074: throw new IllegalStateException("PageMap cannot be null");
1075: }
1076:
1077: // Set the numeric id on this page
1078: setNumericId(getPageMap().nextId());
1079:
1080: // Set versioning of page based on default
1081: setVersioned(Application.get().getPageSettings()
1082: .getVersionPagesByDefault());
1083:
1084: // All Pages are born dirty so they get clustered right away
1085: dirty();
1086: }
1087:
1088: /**
1089: * For the given component, whether we may record changes.
1090: *
1091: * @param component
1092: * The component which is affected
1093: * @param parent
1094: * @return True if the change is okay to report
1095: */
1096: private final boolean mayTrackChangesFor(final Component component,
1097: MarkupContainer parent) {
1098: // Auto components do not participate in versioning since they are
1099: // added during the rendering phase (which is normally illegal).
1100: if (component.isAuto()
1101: || (parent == null && !component.isVersioned())
1102: || (parent != null && !parent.isVersioned())) {
1103: return false;
1104: } else {
1105: // the component is versioned... are we tracking changes at all?
1106: if (getFlag(FLAG_TRACK_CHANGES)) {
1107: // we are tracking changes... do we need to start new version?
1108: if (!getFlag(FLAG_NEW_VERSION)) {
1109: // if we have no version manager
1110: if (versionManager == null) {
1111: // then install a new version manager
1112: versionManager = newVersionManager();
1113: }
1114:
1115: // start a new version
1116: versionManager.beginVersion();
1117: setFlag(FLAG_NEW_VERSION, true);
1118: }
1119:
1120: // return true as we are ready for versioning
1121: return true;
1122: }
1123:
1124: // we are not tracking changes or the component not versioned
1125: return false;
1126: }
1127: }
1128:
1129: }
|