WICKET-6503 beforeRender clean-up - prepare all components before writing them to Ajax response - simplified delayed preparation of feedbacks - clean-up internal component API an flags
this closes #250 Project: http://git-wip-us.apache.org/repos/asf/wicket/repo Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/810185e6 Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/810185e6 Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/810185e6 Branch: refs/heads/WICKET-6523-ajax-timers Commit: 810185e66b574b66a5f2039f17f6e32ffdf47cea Parents: da3b69f Author: Sven Meier <svenme...@apache.org> Authored: Thu Dec 14 07:55:53 2017 +0100 Committer: Sven Meier <svenme...@apache.org> Committed: Thu Jan 25 17:13:01 2018 +0100 ---------------------------------------------------------------------- .../main/java/org/apache/wicket/Component.java | 332 ++++++------------- .../java/org/apache/wicket/MarkupContainer.java | 22 -- .../src/main/java/org/apache/wicket/Page.java | 36 +- .../ComponentRenderingRequestHandler.java | 9 +- .../handler/PageAndComponentProvider.java | 2 +- .../core/util/string/ComponentRenderer.java | 3 +- .../apache/wicket/feedback/FeedbackDelay.java | 119 +++++++ .../org/apache/wicket/feedback/IFeedback.java | 5 +- .../apache/wicket/page/PartialPageUpdate.java | 74 ++++- .../wicket/page/XmlPartialPageUpdate.java | 50 +-- .../wicket/feedback/FeedbackRenderTest.java | 22 +- .../apache/wicket/feedback/FeedbacksPage.html | 16 +- .../apache/wicket/feedback/FeedbacksPage.java | 31 +- .../markup/html/internal/EnclosurePage_1.java | 50 ++- .../markup/html/internal/EnclosureTest.java | 4 + 15 files changed, 422 insertions(+), 353 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/wicket/blob/810185e6/wicket-core/src/main/java/org/apache/wicket/Component.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/Component.java b/wicket-core/src/main/java/org/apache/wicket/Component.java index 7240a13..fc32b09 100644 --- a/wicket-core/src/main/java/org/apache/wicket/Component.java +++ b/wicket-core/src/main/java/org/apache/wicket/Component.java @@ -17,11 +17,11 @@ package org.apache.wicket; import java.io.Serializable; -import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.Optional; import org.apache.wicket.ajax.IAjaxRegionMarkupIdProvider; import org.apache.wicket.application.IComponentInstantiationListener; @@ -40,6 +40,7 @@ import org.apache.wicket.event.Broadcast; import org.apache.wicket.event.IEvent; import org.apache.wicket.event.IEventSink; import org.apache.wicket.event.IEventSource; +import org.apache.wicket.feedback.FeedbackDelay; import org.apache.wicket.feedback.FeedbackMessage; import org.apache.wicket.feedback.FeedbackMessages; import org.apache.wicket.feedback.IFeedback; @@ -86,7 +87,6 @@ import org.apache.wicket.util.lang.Classes; import org.apache.wicket.util.string.PrependingStringBuffer; import org.apache.wicket.util.string.Strings; import org.apache.wicket.util.value.ValueMap; -import org.apache.wicket.util.visit.IVisit; import org.apache.wicket.util.visit.IVisitFilter; import org.apache.wicket.util.visit.IVisitor; import org.apache.wicket.util.visit.Visit; @@ -312,12 +312,6 @@ public abstract class Component } }; - /** an unused flag */ - private static final int FLAG_UNUSED0 = 0x20000000; - private static final int FLAG_UNUSED1 = 0x800000; - private static final int FLAG_UNUSED2 = 0x1000000; - private static final int FLAG_UNUSED3 = 0x10000000; - /** True when a component is being auto-added */ private static final int FLAG_AUTO = 0x0001; @@ -389,17 +383,6 @@ public abstract class Component */ private static final int FLAG_MODEL_SET = 0x100000; - /** True when a component is being removed from the hierarchy */ - protected static final int FLAG_REMOVING_FROM_HIERARCHY = 0x200000; - - /** - * Flag that makes we are in before-render callback phase Set after component.onBeforeRender is - * invoked (right before invoking beforeRender on children) - */ - protected static final int FLAG_RENDERING = 0x2000000; - protected static final int FLAG_PREPARED_FOR_RENDER = 0x4000000; - protected static final int FLAG_AFTER_RENDERING = 0x8000000; - /** * Flag that restricts visibility of a component when set to true. This is usually used when a * component wants to restrict visibility of another component. Calling @@ -408,8 +391,6 @@ public abstract class Component */ private static final int FLAG_VISIBILITY_ALLOWED = 0x40000000; - private static final int FLAG_DETACHING = 0x80000000; - /** * The name of attribute that will hold markup id */ @@ -447,6 +428,16 @@ public abstract class Component private static final short RFLAG_INITIALIZE_SUPER_CALL_VERIFIED = 0x40; protected static final short RFLAG_CONTAINER_DEQUEING = 0x80; private static final short RFLAG_ON_RE_ADD_SUPER_CALL_VERIFIED = 0x100; + /** + * Flag that makes we are in before-render callback phase Set after component.onBeforeRender is + * invoked (right before invoking beforeRender on children) + */ + private static final short RFLAG_RENDERING = 0x200; + private static final short RFLAG_PREPARED_FOR_RENDER = 0x400; + private static final short RFLAG_AFTER_RENDERING = 0x800; + private static final short RFLAG_DETACHING = 0x1000; + /** True when a component is being removed from the hierarchy */ + private static final short RFLAG_REMOVING_FROM_HIERARCHY = 0x2000; /** * Flags that only keep their value during the request. Useful for cache markers, etc. At the @@ -789,7 +780,7 @@ public abstract class Component } /** - * Called once per request on components before they are about to be rendered. This method + * Called on all components before any component is rendered. This method * should be used to configure such things as visibility and enabled flags. * <p> * Overrides must call {@code super.onConfigure()}, usually before any other code @@ -857,7 +848,7 @@ public abstract class Component } /** - * THIS METHOD IS NOT PART OF THE PUBLIC API, DO NOT CALL IT + * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT! * * Used to call {@link #onInitialize()} */ @@ -903,22 +894,19 @@ public abstract class Component } /** - * Called on every component after the page is rendered. It will call onAfterRender for it self - * and its children. + * Called on every component after the page is rendered. Calls hook {@link #onAfterRender()}. */ - public final void afterRender() + final void afterRender() { + setRequestFlag(RFLAG_PREPARED_FOR_RENDER, false); + try { - setFlag(FLAG_AFTER_RENDERING, true); - - // always detach children because components can be attached - // independently of their parents - onAfterRenderChildren(); + setRequestFlag(RFLAG_AFTER_RENDERING, true); onAfterRender(); getApplication().getComponentOnAfterRenderListeners().onAfterRender(this); - if (getFlag(FLAG_AFTER_RENDERING)) + if (getRequestFlag(RFLAG_AFTER_RENDERING)) { throw new IllegalStateException(Component.class.getName() + " has not been properly detached. Something in the hierarchy of " + @@ -929,91 +917,53 @@ public abstract class Component finally { // this flag must always be set to false. - markRendering(false); + setRequestFlag(RFLAG_RENDERING, false); } } - private void internalBeforeRender() - { - configure(); - - if ((determineVisibility()) && !getFlag(FLAG_RENDERING) && - !getFlag(FLAG_PREPARED_FOR_RENDER)) - { - setRequestFlag(RFLAG_BEFORE_RENDER_SUPER_CALL_VERIFIED, false); - - Application application = getApplication(); - application.getComponentPreOnBeforeRenderListeners().onBeforeRender(this); - - onBeforeRender(); - application.getComponentPostOnBeforeRenderListeners().onBeforeRender(this); - - if (!getRequestFlag(RFLAG_BEFORE_RENDER_SUPER_CALL_VERIFIED)) - { - throw new IllegalStateException(Component.class.getName() + - " has not been properly rendered. Something in the hierarchy of " + - getClass().getName() + - " has not called super.onBeforeRender() in the override of onBeforeRender() method"); - } - } - } - - /** - * We need to postpone calling beforeRender() on components that implement {@link IFeedback}, to - * be sure that all other component's beforeRender() has been already called, so that IFeedbacks - * can collect all feedback messages. This is the key under list of postponed {@link IFeedback} - * is stored to request cycle metadata. The List is then iterated over in - * {@link #prepareForRender()} after calling {@link #beforeRender()}, to initialize postponed - * components. - */ - private static final MetaDataKey<List<Component>> FEEDBACK_LIST = new MetaDataKey<List<Component>>() - { - private static final long serialVersionUID = 1L; - }; - /** - * Called for every component when the page is getting to be rendered. it will call - * {@link #configure()} and {@link #onBeforeRender()} for this component and all the child - * components + * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT! + * + * Called on all components before any component is rendered. Calls hooks + * {@link #configure()} and (if visible) {@link #onBeforeRender()} + * and delegates to {@link #beforeRender()} of all child components. */ public final void beforeRender() { if (this instanceof IFeedback) { - // this component is a feedback. Feedback must be initialized last, so that - // they can collect messages from other components - List<Component> feedbacks = getRequestCycle().getMetaData(FEEDBACK_LIST); - if (feedbacks == null) - { - feedbacks = new ArrayList<>(); - getRequestCycle().setMetaData(FEEDBACK_LIST, feedbacks); + Optional<FeedbackDelay> delay = FeedbackDelay.get(getRequestCycle()); + if (delay.isPresent()) { + delay.get().postpone((IFeedback)this); + return; } + } - if (this instanceof MarkupContainer) - { - ((MarkupContainer)this).visitChildren(IFeedback.class, - new IVisitor<Component, Void>() - { - @Override - public void component(Component feedback, IVisit<Void> visit) - { - feedback.beforeRender(); - - // don't need to go deeper, - // as the feedback will visit its children on its own - visit.dontGoDeeper(); - } - }); - } + configure(); - if (!feedbacks.contains(this)) - { - feedbacks.add(this); - } - } - else + if ((determineVisibility()) && !getRequestFlag(RFLAG_RENDERING) && + !getRequestFlag(RFLAG_PREPARED_FOR_RENDER)) { - internalBeforeRender(); + try { + setRequestFlag(RFLAG_BEFORE_RENDER_SUPER_CALL_VERIFIED, false); + + Application application = getApplication(); + application.getComponentPreOnBeforeRenderListeners().onBeforeRender(this); + + onBeforeRender(); + application.getComponentPostOnBeforeRenderListeners().onBeforeRender(this); + + if (!getRequestFlag(RFLAG_BEFORE_RENDER_SUPER_CALL_VERIFIED)) + { + throw new IllegalStateException(Component.class.getName() + + " has not been properly rendered. Something in the hierarchy of " + + getClass().getName() + + " has not called super.onBeforeRender() in the override of onBeforeRender() method"); + } + } catch (RuntimeException ex) { + setRequestFlag(RFLAG_PREPARED_FOR_RENDER, false); + throw ex; + } } } @@ -1072,9 +1022,6 @@ public abstract class Component } /** - * - * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT! - * * Called after the {@link #onConfigure()}, but before {@link #onBeforeRender()} */ void internalOnAfterConfigure() @@ -1134,10 +1081,10 @@ public abstract class Component */ final void internalOnRemove() { - setFlag(FLAG_REMOVING_FROM_HIERARCHY, true); + setRequestFlag(RFLAG_REMOVING_FROM_HIERARCHY, true); onRemove(); setFlag(FLAG_REMOVED, true); - if (getFlag(FLAG_REMOVING_FROM_HIERARCHY)) + if (getRequestFlag(RFLAG_REMOVING_FROM_HIERARCHY)) { throw new IllegalStateException(Component.class.getName() + " has not been properly removed from hierachy. Something in the hierarchy of " + @@ -1157,9 +1104,9 @@ public abstract class Component { try { - setFlag(FLAG_DETACHING, true); + setRequestFlag(RFLAG_DETACHING, true); onDetach(); - if (getFlag(FLAG_DETACHING)) + if (getRequestFlag(RFLAG_DETACHING)) { throw new IllegalStateException(Component.class.getName() + " has not been properly detached. Something in the hierarchy of " + @@ -1469,7 +1416,7 @@ public abstract class Component } /** - * THIS IS WICKET INTERNAL ONLY. DO NOT USE IT. + * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT! * * Get a copy of the markup's attributes which are associated with the component. * <p> @@ -2170,23 +2117,6 @@ public abstract class Component } /** - * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT! - * - * Sets the RENDERING flag and removes the PREPARED_FOR_RENDER flag on component and it's - * children. - * - * @param setRenderingFlag - * if this is false only the PREPARED_FOR_RENDER flag is removed from component, the - * RENDERING flag is not set. - * - * @see #internalPrepareForRender(boolean) - */ - public final void markRendering(boolean setRenderingFlag) - { - internalMarkRendering(setRenderingFlag); - } - - /** * Called to indicate that the model content for this component has been changed */ public final void modelChanged() @@ -2215,62 +2145,6 @@ public abstract class Component } /** - * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT! - * <p> - * Prepares the component and it's children for rendering. On whole page render this method must - * be called on the page. On AJAX request, this method must be called on the updated component. - * - * @param setRenderingFlag - * Whether to set the rendering flag. This must be true if the page is about to be - * rendered. However, there are usecases to call this method without an immediate - * render (e.g. on stateless listener request target to build the component - * hierarchy), in that case setRenderingFlag should be false. - */ - public void internalPrepareForRender(boolean setRenderingFlag) - { - beforeRender(); - - if (setRenderingFlag) - { - // only process feedback panel when we are about to be rendered. - // setRenderingFlag is false in case prepareForRender is called only to build component - // hierarchy (i.e. in BookmarkableListenerRequestHandler). - // prepareForRender(true) is always called before the actual rendering is done so - // that's where feedback panels gather the messages - - List<Component> feedbacks = getRequestCycle().getMetaData(FEEDBACK_LIST); - if (feedbacks != null) - { - // iterate over a copy because a IFeedback may add more IFeedback children -// (WICKET-4687) - Component[] feedbacksCopy = feedbacks.toArray(new Component[feedbacks.size()]); - for (Component feedback : feedbacksCopy) - { - // render it only if it is still in the page hierarchy (WICKET-4895) - if (feedback.findPage() != null) - { - feedback.internalBeforeRender(); - } - } - } - getRequestCycle().setMetaData(FEEDBACK_LIST, null); - } - - markRendering(setRenderingFlag); - } - - /** - * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT! - * - * Prepares the component and it's children for rendering. On whole page render this method must - * be called on the page. On AJAX request, this method must be called on updated component. - */ - public final void prepareForRender() - { - internalPrepareForRender(true); - } - - /** * Redirects browser to an intermediate page such as a sign-in page. The current request's url * is saved for future use by method continueToOriginalDestination(); Only use this method when * you plan to continue to the current url at some later time; otherwise just use @@ -2303,24 +2177,42 @@ public abstract class Component parent.remove(this); } + /** + * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT! + * <p> + * Renders this component as a part of a response - the caller has to + * make sure that this component is prepared for render. + * + * @see #beforeRender() + */ + public final void renderPart() { + Page page = getPage(); + + page.startComponentRender(this); + + render(); + + page.endComponentRender(this); + } /** - * Render the Component. + * Render this component and all its children. Always calls hook {@link #onAfterRender()} + * regardless of any exception. */ public final void render() { - RuntimeException exception = null; + if (isAuto()) + { + // auto components are prepared when rendered + beforeRender(); + } + // Do the render + RuntimeException exception = null; try { - // Invoke prepareForRender only if this is the root component to be rendered - MarkupContainer parent = getParent(); - if ((parent == null) || (parent.getFlag(FLAG_RENDERING) == false) || isAuto()) - { - internalPrepareForRender(true); - } - - // Do the render + setRequestFlag(RFLAG_RENDERING, true); + internalRender(); } catch (final RuntimeException ex) @@ -2367,9 +2259,6 @@ public abstract class Component // MarkupStream is an Iterator for the markup MarkupStream markupStream = new MarkupStream(markup); - // Flag: we started the render process - markRendering(true); - MarkupElement elem = markup.get(0); if (elem instanceof ComponentTag) { @@ -2513,13 +2402,13 @@ public abstract class Component /** - * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT. + * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT! * <p> * Renders the component at the current position in the given markup stream. The method * onComponentTag() is called to allow the component to mutate the start tag. The method * onComponentTagBody() is then called to permit the component to render its body. */ - public final void internalRenderComponent() + protected final void internalRenderComponent() { final IMarkupFragment markup = getMarkup(); if (markup == null) @@ -2698,7 +2587,7 @@ public abstract class Component } /** - * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT. + * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT! * * Print to the web response what ever the component wants to contribute to the head section. * Make sure that all attached behaviors are asked as well. @@ -3645,7 +3534,7 @@ public abstract class Component protected void checkHierarchyChange(final Component component) { // Throw exception if modification is attempted during rendering - if (getFlag(FLAG_RENDERING) && !component.isAuto()) + if (getRequestFlag(RFLAG_RENDERING) && !component.isAuto()) { throw new WicketRuntimeException( "Cannot modify component hierarchy after render phase has started (page version cant change then anymore)"); @@ -3876,15 +3765,16 @@ public abstract class Component } /** - * Called just after a component is rendered. + * Called immediately after a component and all its children have been rendered, + * regardless of any exception. */ protected void onAfterRender() { - setFlag(FLAG_AFTER_RENDERING, false); + setRequestFlag(RFLAG_AFTER_RENDERING, false); } /** - * Called just before a component is rendered only if the component is visible. + * Called on all visible components before any component is rendered. * <p> * <strong>NOTE</strong>: If you override this, you *must* call super.onBeforeRender() within * your implementation. @@ -3900,7 +3790,6 @@ public abstract class Component */ protected void onBeforeRender() { - setFlag(FLAG_PREPARED_FOR_RENDER, true); onBeforeRenderChildren(); setRequestFlag(RFLAG_BEFORE_RENDER_SUPER_CALL_VERIFIED, true); } @@ -3958,7 +3847,7 @@ public abstract class Component */ protected void onDetach() { - setFlag(FLAG_DETACHING, false); + setRequestFlag(RFLAG_DETACHING, false); } /** @@ -3969,7 +3858,7 @@ public abstract class Component */ protected void onRemove() { - setFlag(FLAG_REMOVING_FROM_HIERARCHY, false); + setRequestFlag(RFLAG_REMOVING_FROM_HIERARCHY, false); } /** @@ -4161,14 +4050,12 @@ public abstract class Component } /** - * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT! - * * @param flag * The flag to set * @param set * True to turn the flag on, false to turn it off */ - protected final Component setRequestFlag(final short flag, final boolean set) + final Component setRequestFlag(final short flag, final boolean set) { if (set) { @@ -4245,18 +4132,6 @@ public abstract class Component } /** - * @param setRenderingFlag - * rendering flag - */ - void internalMarkRendering(boolean setRenderingFlag) - { - // WICKET-5460 no longer prepared for render - setFlag(FLAG_PREPARED_FOR_RENDER, false); - - setFlag(FLAG_RENDERING, setRenderingFlag); - } - - /** * @return True if this component or any of its parents is in auto-add mode */ public final boolean isAuto() @@ -4278,14 +4153,7 @@ public abstract class Component */ boolean isPreparedForRender() { - return getFlag(FLAG_PREPARED_FOR_RENDER); - } - - /** - * - */ - protected void onAfterRenderChildren() - { + return getRequestFlag(RFLAG_PREPARED_FOR_RENDER); } /** @@ -4474,7 +4342,7 @@ public abstract class Component */ public final boolean isRendering() { - return getFlag(FLAG_RENDERING); + return getRequestFlag(RFLAG_RENDERING); } /** http://git-wip-us.apache.org/repos/asf/wicket/blob/810185e6/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java b/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java index 36b7684..aad0aac 100644 --- a/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java +++ b/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java @@ -1694,17 +1694,6 @@ public abstract class MarkupContainer extends Component implements Iterable<Comp } } - @Override - void internalMarkRendering(boolean setRenderingFlag) - { - super.internalMarkRendering(setRenderingFlag); - - for (Component child : this) - { - child.internalMarkRendering(setRenderingFlag); - } - } - /** * @return a copy of the children array. */ @@ -1793,17 +1782,6 @@ public abstract class MarkupContainer extends Component implements Iterable<Comp } @Override - protected void onAfterRenderChildren() - { - for (Component child : this) - { - // set RENDERING_FLAG to false for auto-component's children (like Enclosure) - child.markRendering(false); - } - super.onAfterRenderChildren(); - } - - @Override protected void onDetach() { super.onDetach(); http://git-wip-us.apache.org/repos/asf/wicket/blob/810185e6/wicket-core/src/main/java/org/apache/wicket/Page.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/Page.java b/wicket-core/src/main/java/org/apache/wicket/Page.java index fb4f04d..9b8dc7f 100644 --- a/wicket-core/src/main/java/org/apache/wicket/Page.java +++ b/wicket-core/src/main/java/org/apache/wicket/Page.java @@ -24,6 +24,7 @@ import java.util.Set; import org.apache.wicket.authorization.UnauthorizedActionException; import org.apache.wicket.core.util.lang.WicketObjects; +import org.apache.wicket.feedback.FeedbackDelay; import org.apache.wicket.markup.MarkupException; import org.apache.wicket.markup.MarkupStream; import org.apache.wicket.markup.MarkupType; @@ -229,14 +230,15 @@ public abstract class Page extends MarkupContainer } @Override - public void internalPrepareForRender(boolean setRenderingFlag) + protected void onConfigure() { if (!isInitialized()) { // initialize the page if not yet initialized internalInitialize(); } - super.internalPrepareForRender(setRenderingFlag); + + super.onConfigure(); } /** @@ -300,14 +302,14 @@ public abstract class Page extends MarkupContainer } /** - * 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 <code> + * This method is called when a component was rendered as a part. If it is a <code> * MarkupContainer</code> then the rendering for that container is checked. * * @param component + * + * @see Component#renderPart() */ - public final void endComponentRender(Component component) + final void endComponentRender(Component component) { if (component instanceof MarkupContainer) { @@ -527,14 +529,13 @@ public abstract class Page extends MarkupContainer } /** - * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL. - * - * This method is called when a component will be rendered standalone. + * This method is called when a component will be rendered as a part. * * @param component * + * @see Component#renderPart() */ - public final void startComponentRender(Component component) + final void startComponentRender(Component component) { renderedComponents = null; } @@ -984,9 +985,17 @@ public abstract class Page extends MarkupContainer try { ++renderCount; - render(); - // stateless = null; + FeedbackDelay delay = new FeedbackDelay(getRequestCycle()); + try { + beforeRender(); + + delay.beforeRender(); + } finally { + delay.release(); + } + + render(); } finally { @@ -1004,5 +1013,4 @@ public abstract class Page extends MarkupContainer { return renderedComponents != null && renderedComponents.contains(component); } - -} +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/810185e6/wicket-core/src/main/java/org/apache/wicket/core/request/handler/ComponentRenderingRequestHandler.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/handler/ComponentRenderingRequestHandler.java b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/ComponentRenderingRequestHandler.java index 1636707..e6d5874 100644 --- a/wicket-core/src/main/java/org/apache/wicket/core/request/handler/ComponentRenderingRequestHandler.java +++ b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/ComponentRenderingRequestHandler.java @@ -64,13 +64,8 @@ public class ComponentRenderingRequestHandler implements IComponentRequestHandle response.disableCaching(); } - Page page = component.getPage(); - - page.startComponentRender(component); - - component.render(); - - page.endComponentRender(component); + component.beforeRender(); + component.renderPart(); } @Override http://git-wip-us.apache.org/repos/asf/wicket/blob/810185e6/wicket-core/src/main/java/org/apache/wicket/core/request/handler/PageAndComponentProvider.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/handler/PageAndComponentProvider.java b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/PageAndComponentProvider.java index 8bdfa93..de59ecc 100644 --- a/wicket-core/src/main/java/org/apache/wicket/core/request/handler/PageAndComponentProvider.java +++ b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/PageAndComponentProvider.java @@ -169,7 +169,7 @@ public class PageAndComponentProvider extends PageProvider implements IPageAndCo { Page p = (Page)page; p.internalInitialize(); - p.internalPrepareForRender(false); + p.beforeRender(); component = page.get(componentPath); } } http://git-wip-us.apache.org/repos/asf/wicket/blob/810185e6/wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java b/wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java index d583057..9104eb9 100644 --- a/wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java +++ b/wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java @@ -416,7 +416,8 @@ public class ComponentRenderer RenderPage page = new RenderPage(component); page.internalInitialize(); - component.render(); + component.beforeRender(); + component.renderPart(); } finally { http://git-wip-us.apache.org/repos/asf/wicket/blob/810185e6/wicket-core/src/main/java/org/apache/wicket/feedback/FeedbackDelay.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/feedback/FeedbackDelay.java b/wicket-core/src/main/java/org/apache/wicket/feedback/FeedbackDelay.java new file mode 100644 index 0000000..4d0ef30 --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/feedback/FeedbackDelay.java @@ -0,0 +1,119 @@ +/* + * 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.feedback; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.apache.wicket.Component; +import org.apache.wicket.MetaDataKey; +import org.apache.wicket.Page; +import org.apache.wicket.WicketRuntimeException; +import org.apache.wicket.request.cycle.RequestCycle; + +/** + * Postpone calling {@link IFeedback#beforeRender()} after other components. + * <p> + * This gives other {@link Component#beforeRender()} the possibility to report feedbacks, + * which can then be collected by {@link IFeedback}s afterwards. + */ +public class FeedbackDelay implements Serializable +{ + private static final MetaDataKey<FeedbackDelay> KEY = new MetaDataKey<FeedbackDelay>() + { + private static final long serialVersionUID = 1L; + }; + + private List<IFeedback> feedbacks = new ArrayList<>(); + + private RequestCycle cycle; + + /** + * Delay all feedbacks for the given cycle. + * <p> + * All postponed feedbacks will be prepared for render with {@link #beforeRender()}. + * + * @param cycle + * request cycle + */ + public FeedbackDelay(RequestCycle cycle) { + if (get(cycle).isPresent()) { + throw new WicketRuntimeException("feedbacks are already delayed"); + } + + cycle.setMetaData(KEY, this); + + this.cycle = cycle; + } + + /** + * Get the current delay. + * + * @param cycle + * @return optional delay + */ + public static Optional<FeedbackDelay> get(RequestCycle cycle) { + return Optional.ofNullable(cycle.getMetaData(KEY)); + } + + /** + * Postpone {@link Component#beforeRender()} on the given feedback. + * + * @param feedback + * @return + */ + public FeedbackDelay postpone(IFeedback feedback) { + feedbacks.add(feedback); + + return this; + } + + /** + * Prepares all postponed feedbacks for render. + * + * @see IFeedback#beforeRender() + */ + public void beforeRender() { + cycle.setMetaData(KEY, null); + cycle = null; + + for (IFeedback feedback : feedbacks) + { + if (feedback instanceof Component) { + Component component = (Component)feedback; + + // render only if it is still in the page hierarchy (WICKET-4895) + if (component.findParent(Page.class) == null) + { + continue; + } + } + + feedback.beforeRender(); + } + } + + public void release() { + if (cycle != null) { + cycle.setMetaData(KEY, null); + cycle = null; + feedbacks.clear(); + } + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/810185e6/wicket-core/src/main/java/org/apache/wicket/feedback/IFeedback.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/feedback/IFeedback.java b/wicket-core/src/main/java/org/apache/wicket/feedback/IFeedback.java index a1bd08c..6e0085e 100644 --- a/wicket-core/src/main/java/org/apache/wicket/feedback/IFeedback.java +++ b/wicket-core/src/main/java/org/apache/wicket/feedback/IFeedback.java @@ -20,12 +20,13 @@ package org.apache.wicket.feedback; * Interface for components that present some kind of feedback to the user, normally based on the * feedback messages attached to various components on a given page. * - * This is basically a marker interface that tells Wicket that this component's onBeforeRender - * method must be called after all non feedback components have been initialized. + * This is tells Wicket that a component's {@link Component#beforeRender()} must be called after all non + * feedback components have been initialized. * * @author Jonathan Locke * @author Eelco Hillenius */ public interface IFeedback { + void beforeRender(); } http://git-wip-us.apache.org/repos/asf/wicket/blob/810185e6/wicket-core/src/main/java/org/apache/wicket/page/PartialPageUpdate.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/page/PartialPageUpdate.java b/wicket-core/src/main/java/org/apache/wicket/page/PartialPageUpdate.java index 2bef5e2..caec116 100644 --- a/wicket-core/src/main/java/org/apache/wicket/page/PartialPageUpdate.java +++ b/wicket-core/src/main/java/org/apache/wicket/page/PartialPageUpdate.java @@ -28,6 +28,7 @@ import javax.servlet.http.Cookie; import org.apache.wicket.Component; import org.apache.wicket.Page; +import org.apache.wicket.feedback.FeedbackDelay; import org.apache.wicket.markup.head.HeaderItem; import org.apache.wicket.markup.head.IHeaderResponse; import org.apache.wicket.markup.head.IWrappedHeaderItem; @@ -238,19 +239,36 @@ public abstract class PartialPageUpdate { componentsFrozen = true; - // process component markup - for (Map.Entry<String, Component> stringComponentEntry : markupIdToComponent.entrySet()) - { - final Component component = stringComponentEntry.getValue(); - - if (!containsAncestorFor(component)) + List<Component> prepared = new ArrayList<>(markupIdToComponent.size()); + + // prepare components + FeedbackDelay delay = new FeedbackDelay(RequestCycle.get()); + try { + for (Component component : markupIdToComponent.values()) { - writeComponent(response, component.getAjaxRegionMarkupId(), component, encoding); + if (!containsAncestorFor(component)) + { + prepareComponent(component); + prepared.add(component); + } } + + // .. now prepare all postponed feedbacks + delay.beforeRender(); + } finally { + delay.release(); + } + + // write components + for (Component component : prepared) + { + writeComponent(response, component.getAjaxRegionMarkupId(), component, encoding); } if (header != null) { + RequestCycle cycle = RequestCycle.get(); + // some header responses buffer all calls to render*** until close is called. // when they are closed, they do something (i.e. aggregate all JS resource urls to a // single url), and then "flush" (by writing to the real response) before closing. @@ -258,14 +276,14 @@ public abstract class PartialPageUpdate // tag, which we do here: headerRendering = true; // save old response, set new - Response oldResponse = RequestCycle.get().setResponse(headerBuffer); + Response oldResponse = cycle.setResponse(headerBuffer); headerBuffer.reset(); // now, close the response (which may render things) header.getHeaderResponse().close(); // revert to old response - RequestCycle.get().setResponse(oldResponse); + cycle.setResponse(oldResponse); // write the XML tags and we're done writeHeaderContribution(response); @@ -274,6 +292,44 @@ public abstract class PartialPageUpdate } /** + * Prepare a single component + * + * @param component + * the component to prepare + */ + protected void prepareComponent(Component component) + { + if (component.getRenderBodyOnly() == true) + { + throw new IllegalStateException( + "A partial update is not possible for a component that has renderBodyOnly enabled. Component: " + + component.toString()); + } + + component.setOutputMarkupId(true); + + // Initialize temporary variables + final Page page = component.findParent(Page.class); + if (page == null) + { + // dont throw an exception but just ignore this component, somehow + // it got removed from the page. + LOG.warn("Component '{}' not rendered because it was already removed from page", component); + return; + } + + try + { + component.beforeRender(); + } + catch (RuntimeException e) + { + bodyBuffer.reset(); + throw e; + } + } + + /** * Writes a single component * * @param response http://git-wip-us.apache.org/repos/asf/wicket/blob/810185e6/wicket-core/src/main/java/org/apache/wicket/page/XmlPartialPageUpdate.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/page/XmlPartialPageUpdate.java b/wicket-core/src/main/java/org/apache/wicket/page/XmlPartialPageUpdate.java index 28c2ffa..5f78ef3 100644 --- a/wicket-core/src/main/java/org/apache/wicket/page/XmlPartialPageUpdate.java +++ b/wicket-core/src/main/java/org/apache/wicket/page/XmlPartialPageUpdate.java @@ -59,29 +59,11 @@ public class XmlPartialPageUpdate extends PartialPageUpdate response.write("\"?>"); response.write(START_ROOT_ELEMENT); } - + @Override protected void writeComponent(Response response, String markupId, Component component, String encoding) { - if (component.getRenderBodyOnly() == true) - { - throw new IllegalStateException( - "A partial update is not possible for a component that has renderBodyOnly enabled. Component: " + - component.toString()); - } - - component.setOutputMarkupId(true); - - // Initialize temporary variables final Page page = component.findParent(Page.class); - if (page == null) - { - // dont throw an exception but just ignore this component, somehow - // it got removed from the page. - LOG.warn("Component '{}' with markupid: '{}' not rendered because it was already removed from page", - component, markupId); - return; - } // substitute our encoding response for the old one so we can capture // component's markup in a manner safe for transport inside CDATA block @@ -89,42 +71,20 @@ public class XmlPartialPageUpdate extends PartialPageUpdate try { + // render any associated headers of the component + writeHeaderContribution(response, component); + bodyBuffer.reset(); - page.startComponentRender(component); - - try - { - component.prepareForRender(); - - // render any associated headers of the component - writeHeaderContribution(response, component); - } - catch (RuntimeException e) - { - try - { - component.afterRender(); - } - catch (RuntimeException e2) - { - // ignore this one could be a result off. - } - bodyBuffer.reset(); - throw e; - } - try { - component.render(); + component.renderPart(); } catch (RuntimeException e) { bodyBuffer.reset(); throw e; } - - page.endComponentRender(component); } finally { http://git-wip-us.apache.org/repos/asf/wicket/blob/810185e6/wicket-core/src/test/java/org/apache/wicket/feedback/FeedbackRenderTest.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/test/java/org/apache/wicket/feedback/FeedbackRenderTest.java b/wicket-core/src/test/java/org/apache/wicket/feedback/FeedbackRenderTest.java index 8f0f7cb..c12fb2a 100644 --- a/wicket-core/src/test/java/org/apache/wicket/feedback/FeedbackRenderTest.java +++ b/wicket-core/src/test/java/org/apache/wicket/feedback/FeedbackRenderTest.java @@ -36,7 +36,25 @@ public class FeedbackRenderTest extends WicketTestCase tester.startPage(page); - // non-IFeedback first, then IFeedback from nested to top - assertEquals("|id4|id3|id2|id1", page.onBeforeRenderOrder.toString()); + // non-IFeedback first, then IFeedback top-down + assertEquals("|id4|id1|id2|id3", page.onBeforeRenderOrder.toString()); + } + + /** + * @throws Exception + */ + @Test + public void testAjax() throws Exception + { + final FeedbacksPage page = new FeedbacksPage(); + + tester.startPage(page); + + page.onBeforeRenderOrder.setLength(0); + + tester.executeAjaxEvent(page.getAjaxLink(), "click"); + + // non-IFeedback first, then IFeedback top-down + assertEquals("|id4|id1|id2|id3", page.onBeforeRenderOrder.toString()); } } http://git-wip-us.apache.org/repos/asf/wicket/blob/810185e6/wicket-core/src/test/java/org/apache/wicket/feedback/FeedbacksPage.html ---------------------------------------------------------------------- diff --git a/wicket-core/src/test/java/org/apache/wicket/feedback/FeedbacksPage.html b/wicket-core/src/test/java/org/apache/wicket/feedback/FeedbacksPage.html index 8d9000f..bc6c4f1 100644 --- a/wicket-core/src/test/java/org/apache/wicket/feedback/FeedbacksPage.html +++ b/wicket-core/src/test/java/org/apache/wicket/feedback/FeedbacksPage.html @@ -14,14 +14,20 @@ --> <html> <body> -<span wicket:id="id1"> - <span wicket:id="id2"> - <span wicket:id="id3"> +<span wicket:id="feedbacks"> + <span wicket:id="id1"> + <span wicket:id="id2"> + <span wicket:id="id3"> + </span> </span> - </span> -</span> + </span> +</span> <span wicket:id="id4"> </span> + +<span wicket:id="ajax"> +</span> + </body> </html> http://git-wip-us.apache.org/repos/asf/wicket/blob/810185e6/wicket-core/src/test/java/org/apache/wicket/feedback/FeedbacksPage.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/test/java/org/apache/wicket/feedback/FeedbacksPage.java b/wicket-core/src/test/java/org/apache/wicket/feedback/FeedbacksPage.java index 840495c..50c920f 100644 --- a/wicket-core/src/test/java/org/apache/wicket/feedback/FeedbacksPage.java +++ b/wicket-core/src/test/java/org/apache/wicket/feedback/FeedbacksPage.java @@ -16,6 +16,8 @@ */ package org.apache.wicket.feedback; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.WebPage; @@ -28,13 +30,18 @@ public class FeedbacksPage extends WebPage public final StringBuilder onBeforeRenderOrder = new StringBuilder(); + private AjaxLink<Void> ajaxLink; + /** */ public FeedbacksPage() { - + WebMarkupContainer feedbacks = new WebMarkupContainer("feedbacks"); + feedbacks.setOutputMarkupId(true); + add(feedbacks); + Impl impl1 = new FeedbackImpl("id1"); - add(impl1); + feedbacks.add(impl1); Impl impl2 = new FeedbackImpl("id2"); impl1.add(impl2); @@ -43,7 +50,25 @@ public class FeedbacksPage extends WebPage impl2.add(impl3); Impl impl4 = new Impl("id4"); + impl4.setOutputMarkupId(true); add(impl4); + + ajaxLink = new AjaxLink<Void>("ajax") + { + @Override + public void onClick(AjaxRequestTarget target) + { + // feedbacks added first, but should be prepared last + target.add(feedbacks); + target.add(impl4); + } + }; + add(ajaxLink); + } + + public AjaxLink<Void> getAjaxLink() + { + return ajaxLink; } private class Impl extends WebMarkupContainer @@ -76,4 +101,4 @@ public class FeedbacksPage extends WebPage super(id); } } -} +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/810185e6/wicket-core/src/test/java/org/apache/wicket/markup/html/internal/EnclosurePage_1.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/html/internal/EnclosurePage_1.java b/wicket-core/src/test/java/org/apache/wicket/markup/html/internal/EnclosurePage_1.java index 650bdbf..22a4a3f 100644 --- a/wicket-core/src/test/java/org/apache/wicket/markup/html/internal/EnclosurePage_1.java +++ b/wicket-core/src/test/java/org/apache/wicket/markup/html/internal/EnclosurePage_1.java @@ -29,23 +29,53 @@ import org.apache.wicket.markup.html.basic.Label; public class EnclosurePage_1 extends WebPage { private static final long serialVersionUID = 1L; + + public int pendingAfterRenderCount = 0; /** * Construct. */ public EnclosurePage_1() { - add(new Label("label1", "Test Label 1")); - add(new Label("label2", "Test Label 2")); - add(new Label("label3", "Test Label 3").setVisible(false)); - add(new Label("label4", "Test Label 2")); - add(new Label("label5", "Test Label 2")); - add(new Label("label6", "Test Label 2")); - add(new Label("label7", "Test Label 2")); + add(new AfterRenderCountingLabel("label1", "Test Label 1")); + add(new AfterRenderCountingLabel("label2", "Test Label 2")); + add(new AfterRenderCountingLabel("label3", "Test Label 3").setVisible(false)); + add(new AfterRenderCountingLabel("label4", "Test Label 2")); + add(new AfterRenderCountingLabel("label5", "Test Label 2")); + add(new AfterRenderCountingLabel("label6", "Test Label 2")); + add(new AfterRenderCountingLabel("label7", "Test Label 2")); + WebMarkupContainer container = new WebMarkupContainer("container"); add(container); - container.add(new Label("label8", "Test Label 2")); - add(new Label("label9", "Test Label 2")); - add(new Label("label10", "Test Label 3")); + + container.add(new AfterRenderCountingLabel("label8", "Test Label 2")); + + add(new AfterRenderCountingLabel("label9", "Test Label 2")); + add(new AfterRenderCountingLabel("label10", "Test Label 3")); + } + + class AfterRenderCountingLabel extends Label { + + public AfterRenderCountingLabel(String id, String model) + { + super(id, model); + } + + @Override + protected void onBeforeRender() + { + super.onBeforeRender(); + + + pendingAfterRenderCount++; + } + + @Override + protected void onAfterRender() + { + super.onAfterRender(); + + pendingAfterRenderCount--; + } } } http://git-wip-us.apache.org/repos/asf/wicket/blob/810185e6/wicket-core/src/test/java/org/apache/wicket/markup/html/internal/EnclosureTest.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/html/internal/EnclosureTest.java b/wicket-core/src/test/java/org/apache/wicket/markup/html/internal/EnclosureTest.java index a34ea5c..7067c7d 100644 --- a/wicket-core/src/test/java/org/apache/wicket/markup/html/internal/EnclosureTest.java +++ b/wicket-core/src/test/java/org/apache/wicket/markup/html/internal/EnclosureTest.java @@ -52,6 +52,10 @@ public class EnclosureTest extends WicketTestCase public void testRenderHomePage() throws Exception { executeTest(EnclosurePage_1.class, "EnclosurePageExpectedResult_1.html"); + + EnclosurePage_1 page = (EnclosurePage_1)tester.getLastRenderedPage(); + + assertEquals(0, page.pendingAfterRenderCount); } /**