You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by iv...@apache.org on 2009/10/11 02:02:29 UTC

svn commit: r823984 [5/11] - in /wicket/trunk: ./ wicket/src/main/java/org/apache/wicket/ng/ wicket/src/main/java/org/apache/wicket/ng/application/ wicket/src/main/java/org/apache/wicket/ng/behavior/ wicket/src/main/java/org/apache/wicket/ng/markup/ wi...

Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/BookmarkablePageRequestHandler.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/BookmarkablePageRequestHandler.java?rev=823984&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/BookmarkablePageRequestHandler.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/BookmarkablePageRequestHandler.java Sun Oct 11 00:02:25 2009
@@ -0,0 +1,69 @@
+/*
+ * 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.ng.request.handler.impl;
+
+import org.apache.wicket.ng.request.component.PageParameters;
+import org.apache.wicket.ng.request.component.RequestablePage;
+import org.apache.wicket.ng.request.cycle.RequestCycle;
+import org.apache.wicket.ng.request.handler.PageClassRequestHandler;
+import org.apache.wicket.ng.request.handler.PageProvider;
+import org.apache.wicket.ng.util.lang.Check;
+
+/**
+ * Request handler for bookmarkable pages. This handler is only used to generate URLs. Rendering is
+ * always handled by {@link RenderPageRequestHandler}.
+ * 
+ * @author Matej Knopp
+ */
+public class BookmarkablePageRequestHandler implements PageClassRequestHandler
+{
+	private final PageProvider pageProvider;
+  
+	/**
+	 * Construct.
+	 * 
+	 * @param pageProvider
+	 */
+	public BookmarkablePageRequestHandler(PageProvider pageProvider)
+	{
+		
+		Check.argumentNotNull(pageProvider, "pageProvider");
+		
+		this.pageProvider = pageProvider;
+	}
+
+	public Class<? extends RequestablePage> getPageClass()
+	{
+		return pageProvider.getPageClass();
+	}
+
+	public PageParameters getPageParameters()
+	{
+		return pageProvider.getPageParameters();
+	}
+
+	public void respond(RequestCycle requestCycle)
+	{
+		// not used as BookmarkablePageRequestHandler is only used when generating URLs.
+		// However URL will never be resolved to BookmarkablePageRequestTarget
+	}
+
+	public void detach(RequestCycle requestCycle)
+	{
+	}
+
+}

Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/BufferedResponseRequestHandler.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/BufferedResponseRequestHandler.java?rev=823984&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/BufferedResponseRequestHandler.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/BufferedResponseRequestHandler.java Sun Oct 11 00:02:25 2009
@@ -0,0 +1,27 @@
+package org.apache.wicket.ng.request.handler.impl;
+
+import org.apache.wicket.ng.request.RequestHandler;
+import org.apache.wicket.ng.request.cycle.RequestCycle;
+import org.apache.wicket.ng.request.response.BufferedWebResponse;
+import org.apache.wicket.ng.request.response.WebResponse;
+
+public class BufferedResponseRequestHandler implements RequestHandler
+{
+	private final BufferedWebResponse bufferedWebResponse;
+	
+	public BufferedResponseRequestHandler(BufferedWebResponse bufferedWebResponse)
+	{
+		this.bufferedWebResponse = bufferedWebResponse;
+	}
+	
+	public void detach(RequestCycle requestCycle)
+	{
+
+	}
+
+	public void respond(RequestCycle requestCycle)
+	{
+		bufferedWebResponse.writeTo((WebResponse) requestCycle.getResponse());
+	}
+
+}

Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/ListenerInterfaceRequestHandler.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/ListenerInterfaceRequestHandler.java?rev=823984&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/ListenerInterfaceRequestHandler.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/ListenerInterfaceRequestHandler.java Sun Oct 11 00:02:25 2009
@@ -0,0 +1,153 @@
+/*
+ * 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.ng.request.handler.impl;
+
+import org.apache.wicket.ng.WicketRuntimeException;
+import org.apache.wicket.ng.behavior.IBehavior;
+import org.apache.wicket.ng.request.component.PageParameters;
+import org.apache.wicket.ng.request.component.RequestableComponent;
+import org.apache.wicket.ng.request.component.RequestablePage;
+import org.apache.wicket.ng.request.cycle.RequestCycle;
+import org.apache.wicket.ng.request.handler.ComponentRequestHandler;
+import org.apache.wicket.ng.request.handler.PageAndComponentProvider;
+import org.apache.wicket.ng.request.handler.PageProvider;
+import org.apache.wicket.ng.request.handler.PageRequestHandler;
+import org.apache.wicket.ng.request.handler.impl.RenderPageRequestHandler.RedirectPolicy;
+import org.apache.wicket.ng.request.listener.RequestListenerInterface;
+import org.apache.wicket.ng.util.lang.Check;
+
+/**
+ * Request handler that invokes the listener interface on component and renders page afterwards.
+ * 
+ * @author Matej Knopp
+ */
+public class ListenerInterfaceRequestHandler implements PageRequestHandler, ComponentRequestHandler
+{
+	private final PageAndComponentProvider pageComponentProvider;
+	private final RequestListenerInterface listenerInterface;
+	private final Integer behaviorIndex;
+
+	/**
+	 * Construct.
+	 * 
+	 * @param pageComponentProvider
+	 * @param listenerInterface
+	 * @param behaviorIndex
+	 */
+	public ListenerInterfaceRequestHandler(PageAndComponentProvider pageComponentProvider,
+		RequestListenerInterface listenerInterface, Integer behaviorIndex)
+	{
+		Check.argumentNotNull(pageComponentProvider, "pageComponentProvider");
+		Check.argumentNotNull(listenerInterface, "listenerInterface");
+
+		this.pageComponentProvider = pageComponentProvider;
+		this.listenerInterface = listenerInterface;
+		this.behaviorIndex = behaviorIndex;
+	}
+	
+	/**
+	 * Construct.
+	 * 
+	 * @param pageComponentProvider
+	 * @param listenerInterface
+	 */
+	public ListenerInterfaceRequestHandler(PageAndComponentProvider pageComponentProvider,
+		RequestListenerInterface listenerInterface)
+	{
+		this(pageComponentProvider, listenerInterface, null);
+	}
+
+	public RequestableComponent getComponent()
+	{
+		return pageComponentProvider.getComponent();
+	}
+
+	public RequestablePage getPage()
+	{
+		return pageComponentProvider.getPageInstance();
+	}
+
+	public Class<? extends RequestablePage> getPageClass()
+	{
+		return pageComponentProvider.getPageClass();
+	}
+
+	public PageParameters getPageParameters()
+	{
+		return pageComponentProvider.getPageParameters();
+	}
+
+	public void detach(RequestCycle requestCycle)
+	{
+		pageComponentProvider.detach();
+	}
+
+	/**
+	 * Returns the listener interface.
+	 * 
+	 * @return listener interface
+	 */
+	public RequestListenerInterface getListenerInterface()
+	{
+		return listenerInterface;
+	}
+
+	/**
+	 * Index of target behavior or <code>null</code> if component is the target.
+	 * 
+	 * @return behavior index or <code>null</code>
+	 */
+	public Integer getBehaviorIndex()
+	{
+		return behaviorIndex;
+	}
+
+	public void respond(RequestCycle requestCycle)
+	{
+		if (getComponent().getPage() == getPage())
+		{
+			// schedule page render after current request handler is done. this can be overridden during invocation of listener
+			// method (i.e. by calling RequestCycle#setResponsePage)
+			RedirectPolicy policy = getPage().isPageStateless() ? RedirectPolicy.NEVER_REDIRECT : RedirectPolicy.AUTO_REDIRECT;
+			requestCycle.scheduleRequestHandlerAfterCurrent(new RenderPageRequestHandler(new PageProvider(getPage()), policy));
+
+			if (getBehaviorIndex() == null)
+			{
+				listenerInterface.invoke(getComponent());
+			}
+			else
+			{
+				try
+				{
+					IBehavior behavior = getComponent().getBehaviors().get(behaviorIndex);
+					listenerInterface.invoke(getComponent(), behavior);
+				}
+				catch (IndexOutOfBoundsException e)
+				{
+					throw new WicketRuntimeException("Couldn't find component behavior.");
+				}
+
+			}
+		}
+		else
+		{
+			throw new WicketRuntimeException("Component " + getComponent() +
+				" has been removed from page.");
+		}
+	}
+
+}

Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/RenderPageRequestHandler.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/RenderPageRequestHandler.java?rev=823984&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/RenderPageRequestHandler.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/RenderPageRequestHandler.java Sun Oct 11 00:02:25 2009
@@ -0,0 +1,132 @@
+/*
+ * 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.ng.request.handler.impl;
+
+import org.apache.wicket.ng.Application;
+import org.apache.wicket.ng.request.RequestHandler;
+import org.apache.wicket.ng.request.component.PageParameters;
+import org.apache.wicket.ng.request.component.RequestablePage;
+import org.apache.wicket.ng.request.cycle.RequestCycle;
+import org.apache.wicket.ng.request.handler.PageClassRequestHandler;
+import org.apache.wicket.ng.request.handler.PageProvider;
+import org.apache.wicket.ng.request.handler.PageRequestHandler;
+import org.apache.wicket.ng.request.handler.impl.render.RenderPageRequestHandlerDelegate;
+import org.apache.wicket.ng.util.lang.Check;
+
+/**
+ * {@link RequestHandler} that renders page instance. Depending on the <code>redirectPolicy</code>
+ * flag and current request strategy the handler either just renders the page to the response, or
+ * redirects to render the page. <code>REDIRECT_TO_BUFFER</code> strategy is also supported.
+ * <p>
+ * 
+ * @author Matej Knopp
+ */
+public class RenderPageRequestHandler implements PageRequestHandler, PageClassRequestHandler
+{
+	private final PageProvider pageProvider;
+	private final RedirectPolicy redirectPolicy;
+
+	/**
+	 * Determines whether Wicket does a redirect when rendering a page
+	 * 
+	 * @author Matej Knopp
+	 */
+	public enum RedirectPolicy
+	{
+		/**
+		 * Always redirect if current request URL is different than page URL.
+		 */
+		ALWAYS_REDIRECT,
+
+		/**
+		 * Never redirect - always render the page to current response.
+		 */
+		NEVER_REDIRECT,
+
+		/**
+		 * Redirect if necessary. The redirect will happen when all of the following conditions are
+		 * met:
+		 * <ul>
+		 * <li>current request URL is different than page URL
+		 * <li>page is not stateless or (page is stateless and session is not temporary)
+		 * <li>render strategy is either REDIRECT_TO_BUFFER or REDIRECT_TO_RENDER
+		 * </ul>
+		 */
+		AUTO_REDIRECT
+	};
+
+	/**
+	 * Construct. Renders the page with a redirect if necessary.
+	 * 
+	 * @param pageProvider
+	 */
+	public RenderPageRequestHandler(PageProvider pageProvider)
+	{
+		this(pageProvider, RedirectPolicy.AUTO_REDIRECT);
+	}
+
+	/**
+	 * Construct.
+	 * 
+	 * @param pageProvider
+	 * @param redirectPolicy
+	 */
+	public RenderPageRequestHandler(PageProvider pageProvider, RedirectPolicy redirectPolicy)
+	{
+		Check.argumentNotNull(pageProvider, "pageProvider");
+		Check.argumentNotNull(redirectPolicy, "redirectPolicy");
+
+		this.redirectPolicy = redirectPolicy;
+		this.pageProvider = pageProvider;
+	}
+
+	public PageProvider getPageProvider()
+	{
+		return pageProvider;
+	}
+	
+	public RedirectPolicy getRedirectPolicy()
+	{
+		return redirectPolicy;
+	}
+
+	public Class<? extends RequestablePage> getPageClass()
+	{
+		return pageProvider.getPageClass();		
+	}
+
+	public PageParameters getPageParameters()
+	{
+		return pageProvider.getPageParameters();
+	}
+	
+	public void detach(RequestCycle requestCycle)
+	{
+		pageProvider.detach();
+	}
+	
+	public RequestablePage getPage()
+	{
+		return pageProvider.getPageInstance();
+	}
+	
+	public void respond(RequestCycle requestCycle)
+	{
+		RenderPageRequestHandlerDelegate delegate = Application.get().getRenderPageRequestHandlerDelegate(this);
+		delegate.respond(requestCycle);
+	}
+}

Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/render/RenderPageRequestHandlerDelegate.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/render/RenderPageRequestHandlerDelegate.java?rev=823984&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/render/RenderPageRequestHandlerDelegate.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/render/RenderPageRequestHandlerDelegate.java Sun Oct 11 00:02:25 2009
@@ -0,0 +1,40 @@
+package org.apache.wicket.ng.request.handler.impl.render;
+
+import org.apache.wicket.ng.request.cycle.RequestCycle;
+import org.apache.wicket.ng.request.handler.PageProvider;
+import org.apache.wicket.ng.request.handler.impl.RenderPageRequestHandler;
+import org.apache.wicket.ng.request.handler.impl.RenderPageRequestHandler.RedirectPolicy;
+
+/**
+ * Delegate responsible for rendering the page. Depending on the implementation (web, test, portlet,
+ * etc.) the delegate may or may not support the redirect policy set in the
+ * {@link RenderPageRequestHandler}.
+ * 
+ * @author Matej Knopp
+ */
+public abstract class RenderPageRequestHandlerDelegate
+{
+	private final RenderPageRequestHandler renderPageRequestHandler;
+
+	public RenderPageRequestHandlerDelegate(RenderPageRequestHandler renderPageRequestHandler)
+	{
+		this.renderPageRequestHandler = renderPageRequestHandler;
+	}
+
+	public PageProvider getPageProvider()
+	{
+		return renderPageRequestHandler.getPageProvider();
+	}
+
+	public RedirectPolicy getRedirectPolicy()
+	{
+		return renderPageRequestHandler.getRedirectPolicy();
+	}
+
+	public RenderPageRequestHandler getRenderPageRequestHandler()
+	{
+		return renderPageRequestHandler;
+	}
+
+	public abstract void respond(RequestCycle requestCycle);
+}

Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/render/WebRenderPageRequestHandlerDelegate.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/render/WebRenderPageRequestHandlerDelegate.java?rev=823984&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/render/WebRenderPageRequestHandlerDelegate.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/impl/render/WebRenderPageRequestHandlerDelegate.java Sun Oct 11 00:02:25 2009
@@ -0,0 +1,226 @@
+package org.apache.wicket.ng.request.handler.impl.render;
+
+import org.apache.wicket.ng.Application;
+import org.apache.wicket.ng.Session;
+import org.apache.wicket.ng.protocol.http.WebApplication;
+import org.apache.wicket.ng.request.Url;
+import org.apache.wicket.ng.request.component.RequestablePage;
+import org.apache.wicket.ng.request.cycle.RequestCycle;
+import org.apache.wicket.ng.request.handler.impl.RenderPageRequestHandler;
+import org.apache.wicket.ng.request.handler.impl.RenderPageRequestHandler.RedirectPolicy;
+import org.apache.wicket.ng.request.response.BufferedWebResponse;
+import org.apache.wicket.ng.request.response.Response;
+import org.apache.wicket.ng.request.response.WebResponse;
+import org.apache.wicket.ng.settings.RequestCycleSettings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class WebRenderPageRequestHandlerDelegate extends RenderPageRequestHandlerDelegate
+{
+
+	public WebRenderPageRequestHandlerDelegate(RenderPageRequestHandler renderPageRequestHandler)
+	{
+		super(renderPageRequestHandler);
+	}
+
+	public RequestablePage getPage()
+	{
+		return getPageProvider().getPageInstance();
+	}
+
+	private boolean isOnePassRender()
+	{
+		return Application.get().getRequestCycleSettings().getRenderStrategy() == RequestCycleSettings.RenderStrategy.ONE_PASS_RENDER;
+	}
+
+	private boolean isRedirectToRender()
+	{
+		return Application.get().getRequestCycleSettings().getRenderStrategy() == RequestCycleSettings.RenderStrategy.REDIRECT_TO_RENDER;
+	}
+
+	private boolean isRedirectToBuffer()
+	{
+		return Application.get().getRequestCycleSettings().getRenderStrategy() == RequestCycleSettings.RenderStrategy.REDIRECT_TO_BUFFER;
+	}
+
+	private void renderPage()
+	{
+		getPage().renderPage();
+	}
+
+	protected String getSessionId()
+	{
+		return Session.get().getId();
+	}
+
+	protected boolean isSessionTemporary()
+	{
+		return Session.get().isTemporary();
+	}
+
+	protected BufferedWebResponse getAndRemoveBufferedResponse(Url url)
+	{
+		return WebApplication.get().getAndRemoveBufferedResponse(getSessionId(), url);
+	}
+
+	protected void storeBufferedResponse(Url url, BufferedWebResponse response)
+	{
+		WebApplication.get().storeBufferedResponse(getSessionId(), url, response);
+	}
+
+	/**
+	 * Renders page to a {@link BufferedWebResponse}. All URLs in page will be rendered relative to
+	 * <code>targetUrl</code>
+	 * 
+	 * @param targetUrl
+	 * @param requestCycle
+	 * @return BufferedWebResponse containing page body
+	 */
+	protected BufferedWebResponse renderPage(Url targetUrl, RequestCycle requestCycle)
+	{
+		// keep the original response
+		final Response originalResponse = requestCycle.getResponse();
+
+		// buffered web response for page
+		BufferedWebResponse response = new BufferedWebResponse()
+		{
+			@Override
+			public String encodeURL(String url)
+			{
+				return originalResponse.encodeURL(url);
+			}
+		};
+
+		// keep the original base URL
+		Url originalBaseUrl = requestCycle.getUrlRenderer().setBaseUrl(targetUrl);
+
+		try
+		{
+			requestCycle.setResponse(response);
+			getPage().renderPage();
+			return response;
+		}
+		finally
+		{
+			// restore original response and base URL
+			requestCycle.setResponse(originalResponse);
+			requestCycle.getUrlRenderer().setBaseUrl(originalBaseUrl);
+		}
+	}
+
+	private void redirectTo(Url url, RequestCycle requestCycle)
+	{
+		WebResponse response = (WebResponse) requestCycle.getResponse();
+		String relativeUrl = requestCycle.getUrlRenderer().renderUrl(url);
+		response.sendRedirect(relativeUrl);
+	}
+
+	public void respond(RequestCycle requestCycle)
+	{
+		Url currentUrl = requestCycle.getRequest().getUrl();
+		Url targetUrl = requestCycle.urlFor(getRenderPageRequestHandler());
+
+		//
+		// the code below is little hairy but we have to handle 3 redirect policies 
+		// and 3 rendering strategies
+		//
+		
+		// try to get an already rendered buffered response for current URL
+		BufferedWebResponse bufferedResponse = getAndRemoveBufferedResponse(currentUrl);
+
+		if (bufferedResponse != null)
+		{
+			logger.warn("The Buffered response should be handled by BufferedResponseRequestHandler");
+			// if there is saved response for this URL render it
+			bufferedResponse.writeTo((WebResponse) requestCycle.getResponse());
+		}
+		else if (getRedirectPolicy() == RedirectPolicy.NEVER_REDIRECT || isOnePassRender() // 
+				|| (targetUrl.equals(currentUrl) && !getPage().isPageStateless()) //
+				|| (targetUrl.equals(currentUrl) && isRedirectToRender() && getPage().isPageStateless()))
+		{
+			// if the policy is never to redirect
+			// or one pass render mode is on
+			// or the targetUrl matches current url and the page is not stateless
+			// or the targetUrl matches current url, page is stateless but it's redirect-to-render
+			// just render the page
+			renderPage();
+		}
+		else if (!targetUrl.equals(currentUrl) //
+				&& (getRedirectPolicy() == RedirectPolicy.ALWAYS_REDIRECT || isRedirectToRender()))
+		{
+			// if target URL is different
+			// and render policy is always-redirect or it's redirect-to-render
+			redirectTo(targetUrl, requestCycle);
+		}
+		else if (!targetUrl.equals(currentUrl) //
+				&& isSessionTemporary() && getPage().isPageStateless())		
+		{
+			// if target URL is different and session is temporary and page is stateless
+			// this is  special case when page is stateless but there is no session so we can't render it to buffer
+			
+			// note: if we had session here we would render the page to buffer and then redirect to URL generated 
+			// *after* page has been rendered (the statelessness may change during render). this would save one redirect
+			// because now we have to render to URL generated *before* page is rendered, render the page, get URL
+			// after render and if the URL is different (meaning page is not stateless), save the buffer and redirect again
+			// (which is pretty much what the next step does)
+			redirectTo(targetUrl, requestCycle);
+		}
+		else if (isRedirectToBuffer())
+		{
+			// redirect to buffer
+			BufferedWebResponse response = renderPage(targetUrl, requestCycle);
+
+			// check if the url hasn't changed after page has been rendered
+			// (i.e. the stateless flag might have changed which could result in different page url)
+			Url targetUrl2 = requestCycle.urlFor(this.getRenderPageRequestHandler());
+
+			if (targetUrl.getSegments().equals(targetUrl2.getSegments()) == false)
+			{
+				// the amount of segments is different - generated relative URLs will not work, we
+				// need to rerender the page. This shouldn't happen, but in theory it can - with
+				// RequestHandlerEncoders that produce different URLs with different amount of
+				// segments for stateless and stateful pages
+				response = renderPage(targetUrl2, requestCycle);
+			}
+
+			// if page is still stateless after render
+			if (getPage().isPageStateless() && !enableRedirectForStatelessPage())
+			{
+				// we don't want the redirect to happen for stateless page
+				// example:
+				// when a normal mounted stateful page is hit at /mount/point
+				// wicket renders the page to buffer and redirects to /mount/point?12
+				// but for stateless page the redirect is not necessary
+				// also for listener interface on stateful page we want to redirect
+				// after the listener is invoked, but on stateless page the user
+				// must ask for redirect explicitely
+				response.writeTo((WebResponse) requestCycle.getResponse());
+			}
+			else
+			{
+				storeBufferedResponse(targetUrl2, response);
+
+				redirectTo(targetUrl2, requestCycle);
+			}
+		}
+		else
+		{
+			throw new IllegalStateException("Unknown RenderStrategy.");
+		}
+	}
+
+	/**
+	 * When the page renders to buffer and it is still stateless after rendering, this flag
+	 * determines whether the redirect will take place or not.
+	 * <p>
+	 * Normally there is no reason for a stateless page to redirect
+	 * 
+	 * @return boolean value
+	 */
+	protected boolean enableRedirectForStatelessPage()
+	{
+		return false;
+	}
+
+	private static Logger logger = LoggerFactory.getLogger(WebRenderPageRequestHandlerDelegate.class);
+}

Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/resource/ResourceReferenceRequestHandler.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/resource/ResourceReferenceRequestHandler.java?rev=823984&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/resource/ResourceReferenceRequestHandler.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/resource/ResourceReferenceRequestHandler.java Sun Oct 11 00:02:25 2009
@@ -0,0 +1,73 @@
+/*
+ * 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.ng.request.handler.resource;
+
+import org.apache.wicket.ng.request.RequestHandler;
+import org.apache.wicket.ng.request.component.PageParameters;
+import org.apache.wicket.ng.request.cycle.RequestCycle;
+import org.apache.wicket.ng.resource.ResourceReference;
+import org.apache.wicket.ng.util.lang.Check;
+
+/**
+ * Request handler for {@link ResourceReference}. This handler is only used to generate URLs.
+ * 
+ * @author Matej Knopp
+ */
+public class ResourceReferenceRequestHandler implements RequestHandler
+{
+	private final ResourceReference resourceReference;
+	private final PageParameters pageParameters;
+	
+	/**
+	 * Construct.
+	 * 
+	 * @param resourceReference
+	 * @param pageParameters
+	 */
+	public ResourceReferenceRequestHandler(ResourceReference resourceReference, PageParameters pageParameters)
+	{
+		Check.argumentNotNull(resourceReference, "resourceReference");
+
+		this.resourceReference = resourceReference;
+		this.pageParameters = pageParameters;
+	}
+	
+	/**
+	 * @return resource reference
+	 */
+	public ResourceReference getResourceReference()
+	{
+		return resourceReference;
+	}
+	
+	/**
+	 * @return page parameters
+	 */
+	public PageParameters getPageParameters()
+	{
+		return pageParameters;
+	}
+
+	public void detach(RequestCycle requestCycle)
+	{
+	}
+
+	public void respond(RequestCycle requestCycle)
+	{
+	}
+
+}

Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/resource/ResourceRequestHandler.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/resource/ResourceRequestHandler.java?rev=823984&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/resource/ResourceRequestHandler.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/handler/resource/ResourceRequestHandler.java Sun Oct 11 00:02:25 2009
@@ -0,0 +1,100 @@
+/*
+ * 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.ng.request.handler.resource;
+
+import java.util.Locale;
+
+import org.apache.wicket.ng.request.RequestHandler;
+import org.apache.wicket.ng.request.component.PageParameters;
+import org.apache.wicket.ng.request.cycle.RequestCycle;
+import org.apache.wicket.ng.resource.Resource;
+import org.apache.wicket.ng.util.lang.Check;
+
+/**
+ * Request handler that renders a resource.
+ * 
+ * @author Matej Knopp
+ */
+public class ResourceRequestHandler implements RequestHandler
+{
+	private final Resource resource;
+	private final Locale locale;
+	private final String style;
+	private final PageParameters pageParameters;
+
+	/**
+	 * Construct.
+	 * 
+	 * @param resource
+	 * @param locale
+	 * @param style
+	 * @param pageParameters
+	 */
+	public ResourceRequestHandler(Resource resource, Locale locale, String style,
+		PageParameters pageParameters)
+	{
+		Check.argumentNotNull(resource, "resource");
+
+		this.resource = resource;
+		this.locale = locale;
+		this.style = style;
+		this.pageParameters = pageParameters != null ? pageParameters : new PageParameters();
+	}
+
+	/**
+	 * @return locale
+	 */
+	public Locale getLocale()
+	{
+		return locale;
+	}
+	
+	/**
+	 * @return style
+	 */
+	public String getStyle()
+	{
+		return style;
+	}
+	
+	/**
+	 * @return page parameters
+	 */
+	public PageParameters getPageParameters()
+	{
+		return pageParameters;
+	}
+	
+	/**
+	 * @return resource
+	 */
+	public Resource getResource()
+	{
+		return resource;
+	}
+
+	public void respond(RequestCycle requestCycle)
+	{
+		Resource.Attributes a = new Resource.Attributes(requestCycle.getRequest(),
+			requestCycle.getResponse(), locale, style, pageParameters);
+		resource.respond(a);
+	}
+
+	public void detach(RequestCycle requestCycle)
+	{
+	}
+}

Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/listener/IRequestListener.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/listener/IRequestListener.java?rev=823984&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/listener/IRequestListener.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/listener/IRequestListener.java Sun Oct 11 00:02:25 2009
@@ -0,0 +1,36 @@
+/*
+ * 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.ng.request.listener;
+
+import java.io.Serializable;
+
+
+/**
+ * Base interface for all interfaces that listen for requests from the client browser. All
+ * sub-interfaces of this interface must have a single method which takes no arguments.
+ * <p>
+ * New listener interfaces must be registered with Wicket by constructing a
+ * {@link RequestListenerInterface} object for the given interface class. The best way to do this is
+ * to create a public static final constant field in your request listener interface. Doing this
+ * will cause the interface to automatically register whenever it is used. For example, see
+ * {@link org.apache.wicket.IRedirectListener#INTERFACE}.
+ * 
+ * @author Jonathan Locke
+ */
+public interface IRequestListener extends Serializable
+{
+}

Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/listener/RequestListenerInterface.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/listener/RequestListenerInterface.java?rev=823984&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/listener/RequestListenerInterface.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/listener/RequestListenerInterface.java Sun Oct 11 00:02:25 2009
@@ -0,0 +1,266 @@
+/*
+ * 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.ng.request.listener;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.wicket.ng.WicketRuntimeException;
+import org.apache.wicket.ng.behavior.IBehavior;
+import org.apache.wicket.ng.request.component.RequestableComponent;
+import org.apache.wicket.ng.request.cycle.RequestHandlerStack.ReplaceHandlerException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Base class for request listener interfaces.
+ * 
+ * @author Jonathan Locke
+ */
+public class RequestListenerInterface
+{
+	/** Map from name to request listener interface */
+	private static final Map<String, RequestListenerInterface> interfaces = Collections.synchronizedMap(new HashMap<String, RequestListenerInterface>());
+
+	/** Log. */
+	private static final Logger log = LoggerFactory.getLogger(RequestListenerInterface.class);
+
+	/**
+	 * Looks up a request interface listener by name.
+	 * 
+	 * @param interfaceName
+	 *            The interface name
+	 * @return The RequestListenerInterface object, or null if none is found
+	 * 
+	 */
+	public static final RequestListenerInterface forName(final String interfaceName)
+	{
+		return interfaces.get(interfaceName);
+	}
+
+	/** The listener interface method */
+	private Method method;
+
+	/** The name of this listener interface */
+	private final String name;
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param listenerInterfaceClass
+	 *            The interface class, which must extend IRequestListener.
+	 */
+	public RequestListenerInterface(final Class<? extends IRequestListener> listenerInterfaceClass)
+	{
+		// Ensure that it extends IRequestListener
+		if (!IRequestListener.class.isAssignableFrom(listenerInterfaceClass))
+		{
+			throw new IllegalArgumentException("Class " + listenerInterfaceClass +
+				" must extend IRequestListener");
+		}
+
+
+		// Get interface methods
+		final Method[] methods = listenerInterfaceClass.getMethods();
+
+		// If there is only one method
+		if (methods.length == 1)
+		{
+			// and that method takes no parameters
+			if (methods[0].getParameterTypes().length == 0)
+			{
+				method = methods[0];
+			}
+			else
+			{
+				throw new IllegalArgumentException("Method " + methods[0] + " in interface " +
+					listenerInterfaceClass + " cannot take any arguments");
+			}
+		}
+		else
+		{
+			throw new IllegalArgumentException("Interface " + listenerInterfaceClass +
+				" can have only one method");
+		}
+
+		// Save short class name
+		name = listenerInterfaceClass.getSimpleName();
+
+		// Register this listener
+		register();
+	}
+
+	/**
+	 * @return The method for this request listener interface
+	 */
+	public final Method getMethod()
+	{
+		return method;
+	}
+
+	/**
+	 * @return The name of this request listener interface
+	 */
+	public final String getName()
+	{
+		return name;
+	}
+
+	/**
+	 * Invokes a given interface on a component.
+	 * 
+	 * @param component
+	 *            The component
+	 */
+	public final void invoke(final RequestableComponent component)
+	{
+		if (!component.canCallListenerInterface())
+		{
+			// just return so that we have a silent fail and just re-render the
+			// page
+			log.info("component not enabled or visible; ignoring call. Component: " + component);
+			return;
+		}
+
+		try
+		{
+			// Invoke the interface method on the component
+			method.invoke(component, new Object[] {});
+		}
+		catch (InvocationTargetException e)
+		{
+			// Honor redirect exception contract defined in IPageFactory
+			// TODO: 
+			if (/*e.getTargetException() instanceof AbstractRestartResponseException ||
+				e.getTargetException() instanceof AuthorizationException ||*/
+				e.getTargetException() instanceof ReplaceHandlerException ||
+				e.getTargetException() instanceof WicketRuntimeException)
+			{
+				throw (RuntimeException)e.getTargetException();
+			}
+			throw new WicketRuntimeException("Method " + method.getName() + " of " +
+				method.getDeclaringClass() + " targeted at component " + component +
+				" threw an exception", e);
+		}
+		catch (Exception e)
+		{
+			throw new WicketRuntimeException("Method " + method.getName() + " of " +
+				method.getDeclaringClass() + " targeted at component " + component +
+				" threw an exception", e);
+		}		
+	}
+
+	/**
+	 * Invokes a given interface on a component's behavior.
+	 * 
+	 * @param component
+	 *            The component
+	 * @param behavior
+	 */
+	public final void invoke(final RequestableComponent component, final IBehavior behavior)
+	{
+		if (!component.canCallListenerInterface())
+		{
+			// just return so that we have a silent fail and just re-render the
+			// page
+			log.info("component not enabled or visible; ignoring call. Component: " + component);
+			return;
+		}
+
+		try
+		{
+			// Invoke the interface method on the component
+			method.invoke(behavior, new Object[] {});
+		}
+		catch (InvocationTargetException e)
+		{
+			// Honor redirect exception contract defined in IPageFactory
+			// TODO
+			if (/*e.getTargetException() instanceof AbstractRestartResponseException ||
+				e.getTargetException() instanceof AuthorizationException ||*/
+				e.getTargetException() instanceof WicketRuntimeException)
+			{
+				throw (RuntimeException)e.getTargetException();
+			}
+			throw new WicketRuntimeException("Method " + method.getName() + " of " +
+				method.getDeclaringClass() + " targeted at behavior " + behavior +
+				" on component " + component + " threw an exception", e);
+		}
+		catch (Exception e)
+		{
+			throw new WicketRuntimeException("Method " + method.getName() + " of " +
+				method.getDeclaringClass() + " targeted at behavior " + behavior +
+				" on component " + component + " threw an exception", e);
+		}		
+	}
+
+
+	/**
+	 * Method to call to register this interface for use
+	 */
+	public void register()
+	{
+		// Register this listener interface
+		registerRequestListenerInterface(this);
+	}
+
+	/**
+	 * @see java.lang.Object#toString()
+	 */
+	@Override
+	public String toString()
+	{
+		return "[RequestListenerInterface name=" + name + ", method=" + method + "]";
+	}
+
+
+	/**
+	 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
+	 * <p>
+	 * In previous versions of Wicket, request listeners were manually registered by calling this
+	 * method. Now there is a first class RequestListenerInterface object which should be
+	 * constructed as a constant member of the interface to enable automatic interface registration.
+	 * <p>
+	 * Adds a request listener interface to the map of interfaces that can be invoked by outsiders.
+	 * 
+	 * @param requestListenerInterface
+	 *            The request listener interface object
+	 */
+	private final void registerRequestListenerInterface(
+		final RequestListenerInterface requestListenerInterface)
+	{
+		// Check that a different interface method with the same name has not
+		// already been registered
+		final RequestListenerInterface existingInterface = RequestListenerInterface.forName(requestListenerInterface.getName());
+		if (existingInterface != null &&
+			existingInterface.getMethod() != requestListenerInterface.getMethod())
+		{
+			throw new IllegalStateException("Cannot register listener interface " +
+				requestListenerInterface +
+				" because it conflicts with the already registered interface " + existingInterface);
+		}
+
+		// Save this interface method by the non-qualified class name
+		interfaces.put(requestListenerInterface.getName(), requestListenerInterface);
+
+		log.info("registered listener interface " + this);
+	}
+}

Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/AbstractBookmarkableMapper.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/AbstractBookmarkableMapper.java?rev=823984&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/AbstractBookmarkableMapper.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/AbstractBookmarkableMapper.java Sun Oct 11 00:02:25 2009
@@ -0,0 +1,298 @@
+/*
+ * 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.ng.request.mapper;
+
+import org.apache.wicket.ng.request.Request;
+import org.apache.wicket.ng.request.RequestHandler;
+import org.apache.wicket.ng.request.RequestMapper;
+import org.apache.wicket.ng.request.Url;
+import org.apache.wicket.ng.request.component.PageParameters;
+import org.apache.wicket.ng.request.component.RequestablePage;
+import org.apache.wicket.ng.request.handler.PageAndComponentProvider;
+import org.apache.wicket.ng.request.handler.PageProvider;
+import org.apache.wicket.ng.request.handler.impl.BookmarkableListenerInterfaceRequestHandler;
+import org.apache.wicket.ng.request.handler.impl.BookmarkablePageRequestHandler;
+import org.apache.wicket.ng.request.handler.impl.ListenerInterfaceRequestHandler;
+import org.apache.wicket.ng.request.handler.impl.RenderPageRequestHandler;
+import org.apache.wicket.ng.request.listener.RequestListenerInterface;
+import org.apache.wicket.ng.request.mapper.info.ComponentInfo;
+import org.apache.wicket.ng.request.mapper.info.PageComponentInfo;
+import org.apache.wicket.ng.request.mapper.info.PageInfo;
+import org.apache.wicket.ng.util.lang.Check;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract encoder for Bookmarkable, Hybrid and BookmarkableListenerInterface URLs.
+ * 
+ * @author Matej Knopp
+ */
+public abstract class AbstractBookmarkableMapper extends AbstractMapper
+{
+	/**
+	 * Construct.
+	 */
+	public AbstractBookmarkableMapper()
+	{
+	}
+
+	/**
+	 * Represents information stored in URL.
+	 * 
+	 * @author Matej Knopp
+	 */
+	protected static final class UrlInfo
+	{
+		private final PageComponentInfo pageComponentInfo;
+		private final PageParameters pageParameters;
+		private final Class<? extends RequestablePage> pageClass;
+
+		/**
+		 * Construct.
+		 * 
+		 * @param pageComponentInfo
+		 *            optional parameter providing the page instance and component information
+		 * @param pageClass
+		 *            mandatory parameter
+		 * @param pageParameters
+		 *            optional parameter providing pageParameters
+		 */
+		public UrlInfo(PageComponentInfo pageComponentInfo, Class<? extends RequestablePage> pageClass,
+				PageParameters pageParameters)
+		{
+			Check.argumentNotNull(pageClass, "pageClass");
+			
+			this.pageComponentInfo = pageComponentInfo;
+			this.pageParameters = pageParameters != null ? pageParameters : null;
+			this.pageClass = pageClass;
+		}
+
+		/**
+		 * @return PageComponentInfo instance or <code>null</code>
+		 */
+		public PageComponentInfo getPageComponentInfo()
+		{
+			return pageComponentInfo;
+		}
+
+		/**
+		 * @return page class
+		 */
+		public Class<? extends RequestablePage> getPageClass()
+		{
+			return pageClass;
+		}
+
+		/**
+		 * @return PageParameters instance (never <code>null</code>)
+		 */
+		public PageParameters getPageParameters()
+		{
+			return pageParameters;
+		}
+	}
+
+	/**
+	 * Parse the given request to an {@link UrlInfo} instance.
+	 * 
+	 * @param request
+	 * @return UrlInfo instance or <code>null</code> if this encoder can not handle the request
+	 */
+	protected abstract UrlInfo parseRequest(Request request);
+
+	/**
+	 * Builds URL for the given {@link UrlInfo} instance. The URL this method produces must be
+	 * parseable by the {@link #parseRequest(Request)} method.
+	 * 
+	 * @param info
+	 * @return Url result URL
+	 */
+	protected abstract Url buildUrl(UrlInfo info);
+
+	/**
+	 * Indicates whether hybrid {@link RenderPageRequestHandler} URL for page will be generated only
+	 * if page has been created with bookmarkable URL.
+	 * <p>
+	 * Generic bookmarkable encoders this method should return <code>true</code>. For explicit
+	 * (mounted) encoders this method should return <code>false</code>
+	 * 
+	 * @return <code>true</code> if hybrid URL requires page created bookmarkable,
+	 *         <code>false</code> otherwise.
+	 */
+	protected abstract boolean pageMustHaveBeenCreatedBookmarkable();
+	
+	/**
+	 * @see RequestMapper#getCompatibilityScore(Request)
+	 */
+	public abstract int getCompatibilityScore(Request request);
+	
+	private RequestHandler processBookmarkable(Class<? extends RequestablePage> pageClass, PageParameters pageParameters)
+	{
+		PageProvider provider = new PageProvider(pageClass, pageParameters);
+		provider.setPageSource(getContext());
+		return new RenderPageRequestHandler(provider);
+	}
+
+	private RequestHandler processHybrid(PageInfo pageInfo, Class<? extends RequestablePage> pageClass,
+			PageParameters pageParameters, Integer renderCount)
+	{
+		PageProvider provider = new PageProvider(pageInfo.getPageId(), pageClass, pageParameters,
+				renderCount);
+		provider.setPageSource(getContext());
+		return new RenderPageRequestHandler(provider);
+	}
+
+	private RequestHandler processListener(PageComponentInfo pageComponentInfo, Class<? extends RequestablePage> pageClass,
+			PageParameters pageParameters)
+	{
+		PageInfo pageInfo = pageComponentInfo.getPageInfo();
+		ComponentInfo componentInfo = pageComponentInfo.getComponentInfo();
+		Integer renderCount = componentInfo != null ? componentInfo.getRenderCount() : null;
+
+		RequestListenerInterface listenerInterface = requestListenerInterfaceFromString(componentInfo
+				.getListenerInterface());
+
+		if (listenerInterface != null)
+		{
+			PageAndComponentProvider provider = new PageAndComponentProvider(pageInfo.getPageId(), pageClass,
+					pageParameters, renderCount, componentInfo.getComponentPath());
+
+			provider.setPageSource(getContext());
+			
+			return new ListenerInterfaceRequestHandler(provider, listenerInterface, componentInfo.getBehaviorIndex());	
+		}
+		else
+		{
+			logger.warn("Unknown listener interface '" + componentInfo.getListenerInterface() + "'");
+			return null;
+		}		
+	}
+	
+	public RequestHandler mapRequest(Request request)
+	{
+		UrlInfo urlInfo = parseRequest(request);
+
+		// check if the URL is long enough and starts with the proper segments
+		if (urlInfo != null)
+		{
+			PageComponentInfo info = urlInfo.getPageComponentInfo();
+			Class<? extends RequestablePage> pageClass = urlInfo.getPageClass();
+			PageParameters pageParameters = urlInfo.getPageParameters();
+
+			if (info == null || info.getPageInfo().getPageId() == null)
+			{
+				// if there are is no page instance information (only page map name - optionally)
+				// then this is a simple bookmarkable URL
+				return processBookmarkable(pageClass, pageParameters);
+			}
+			else if (info.getPageInfo().getPageId() != null && info.getComponentInfo() == null)
+			{
+				// if there is page instance information in the URL but no component and listener
+				// interface then this is a hybrid URL - we need to try to reuse existing page
+				// instance
+				return processHybrid(info.getPageInfo(), pageClass, pageParameters, null);
+			}
+			else if (info.getComponentInfo() != null)
+			{
+				// with both page instance and component+listener this is a listener interface URL
+				return processListener(info, pageClass, pageParameters);
+			}
+		}
+		return null;
+	}
+	
+	protected boolean checkPageClass(Class<? extends RequestablePage> pageClass)
+	{
+		return true;
+	}
+
+	public Url mapHandler(RequestHandler requestHandler)
+	{
+		if (requestHandler instanceof BookmarkablePageRequestHandler)
+		{
+			// simple bookmarkable URL with no page instance information
+			BookmarkablePageRequestHandler handler = (BookmarkablePageRequestHandler) requestHandler;
+
+			if (!checkPageClass(handler.getPageClass()))
+			{
+				return null;
+			}
+
+			PageInfo info = new PageInfo();
+			UrlInfo urlInfo = new UrlInfo(new PageComponentInfo(info, null), handler.getPageClass(), handler
+					.getPageParameters());
+
+			return buildUrl(urlInfo);
+		}
+		else if (requestHandler instanceof RenderPageRequestHandler)
+		{
+			// possibly hybrid URL - bookmarkable URL with page instance information
+			// but only allowed if the page was created by bookamarkable URL
+
+			RenderPageRequestHandler handler = (RenderPageRequestHandler) requestHandler;
+			RequestablePage page = handler.getPage();
+
+			if (!checkPageClass(page.getClass()))
+			{
+				return null;
+			}
+
+			if (!pageMustHaveBeenCreatedBookmarkable() || page.wasCreatedBookmarkable())
+			{
+				PageInfo info = null;
+				if (!page.isPageStateless())
+				{
+					info = new PageInfo(page);
+				}
+				PageComponentInfo pageComponentInfo = info != null ? new PageComponentInfo(info, null) : null;
+
+				UrlInfo urlInfo = new UrlInfo(pageComponentInfo, page.getClass(), handler.getPageParameters());
+				return buildUrl(urlInfo);
+			}
+			else
+			{
+				return null;
+			}
+
+		}
+		else if (requestHandler instanceof BookmarkableListenerInterfaceRequestHandler)
+		{
+			// listener interface URL with page class information
+			BookmarkableListenerInterfaceRequestHandler handler = (BookmarkableListenerInterfaceRequestHandler) requestHandler;
+			RequestablePage page = handler.getPage();
+
+			if (!checkPageClass(page.getClass()))
+			{
+				return null;
+			}
+
+			PageInfo pageInfo = new PageInfo(page);
+			ComponentInfo componentInfo = new ComponentInfo(page.getRenderCount(),
+					requestListenerInterfaceToString(handler.getListenerInterface()), handler.getComponent().getPath(),
+					handler.getBehaviorIndex());
+
+			UrlInfo urlInfo = new UrlInfo(new PageComponentInfo(pageInfo, componentInfo), page.getClass(), handler
+					.getPageParameters());
+			return buildUrl(urlInfo);
+		}
+
+		return null;
+	}	
+
+	
+	private static Logger logger = LoggerFactory.getLogger(AbstractBookmarkableMapper.class);
+}

Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/AbstractMapper.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/AbstractMapper.java?rev=823984&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/AbstractMapper.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/AbstractMapper.java Sun Oct 11 00:02:25 2009
@@ -0,0 +1,295 @@
+/*
+ * 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.ng.request.mapper;
+
+import org.apache.wicket.ng.Application;
+import org.apache.wicket.ng.request.Request;
+import org.apache.wicket.ng.request.RequestMapper;
+import org.apache.wicket.ng.request.Url;
+import org.apache.wicket.ng.request.Url.QueryParameter;
+import org.apache.wicket.ng.request.component.PageParameters;
+import org.apache.wicket.ng.request.component.RequestablePage;
+import org.apache.wicket.ng.request.listener.RequestListenerInterface;
+import org.apache.wicket.ng.request.mapper.info.PageComponentInfo;
+import org.apache.wicket.ng.request.mapper.parameters.PageParametersEncoder;
+import org.apache.wicket.ng.util.lang.Check;
+import org.apache.wicket.ng.util.lang.Classes;
+import org.apache.wicket.ng.util.string.Strings;
+
+/**
+ * Convenience class for implementing page/components related encoders.
+ * 
+ * @author Matej Knopp
+ */
+public abstract class AbstractMapper implements RequestMapper
+{
+    protected MapperContext getContext()
+    {
+        return Application.get().getEncoderContext();
+    }
+
+    /**
+     * Converts the specified listener interface to String.
+     * 
+     * @param listenerInterface
+     * @return listenerInterface name as string
+     */
+    protected String requestListenerInterfaceToString(RequestListenerInterface listenerInterface)
+    {
+        Check.argumentNotNull(listenerInterface, "listenerInterface");
+
+        return getContext().requestListenerInterfaceToString(listenerInterface);
+    }
+
+    /**
+     * Creates listener interface from the specified string
+     * 
+     * @param interfaceName
+     * @return listener interface
+     */
+    protected RequestListenerInterface requestListenerInterfaceFromString(String interfaceName)
+    {
+        Check.argumentNotEmpty(interfaceName, "interfaceName");
+
+        return getContext().requestListenerInterfaceFromString(interfaceName);
+    }
+
+    /**
+     * If the string is in a placeholder format ${key} this method returns the key.
+     * 
+     * @param s
+     * @return placeholder key or <code>null</code> if string is not in right format
+     */
+    protected static String getPlaceholder(String s)
+    {
+        if (s == null || s.length() < 4 || !s.startsWith("${") || !s.endsWith("}"))
+        {
+            return null;
+        }
+        else
+        {
+            return s.substring(2, s.length() - 1);
+        }
+    }
+
+    /**
+     * Returns true if the given url starts with specified segments. Segments that contain
+     * placelhoders are not compared.
+     * 
+     * @param url
+     * @param segments
+     * @return <code>true</code> if the URL starts with the specified segments, <code>false</code>
+     *         otherwise
+     */
+    /*
+     * TODO its funny to have a dependency in this method on the #getPlaceHolder method because we
+     * are forcing everyone who extends this to use the ${placeholder} syntax. We should move
+     * #getPlaceHolder out and rewrite this method to have no dependency on it and match only exact
+     * paths. The mappers that use the syntax will have to provide their own implementations.
+     * 
+     * Either that or have a pluggable placeholder syntax Placeholder { string start, string end}
+     */
+    protected boolean urlStartsWith(Url url, String... segments)
+    {
+        if (url == null)
+        {
+            return false;
+        }
+        else
+        {
+            if (url.getSegments().size() < segments.length)
+            {
+                return false;
+            }
+            else
+            {
+                for (int i = 0; i < segments.length; ++i)
+                {
+                    if (segments[i].equals(url.getSegments().get(i)) == false &&
+                            getPlaceholder(segments[i]) == null)
+                    {
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Extracts the {@link PageComponentInfo} from the URL. The {@link PageComponentInfo} is encoded
+     * as the very first query parameter and the parameter consists of name only (no value).
+     * 
+     * @param url
+     * 
+     * @return PageComponentInfo instance if one was encoded in URL, <code>null</code> otherwise.
+     */
+    protected PageComponentInfo getPageComponentInfo(Url url)
+    {
+        if (url == null)
+        {
+            throw new IllegalStateException("Argument 'url' may not be null.");
+        }
+        if (url.getQueryParameters().size() > 0)
+        {
+            QueryParameter param = url.getQueryParameters().get(0);
+            if (Strings.isEmpty(param.getValue()))
+            {
+                return PageComponentInfo.parse(param.getName());
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Encodes the {@link PageComponentInfo} instance as the first query string parameter to the
+     * URL.
+     * 
+     * @param url
+     * @param info
+     */
+    protected void encodePageComponentInfo(Url url, PageComponentInfo info)
+    {
+        if (url == null)
+        {
+            throw new IllegalStateException("Argument 'url' may not be null.");
+        }
+        if (info != null)
+        {
+            String s = info.toString();
+            if (!Strings.isEmpty(s))
+            {
+                QueryParameter parameter = new QueryParameter(s, "");
+                url.getQueryParameters().add(parameter);
+            }
+        }
+    }
+
+    /**
+     * Loads page class with given name.
+     * 
+     * @param name
+     * @return class
+     */
+    protected Class< ? extends RequestablePage> getPageClass(String name)
+    {
+        Check.argumentNotEmpty(name, "name");
+
+        return Classes.resolveClass(name);
+    }
+
+    /**
+     * Extracts {@link PageParameters} from the URL using the given {@link PageParametersEncoder}.
+     * 
+     * @param request
+     * @param segmentsToSkip
+     *            how many URL segments should be skipped because they "belong" to the
+     *            {@link RequestMapper}
+     * @param encoder
+     * @return PageParameters instance
+     */
+    protected PageParameters extractPageParameters(Request request, int segmentsToSkip,
+            PageParametersEncoder encoder)
+    {
+        Check.argumentNotNull(request, "request");
+        Check.argumentNotNull(encoder, "encoder");
+
+        // strip the segments and first query parameter from URL
+        Url urlCopy = new Url(request.getUrl());
+        while (segmentsToSkip > 0 && urlCopy.getSegments().isEmpty() == false)
+        {
+            urlCopy.getSegments().remove(0);
+            --segmentsToSkip;
+        }
+
+        if (!urlCopy.getQueryParameters().isEmpty() &&
+                Strings.isEmpty(urlCopy.getQueryParameters().get(0).getValue()))
+        {
+            urlCopy.getQueryParameters().remove(0);
+        }
+
+        PageParameters decoded = encoder.decodePageParameters(request.requestWithUrl(urlCopy));
+        return decoded != null ? decoded : new PageParameters();
+    }
+
+    /**
+     * Encodes the given {@link PageParameters} to the URL using the given
+     * {@link PageParametersEncoder}. The original URL object is unchanged.
+     * 
+     * @param url
+     * @param pageParameters
+     * @param encoder
+     * @return URL with encoded parameters
+     */
+    protected Url encodePageParameters(Url url, PageParameters pageParameters,
+            PageParametersEncoder encoder)
+    {
+        Check.argumentNotNull(url, "url");
+        Check.argumentNotNull(encoder, "encoder");
+
+        if (pageParameters == null)
+        {
+            pageParameters = new PageParameters();
+        }
+
+        Url parametersUrl = encoder.encodePageParameters(pageParameters);
+        if (parametersUrl != null)
+        {
+            // copy the url
+            url = new Url(url);
+
+            for (String s : parametersUrl.getSegments())
+            {
+                url.getSegments().add(s);
+            }
+            for (QueryParameter p : parametersUrl.getQueryParameters())
+            {
+                url.getQueryParameters().add(p);
+            }
+        }
+
+        return url;
+    }
+
+    /**
+     * Convenience method for representing mountPath as array of segments
+     * 
+     * @param mountPath
+     * @return array of path segments
+     */
+    protected String[] getMountSegments(String mountPath)
+    {
+        if (mountPath.startsWith("/"))
+        {
+            mountPath = mountPath.substring(1);
+        }
+        Url url = Url.parse(mountPath);
+
+        if (url.getSegments().isEmpty())
+        {
+            throw new IllegalArgumentException("Mount path must have at least one segment.");
+        }
+
+        String[] res = new String[url.getSegments().size()];
+        for (int i = 0; i < res.length; ++i)
+        {
+            res[i] = url.getSegments().get(i);
+        }
+        return res;
+    }
+
+}

Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/AbstractResourceReferenceMapper.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/AbstractResourceReferenceMapper.java?rev=823984&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/AbstractResourceReferenceMapper.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/AbstractResourceReferenceMapper.java Sun Oct 11 00:02:25 2009
@@ -0,0 +1,133 @@
+/*
+ * 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.ng.request.mapper;
+
+import java.util.Locale;
+
+import org.apache.wicket.ng.request.Url;
+import org.apache.wicket.ng.request.Url.QueryParameter;
+import org.apache.wicket.ng.resource.ResourceReference;
+import org.apache.wicket.ng.util.string.Strings;
+
+/**
+ * Base class for encoding and decoding {@link ResourceReference}s
+ * 
+ * @author Matej Knopp
+ */
+public abstract class AbstractResourceReferenceMapper extends AbstractMapper
+{
+
+	protected static class ResourceReferenceAttributes
+	{
+		protected Locale locale;
+		protected String style;
+	};
+	
+	protected static String encodeResourceReferenceAttributes(ResourceReferenceAttributes attributes)
+	{
+		if (attributes == null || (attributes.locale == null && attributes.style == null))
+		{
+			return null;
+		}
+		else
+		{
+			StringBuilder res = new StringBuilder();
+			if (attributes.locale != null)
+			{
+				res.append(attributes.locale.toString());
+			}
+			if (!Strings.isEmpty(attributes.style))
+			{
+				res.append("-");
+				res.append(attributes.style);
+			}
+			return res.toString();
+		}
+	};
+	
+	protected static ResourceReferenceAttributes decodeResourceReferenceAttributes(String attributes)
+	{
+		ResourceReferenceAttributes res = new ResourceReferenceAttributes();		
+		if (!Strings.isEmpty(attributes))
+		{
+			String split[] = attributes.split("-", 2);
+			res.locale = parseLocale(split[0]);
+			if (split.length == 2)
+			{
+				res.style = split[1];
+			}			
+		}
+		return res;
+	}
+	
+	private static Locale parseLocale(String locale)
+	{
+		if (Strings.isEmpty(locale))
+		{
+			return null;
+		}
+		else
+		{
+			String parts[] = locale.toLowerCase().split("_", 3);
+			if (parts.length == 1)
+			{
+				return new Locale(parts[0]);
+			}
+			else if (parts.length == 2)
+			{
+				return new Locale(parts[0], parts[1]);
+			}
+			else if (parts.length == 3)
+			{
+				return new Locale(parts[0], parts[1], parts[2]);	
+			}
+			else
+			{
+				return null;
+			}
+		}
+	}	
+	
+	protected void encodeResourceReferenceAttributes(Url url, ResourceReference reference)
+	{
+		ResourceReferenceAttributes attributes = new ResourceReferenceAttributes();
+		attributes.locale = reference.getLocale();
+		attributes.style = reference.getStyle();
+		String encoded = encodeResourceReferenceAttributes(attributes);
+		if (!Strings.isEmpty(encoded))
+		{
+			url.getQueryParameters().add(new Url.QueryParameter(encoded, ""));
+		}
+	}
+	
+	protected ResourceReferenceAttributes getResourceReferenceAttributes(Url url)
+	{
+		if (url == null)
+		{
+			throw new IllegalStateException("Argument 'url' may not be null.");
+		}
+		if (url.getQueryParameters().size() > 0)
+		{
+			QueryParameter param = url.getQueryParameters().get(0);
+			if (Strings.isEmpty(param.getValue()))
+			{
+				return decodeResourceReferenceAttributes(param.getName());
+			}
+		}
+		return new ResourceReferenceAttributes();
+	}
+}

Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/BookmarkableMapper.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/BookmarkableMapper.java?rev=823984&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/BookmarkableMapper.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/BookmarkableMapper.java Sun Oct 11 00:02:25 2009
@@ -0,0 +1,124 @@
+/*
+ * 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.ng.request.mapper;
+
+import org.apache.wicket.ng.request.Request;
+import org.apache.wicket.ng.request.Url;
+import org.apache.wicket.ng.request.component.PageParameters;
+import org.apache.wicket.ng.request.component.RequestablePage;
+import org.apache.wicket.ng.request.mapper.info.PageComponentInfo;
+import org.apache.wicket.ng.request.mapper.parameters.PageParametersEncoder;
+import org.apache.wicket.ng.request.mapper.parameters.SimplePageParametersEncoder;
+import org.apache.wicket.ng.util.lang.Check;
+
+/**
+ * Decodes and encodes the following URLs:
+ * 
+ * <pre>
+ *  Page Class - Render (BookmarkablePageRequestHandler)
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage?pageMap
+ *  (these will redirect to hybrid alternative if page is not stateless)
+ * 
+ *  Page Instance - Render Hybrid (RenderPageRequestHandler for pages that were created using bookmarkable URLs)
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage?2
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage?2.4
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage?pageMap.2.4
+ * 
+ *  Page Instance - Bookmarkable Listener (BookmarkableListenerInterfaceRequestHandler)
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage?2-click-foo-bar-baz
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage?2.4-click-foo-bar-baz
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage?pageMap.2.4-click-foo-bar-baz
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage?2.4-click.1-foo-bar-baz (1 is behavior index)
+ *  (these will redirect to hybrid if page is not stateless)
+ * </pre>
+ * 
+ * @author Matej Knopp
+ */
+public class BookmarkableMapper extends AbstractBookmarkableMapper
+{
+	private final PageParametersEncoder pageParametersEncoder;
+
+	/**
+	 * Construct.
+	 * 
+	 * @param pageParametersEncoder
+	 */
+	public BookmarkableMapper(PageParametersEncoder pageParametersEncoder)
+	{
+		Check.argumentNotNull(pageParametersEncoder, "pageParametersEncoder");
+		
+		this.pageParametersEncoder = pageParametersEncoder;
+	}
+
+	/**
+	 * Construct.
+	 */
+	public BookmarkableMapper()
+	{
+		this(new SimplePageParametersEncoder());
+	}
+
+	@Override
+	protected Url buildUrl(UrlInfo info)
+	{
+		Url url = new Url();
+		url.getSegments().add(getContext().getNamespace());
+		url.getSegments().add(getContext().getBookmarkableIdentifier());
+		url.getSegments().add(info.getPageClass().getName());
+
+		encodePageComponentInfo(url, info.getPageComponentInfo());
+
+		return encodePageParameters(url, info.getPageParameters(), pageParametersEncoder);
+	}
+
+	@Override
+	protected UrlInfo parseRequest(Request request)
+	{
+		Url url = request.getUrl();
+		if (url.getSegments().size() >= 3 &&
+			urlStartsWith(url, getContext().getNamespace(),
+				getContext().getBookmarkableIdentifier()))
+		{
+			// try to extract page and component information from URL
+			PageComponentInfo info = getPageComponentInfo(url);
+
+			// load the page class
+			String className = url.getSegments().get(2);
+			Class<? extends RequestablePage> pageClass = getPageClass(className);
+
+			// extract the PageParameters from URL if there are any
+			PageParameters pageParameters = extractPageParameters(request, 3, pageParametersEncoder);
+
+			return new UrlInfo(info, pageClass, pageParameters);
+		}
+		return null;
+	}
+	
+	@Override
+	protected boolean pageMustHaveBeenCreatedBookmarkable()
+	{
+		return true;
+	}
+
+	public int getCompatibilityScore(Request request)
+	{
+		// always return 0 here so that the mounts have higher priority
+		return 0;
+	}
+
+}

Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/BufferedResponseMapper.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/BufferedResponseMapper.java?rev=823984&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/BufferedResponseMapper.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/BufferedResponseMapper.java Sun Oct 11 00:02:25 2009
@@ -0,0 +1,99 @@
+package org.apache.wicket.ng.request.mapper;
+
+import org.apache.wicket.ng.Session;
+import org.apache.wicket.ng.protocol.http.WebApplication;
+import org.apache.wicket.ng.request.Request;
+import org.apache.wicket.ng.request.RequestHandler;
+import org.apache.wicket.ng.request.RequestMapper;
+import org.apache.wicket.ng.request.Url;
+import org.apache.wicket.ng.request.cycle.RequestCycle;
+import org.apache.wicket.ng.request.handler.impl.BufferedResponseRequestHandler;
+import org.apache.wicket.ng.request.response.BufferedWebResponse;
+
+/**
+ * Encoder that intercepts requests for which there is already stored buffer with rendered data.
+ * 
+ * @author Matej Knopp
+ */
+public class BufferedResponseMapper implements RequestMapper
+{
+
+	public BufferedResponseMapper()
+	{
+	}
+
+	protected String getSessionId()
+	{
+		return Session.get().getId();
+	}
+
+	protected boolean hasBufferedResponse(Url url)
+	{
+		String sessionId = getSessionId();
+		if (sessionId != null)
+		{
+			return WebApplication.get().hasBufferedResponse(sessionId, url);
+		}
+		else
+		{
+			return false;
+		}		
+	}
+
+	protected BufferedWebResponse getAndRemoveBufferedResponse(Url url)
+	{
+		return WebApplication.get().getAndRemoveBufferedResponse(getSessionId(), url);
+	}
+	
+	private Request getRequest(Request original)
+	{
+		// The buffers are stored under "real" URL which can be different
+		// than the URL handlers get due to global URL pre/postprocessing
+		// (i.e. prepending URL with language segment).
+		// Because of that we need find out the real URL from request cycle
+		
+		if (RequestCycle.get() != null)
+		{
+			return RequestCycle.get().getRequest();
+		}
+		else
+		{
+			return original;
+		}
+	}
+	
+	public RequestHandler mapRequest(Request request)
+	{		
+		request = getRequest(request);
+		
+		BufferedWebResponse response = getAndRemoveBufferedResponse(request.getUrl());
+		if (response != null)
+		{
+			return new BufferedResponseRequestHandler(response);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public Url mapHandler(RequestHandler requestHandler)
+	{
+		return null;
+	}
+
+	public int getCompatibilityScore(Request request)
+	{
+		request = getRequest(request);
+		
+		if (hasBufferedResponse(request.getUrl()))
+		{
+			return Integer.MAX_VALUE;
+		}
+		else
+		{
+			return 0;
+		}
+	}
+
+}

Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/MapperContext.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/MapperContext.java?rev=823984&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/MapperContext.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/MapperContext.java Sun Oct 11 00:02:25 2009
@@ -0,0 +1,77 @@
+/*
+ * 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.ng.request.mapper;
+
+import org.apache.wicket.ng.request.component.RequestablePage;
+import org.apache.wicket.ng.request.listener.RequestListenerInterface;
+import org.apache.wicket.ng.resource.ResourceReferenceRegistry;
+
+/**
+ * Utility interface for providing and creating new page instances.
+ * 
+ * @author Matej Knopp
+ */
+public interface MapperContext extends PageSource
+{
+	/**
+	 * @return the namespace for Wicket URLs.
+	 */
+	public String getNamespace();
+	
+	/**
+	 * @return identifier for non bookmarkable URLs
+	 */
+	public String getPageIdentifier();
+	
+	/**
+	 * @return identifier for bookmarkable URLs
+	 */
+	public String getBookmarkableIdentifier();
+	
+	/**
+	 * @return identifier for resources
+	 */
+	public String getResourceIdentifier();
+	
+	/**
+	 * @return {@link ResourceReferenceRegistry}
+	 */
+	public ResourceReferenceRegistry getResourceReferenceRegistry();
+	
+	/**
+	 * Returns the listener interface name as string.
+	 * 
+	 * @param listenerInterface
+	 * @return listener interface name as string
+	 */
+	public String requestListenerInterfaceToString(RequestListenerInterface listenerInterface);
+	
+	/**
+	 * Returns listener interface for the name
+	 * 
+	 * @param interfaceName
+	 * @return listener interface
+	 */
+	public RequestListenerInterface requestListenerInterfaceFromString(String interfaceName);
+	
+	/**
+	 * Returns the home page class.
+	 * 
+	 * @return home page class
+	 */
+	public Class<? extends RequestablePage> getHomePageClass();
+}

Added: wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/MountedMapper.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/MountedMapper.java?rev=823984&view=auto
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/MountedMapper.java (added)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/ng/request/mapper/MountedMapper.java Sun Oct 11 00:02:25 2009
@@ -0,0 +1,221 @@
+/*
+ * 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.ng.request.mapper;
+
+import java.lang.ref.WeakReference;
+
+import org.apache.wicket.ng.request.Request;
+import org.apache.wicket.ng.request.Url;
+import org.apache.wicket.ng.request.component.PageParameters;
+import org.apache.wicket.ng.request.component.RequestablePage;
+import org.apache.wicket.ng.request.mapper.info.PageComponentInfo;
+import org.apache.wicket.ng.request.mapper.parameters.PageParametersEncoder;
+import org.apache.wicket.ng.request.mapper.parameters.SimplePageParametersEncoder;
+import org.apache.wicket.ng.util.lang.Check;
+
+/**
+ * Encoder for mounted URL. The mount path can contain parameter placeholders, i.e.
+ * <code>/mount/${foo}/path</code>. In that case the appropriate segment from the URL will be
+ * accessible as named parameter "foo" in the {@link PageParameters}. Similarly when the URL is
+ * constructed, the second segment will contain the value of the "foo" named page parameter.
+ * <p>
+ * Decodes and encodes the following URLs:
+ * 
+ * <pre>
+ *  Page Class - Render (BookmarkablePageRequestHandler for mounted pages)
+ *  /mount/point
+ *  (these will redirect to hybrid alternative if page is not stateless)
+ * 
+ *  IPage Instance - Render Hybrid (RenderPageRequestHandler for mounted pages) 
+ *  /mount/point?2
+ * 
+ *  IPage Instance - Bookmarkable Listener (BookmarkableListenerInterfaceRequestHandler for mounted pages) 
+ *  /mount/point?2-click-foo-bar-baz
+ *  /mount/point?2-5.click.1-foo-bar-baz (1 is behavior index, 5 is render count)
+ *  (these will redirect to hybrid if page is not stateless)
+ * </pre>
+ * 
+ * @author Matej Knopp
+ */
+public class MountedMapper extends AbstractBookmarkableMapper
+{
+	private final PageParametersEncoder pageParametersEncoder;
+	private final String[] mountSegments;
+
+	/** bookmarkable page class. */
+	protected final WeakReference<Class<? extends RequestablePage>> pageClass;
+
+	/**
+	 * Construct.
+	 * 
+	 * @param mountPath
+	 * @param pageClass
+	 * @param pageParametersEncoder
+	 */
+	public MountedMapper(String mountPath, Class<? extends RequestablePage> pageClass,
+			PageParametersEncoder pageParametersEncoder)
+	{
+		Check.argumentNotEmpty(mountPath, "mountPath");
+		Check.argumentNotNull(pageClass, "pageClass");
+		Check.argumentNotNull(pageParametersEncoder, "pageParametersEncoder");
+
+		this.pageParametersEncoder = pageParametersEncoder;
+		this.pageClass = new WeakReference<Class<? extends RequestablePage>>(pageClass);
+		this.mountSegments = getMountSegments(mountPath);
+	}
+
+	/**
+	 * Construct.
+	 * 
+	 * @param mountPath
+	 * @param pageClass
+	 */
+	public MountedMapper(String mountPath, Class<? extends RequestablePage> pageClass)
+	{
+		this(mountPath, pageClass, new SimplePageParametersEncoder());
+	}
+
+	@Override
+	protected UrlInfo parseRequest(Request request)
+	{
+		Url url = request.getUrl();
+
+		// when redirect to buffer/render is active and redirectFromHomePage returns true
+		// check mounted class against the home page class. if it matches let wicket redirect
+		// to the mounted URL
+		if (redirectFromHomePage() && checkHomePage(url))
+		{
+			UrlInfo info = new UrlInfo(null, getContext().getHomePageClass(), newPageParameters());
+			return info;
+		}
+		// check if the URL is long enough and starts with the proper segments
+		else if (url.getSegments().size() >= mountSegments.length && urlStartsWith(url, mountSegments))
+		{
+			// try to extract page and component information from URL
+			PageComponentInfo info = getPageComponentInfo(url);
+
+			Class<? extends RequestablePage> pageClass = this.pageClass.get();
+
+			// extract the PageParameters from URL if there are any
+			PageParameters pageParameters = extractPageParameters(request, mountSegments.length, pageParametersEncoder);
+
+			// check if there are placeholders in mount segments
+			for (int i = 0; i < mountSegments.length; ++i)
+			{
+				String placeholder = getPlaceholder(mountSegments[i]);
+				if (placeholder != null)
+				{
+					// extract the parameter from URL
+					pageParameters.addNamedParameter(placeholder, url.getSegments().get(i));
+				}
+			}
+
+			return new UrlInfo(info, pageClass, pageParameters);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	protected PageParameters newPageParameters()
+	{
+		return new PageParameters();
+	}
+
+	@Override
+	protected Url buildUrl(UrlInfo info)
+	{
+		Url url = new Url();
+		for (String s : mountSegments)
+		{
+			url.getSegments().add(s);
+		}
+		encodePageComponentInfo(url, info.getPageComponentInfo());
+
+		PageParameters copy = new PageParameters(info.getPageParameters());
+
+		for (int i = 0; i < mountSegments.length; ++i)
+		{
+			String placeholder = getPlaceholder(mountSegments[i]);
+			if (placeholder != null)
+			{
+				url.getSegments().set(i, copy.getNamedParameter(placeholder).toString(""));
+				copy.removeNamedParameter(placeholder);
+			}
+		}
+
+		return encodePageParameters(url, copy, pageParametersEncoder);
+	}
+
+	/**
+	 * Check if the URL is for home page and the home page class match mounted class. If so,
+	 * redirect to mounted URL.
+	 * 
+	 * @param url
+	 * @return request handler or <code>null</code>
+	 */
+	private boolean checkHomePage(Url url)
+	{
+		if (url.getSegments().isEmpty() && url.getQueryParameters().isEmpty())
+		{
+			// this is home page
+			if (pageClass.get().equals(getContext().getHomePageClass()) && redirectFromHomePage())
+			{
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * If this method returns <code>true</code> and application home page class is same as the class
+	 * mounted with this encoder, request to home page will result in a redirect to the mounted
+	 * path.
+	 * 
+	 * @return whether this encode should respond to home page request when home page class is same
+	 *         as mounted class.
+	 */
+	protected boolean redirectFromHomePage()
+	{
+		return true;
+	}
+
+	@Override
+	protected boolean pageMustHaveBeenCreatedBookmarkable()
+	{
+		return false;
+	}
+
+	public int getCompatibilityScore(Request request)
+	{
+		if (urlStartsWith(request.getUrl(), mountSegments))
+		{
+			return mountSegments.length;
+		}
+		else
+		{
+			return 0;
+		}
+	}
+
+	@Override
+	protected boolean checkPageClass(Class<? extends RequestablePage> pageClass)
+	{
+		return pageClass.equals(this.pageClass.get());
+	}
+}