You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by sv...@apache.org on 2015/06/27 09:11:30 UTC

[1/2] wicket git commit: WICKET-5929 renamed *AjaxResponse to *PartialPageUpdate; moved all XML related encoding into XmlPartialPageUpdate

Repository: wicket
Updated Branches:
  refs/heads/master e5f08f1b2 -> 470f2a286


WICKET-5929 renamed *AjaxResponse to *PartialPageUpdate; moved all XML related encoding into XmlPartialPageUpdate


Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/7c40e4da
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/7c40e4da
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/7c40e4da

Branch: refs/heads/master
Commit: 7c40e4dafa75973bf99b34567d977f310a5f02d0
Parents: e5f08f1
Author: Sven Meier <sv...@apache.org>
Authored: Fri Jun 26 23:01:50 2015 +0200
Committer: Sven Meier <sv...@apache.org>
Committed: Sat Jun 27 09:03:04 2015 +0200

----------------------------------------------------------------------
 .../wicket/ajax/AbstractAjaxResponse.java       | 830 -------------------
 .../apache/wicket/ajax/AjaxRequestHandler.java  |   4 +-
 .../apache/wicket/ajax/PartialPageUpdate.java   | 786 ++++++++++++++++++
 .../org/apache/wicket/ajax/XmlAjaxResponse.java | 233 ------
 .../wicket/ajax/XmlPartialPageUpdate.java       | 234 ++++++
 .../filter/XmlCleaningResponseFilter.java       |   4 +-
 .../filter/XmlCleaningResponseFilterTest.java   |   4 +-
 .../ws/api/WebSocketRequestHandler.java         |  27 +-
 8 files changed, 1039 insertions(+), 1083 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/7c40e4da/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractAjaxResponse.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractAjaxResponse.java b/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractAjaxResponse.java
deleted file mode 100644
index f949171..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractAjaxResponse.java
+++ /dev/null
@@ -1,830 +0,0 @@
-/*
- * 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.ajax;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.servlet.http.Cookie;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.Page;
-import org.apache.wicket.markup.head.HeaderItem;
-import org.apache.wicket.markup.head.IHeaderResponse;
-import org.apache.wicket.markup.head.IWrappedHeaderItem;
-import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
-import org.apache.wicket.markup.head.OnEventHeaderItem;
-import org.apache.wicket.markup.head.OnLoadHeaderItem;
-import org.apache.wicket.markup.head.PriorityHeaderItem;
-import org.apache.wicket.markup.head.internal.HeaderResponse;
-import org.apache.wicket.markup.html.internal.HtmlHeaderContainer;
-import org.apache.wicket.markup.parser.filter.HtmlHeaderSectionHandler;
-import org.apache.wicket.markup.renderStrategy.AbstractHeaderRenderStrategy;
-import org.apache.wicket.markup.renderStrategy.IHeaderRenderStrategy;
-import org.apache.wicket.markup.repeater.AbstractRepeater;
-import org.apache.wicket.request.IRequestCycle;
-import org.apache.wicket.request.Response;
-import org.apache.wicket.request.cycle.RequestCycle;
-import org.apache.wicket.request.http.WebResponse;
-import org.apache.wicket.util.lang.Args;
-import org.apache.wicket.util.lang.Classes;
-import org.apache.wicket.util.lang.Generics;
-import org.apache.wicket.util.string.AppendingStringBuffer;
-import org.apache.wicket.util.string.Strings;
-import org.apache.wicket.util.time.Time;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A POJO-like that collects the data for the Ajax response written to the client
- * and serializes it to specific String-based format (XML, JSON, ...).
- */
-public abstract class AbstractAjaxResponse
-{
-	private static final Logger LOG = LoggerFactory.getLogger(AbstractAjaxResponse.class);
-
-	/**
-	 * A list of scripts (JavaScript) which should be executed on the client side before the
-	 * components' replacement
-	 */
-	protected final List<CharSequence> prependJavaScripts = Generics.newArrayList();
-
-	/**
-	 * A list of scripts (JavaScript) which should be executed on the client side after the
-	 * components' replacement
-	 */
-	protected final List<CharSequence> appendJavaScripts = Generics.newArrayList();
-
-	/**
-	 * A list of scripts (JavaScript) which should be executed on the client side after the
-	 * components' replacement.
-	 * Executed immediately after the replacement of the components, and before appendJavaScripts
-	 */
-	protected final List<CharSequence> domReadyJavaScripts = Generics.newArrayList();
-
-	/**
-	 * The component instances that will be rendered/replaced.
-	 */
-	protected final Map<String, Component> markupIdToComponent = new LinkedHashMap<String, Component>();
-
-	/**
-	 * A flag that indicates that components cannot be added to AjaxRequestTarget anymore.
-	 * See https://issues.apache.org/jira/browse/WICKET-3564
-	 */
-	protected transient boolean componentsFrozen;
-
-	/**
-	 * Create a response for component body and javascript that will escape output to make it safe
-	 * to use inside a CDATA block
-	 */
-	protected final AjaxResponse encodingBodyResponse;
-
-	/**
-	 * Response for header contribution that will escape output to make it safe to use inside a
-	 * CDATA block
-	 */
-	protected final AjaxResponse encodingHeaderResponse;
-
-	protected HtmlHeaderContainer header = null;
-
-	// whether a header contribution is being rendered
-	private boolean headerRendering = false;
-
-	private IHeaderResponse headerResponse;
-
-	/**
-	 * The page which components are being updated.
-	 */
-	private final Page page;
-
-	/**
-	 * Constructor.
-	 *
-	 * @param page
-	 *      the page which components are being updated.
-	 */
-	public AbstractAjaxResponse(final Page page)
-	{
-		this.page = page;
-
-		WebResponse response = (WebResponse) page.getResponse();
-		encodingBodyResponse = new AjaxResponse(response);
-		encodingHeaderResponse = new AjaxResponse(response);
-	}
-
-	/**
-	 * Serializes this object to the response.
-	 *
-	 * @param response
-	 *      the response to write to
-	 * @param encoding
-	 *      the encoding for the response
-	 */
-	public void writeTo(final Response response, final String encoding)
-	{
-		writeHeader(response, encoding);
-
-		// invoke onbeforerespond event on listeners
-		fireOnBeforeRespondListeners();
-
-		// process added components
-		writeComponents(response, encoding);
-
-		fireOnAfterRespondListeners(response);
-
-		// queue up prepend javascripts. unlike other steps these are executed out of order so that
-		// components can contribute them from inside their onbeforerender methods.
-		writePriorityEvaluations(response, prependJavaScripts);
-
-		// execute the dom ready javascripts as first javascripts
-		// after component replacement
-		List<CharSequence> evaluationScripts = new ArrayList<>();
-		evaluationScripts.addAll(domReadyJavaScripts);
-		evaluationScripts.addAll(appendJavaScripts);
-		writeNormalEvaluations(response, evaluationScripts);
-
-		writeFooter(response, encoding);
-	}
-
-	protected abstract void fireOnAfterRespondListeners(Response response);
-
-	protected abstract void fireOnBeforeRespondListeners();
-
-	/**
-	 * @param response
-	 *      the response to write to
-	 * @param encoding
-	 *      the encoding for the response
-	 */
-    protected abstract void writeFooter(Response response, String encoding);
-
-	/**
-	 *
-	 * @param response
-	 *      the response to write to
-	 * @param js
-	 *      the JavaScript to evaluate
-	 */
-	protected abstract void writePriorityEvaluations(Response response, Collection<CharSequence> js);
-
-	/**
-	 *
-	 * @param response
-	 *      the response to write to
-	 * @param js
-	 *      the JavaScript to evaluate
-	 */
-	protected abstract void writeNormalEvaluations(Response response, Collection<CharSequence> js);
-
-	/**
-	 * Processes components added to the target. This involves attaching components, rendering
-	 * markup into a client side xml envelope, and detaching them
-	 *
-	 * @param response
-	 *      the response to write to
-	 * @param encoding
-	 *      the encoding for the response
-	 */
-	private void writeComponents(Response response, String encoding)
-	{
-		componentsFrozen = true;
-
-		// process component markup
-		for (Map.Entry<String, Component> stringComponentEntry : markupIdToComponent.entrySet())
-		{
-			final Component component = stringComponentEntry.getValue();
-
-			if (!containsAncestorFor(component))
-			{
-				writeComponent(response, component.getAjaxRegionMarkupId(), component, encoding);
-			}
-		}
-
-		if (header != null)
-		{
-			// 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.
-			// to support this, we need to allow header contributions to be written in the close
-			// tag, which we do here:
-			headerRendering = true;
-			// save old response, set new
-			Response oldResponse = RequestCycle.get().setResponse(encodingHeaderResponse);
-			encodingHeaderResponse.reset();
-
-			// now, close the response (which may render things)
-			header.getHeaderResponse().close();
-
-			// revert to old response
-			RequestCycle.get().setResponse(oldResponse);
-
-			// write the XML tags and we're done
-			writeHeaderContribution(response);
-			headerRendering = false;
-		}
-	}
-
-	/**
-	 * Writes a single component
-	 *
-	 * @param response
-	 *      the response to write to
-	 * @param markupId
-	 *      the markup id to use for the component replacement
-	 * @param component
-	 *      the component which markup will be used as replacement
-	 * @param encoding
-	 *      the encoding for the response
-	 */
-	protected abstract void writeComponent(Response response, String markupId, Component component, String encoding);
-
-	/**
-	 * Writes the head part of the response.
-	 * For example XML preamble
-	 *
-	 * @param response
-	 *      the response to write to
-	 * @param encoding
-	 *      the encoding for the response
-	 */
-	protected abstract void writeHeader(Response response, String encoding);
-
-	/**
-	 * Writes header contribution (<link/> or <script/>) to the response.
-	 *
-	 * @param response
-	 *      the response to write to
-	 */
-	protected abstract void writeHeaderContribution(Response response);
-
-	@Override
-	public boolean equals(Object o)
-	{
-		if (this == o) return true;
-		if (o == null || getClass() != o.getClass()) return false;
-
-		AbstractAjaxResponse that = (AbstractAjaxResponse) o;
-
-		if (!appendJavaScripts.equals(that.appendJavaScripts)) return false;
-		if (!domReadyJavaScripts.equals(that.domReadyJavaScripts)) return false;
-		return prependJavaScripts.equals(that.prependJavaScripts);
-	}
-
-	@Override
-	public int hashCode()
-	{
-		int result = prependJavaScripts.hashCode();
-		result = 31 * result + appendJavaScripts.hashCode();
-		result = 31 * result + domReadyJavaScripts.hashCode();
-		return result;
-	}
-
-	/**
-	 * Adds script to the ones which are executed after the component replacement.
-	 *
-	 * @param javascript
-	 *      the javascript to execute
-	 */
-	public final void appendJavaScript(final CharSequence javascript)
-	{
-		Args.notNull(javascript, "javascript");
-
-		appendJavaScripts.add(javascript);
-	}
-
-	/**
-	 * Adds script to the ones which are executed before the component replacement.
-	 *
-	 * @param javascript
-	 *      the javascript to execute
-	 */
-	public final void prependJavaScript(CharSequence javascript)
-	{
-		Args.notNull(javascript, "javascript");
-
-		prependJavaScripts.add(javascript);
-	}
-
-	/**
-	 * Adds a component to be updated at the client side with its current markup
-	 *
-	 * @param component
-	 *      the component to update
-	 * @param markupId
-	 *      the markup id to use to find the component in the page's markup
-	 * @throws IllegalArgumentException
-	 *      thrown when a Page or an AbstractRepeater is added
-	 * @throws IllegalStateException
-	 *      thrown when components no more can be added for replacement.
-	 */
-	public final void add(final Component component, final String markupId)
-			throws IllegalArgumentException, IllegalStateException
-	{
-		Args.notEmpty(markupId, "markupId");
-		Args.notNull(component, "component");
-
-		if (component instanceof Page)
-		{
-			if (component != page)
-			{
-				throw new IllegalArgumentException("component cannot be a page");
-			}
-		}
-		else if (component instanceof AbstractRepeater)
-		{
-			throw new IllegalArgumentException(
-					"Component " +
-							component.getClass().getName() +
-							" has been added to the target. This component is a repeater and cannot be repainted via ajax directly. " +
-							"Instead add its parent or another markup container higher in the hierarchy.");
-		}
-
-		assertComponentsNotFrozen();
-
-		component.setMarkupId(markupId);
-		markupIdToComponent.put(markupId, component);
-	}
-
-	/**
-	 * @return a read-only collection of all components which have been added for replacement so far.
-	 */
-	public final Collection<? extends Component> getComponents()
-	{
-		return Collections.unmodifiableCollection(markupIdToComponent.values());
-	}
-
-	/**
-	 * Detaches the page if at least one of its components was updated.
-	 *
-	 * @param requestCycle
-	 *      the current request cycle
-	 */
-	public void detach(IRequestCycle requestCycle)
-	{
-		Iterator<Component> iterator = markupIdToComponent.values().iterator();
-		while (iterator.hasNext())
-		{
-			final Component component = iterator.next();
-			final Page parentPage = component.findParent(Page.class);
-			if (parentPage != null)
-			{
-				parentPage.detach();
-				break;
-			}
-		}
-	}
-
-	/**
-	 * Checks if the target contains an ancestor for the given component
-	 *
-	 * @param component
-	 *      the component which ancestors should be checked.
-	 * @return <code>true</code> if target contains an ancestor for the given component
-	 */
-	protected boolean containsAncestorFor(Component component)
-	{
-		Component cursor = component.getParent();
-		while (cursor != null)
-		{
-			if (markupIdToComponent.containsValue(cursor))
-			{
-				return true;
-			}
-			cursor = cursor.getParent();
-		}
-		return false;
-	}
-
-	/**
-	 * @return {@code true} if the page has been added for replacement
-	 */
-	boolean containsPage()
-	{
-		return markupIdToComponent.values().contains(page);
-	}
-
-	/**
-	 * Gets or creates an IHeaderResponse instance to use for the header contributions.
-	 *
-	 * @return IHeaderResponse instance to use for the header contributions.
-	 */
-	public IHeaderResponse getHeaderResponse()
-	{
-		if (headerResponse == null)
-		{
-			// we don't need to decorate the header response here because this is called from
-			// within AjaxHtmlHeaderContainer, which decorates the response
-			headerResponse = new AjaxHeaderResponse();
-		}
-		return headerResponse;
-	}
-
-	/**
-	 * @param response
-	 *      the response to write to
-	 * @param component
-	 *      to component which will contribute to the header
-	 */
-	protected void writeHeaderContribution(final Response response, final Component component)
-	{
-		headerRendering = true;
-
-		// create the htmlheadercontainer if needed
-		if (header == null)
-		{
-			header = new AjaxHtmlHeaderContainer(this);
-			final Page parentPage = component.getPage();
-			parentPage.addOrReplace(header);
-		}
-
-		RequestCycle requestCycle = component.getRequestCycle();
-
-		// save old response, set new
-		Response oldResponse = requestCycle.setResponse(encodingHeaderResponse);
-
-		try {
-			encodingHeaderResponse.reset();
-
-			IHeaderRenderStrategy strategy = AbstractHeaderRenderStrategy.get();
-
-			strategy.renderHeader(header, null, component);
-		} finally {
-			// revert to old response
-			requestCycle.setResponse(oldResponse);
-		}
-
-		writeHeaderContribution(response);
-
-		headerRendering = false;
-	}
-
-	/**
-	 * Sets the Content-Type header to indicate the type of the Ajax response.
-	 *
-	 * @param response
-	 *      the current we response
-	 * @param encoding
-	 *      the encoding to use
-	 */
-	protected abstract void setContentType(WebResponse response, String encoding);
-
-
-	/**
-	 * Header container component for ajax header contributions
-	 *
-	 * @author Matej Knopp
-	 */
-	private static class AjaxHtmlHeaderContainer extends HtmlHeaderContainer
-	{
-		private static final long serialVersionUID = 1L;
-
-		private transient AbstractAjaxResponse ajaxResponse;
-
-		/**
-		 * Constructor.
-		 *
-		 * @param ajaxResponse
-		 *      the object that keeps the data for the Ajax response
-		 */
-		public AjaxHtmlHeaderContainer(final AbstractAjaxResponse ajaxResponse)
-		{
-			super(HtmlHeaderSectionHandler.HEADER_ID);
-			this.ajaxResponse = ajaxResponse;
-		}
-
-		/**
-		 *
-		 * @see org.apache.wicket.markup.html.internal.HtmlHeaderContainer#newHeaderResponse()
-		 */
-		@Override
-		protected IHeaderResponse newHeaderResponse()
-		{
-		    if (ajaxResponse != null)
-            {
-		        return ajaxResponse.getHeaderResponse();
-            }
-		    
-		    return super.newHeaderResponse();
-		}
-		
-		@Override
-		protected void onDetach()
-		{
-		    super.onDetach();
-		    ajaxResponse = null;
-		}
-	}
-
-	/**
-	 * Header response for an ajax request.
-	 *
-	 * @author Matej Knopp
-	 */
-	private class AjaxHeaderResponse extends HeaderResponse
-	{
-		@Override
-		public void render(HeaderItem item)
-		{
-			PriorityHeaderItem priorityHeaderItem = null;
-			while (item instanceof IWrappedHeaderItem)
-			{
-				if (item instanceof PriorityHeaderItem)
-				{
-					priorityHeaderItem = (PriorityHeaderItem) item;
-				}
-				item = ((IWrappedHeaderItem) item).getWrapped();
-			}
-
-			if (item instanceof OnLoadHeaderItem)
-			{
-				if (!wasItemRendered(item))
-				{
-					AbstractAjaxResponse.this.appendJavaScript(((OnLoadHeaderItem) item).getJavaScript());
-					markItemRendered(item);
-				}
-			}
-			else if (item instanceof OnEventHeaderItem)
-			{
-				if (!wasItemRendered(item))
-				{
-					AbstractAjaxResponse.this.appendJavaScript(((OnEventHeaderItem) item).getCompleteJavaScript());
-					markItemRendered(item);
-				}
-			}
-			else if (item instanceof OnDomReadyHeaderItem)
-			{
-				if (!wasItemRendered(item))
-				{
-					if (priorityHeaderItem != null)
-					{
-						AbstractAjaxResponse.this.domReadyJavaScripts.add(0, ((OnDomReadyHeaderItem)item).getJavaScript());
-					}
-					else
-					{
-						AbstractAjaxResponse.this.domReadyJavaScripts.add(((OnDomReadyHeaderItem)item).getJavaScript());
-					}
-					markItemRendered(item);
-				}
-			}
-			else if (headerRendering)
-			{
-				super.render(item);
-			}
-			else
-			{
-				LOG.debug("Only methods that can be called on IHeaderResponse outside renderHead() are #render(OnLoadHeaderItem) and #render(OnDomReadyHeaderItem)");
-			}
-		}
-
-		@Override
-		protected Response getRealResponse()
-		{
-			return RequestCycle.get().getResponse();
-		}
-	}
-
-	/**
-	 * Response that uses an encoder to encode its contents
-	 *
-	 * @author Igor Vaynberg (ivaynberg)
-	 */
-	protected static final class AjaxResponse extends WebResponse
-	{
-		private final AppendingStringBuffer buffer = new AppendingStringBuffer(256);
-
-		private boolean escaped = false;
-
-		private final WebResponse originalResponse;
-
-		/**
-		 * Constructor.
-		 *
-		 * @param originalResponse
-		 *      the original request cycle response
-		 */
-		private AjaxResponse(WebResponse originalResponse)
-		{
-			this.originalResponse = originalResponse;
-		}
-
-		/**
-		 * @see org.apache.wicket.request.Response#encodeURL(CharSequence)
-		 */
-		@Override
-		public String encodeURL(CharSequence url)
-		{
-			return originalResponse.encodeURL(url);
-		}
-
-		/**
-		 * @return contents of the response
-		 */
-		public CharSequence getContents()
-		{
-			return buffer;
-		}
-
-		/**
-		 * @return true if any escaping has been performed, false otherwise
-		 */
-		public boolean isContentsEncoded()
-		{
-			return escaped;
-		}
-
-		/**
-		 * @see org.apache.wicket.request.Response#write(CharSequence)
-		 */
-		@Override
-		public void write(CharSequence cs)
-		{
-			String string = cs.toString();
-			if (needsEncoding(string))
-			{
-				string = encode(string);
-				escaped = true;
-				buffer.append(string);
-			}
-			else
-			{
-				buffer.append(cs);
-			}
-		}
-
-		/**
-		 * Resets the response to a clean state so it can be reused to save on garbage.
-		 */
-		@Override
-		public void reset()
-		{
-			buffer.clear();
-			escaped = false;
-		}
-
-		@Override
-		public void write(byte[] array)
-		{
-			throw new UnsupportedOperationException("Cannot write binary data.");
-		}
-
-		@Override
-		public void write(byte[] array, int offset, int length)
-		{
-			throw new UnsupportedOperationException("Cannot write binary data.");
-		}
-
-		@Override
-		public Object getContainerResponse()
-		{
-			return originalResponse.getContainerResponse();
-		}
-
-		@Override
-		public void addCookie(Cookie cookie)
-		{
-			originalResponse.addCookie(cookie);
-		}
-
-		@Override
-		public void clearCookie(Cookie cookie)
-		{
-			originalResponse.clearCookie(cookie);
-		}
-
-		@Override
-		public void setHeader(String name, String value)
-		{
-			originalResponse.setHeader(name, value);
-		}
-
-		@Override
-		public void addHeader(String name, String value)
-		{
-			originalResponse.addHeader(name, value);
-		}
-
-		@Override
-		public void setDateHeader(String name, Time date)
-		{
-			originalResponse.setDateHeader(name, date);
-		}
-
-		@Override
-		public void setContentLength(long length)
-		{
-			originalResponse.setContentLength(length);
-		}
-
-		@Override
-		public void setContentType(String mimeType)
-		{
-			originalResponse.setContentType(mimeType);
-		}
-
-		@Override
-		public void setStatus(int sc)
-		{
-			originalResponse.setStatus(sc);
-		}
-
-		@Override
-		public void sendError(int sc, String msg)
-		{
-			originalResponse.sendError(sc, msg);
-		}
-
-		@Override
-		public String encodeRedirectURL(CharSequence url)
-		{
-			return originalResponse.encodeRedirectURL(url);
-		}
-
-		@Override
-		public void sendRedirect(String url)
-		{
-			originalResponse.sendRedirect(url);
-		}
-
-		@Override
-		public boolean isRedirect()
-		{
-			return originalResponse.isRedirect();
-		}
-
-		@Override
-		public void flush()
-		{
-			originalResponse.flush();
-		}
-	}
-
-	/**
-	 * Encodes a string so it is safe to use inside CDATA blocks
-	 *
-	 * @param str
-	 *      the string to encode.
-	 * @return encoded string
-	 */
-	static String encode(CharSequence str)
-	{
-		if (str == null)
-		{
-			return null;
-		}
-
-		// split each CDATA end sequence
-		return Strings.replaceAll(str, "]]>", "]]]]><![CDATA[>").toString();
-	}
-
-	/**
-	 *
-	 * @param str
-	 *      the string to check
-	 * @return {@code true} if string needs to be encoded, {@code false} otherwise
-	 */
-	static boolean needsEncoding(CharSequence str)
-	{
-		/*
-		 * TODO Post 1.2: Ajax: we can improve this by keeping a buffer of at least 3 characters and
-		 * checking that buffer so that we can narrow down escaping occurring only for ']]>'
-		 * sequence, or at least for ]] if ] is the last char in this buffer.
-		 *
-		 * but this improvement will only work if we write first and encode later instead of working
-		 * on fragments sent to write
-		 */
-		return Strings.indexOf(str, ']') >= 0;
-	}
-
-	private void assertComponentsNotFrozen()
-	{
-		assertNotFrozen(componentsFrozen, Component.class);
-	}
-
-	private void assertNotFrozen(boolean frozen, Class<?> clazz)
-	{
-		if (frozen)
-		{
-			throw new IllegalStateException(Classes.simpleName(clazz) + "s can no " +
-					" longer be added");
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/wicket/blob/7c40e4da/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxRequestHandler.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxRequestHandler.java b/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxRequestHandler.java
index cbb9770..439319d 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxRequestHandler.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxRequestHandler.java
@@ -84,7 +84,7 @@ public class AjaxRequestHandler implements AjaxRequestTarget
 	 * A POJO-like that collects the data for the Ajax response written to the client and serializes
 	 * it to specific String-based format (XML, JSON, ...).
 	 */
-	private final AbstractAjaxResponse responseObject;
+	private final PartialPageUpdate responseObject;
 
 	/** a list of listeners */
 	private List<AjaxRequestTarget.IListener> listeners = null;
@@ -111,7 +111,7 @@ public class AjaxRequestHandler implements AjaxRequestTarget
 	{
 		this.page = Args.notNull(page, "page");
 
-		responseObject = new XmlAjaxResponse(page)
+		responseObject = new XmlPartialPageUpdate(page)
 		{
 
 			/**

http://git-wip-us.apache.org/repos/asf/wicket/blob/7c40e4da/wicket-core/src/main/java/org/apache/wicket/ajax/PartialPageUpdate.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/PartialPageUpdate.java b/wicket-core/src/main/java/org/apache/wicket/ajax/PartialPageUpdate.java
new file mode 100644
index 0000000..5970f38
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/PartialPageUpdate.java
@@ -0,0 +1,786 @@
+/*
+ * 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.ajax;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.Cookie;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.Page;
+import org.apache.wicket.markup.head.HeaderItem;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.IWrappedHeaderItem;
+import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
+import org.apache.wicket.markup.head.OnEventHeaderItem;
+import org.apache.wicket.markup.head.OnLoadHeaderItem;
+import org.apache.wicket.markup.head.PriorityHeaderItem;
+import org.apache.wicket.markup.head.internal.HeaderResponse;
+import org.apache.wicket.markup.html.internal.HtmlHeaderContainer;
+import org.apache.wicket.markup.parser.filter.HtmlHeaderSectionHandler;
+import org.apache.wicket.markup.renderStrategy.AbstractHeaderRenderStrategy;
+import org.apache.wicket.markup.renderStrategy.IHeaderRenderStrategy;
+import org.apache.wicket.markup.repeater.AbstractRepeater;
+import org.apache.wicket.request.IRequestCycle;
+import org.apache.wicket.request.Response;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.http.WebResponse;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.lang.Classes;
+import org.apache.wicket.util.lang.Generics;
+import org.apache.wicket.util.string.AppendingStringBuffer;
+import org.apache.wicket.util.time.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A partial update of a page that collects components and header contributions to be written to the client in a specific
+ * String-based format (XML, JSON, * ...).
+ * <p>
+ * The elements of such response are:
+ * <ul>
+ * <li>priority-evaluate - an item of the prepend JavaScripts</li>
+ * <li>component - the markup of the updated component</li>
+ * <li>evaluate - an item of the onDomReady and append JavaScripts</li>
+ * <li>header-contribution - all HeaderItems which have been contributed in
+ * components' and their behaviors' #renderHead(Component, IHeaderResponse)</li>
+ * </ul>
+ */
+public abstract class PartialPageUpdate
+{
+	private static final Logger LOG = LoggerFactory.getLogger(PartialPageUpdate.class);
+
+	/**
+	 * A list of scripts (JavaScript) which should be executed on the client side before the
+	 * components' replacement
+	 */
+	protected final List<CharSequence> prependJavaScripts = Generics.newArrayList();
+
+	/**
+	 * A list of scripts (JavaScript) which should be executed on the client side after the
+	 * components' replacement
+	 */
+	protected final List<CharSequence> appendJavaScripts = Generics.newArrayList();
+
+	/**
+	 * A list of scripts (JavaScript) which should be executed on the client side after the
+	 * components' replacement.
+	 * Executed immediately after the replacement of the components, and before appendJavaScripts
+	 */
+	protected final List<CharSequence> domReadyJavaScripts = Generics.newArrayList();
+
+	/**
+	 * The component instances that will be rendered/replaced.
+	 */
+	protected final Map<String, Component> markupIdToComponent = new LinkedHashMap<String, Component>();
+
+	/**
+	 * A flag that indicates that components cannot be added anymore.
+	 * See https://issues.apache.org/jira/browse/WICKET-3564
+	 * 
+	 * @see #add(Component, String)
+	 */
+	protected transient boolean componentsFrozen;
+
+	/**
+	 * Create a response for component body and javascript that will escape output to make it safe
+	 * to use inside a CDATA block
+	 */
+	protected final ResponseWrapper encodingBodyResponse;
+
+	/**
+	 * Response for header contribution that will escape output to make it safe to use inside a
+	 * CDATA block
+	 */
+	protected final ResponseWrapper encodingHeaderResponse;
+
+	protected HtmlHeaderContainer header = null;
+
+	// whether a header contribution is being rendered
+	private boolean headerRendering = false;
+
+	private IHeaderResponse headerResponse;
+
+	/**
+	 * The page which components are being updated.
+	 */
+	private final Page page;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param page
+	 *      the page which components are being updated.
+	 */
+	public PartialPageUpdate(final Page page)
+	{
+		this.page = page;
+
+		WebResponse response = (WebResponse) page.getResponse();
+		encodingBodyResponse = new ResponseWrapper(response);
+		encodingHeaderResponse = new ResponseWrapper(response);
+	}
+
+	/**
+	 * Serializes this object to the response.
+	 *
+	 * @param response
+	 *      the response to write to
+	 * @param encoding
+	 *      the encoding for the response
+	 */
+	public void writeTo(final Response response, final String encoding)
+	{
+		writeHeader(response, encoding);
+
+		// invoke onbeforerespond event on listeners
+		fireOnBeforeRespondListeners();
+
+		// process added components
+		writeComponents(response, encoding);
+
+		fireOnAfterRespondListeners(response);
+
+		// queue up prepend javascripts. unlike other steps these are executed out of order so that
+		// components can contribute them from inside their onbeforerender methods.
+		writePriorityEvaluations(response, prependJavaScripts);
+
+		// execute the dom ready javascripts as first javascripts
+		// after component replacement
+		List<CharSequence> evaluationScripts = new ArrayList<>();
+		evaluationScripts.addAll(domReadyJavaScripts);
+		evaluationScripts.addAll(appendJavaScripts);
+		writeNormalEvaluations(response, evaluationScripts);
+
+		writeFooter(response, encoding);
+	}
+
+	protected abstract void fireOnAfterRespondListeners(Response response);
+
+	protected abstract void fireOnBeforeRespondListeners();
+
+	/**
+	 * @param response
+	 *      the response to write to
+	 * @param encoding
+	 *      the encoding for the response
+	 */
+    protected abstract void writeFooter(Response response, String encoding);
+
+	/**
+	 *
+	 * @param response
+	 *      the response to write to
+	 * @param js
+	 *      the JavaScript to evaluate
+	 */
+	protected abstract void writePriorityEvaluations(Response response, Collection<CharSequence> js);
+
+	/**
+	 *
+	 * @param response
+	 *      the response to write to
+	 * @param js
+	 *      the JavaScript to evaluate
+	 */
+	protected abstract void writeNormalEvaluations(Response response, Collection<CharSequence> js);
+
+	/**
+	 * Processes components added to the target. This involves attaching components, rendering
+	 * markup into a client side xml envelope, and detaching them
+	 *
+	 * @param response
+	 *      the response to write to
+	 * @param encoding
+	 *      the encoding for the response
+	 */
+	private void writeComponents(Response response, String encoding)
+	{
+		componentsFrozen = true;
+
+		// process component markup
+		for (Map.Entry<String, Component> stringComponentEntry : markupIdToComponent.entrySet())
+		{
+			final Component component = stringComponentEntry.getValue();
+
+			if (!containsAncestorFor(component))
+			{
+				writeComponent(response, component.getAjaxRegionMarkupId(), component, encoding);
+			}
+		}
+
+		if (header != null)
+		{
+			// 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.
+			// to support this, we need to allow header contributions to be written in the close
+			// tag, which we do here:
+			headerRendering = true;
+			// save old response, set new
+			Response oldResponse = RequestCycle.get().setResponse(encodingHeaderResponse);
+			encodingHeaderResponse.reset();
+
+			// now, close the response (which may render things)
+			header.getHeaderResponse().close();
+
+			// revert to old response
+			RequestCycle.get().setResponse(oldResponse);
+
+			// write the XML tags and we're done
+			writeHeaderContribution(response);
+			headerRendering = false;
+		}
+	}
+
+	/**
+	 * Writes a single component
+	 *
+	 * @param response
+	 *      the response to write to
+	 * @param markupId
+	 *      the markup id to use for the component replacement
+	 * @param component
+	 *      the component which markup will be used as replacement
+	 * @param encoding
+	 *      the encoding for the response
+	 */
+	protected abstract void writeComponent(Response response, String markupId, Component component, String encoding);
+
+	/**
+	 * Writes the head part of the response.
+	 * For example XML preamble
+	 *
+	 * @param response
+	 *      the response to write to
+	 * @param encoding
+	 *      the encoding for the response
+	 */
+	protected abstract void writeHeader(Response response, String encoding);
+
+	/**
+	 * Writes header contribution (<link/> or <script/>) to the response.
+	 *
+	 * @param response
+	 *      the response to write to
+	 */
+	protected abstract void writeHeaderContribution(Response response);
+
+	@Override
+	public boolean equals(Object o)
+	{
+		if (this == o) return true;
+		if (o == null || getClass() != o.getClass()) return false;
+
+		PartialPageUpdate that = (PartialPageUpdate) o;
+
+		if (!appendJavaScripts.equals(that.appendJavaScripts)) return false;
+		if (!domReadyJavaScripts.equals(that.domReadyJavaScripts)) return false;
+		return prependJavaScripts.equals(that.prependJavaScripts);
+	}
+
+	@Override
+	public int hashCode()
+	{
+		int result = prependJavaScripts.hashCode();
+		result = 31 * result + appendJavaScripts.hashCode();
+		result = 31 * result + domReadyJavaScripts.hashCode();
+		return result;
+	}
+
+	/**
+	 * Adds script to the ones which are executed after the component replacement.
+	 *
+	 * @param javascript
+	 *      the javascript to execute
+	 */
+	public final void appendJavaScript(final CharSequence javascript)
+	{
+		Args.notNull(javascript, "javascript");
+
+		appendJavaScripts.add(javascript);
+	}
+
+	/**
+	 * Adds script to the ones which are executed before the component replacement.
+	 *
+	 * @param javascript
+	 *      the javascript to execute
+	 */
+	public final void prependJavaScript(CharSequence javascript)
+	{
+		Args.notNull(javascript, "javascript");
+
+		prependJavaScripts.add(javascript);
+	}
+
+	/**
+	 * Adds a component to be updated at the client side with its current markup
+	 *
+	 * @param component
+	 *      the component to update
+	 * @param markupId
+	 *      the markup id to use to find the component in the page's markup
+	 * @throws IllegalArgumentException
+	 *      thrown when a Page or an AbstractRepeater is added
+	 * @throws IllegalStateException
+	 *      thrown when components no more can be added for replacement.
+	 */
+	public final void add(final Component component, final String markupId)
+			throws IllegalArgumentException, IllegalStateException
+	{
+		Args.notEmpty(markupId, "markupId");
+		Args.notNull(component, "component");
+
+		if (component instanceof Page)
+		{
+			if (component != page)
+			{
+				throw new IllegalArgumentException("component cannot be a page");
+			}
+		}
+		else if (component instanceof AbstractRepeater)
+		{
+			throw new IllegalArgumentException(
+					"Component " +
+							component.getClass().getName() +
+							" has been added to a partial page update. This component is a repeater and cannot be repainted directly. " +
+							"Instead add its parent or another markup container higher in the hierarchy.");
+		}
+
+		assertComponentsNotFrozen();
+
+		component.setMarkupId(markupId);
+		markupIdToComponent.put(markupId, component);
+	}
+
+	/**
+	 * @return a read-only collection of all components which have been added for replacement so far.
+	 */
+	public final Collection<? extends Component> getComponents()
+	{
+		return Collections.unmodifiableCollection(markupIdToComponent.values());
+	}
+
+	/**
+	 * Detaches the page if at least one of its components was updated.
+	 *
+	 * @param requestCycle
+	 *      the current request cycle
+	 */
+	public void detach(IRequestCycle requestCycle)
+	{
+		Iterator<Component> iterator = markupIdToComponent.values().iterator();
+		while (iterator.hasNext())
+		{
+			final Component component = iterator.next();
+			final Page parentPage = component.findParent(Page.class);
+			if (parentPage != null)
+			{
+				parentPage.detach();
+				break;
+			}
+		}
+	}
+
+	/**
+	 * Checks if the target contains an ancestor for the given component
+	 *
+	 * @param component
+	 *      the component which ancestors should be checked.
+	 * @return <code>true</code> if target contains an ancestor for the given component
+	 */
+	protected boolean containsAncestorFor(Component component)
+	{
+		Component cursor = component.getParent();
+		while (cursor != null)
+		{
+			if (markupIdToComponent.containsValue(cursor))
+			{
+				return true;
+			}
+			cursor = cursor.getParent();
+		}
+		return false;
+	}
+
+	/**
+	 * @return {@code true} if the page has been added for replacement
+	 */
+	boolean containsPage()
+	{
+		return markupIdToComponent.values().contains(page);
+	}
+
+	/**
+	 * Gets or creates an IHeaderResponse instance to use for the header contributions.
+	 *
+	 * @return IHeaderResponse instance to use for the header contributions.
+	 */
+	public IHeaderResponse getHeaderResponse()
+	{
+		if (headerResponse == null)
+		{
+			// we don't need to decorate the header response here because this is called from
+			// within PartialHtmlHeaderContainer, which decorates the response
+			headerResponse = new PartialHeaderResponse();
+		}
+		return headerResponse;
+	}
+
+	/**
+	 * @param response
+	 *      the response to write to
+	 * @param component
+	 *      to component which will contribute to the header
+	 */
+	protected void writeHeaderContribution(final Response response, final Component component)
+	{
+		headerRendering = true;
+
+		// create the htmlheadercontainer if needed
+		if (header == null)
+		{
+			header = new PartialHtmlHeaderContainer(this);
+			final Page parentPage = component.getPage();
+			parentPage.addOrReplace(header);
+		}
+
+		RequestCycle requestCycle = component.getRequestCycle();
+
+		// save old response, set new
+		Response oldResponse = requestCycle.setResponse(encodingHeaderResponse);
+
+		try {
+			encodingHeaderResponse.reset();
+
+			IHeaderRenderStrategy strategy = AbstractHeaderRenderStrategy.get();
+
+			strategy.renderHeader(header, null, component);
+		} finally {
+			// revert to old response
+			requestCycle.setResponse(oldResponse);
+		}
+
+		writeHeaderContribution(response);
+
+		headerRendering = false;
+	}
+
+	/**
+	 * Sets the Content-Type header to indicate the type of the response.
+	 *
+	 * @param response
+	 *      the current we response
+	 * @param encoding
+	 *      the encoding to use
+	 */
+	protected abstract void setContentType(WebResponse response, String encoding);
+
+
+	/**
+	 * Header container component partial page updates.
+	 *
+	 * @author Matej Knopp
+	 */
+	private static class PartialHtmlHeaderContainer extends HtmlHeaderContainer
+	{
+		private static final long serialVersionUID = 1L;
+
+		private transient PartialPageUpdate update;
+
+		/**
+		 * Constructor.
+		 *
+		 * @param update
+		 *      the partial page update
+		 */
+		public PartialHtmlHeaderContainer(final PartialPageUpdate update)
+		{
+			super(HtmlHeaderSectionHandler.HEADER_ID);
+			this.update = update;
+		}
+
+		/**
+		 *
+		 * @see org.apache.wicket.markup.html.internal.HtmlHeaderContainer#newHeaderResponse()
+		 */
+		@Override
+		protected IHeaderResponse newHeaderResponse()
+		{
+		    if (update != null)
+            {
+		        return update.getHeaderResponse();
+            }
+		    
+		    return super.newHeaderResponse();
+		}
+		
+		@Override
+		protected void onDetach()
+		{
+		    super.onDetach();
+		    update = null;
+		}
+	}
+
+	/**
+	 * Header response for partial updates.
+	 *
+	 * @author Matej Knopp
+	 */
+	private class PartialHeaderResponse extends HeaderResponse
+	{
+		@Override
+		public void render(HeaderItem item)
+		{
+			PriorityHeaderItem priorityHeaderItem = null;
+			while (item instanceof IWrappedHeaderItem)
+			{
+				if (item instanceof PriorityHeaderItem)
+				{
+					priorityHeaderItem = (PriorityHeaderItem) item;
+				}
+				item = ((IWrappedHeaderItem) item).getWrapped();
+			}
+
+			if (item instanceof OnLoadHeaderItem)
+			{
+				if (!wasItemRendered(item))
+				{
+					PartialPageUpdate.this.appendJavaScript(((OnLoadHeaderItem) item).getJavaScript());
+					markItemRendered(item);
+				}
+			}
+			else if (item instanceof OnEventHeaderItem)
+			{
+				if (!wasItemRendered(item))
+				{
+					PartialPageUpdate.this.appendJavaScript(((OnEventHeaderItem) item).getCompleteJavaScript());
+					markItemRendered(item);
+				}
+			}
+			else if (item instanceof OnDomReadyHeaderItem)
+			{
+				if (!wasItemRendered(item))
+				{
+					if (priorityHeaderItem != null)
+					{
+						PartialPageUpdate.this.domReadyJavaScripts.add(0, ((OnDomReadyHeaderItem)item).getJavaScript());
+					}
+					else
+					{
+						PartialPageUpdate.this.domReadyJavaScripts.add(((OnDomReadyHeaderItem)item).getJavaScript());
+					}
+					markItemRendered(item);
+				}
+			}
+			else if (headerRendering)
+			{
+				super.render(item);
+			}
+			else
+			{
+				LOG.debug("Only methods that can be called on IHeaderResponse outside renderHead() are #render(OnLoadHeaderItem) and #render(OnDomReadyHeaderItem)");
+			}
+		}
+
+		@Override
+		protected Response getRealResponse()
+		{
+			return RequestCycle.get().getResponse();
+		}
+	}
+
+	/**
+	 * Wrapper of a response.
+	 *
+	 * @author Igor Vaynberg (ivaynberg)
+	 * @author Sven Meier (svenmeier)
+	 * 
+	 * @see ResponseWrapper#getContents()
+	 * @see ResponseWrapper#reset()
+	 */
+	protected static final class ResponseWrapper extends WebResponse
+	{
+		private final AppendingStringBuffer buffer = new AppendingStringBuffer(256);
+
+		private final WebResponse originalResponse;
+
+		/**
+		 * Constructor.
+		 *
+		 * @param originalResponse
+		 *      the original request cycle response
+		 */
+		private ResponseWrapper(WebResponse originalResponse)
+		{
+			this.originalResponse = originalResponse;
+		}
+
+		/**
+		 * @see org.apache.wicket.request.Response#encodeURL(CharSequence)
+		 */
+		@Override
+		public String encodeURL(CharSequence url)
+		{
+			return originalResponse.encodeURL(url);
+		}
+
+		/**
+		 * @return contents of the response
+		 */
+		public CharSequence getContents()
+		{
+			return buffer;
+		}
+
+		/**
+		 * @see org.apache.wicket.request.Response#write(CharSequence)
+		 */
+		@Override
+		public void write(CharSequence cs)
+		{
+			buffer.append(cs);
+		}
+
+		/**
+		 * Resets the response to a clean state so it can be reused to save on garbage.
+		 */
+		@Override
+		public void reset()
+		{
+			buffer.clear();
+		}
+
+		@Override
+		public void write(byte[] array)
+		{
+			throw new UnsupportedOperationException("Cannot write binary data.");
+		}
+
+		@Override
+		public void write(byte[] array, int offset, int length)
+		{
+			throw new UnsupportedOperationException("Cannot write binary data.");
+		}
+
+		@Override
+		public Object getContainerResponse()
+		{
+			return originalResponse.getContainerResponse();
+		}
+
+		@Override
+		public void addCookie(Cookie cookie)
+		{
+			originalResponse.addCookie(cookie);
+		}
+
+		@Override
+		public void clearCookie(Cookie cookie)
+		{
+			originalResponse.clearCookie(cookie);
+		}
+
+		@Override
+		public void setHeader(String name, String value)
+		{
+			originalResponse.setHeader(name, value);
+		}
+
+		@Override
+		public void addHeader(String name, String value)
+		{
+			originalResponse.addHeader(name, value);
+		}
+
+		@Override
+		public void setDateHeader(String name, Time date)
+		{
+			originalResponse.setDateHeader(name, date);
+		}
+
+		@Override
+		public void setContentLength(long length)
+		{
+			originalResponse.setContentLength(length);
+		}
+
+		@Override
+		public void setContentType(String mimeType)
+		{
+			originalResponse.setContentType(mimeType);
+		}
+
+		@Override
+		public void setStatus(int sc)
+		{
+			originalResponse.setStatus(sc);
+		}
+
+		@Override
+		public void sendError(int sc, String msg)
+		{
+			originalResponse.sendError(sc, msg);
+		}
+
+		@Override
+		public String encodeRedirectURL(CharSequence url)
+		{
+			return originalResponse.encodeRedirectURL(url);
+		}
+
+		@Override
+		public void sendRedirect(String url)
+		{
+			originalResponse.sendRedirect(url);
+		}
+
+		@Override
+		public boolean isRedirect()
+		{
+			return originalResponse.isRedirect();
+		}
+
+		@Override
+		public void flush()
+		{
+			originalResponse.flush();
+		}
+	}
+
+	private void assertComponentsNotFrozen()
+	{
+		assertNotFrozen(componentsFrozen, Component.class);
+	}
+
+	private void assertNotFrozen(boolean frozen, Class<?> clazz)
+	{
+		if (frozen)
+		{
+			throw new IllegalStateException(Classes.simpleName(clazz) + "s can no " +
+					" longer be added");
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/7c40e4da/wicket-core/src/main/java/org/apache/wicket/ajax/XmlAjaxResponse.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/XmlAjaxResponse.java b/wicket-core/src/main/java/org/apache/wicket/ajax/XmlAjaxResponse.java
deleted file mode 100644
index e7048ec..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/XmlAjaxResponse.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * 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.ajax;
-
-import java.util.Collection;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.Page;
-import org.apache.wicket.request.Response;
-import org.apache.wicket.request.cycle.RequestCycle;
-import org.apache.wicket.request.http.WebResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * An AbstractAjaxResponse that serializes itself to XML.
- * <p>
- *     The elements of such response are:
- *     <ul>
- *         <li>priority-evaluate - an item of the prepend JavaScripts</li>
- *         <li>component - the markup of the updated component</li>
- *         <li>evaluate - an item of the onDomReady and append JavaScripts</li>
- *         <li>header-contribution - all HeaderItems which have been contributed in components'
- *         and their behaviors' #renderHead(Component, IHeaderResponse)</li>
- *     </ul>
- * </p>
- */
-public abstract class XmlAjaxResponse extends AbstractAjaxResponse
-{
-	private static final Logger LOG = LoggerFactory.getLogger(XmlAjaxResponse.class);
-
-	/**
-	 * The name of the root element in the produced XML document.
-	 */
-	public static final String START_ROOT_ELEMENT = "<ajax-response>";
-	public static final String END_ROOT_ELEMENT = "</ajax-response>";
-
-	public XmlAjaxResponse(final Page page)
-	{
-		super(page);
-	}
-
-	@Override
-	public void setContentType(WebResponse response, String encoding)
-	{
-		response.setContentType("text/xml; charset=" + encoding);
-	}
-
-	@Override
-	protected void writeHeader(Response response, String encoding)
-	{
-		response.write("<?xml version=\"1.0\" encoding=\"");
-		response.write(encoding);
-		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(
-					"Ajax render cannot be called on component that has setRenderBodyOnly 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
-		Response oldResponse = RequestCycle.get().setResponse(encodingBodyResponse);
-
-		try
-		{
-			encodingBodyResponse.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.
-				}
-				encodingBodyResponse.reset();
-				throw e;
-			}
-
-			try
-			{
-				component.render();
-			}
-			catch (RuntimeException e)
-			{
-				encodingBodyResponse.reset();
-				throw e;
-			}
-
-			page.endComponentRender(component);
-		}
-		finally
-		{
-			// Restore original response
-			RequestCycle.get().setResponse(oldResponse);
-		}
-
-		response.write("<component id=\"");
-		response.write(markupId);
-		response.write("\" ><![CDATA[");
-		response.write(encodingBodyResponse.getContents());
-		response.write("]]></component>");
-
-		encodingBodyResponse.reset();
-	}
-
-	@Override
-	protected void writeFooter(Response response, String encoding)
-	{
-		response.write(END_ROOT_ELEMENT);
-	}
-
-	@Override
-	protected void writeHeaderContribution(Response response)
-	{
-		if (encodingHeaderResponse.getContents().length() != 0)
-		{
-			response.write("<header-contribution>");
-
-			// we need to write response as CDATA and parse it on client,
-			// because konqueror crashes when there is a <script> element
-			response.write("<![CDATA[<head xmlns:wicket=\"http://wicket.apache.org\">");
-			response.write(encodingHeaderResponse.getContents());
-			response.write("</head>]]>");
-			response.write("</header-contribution>");
-		}
-	}
-
-	@Override
-	protected void writeNormalEvaluations(final Response response, final Collection<CharSequence> scripts)
-	{
-		writeEvaluations(response, "evaluate", scripts);
-
-	}
-
-	@Override
-	protected void writePriorityEvaluations(Response response, Collection<CharSequence> scripts)
-	{
-		writeEvaluations(response, "priority-evaluate", scripts);
-	}
-
-	private void writeEvaluations(final Response response, String elementName, Collection<CharSequence> scripts)
-	{
-		if (scripts.size() > 0)
-		{
-			StringBuilder combinedScript = new StringBuilder(1024);
-			for (CharSequence script : scripts)
-			{
-				combinedScript.append("(function(){").append(script).append("})();");
-			}
-			writeEvaluation(elementName, response, combinedScript);
-		}
-	}
-
-	/**
-	* @param invocation
-	*            type of invocation tag, usually {@literal evaluate} or
-	*            {@literal priority-evaluate}
-	* @param response
-	* @param js
-	*/
-	private void writeEvaluation(final String invocation, final Response response, final CharSequence js)
-	{
-		CharSequence javascript = js;
-
-		// encode the response if needed
-		if (needsEncoding(js))
-		{
-			javascript = encode(js);
-		}
-
-		response.write("<");
-		response.write(invocation);
-		response.write(">");
-
-		response.write("<![CDATA[");
-		response.write(javascript);
-		response.write("]]>");
-
-		response.write("</");
-		response.write(invocation);
-		response.write(">");
-
-		encodingBodyResponse.reset();
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/wicket/blob/7c40e4da/wicket-core/src/main/java/org/apache/wicket/ajax/XmlPartialPageUpdate.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/XmlPartialPageUpdate.java b/wicket-core/src/main/java/org/apache/wicket/ajax/XmlPartialPageUpdate.java
new file mode 100644
index 0000000..d3f6d73
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/XmlPartialPageUpdate.java
@@ -0,0 +1,234 @@
+/*
+ * 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.ajax;
+
+import java.util.Collection;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.Page;
+import org.apache.wicket.request.Response;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.http.WebResponse;
+import org.apache.wicket.util.string.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link PartialPageUpdate} that serializes itself to XML.
+ */
+public abstract class XmlPartialPageUpdate extends PartialPageUpdate
+{
+	private static final Logger LOG = LoggerFactory.getLogger(XmlPartialPageUpdate.class);
+
+	/**
+	 * The name of the root element in the produced XML document.
+	 */
+	public static final String START_ROOT_ELEMENT = "<ajax-response>";
+	public static final String END_ROOT_ELEMENT = "</ajax-response>";
+
+	public XmlPartialPageUpdate(final Page page)
+	{
+		super(page);
+	}
+
+	@Override
+	public void setContentType(WebResponse response, String encoding)
+	{
+		response.setContentType("text/xml; charset=" + encoding);
+	}
+
+	@Override
+	protected void writeHeader(Response response, String encoding)
+	{
+		response.write("<?xml version=\"1.0\" encoding=\"");
+		response.write(encoding);
+		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
+		Response oldResponse = RequestCycle.get().setResponse(encodingBodyResponse);
+
+		try
+		{
+			encodingBodyResponse.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.
+				}
+				encodingBodyResponse.reset();
+				throw e;
+			}
+
+			try
+			{
+				component.render();
+			}
+			catch (RuntimeException e)
+			{
+				encodingBodyResponse.reset();
+				throw e;
+			}
+
+			page.endComponentRender(component);
+		}
+		finally
+		{
+			// Restore original response
+			RequestCycle.get().setResponse(oldResponse);
+		}
+
+		response.write("<component id=\"");
+		response.write(markupId);
+		response.write("\" ><![CDATA[");
+		response.write(encode(encodingBodyResponse.getContents()));
+		response.write("]]></component>");
+
+		encodingBodyResponse.reset();
+	}
+
+	@Override
+	protected void writeFooter(Response response, String encoding)
+	{
+		response.write(END_ROOT_ELEMENT);
+	}
+
+	@Override
+	protected void writeHeaderContribution(Response response)
+	{
+		if (encodingHeaderResponse.getContents().length() != 0)
+		{
+			response.write("<header-contribution>");
+
+			// we need to write response as CDATA and parse it on client,
+			// because konqueror crashes when there is a <script> element
+			response.write("<![CDATA[<head xmlns:wicket=\"http://wicket.apache.org\">");
+			response.write(encode(encodingHeaderResponse.getContents()));
+			response.write("</head>]]>");
+			response.write("</header-contribution>");
+		}
+	}
+
+	@Override
+	protected void writeNormalEvaluations(final Response response, final Collection<CharSequence> scripts)
+	{
+		writeEvaluations(response, "evaluate", scripts);
+
+	}
+
+	@Override
+	protected void writePriorityEvaluations(Response response, Collection<CharSequence> scripts)
+	{
+		writeEvaluations(response, "priority-evaluate", scripts);
+	}
+
+	private void writeEvaluations(final Response response, String elementName, Collection<CharSequence> scripts)
+	{
+		if (scripts.size() > 0)
+		{
+			StringBuilder combinedScript = new StringBuilder(1024);
+			for (CharSequence script : scripts)
+			{
+				combinedScript.append("(function(){").append(script).append("})();");
+			}
+			writeEvaluation(elementName, response, combinedScript);
+		}
+	}
+
+	/**
+	* @param invocation
+	*            type of invocation tag, usually {@literal evaluate} or
+	*            {@literal priority-evaluate}
+	* @param response
+	* @param js
+	*/
+	private void writeEvaluation(final String invocation, final Response response, final CharSequence js)
+	{
+		response.write("<");
+		response.write(invocation);
+		response.write(">");
+
+		response.write("<![CDATA[");
+		response.write(encode(js));
+		response.write("]]>");
+
+		response.write("</");
+		response.write(invocation);
+		response.write(">");
+
+		encodingBodyResponse.reset();
+	}
+
+	protected CharSequence encode(CharSequence str)
+	{
+		/*
+		 * TODO Post 1.2: we can improve this by keeping a buffer of at least 3 characters and
+		 * checking that buffer so that we can narrow down escaping occurring only for ']]>'
+		 * sequence, or at least for ]] if ] is the last char in this buffer.
+		 *
+		 * but this improvement will only work if we write first and encode later instead of working
+		 * on fragments sent to write
+		 */
+		if (Strings.indexOf(str, ']') >= 0) {
+			str = Strings.replaceAll(str, "]]>", "]]]]><![CDATA[>").toString(); 
+		}
+		
+		return str;
+	}
+
+
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/7c40e4da/wicket-core/src/main/java/org/apache/wicket/response/filter/XmlCleaningResponseFilter.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/response/filter/XmlCleaningResponseFilter.java b/wicket-core/src/main/java/org/apache/wicket/response/filter/XmlCleaningResponseFilter.java
index 5552552..8920ece 100644
--- a/wicket-core/src/main/java/org/apache/wicket/response/filter/XmlCleaningResponseFilter.java
+++ b/wicket-core/src/main/java/org/apache/wicket/response/filter/XmlCleaningResponseFilter.java
@@ -16,7 +16,7 @@
  */
 package org.apache.wicket.response.filter;
 
-import org.apache.wicket.ajax.XmlAjaxResponse;
+import org.apache.wicket.ajax.XmlPartialPageUpdate;
 import org.apache.wicket.util.string.AppendingStringBuffer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -70,7 +70,7 @@ public class XmlCleaningResponseFilter implements IResponseFilter
 		// <?xml version="1.0" encoding="UTF-8" standalone="yes"?><ajax-response>
 		int min = Math.min(150, responseBuffer.length());
 		String firstNChars = responseBuffer.substring(0, min);
-		return firstNChars.contains(XmlAjaxResponse.START_ROOT_ELEMENT);
+		return firstNChars.contains(XmlPartialPageUpdate.START_ROOT_ELEMENT);
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/wicket/blob/7c40e4da/wicket-core/src/test/java/org/apache/wicket/response/filter/XmlCleaningResponseFilterTest.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/response/filter/XmlCleaningResponseFilterTest.java b/wicket-core/src/test/java/org/apache/wicket/response/filter/XmlCleaningResponseFilterTest.java
index 1c648b1..c255a59 100644
--- a/wicket-core/src/test/java/org/apache/wicket/response/filter/XmlCleaningResponseFilterTest.java
+++ b/wicket-core/src/test/java/org/apache/wicket/response/filter/XmlCleaningResponseFilterTest.java
@@ -16,8 +16,8 @@
  */
 package org.apache.wicket.response.filter;
 
-import static org.apache.wicket.ajax.XmlAjaxResponse.END_ROOT_ELEMENT;
-import static org.apache.wicket.ajax.XmlAjaxResponse.START_ROOT_ELEMENT;
+import static org.apache.wicket.ajax.XmlPartialPageUpdate.END_ROOT_ELEMENT;
+import static org.apache.wicket.ajax.XmlPartialPageUpdate.START_ROOT_ELEMENT;
 import org.apache.wicket.util.string.AppendingStringBuffer;
 import org.junit.Assert;
 import org.junit.Test;

http://git-wip-us.apache.org/repos/asf/wicket/blob/7c40e4da/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequestHandler.java
----------------------------------------------------------------------
diff --git a/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequestHandler.java b/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequestHandler.java
index 2a14374..6cbda67 100644
--- a/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequestHandler.java
+++ b/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequestHandler.java
@@ -23,8 +23,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import org.apache.wicket.Component;
 import org.apache.wicket.MarkupContainer;
 import org.apache.wicket.Page;
-import org.apache.wicket.ajax.AbstractAjaxResponse;
-import org.apache.wicket.ajax.XmlAjaxResponse;
+import org.apache.wicket.ajax.PartialPageUpdate;
+import org.apache.wicket.ajax.XmlPartialPageUpdate;
 import org.apache.wicket.core.request.handler.logger.PageLogData;
 import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.request.ILogData;
@@ -41,8 +41,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * An extension of AjaxRequestTarget that also supports pushing data from the server to the
- * client.
+ * A handler of WebSocket requests.
  *
  * @since 6.0
  */
@@ -54,11 +53,11 @@ public class WebSocketRequestHandler implements IWebSocketRequestHandler
 
 	private final IWebSocketConnection connection;
 
-	private final AbstractAjaxResponse ajaxResponse;
+	private final PartialPageUpdate update;
 
 	/**
 	 * A flag indicating that there is data to be written to construct an &lt;ajax-response&gt;
-	 * There is no need to push empty Ajax response if only #push() is used
+	 * There is no need to push empty response if only #push() is used
 	 */
 	private final AtomicBoolean hasData = new AtomicBoolean(false);
 
@@ -68,7 +67,7 @@ public class WebSocketRequestHandler implements IWebSocketRequestHandler
 	{
 		this.page = Args.notNull(component, "component").getPage();
 		this.connection = Args.notNull(connection, "connection");
-		this.ajaxResponse = new XmlAjaxResponse(page)
+		this.update = new XmlPartialPageUpdate(page)
 		{
 			@Override
 			protected void fireOnAfterRespondListeners(Response response)
@@ -126,7 +125,7 @@ public class WebSocketRequestHandler implements IWebSocketRequestHandler
 	public void add(Component component, String markupId)
 	{
 		hasData.set(true);
-		ajaxResponse.add(component, markupId);
+		update.add(component, markupId);
 	}
 
 	@Override
@@ -167,20 +166,20 @@ public class WebSocketRequestHandler implements IWebSocketRequestHandler
 	public void appendJavaScript(CharSequence javascript)
 	{
 		hasData.set(true);
-		ajaxResponse.appendJavaScript(javascript);
+		update.appendJavaScript(javascript);
 	}
 
 	@Override
 	public void prependJavaScript(CharSequence javascript)
 	{
 		hasData.set(true);
-		ajaxResponse.prependJavaScript(javascript);
+		update.prependJavaScript(javascript);
 	}
 
 	@Override
 	public Collection<? extends Component> getComponents()
 	{
-		return ajaxResponse.getComponents();
+		return update.getComponents();
 	}
 
 	@Override
@@ -200,7 +199,7 @@ public class WebSocketRequestHandler implements IWebSocketRequestHandler
 	public IHeaderResponse getHeaderResponse()
 	{
 		hasData.set(true);
-		return ajaxResponse.getHeaderResponse();
+		return update.getHeaderResponse();
 	}
 
 	@Override
@@ -258,7 +257,7 @@ public class WebSocketRequestHandler implements IWebSocketRequestHandler
 	{
 		if (hasData.get())
 		{
-			ajaxResponse.writeTo(requestCycle.getResponse(), "UTF-8");
+			update.writeTo(requestCycle.getResponse(), "UTF-8");
 		}
 	}
 
@@ -270,7 +269,7 @@ public class WebSocketRequestHandler implements IWebSocketRequestHandler
 			logData = new PageLogData(page);
 		}
 
-		ajaxResponse.detach(requestCycle);
+		update.detach(requestCycle);
 		hasData.set(false);
 	}
 }


[2/2] wicket git commit: WICKET-5929 remove ajax from page update

Posted by sv...@apache.org.
WICKET-5929 remove ajax from page update


Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/470f2a28
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/470f2a28
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/470f2a28

Branch: refs/heads/master
Commit: 470f2a286a51e05f78251f6d8f9a6837d42b2125
Parents: 7c40e4d
Author: Sven Meier <sv...@apache.org>
Authored: Sat Jun 27 09:00:38 2015 +0200
Committer: Sven Meier <sv...@apache.org>
Committed: Sat Jun 27 09:03:49 2015 +0200

----------------------------------------------------------------------
 .../apache/wicket/ajax/AjaxRequestHandler.java  | 75 ++++++++++----------
 .../apache/wicket/ajax/PartialPageUpdate.java   | 11 +--
 .../wicket/ajax/XmlPartialPageUpdate.java       |  2 +-
 .../ws/api/WebSocketRequestHandler.java         | 14 +---
 4 files changed, 44 insertions(+), 58 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/470f2a28/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxRequestHandler.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxRequestHandler.java b/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxRequestHandler.java
index 439319d..f7ab416 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxRequestHandler.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxRequestHandler.java
@@ -81,10 +81,9 @@ public class AjaxRequestHandler implements AjaxRequestTarget
 {
 
 	/**
-	 * A POJO-like that collects the data for the Ajax response written to the client and serializes
-	 * it to specific String-based format (XML, JSON, ...).
+	 * Collector of page updates.
 	 */
-	private final PartialPageUpdate responseObject;
+	private final PartialPageUpdate update;
 
 	/** a list of listeners */
 	private List<AjaxRequestTarget.IListener> listeners = null;
@@ -111,8 +110,28 @@ public class AjaxRequestHandler implements AjaxRequestTarget
 	{
 		this.page = Args.notNull(page, "page");
 
-		responseObject = new XmlPartialPageUpdate(page)
+		update = new XmlPartialPageUpdate(page)
 		{
+			/**
+			 * Freezes the {@link AjaxRequestHandler#listeners} before firing the event and
+			 * un-freezes them afterwards to allow components to add more
+			 * {@link AjaxRequestTarget.IListener}s for the second event.
+			 */
+			@Override
+			protected void onBeforeRespond(final Response response)
+			{
+				listenersFrozen = true;
+
+				if (listeners != null)
+				{
+					for (AjaxRequestTarget.IListener listener : listeners)
+					{
+						listener.onBeforeRespond(markupIdToComponent, AjaxRequestHandler.this);
+					}
+				}
+
+				listenersFrozen = false;
+			}
 
 			/**
 			 * Freezes the {@link AjaxRequestHandler#listeners}, and does not un-freeze them as the
@@ -122,7 +141,7 @@ public class AjaxRequestHandler implements AjaxRequestTarget
 			 *      the response to write to
 			 */
 			@Override
-			protected void fireOnAfterRespondListeners(final Response response)
+			protected void onAfterRespond(final Response response)
 			{
 				listenersFrozen = true;
 
@@ -148,28 +167,6 @@ public class AjaxRequestHandler implements AjaxRequestTarget
 					}
 				}
 			}
-
-			/**
-			 * Freezes the {@link AjaxRequestHandler#listeners} before firing the event and
-			 * un-freezes them afterwards to allow components to add more
-			 * {@link AjaxRequestTarget.IListener}s for the second event.
-			 */
-			@Override
-			protected void fireOnBeforeRespondListeners()
-			{
-				listenersFrozen = true;
-
-				if (listeners != null)
-				{
-					for (AjaxRequestTarget.IListener listener : listeners)
-					{
-						listener.onBeforeRespond(markupIdToComponent, AjaxRequestHandler.this);
-					}
-				}
-
-				listenersFrozen = false;
-			}
-
 		};
 	}
 
@@ -236,13 +233,13 @@ public class AjaxRequestHandler implements AjaxRequestTarget
 	@Override
 	public void add(Component component, String markupId)
 	{
-		responseObject.add(component, markupId);
+		update.add(component, markupId);
 	}
 
 	@Override
 	public final Collection<? extends Component> getComponents()
 	{
-		return responseObject.getComponents();
+		return update.getComponents();
 	}
 
 	@Override
@@ -261,7 +258,7 @@ public class AjaxRequestHandler implements AjaxRequestTarget
 	@Override
 	public final void appendJavaScript(CharSequence javascript)
 	{
-		responseObject.appendJavaScript(javascript);
+		update.appendJavaScript(javascript);
 	}
 
 	/**
@@ -275,7 +272,7 @@ public class AjaxRequestHandler implements AjaxRequestTarget
 			logData = new PageLogData(page);
 		}
 
-		responseObject.detach(requestCycle);
+		update.detach(requestCycle);
 	}
 
 	/**
@@ -287,7 +284,7 @@ public class AjaxRequestHandler implements AjaxRequestTarget
 		if (obj instanceof AjaxRequestHandler)
 		{
 			AjaxRequestHandler that = (AjaxRequestHandler)obj;
-			return responseObject.equals(that.responseObject);
+			return update.equals(that.update);
 		}
 		return false;
 	}
@@ -299,14 +296,14 @@ public class AjaxRequestHandler implements AjaxRequestTarget
 	public int hashCode()
 	{
 		int result = "AjaxRequestHandler".hashCode();
-		result += responseObject.hashCode() * 17;
+		result += update.hashCode() * 17;
 		return result;
 	}
 
 	@Override
 	public final void prependJavaScript(CharSequence javascript)
 	{
-		responseObject.prependJavaScript(javascript);
+		update.prependJavaScript(javascript);
 	}
 
 	@Override
@@ -350,20 +347,20 @@ public class AjaxRequestHandler implements AjaxRequestTarget
 		final String encoding = app.getRequestCycleSettings().getResponseRequestEncoding();
 
 		// Set content type based on markup type for page
-		responseObject.setContentType(response, encoding);
+		update.setContentType(response, encoding);
 
 		// Make sure it is not cached by a client
 		response.disableCaching();
 
 		final StringResponse bodyResponse = new StringResponse();
-		responseObject.writeTo(bodyResponse, encoding);
+		update.writeTo(bodyResponse, encoding);
 		CharSequence filteredResponse = invokeResponseFilters(bodyResponse);
 		response.write(filteredResponse);
 	}
 
 	private boolean shouldRedirectToPage(IRequestCycle requestCycle)
 	{
-		if (responseObject.containsPage())
+		if (update.containsPage())
 		{
 			return true;
 		}
@@ -411,13 +408,13 @@ public class AjaxRequestHandler implements AjaxRequestTarget
 	@Override
 	public String toString()
 	{
-		return "[AjaxRequestHandler@" + hashCode() + " responseObject [" + responseObject + "]";
+		return "[AjaxRequestHandler@" + hashCode() + " responseObject [" + update + "]";
 	}
 
 	@Override
 	public IHeaderResponse getHeaderResponse()
 	{
-		return responseObject.getHeaderResponse();
+		return update.getHeaderResponse();
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/wicket/blob/470f2a28/wicket-core/src/main/java/org/apache/wicket/ajax/PartialPageUpdate.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/PartialPageUpdate.java b/wicket-core/src/main/java/org/apache/wicket/ajax/PartialPageUpdate.java
index 5970f38..35ea016 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/PartialPageUpdate.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/PartialPageUpdate.java
@@ -153,13 +153,12 @@ public abstract class PartialPageUpdate
 	{
 		writeHeader(response, encoding);
 
-		// invoke onbeforerespond event on listeners
-		fireOnBeforeRespondListeners();
+		onBeforeRespond(response);
 
 		// process added components
 		writeComponents(response, encoding);
 
-		fireOnAfterRespondListeners(response);
+		onAfterRespond(response);
 
 		// queue up prepend javascripts. unlike other steps these are executed out of order so that
 		// components can contribute them from inside their onbeforerender methods.
@@ -175,9 +174,11 @@ public abstract class PartialPageUpdate
 		writeFooter(response, encoding);
 	}
 
-	protected abstract void fireOnAfterRespondListeners(Response response);
+	protected void onBeforeRespond(Response response) {
+	}
 
-	protected abstract void fireOnBeforeRespondListeners();
+	protected void onAfterRespond(Response response) {
+	}
 
 	/**
 	 * @param response

http://git-wip-us.apache.org/repos/asf/wicket/blob/470f2a28/wicket-core/src/main/java/org/apache/wicket/ajax/XmlPartialPageUpdate.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/XmlPartialPageUpdate.java b/wicket-core/src/main/java/org/apache/wicket/ajax/XmlPartialPageUpdate.java
index d3f6d73..fb157c5 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/XmlPartialPageUpdate.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/XmlPartialPageUpdate.java
@@ -30,7 +30,7 @@ import org.slf4j.LoggerFactory;
 /**
  * A {@link PartialPageUpdate} that serializes itself to XML.
  */
-public abstract class XmlPartialPageUpdate extends PartialPageUpdate
+public class XmlPartialPageUpdate extends PartialPageUpdate
 {
 	private static final Logger LOG = LoggerFactory.getLogger(XmlPartialPageUpdate.class);
 

http://git-wip-us.apache.org/repos/asf/wicket/blob/470f2a28/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequestHandler.java
----------------------------------------------------------------------
diff --git a/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequestHandler.java b/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequestHandler.java
index 6cbda67..5528f76 100644
--- a/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequestHandler.java
+++ b/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequestHandler.java
@@ -29,7 +29,6 @@ import org.apache.wicket.core.request.handler.logger.PageLogData;
 import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.request.ILogData;
 import org.apache.wicket.request.IRequestCycle;
-import org.apache.wicket.request.Response;
 import org.apache.wicket.request.component.IRequestablePage;
 import org.apache.wicket.request.http.WebRequest;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
@@ -67,18 +66,7 @@ public class WebSocketRequestHandler implements IWebSocketRequestHandler
 	{
 		this.page = Args.notNull(component, "component").getPage();
 		this.connection = Args.notNull(connection, "connection");
-		this.update = new XmlPartialPageUpdate(page)
-		{
-			@Override
-			protected void fireOnAfterRespondListeners(Response response)
-			{
-			}
-
-			@Override
-			protected void fireOnBeforeRespondListeners()
-			{
-			}
-		};
+		this.update = new XmlPartialPageUpdate(page);
 	}
 
 	@Override