You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by mg...@apache.org on 2012/03/12 11:23:50 UTC
[6/21] WICKET-4439 Move classes around so that there are no two
packages with the same name in different modules
http://git-wip-us.apache.org/repos/asf/wicket/blob/1257c03d/wicket-core/src/main/java/org/apache/wicket/core/request/handler/PageProvider.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/handler/PageProvider.java b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/PageProvider.java
new file mode 100644
index 0000000..f239c4d
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/PageProvider.java
@@ -0,0 +1,412 @@
+/*
+ * 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.core.request.handler;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.core.request.mapper.IPageSource;
+import org.apache.wicket.core.request.mapper.StalePageException;
+import org.apache.wicket.page.IPageManager;
+import org.apache.wicket.pageStore.IPageStore;
+import org.apache.wicket.protocol.http.PageExpiredException;
+import org.apache.wicket.request.IRequestHandler;
+import org.apache.wicket.request.IRequestMapper;
+import org.apache.wicket.request.component.IRequestablePage;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.util.lang.Args;
+
+/**
+ * Provides page instance for request handlers. Each of the constructors has just enough information
+ * to get existing or create new page instance. Requesting or creating page instance is deferred
+ * until {@link #getPageInstance()} is called.
+ * <p>
+ * Purpose of this class is to reduce complexity of both {@link IRequestMapper}s and
+ * {@link IRequestHandler}s. {@link IRequestMapper} examines the URL, gathers all relevant
+ * information about the page in the URL (combination of page id, page class, page parameters and
+ * render count), creates {@link PageProvider} object and creates a {@link IRequestHandler} instance
+ * that can use the {@link PageProvider} to access the page.
+ * <p>
+ * Apart from simplifying {@link IRequestMapper}s and {@link IRequestHandler}s {@link PageProvider}
+ * also helps performance because creating or obtaining page from {@link IPageManager} is delayed
+ * until the {@link IRequestHandler} actually requires the page.
+ *
+ * @author Matej Knopp
+ */
+public class PageProvider implements IPageProvider
+{
+ private final Integer renderCount;
+
+ private final Integer pageId;
+
+ private IPageSource pageSource;
+
+ private IRequestablePage pageInstance;
+ private boolean pageInstanceIsFresh;
+
+ private Class<? extends IRequestablePage> pageClass;
+
+ private PageParameters pageParameters;
+
+ /**
+ * Creates a new page provider object. Upon calling of {@link #getPageInstance()} this provider
+ * will return page instance with specified id.
+ *
+ * @param pageId
+ * @param renderCount
+ * optional argument
+ */
+ public PageProvider(final int pageId, final Integer renderCount)
+ {
+ this.pageId = pageId;
+ this.renderCount = renderCount;
+ }
+
+ /**
+ * Creates a new page provider object. Upon calling of {@link #getPageInstance()} this provider
+ * will return page instance with specified id if it exists and it's class matches pageClass. If
+ * none of these is true new page instance will be created.
+ *
+ * @param pageId
+ * @param pageClass
+ * @param renderCount
+ * optional argument
+ */
+ public PageProvider(final int pageId, final Class<? extends IRequestablePage> pageClass,
+ Integer renderCount)
+ {
+ this(pageId, pageClass, new PageParameters(), renderCount);
+ }
+
+ /**
+ * Creates a new page provider object. Upon calling of {@link #getPageInstance()} this provider
+ * will return page instance with specified id if it exists and it's class matches pageClass. If
+ * none of these is true new page instance will be created.
+ *
+ * @param pageId
+ * @param pageClass
+ * @param pageParameters
+ * @param renderCount
+ * optional argument
+ */
+ public PageProvider(final int pageId, final Class<? extends IRequestablePage> pageClass,
+ final PageParameters pageParameters, final Integer renderCount)
+ {
+ this.pageId = pageId;
+ setPageClass(pageClass);
+ setPageParameters(pageParameters);
+ this.renderCount = renderCount;
+ }
+
+ /**
+ * Creates a new page provider object. Upon calling of {@link #getPageInstance()} this provider
+ * will return new instance of page with specified class.
+ *
+ * @param pageClass
+ * @param pageParameters
+ */
+ public PageProvider(final Class<? extends IRequestablePage> pageClass,
+ final PageParameters pageParameters)
+ {
+ setPageClass(pageClass);
+ if (pageParameters != null)
+ {
+ setPageParameters(pageParameters);
+ }
+ pageId = null;
+ renderCount = null;
+ }
+
+ /**
+ * Creates a new page provider object. Upon calling of {@link #getPageInstance()} this provider
+ * will return new instance of page with specified class.
+ *
+ * @param pageClass
+ */
+ public PageProvider(Class<? extends IRequestablePage> pageClass)
+ {
+ this(pageClass, null);
+ }
+
+ /**
+ * Creates a new page provider object. Upon calling of {@link #getPageInstance()} this provider
+ * will return the given page instance.
+ *
+ * @param page
+ */
+ public PageProvider(IRequestablePage page)
+ {
+ Args.notNull(page, "page");
+
+ pageInstance = page;
+ pageId = page.getPageId();
+ renderCount = page.getRenderCount();
+ }
+
+ /**
+ * @see IPageProvider#getPageInstance()
+ */
+ @Override
+ public IRequestablePage getPageInstance()
+ {
+ if (pageInstance == null)
+ {
+ resolvePageInstance(pageId, pageClass, pageParameters, renderCount);
+
+ if (pageInstance == null)
+ {
+ throw new PageExpiredException("Page with id '" + pageId + "' has expired.");
+ }
+ }
+ return pageInstance;
+ }
+
+ /**
+ * @see IPageProvider#getPageParameters()
+ */
+ @Override
+ public PageParameters getPageParameters()
+ {
+ if (pageParameters != null)
+ {
+ return pageParameters;
+ }
+ else if (isNewPageInstance() == false)
+ {
+ return pageInstance.getPageParameters();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * The page instance is new only if there is no cached instance or the data stores doesn't have
+ * a page with that id with the same {@linkplain #pageClass}.
+ *
+ * @see IPageProvider#isNewPageInstance()
+ */
+ @Override
+ public boolean isNewPageInstance()
+ {
+ boolean isNew = pageInstance == null;
+ if (isNew && pageId != null)
+ {
+ IRequestablePage storedPageInstance = getStoredPage(pageId);
+ if (storedPageInstance != null)
+ {
+ pageInstance = storedPageInstance;
+ isNew = false;
+ }
+ }
+
+ return isNew;
+ }
+
+ /**
+ * @see IPageProvider#getPageClass()
+ */
+ @Override
+ public Class<? extends IRequestablePage> getPageClass()
+ {
+ if (pageClass != null)
+ {
+ return pageClass;
+ }
+ else
+ {
+ return getPageInstance().getClass();
+ }
+ }
+
+ protected IPageSource getPageSource()
+ {
+ if (pageSource != null)
+ {
+ return pageSource;
+ }
+ if (Application.exists())
+ {
+ return Application.get().getMapperContext();
+ }
+ else
+ {
+ throw new IllegalStateException(
+ "No application is bound to current thread. Call setPageSource() to manually assign pageSource to this provider.");
+ }
+ }
+
+ private void resolvePageInstance(Integer pageId, Class<? extends IRequestablePage> pageClass,
+ PageParameters pageParameters, Integer renderCount)
+ {
+ IRequestablePage page = null;
+
+ boolean freshCreated = false;
+
+ if (pageId != null)
+ {
+ page = getStoredPage(pageId);
+ }
+
+ if (page == null)
+ {
+ if (pageClass != null)
+ {
+ page = getPageSource().newPageInstance(pageClass, pageParameters);
+ freshCreated = true;
+ }
+ }
+
+ if (page != null && !freshCreated)
+ {
+ if (renderCount != null && page.getRenderCount() != renderCount)
+ {
+ throw new StalePageException(page);
+ }
+ }
+
+ pageInstanceIsFresh = freshCreated;
+ pageInstance = page;
+ }
+
+ /**
+ * Looks up a page by id from the {@link IPageStore}. <br/>
+ * If {@linkplain #pageClass} is specified then compares it against the stored instance class
+ * and returns the found instance only if they match.
+ *
+ * @param pageId
+ * the id of the page to look for.
+ * @return the found page instance by id.
+ */
+ private IRequestablePage getStoredPage(final int pageId)
+ {
+ IRequestablePage storedPageInstance = getPageSource().getPageInstance(pageId);
+ if (storedPageInstance != null &&
+ (pageClass == null || pageClass.equals(storedPageInstance.getClass())))
+ {
+ pageInstance = storedPageInstance;
+ pageInstanceIsFresh = false;
+ if (pageInstance != null)
+ {
+ if (renderCount != null && pageInstance.getRenderCount() != renderCount)
+ {
+ throw new StalePageException(pageInstance);
+ }
+ }
+ }
+ return storedPageInstance;
+ }
+
+ /**
+ * Detaches the page if it has been loaded (that means either
+ * {@link #PageProvider(IRequestablePage)} constructor has been used or
+ * {@link #getPageInstance()} has been called).
+ */
+ @Override
+ public void detach()
+ {
+ if (pageInstance != null)
+ {
+ pageInstance.detach();
+ }
+ }
+
+ /**
+ * If the {@link PageProvider} is used outside request thread (thread that does not have
+ * application instance assigned) it is necessary to specify a {@link IPageSource} instance so
+ * that {@link PageProvider} knows how to get a page instance.
+ *
+ * @param pageSource
+ */
+ public void setPageSource(IPageSource pageSource)
+ {
+ this.pageSource = pageSource;
+ }
+
+ /**
+ *
+ * @param pageClass
+ */
+ private void setPageClass(Class<? extends IRequestablePage> pageClass)
+ {
+ Args.notNull(pageClass, "pageClass");
+
+ this.pageClass = pageClass;
+ }
+
+ /**
+ *
+ * @param pageParameters
+ */
+ protected void setPageParameters(PageParameters pageParameters)
+ {
+ this.pageParameters = pageParameters;
+ }
+
+ /**
+ *
+ * @return page id
+ */
+ @Override
+ public Integer getPageId()
+ {
+ return pageId;
+ }
+
+ @Override
+ public Integer getRenderCount()
+ {
+ return renderCount;
+ }
+
+ /**
+ * Checks whether or not the provider has a page instance. This page instance might have been
+ * passed to this page provider directly or it may have been instantiated or retrieved from the
+ * page store.
+ *
+ * @return {@code true} iff page instance has been created or retrieved
+ */
+ @Override
+ public final boolean hasPageInstance()
+ {
+ if (pageInstance == null && pageId != null)
+ {
+ // attempt to load a stored page instance from the page store
+ getStoredPage(pageId);
+ }
+ return pageInstance != null;
+ }
+
+ /**
+ * Returns whether or not the page instance held by this provider has been instantiated by the
+ * provider.
+ *
+ * @throws IllegalStateException
+ * if this method is called and the provider does not yet have a page instance, ie
+ * if {@link #getPageInstance()} has never been called on this provider
+ * @return {@code true} iff the page instance held by this provider was instantiated by the
+ * provider
+ */
+ @Override
+ public final boolean isPageInstanceFresh()
+ {
+ if (!hasPageInstance())
+ {
+ throw new IllegalStateException("Page instance not yet resolved");
+ }
+ return pageInstanceIsFresh;
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/1257c03d/wicket-core/src/main/java/org/apache/wicket/core/request/handler/RenderPageRequestHandler.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/handler/RenderPageRequestHandler.java b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/RenderPageRequestHandler.java
new file mode 100644
index 0000000..94dec22
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/RenderPageRequestHandler.java
@@ -0,0 +1,179 @@
+/*
+ * 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.core.request.handler;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.core.request.handler.logger.PageLogData;
+import org.apache.wicket.request.ILoggableRequestHandler;
+import org.apache.wicket.request.IRequestCycle;
+import org.apache.wicket.request.IRequestHandler;
+import org.apache.wicket.request.component.IRequestablePage;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.handler.render.PageRenderer;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.util.lang.Args;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link IRequestHandler} 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
+ IPageRequestHandler,
+ IPageClassRequestHandler,
+ ILoggableRequestHandler
+{
+ private static final Logger logger = LoggerFactory.getLogger(RenderPageRequestHandler.class);
+
+ private final IPageProvider pageProvider;
+
+ private final RedirectPolicy redirectPolicy;
+
+ private PageLogData logData;
+
+ /**
+ * 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(IPageProvider pageProvider)
+ {
+ this(pageProvider, RedirectPolicy.AUTO_REDIRECT);
+ }
+
+ /**
+ * Construct.
+ *
+ * @param pageProvider
+ * @param redirectPolicy
+ */
+ public RenderPageRequestHandler(IPageProvider pageProvider, RedirectPolicy redirectPolicy)
+ {
+ Args.notNull(pageProvider, "pageProvider");
+ Args.notNull(redirectPolicy, "redirectPolicy");
+
+ this.redirectPolicy = redirectPolicy;
+ this.pageProvider = pageProvider;
+ }
+
+ /**
+ * @return page provider
+ */
+ public IPageProvider getPageProvider()
+ {
+ return pageProvider;
+ }
+
+ /**
+ * @return redirect policy
+ */
+ public RedirectPolicy getRedirectPolicy()
+ {
+ return redirectPolicy;
+ }
+
+ @Override
+ public Class<? extends IRequestablePage> getPageClass()
+ {
+ return pageProvider.getPageClass();
+ }
+
+ @Override
+ public Integer getPageId()
+ {
+ return pageProvider.getPageId();
+ }
+
+ @Override
+ public PageParameters getPageParameters()
+ {
+ return pageProvider.getPageParameters();
+ }
+
+ @Override
+ public void detach(IRequestCycle requestCycle)
+ {
+ if (logData == null)
+ logData = new PageLogData(pageProvider);
+ pageProvider.detach();
+ }
+
+ @Override
+ public PageLogData getLogData()
+ {
+ return logData;
+ }
+
+ @Override
+ public IRequestablePage getPage()
+ {
+ return pageProvider.getPageInstance();
+ }
+
+ @Override
+ public void respond(IRequestCycle requestCycle)
+ {
+ PageRenderer renderer = Application.get().getPageRendererProvider().get(this);
+ renderer.respond((RequestCycle)requestCycle);
+ }
+
+ @Override
+ public final boolean isPageInstanceCreated()
+ {
+ return pageProvider.hasPageInstance();
+ }
+
+ @Override
+ public final Integer getRenderCount()
+ {
+ return pageProvider.getRenderCount();
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/1257c03d/wicket-core/src/main/java/org/apache/wicket/core/request/handler/logger/ListenerInterfaceLogData.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/handler/logger/ListenerInterfaceLogData.java b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/logger/ListenerInterfaceLogData.java
new file mode 100644
index 0000000..8e73163
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/logger/ListenerInterfaceLogData.java
@@ -0,0 +1,243 @@
+/*
+ * 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.core.request.handler.logger;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.RequestListenerInterface;
+import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.core.request.handler.IPageAndComponentProvider;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.IFormSubmitListener;
+import org.apache.wicket.markup.html.form.IFormSubmitter;
+import org.apache.wicket.request.component.IRequestableComponent;
+
+/**
+ * Contains logging data for component/listener-interface request handlers.
+ *
+ * @author Emond Papegaaij
+ */
+public class ListenerInterfaceLogData extends PageLogData
+{
+ private static final long serialVersionUID = 1L;
+
+ private final Class<? extends IRequestableComponent> componentClass;
+ private final String componentPath;
+ private final Integer behaviorIndex;
+ private Class<? extends Behavior> behaviorClass;
+ private final String interfaceName;
+ private final String interfaceMethod;
+ private Class<? extends IRequestableComponent> submittingComponentClass;
+ private String submittingComponentPath;
+
+ /**
+ * Construct.
+ *
+ * @param pageAndComponentProvider
+ * @param listenerInterface
+ * @param behaviorIndex
+ */
+ public ListenerInterfaceLogData(IPageAndComponentProvider pageAndComponentProvider,
+ RequestListenerInterface listenerInterface, Integer behaviorIndex)
+ {
+ super(pageAndComponentProvider);
+ componentClass = tryToGetComponentClass(pageAndComponentProvider);
+ componentPath = tryToGetComponentPath(pageAndComponentProvider);
+ this.behaviorIndex = behaviorIndex;
+ if (behaviorIndex != null && componentClass != null)
+ {
+ try
+ {
+ behaviorClass = pageAndComponentProvider.getComponent()
+ .getBehaviorById(behaviorIndex)
+ .getClass();
+ }
+ catch (Exception ignore)
+ {
+ behaviorClass = null;
+ }
+ }
+ else
+ {
+ behaviorClass = null;
+ }
+ interfaceName = listenerInterface.getName();
+ interfaceMethod = listenerInterface.getMethod().getName();
+ if (listenerInterface.getListenerInterfaceClass().equals(IFormSubmitListener.class))
+ {
+ final Component formSubmitter = tryToGetFormSubmittingComponent(pageAndComponentProvider);
+ if (formSubmitter != null)
+ {
+ submittingComponentClass = formSubmitter.getClass();
+ submittingComponentPath = formSubmitter.getPageRelativePath();
+ }
+ }
+ }
+
+ private static Class<? extends IRequestableComponent> tryToGetComponentClass(
+ IPageAndComponentProvider pageAndComponentProvider)
+ {
+ try
+ {
+ return pageAndComponentProvider.getComponent().getClass();
+ }
+ catch (Exception e)
+ {
+ // getComponent might fail if the page does not exist (ie session timeout)
+ return null;
+ }
+ }
+
+
+ private static String tryToGetComponentPath(IPageAndComponentProvider pageAndComponentProvider)
+ {
+ try
+ {
+ return pageAndComponentProvider.getComponentPath();
+ }
+ catch (Exception e)
+ {
+ // getComponentPath might fail if the page does not exist (ie session timeout)
+ return null;
+ }
+ }
+
+ private static Component tryToGetFormSubmittingComponent(
+ IPageAndComponentProvider pageAndComponentProvider)
+ {
+ try
+ {
+ final IRequestableComponent component = pageAndComponentProvider.getComponent();
+ if (component instanceof Form)
+ {
+ final IFormSubmitter submitter = ((Form<?>)component).findSubmittingButton();
+ return submitter instanceof Component ? (Component)submitter : null;
+ }
+ return null;
+ }
+ catch (Exception e)
+ {
+ // getComponent might fail if the page does not exist (ie session timeout)
+ return null;
+ }
+ }
+
+ /**
+ * @return componentClass
+ */
+ public final Class<? extends IRequestableComponent> getComponentClass()
+ {
+ return componentClass;
+ }
+
+ /**
+ * @return componentPath
+ */
+ public final String getComponentPath()
+ {
+ return componentPath;
+ }
+
+ /**
+ * @return behaviorIndex
+ */
+ public final Integer getBehaviorIndex()
+ {
+ return behaviorIndex;
+ }
+
+ /**
+ * @return behaviorClass
+ */
+ public final Class<? extends Behavior> getBehaviorClass()
+ {
+ return behaviorClass;
+ }
+
+ /**
+ * @return interfaceName
+ */
+ public final String getInterfaceName()
+ {
+ return interfaceName;
+ }
+
+ /**
+ * @return interfaceMethod
+ */
+ public final String getInterfaceMethod()
+ {
+ return interfaceMethod;
+ }
+
+ /**
+ * @return submittingComponentClass
+ */
+ public Class<? extends IRequestableComponent> getSubmittingComponentClass()
+ {
+ return submittingComponentClass;
+ }
+
+ /**
+ * @return submittingComponentPath
+ */
+ public String getSubmittingComponentPath()
+ {
+ return submittingComponentPath;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder(super.toString());
+ sb.setCharAt(sb.length() - 1, ',');
+ if (getComponentClass() != null)
+ {
+ sb.append("componentClass=");
+ sb.append(getComponentClass().getName());
+ sb.append(',');
+ }
+ if (getComponentPath() != null)
+ {
+ sb.append("componentPath=");
+ sb.append(getComponentPath());
+ sb.append(',');
+ }
+ sb.append("behaviorIndex=");
+ sb.append(getBehaviorIndex());
+ if (getBehaviorClass() != null)
+ {
+ sb.append(",behaviorClass=");
+ sb.append(getBehaviorClass().getName());
+ }
+ sb.append(",interfaceName=");
+ sb.append(getInterfaceName());
+ sb.append(",interfaceMethod=");
+ sb.append(getInterfaceMethod());
+ if (getSubmittingComponentClass() != null)
+ {
+ sb.append(",submittingComponentClass=");
+ sb.append(getSubmittingComponentClass().getName());
+ }
+ if (getSubmittingComponentPath() != null)
+ {
+ sb.append(",submittingComponentPath=");
+ sb.append(getSubmittingComponentPath());
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/1257c03d/wicket-core/src/main/java/org/apache/wicket/core/request/handler/logger/PageLogData.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/handler/logger/PageLogData.java b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/logger/PageLogData.java
new file mode 100644
index 0000000..fd3b53f
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/logger/PageLogData.java
@@ -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.core.request.handler.logger;
+
+import org.apache.wicket.Page;
+import org.apache.wicket.core.request.handler.IPageClassRequestHandler;
+import org.apache.wicket.core.request.handler.IPageProvider;
+import org.apache.wicket.core.request.handler.IPageRequestHandler;
+import org.apache.wicket.request.ILogData;
+import org.apache.wicket.request.component.IRequestablePage;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+/**
+ * Contains logging data for request handlers that are related to pages; most likely
+ * {@link IPageRequestHandler} or {@link IPageClassRequestHandler}.
+ *
+ * @author Emond Papegaaij
+ */
+public class PageLogData implements ILogData
+{
+ private static final long serialVersionUID = 1L;
+
+ private final Class<? extends IRequestablePage> pageClass;
+ private final Integer pageId;
+ private final PageParameters pageParameters;
+ private final Integer renderCount;
+
+ /**
+ * Construct.
+ *
+ * @param pageProvider
+ */
+ public PageLogData(IPageProvider pageProvider)
+ {
+ pageClass = tryToGetPageClass(pageProvider);
+ pageId = pageProvider.getPageId();
+ pageParameters = pageProvider.getPageParameters();
+ renderCount = pageProvider.getRenderCount();
+ }
+
+ private static Class<? extends IRequestablePage> tryToGetPageClass(IPageProvider pageProvider)
+ {
+ try
+ {
+ return pageProvider.getPageClass();
+ }
+ catch (Exception e)
+ {
+ // getPageClass might fail if the page does not exist (ie session timeout)
+ return null;
+ }
+ }
+
+ /**
+ * Construct.
+ *
+ * @param page
+ */
+ public PageLogData(Page page)
+ {
+ pageClass = page.getPageClass();
+ pageId = page.getPageId();
+ pageParameters = page.getPageParameters();
+ renderCount = page.getRenderCount();
+ }
+
+ /**
+ * @return pageClass
+ */
+ public final Class<? extends IRequestablePage> getPageClass()
+ {
+ return pageClass;
+ }
+
+ /**
+ * @return pageId
+ */
+ public final Integer getPageId()
+ {
+ return pageId;
+ }
+
+ /**
+ * @return pageParameters
+ */
+ public final PageParameters getPageParameters()
+ {
+ return pageParameters;
+ }
+
+ /**
+ * @return renderCount
+ */
+ public final Integer getRenderCount()
+ {
+ return renderCount;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder("{");
+ if (pageClass != null)
+ {
+ sb.append("pageClass=");
+ sb.append(getPageClass().getName());
+ sb.append(',');
+ }
+ sb.append("pageId=");
+ sb.append(getPageId());
+ sb.append(",pageParameters={");
+ sb.append(getPageParameters());
+ sb.append("},renderCount=");
+ sb.append(getRenderCount());
+ sb.append("}");
+ return sb.toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/1257c03d/wicket-core/src/main/java/org/apache/wicket/core/request/handler/logger/ResourceReferenceLogData.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/handler/logger/ResourceReferenceLogData.java b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/logger/ResourceReferenceLogData.java
new file mode 100644
index 0000000..f4c634b
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/logger/ResourceReferenceLogData.java
@@ -0,0 +1,89 @@
+/*
+ * 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.core.request.handler.logger;
+
+import org.apache.wicket.request.handler.resource.ResourceLogData;
+import org.apache.wicket.request.handler.resource.ResourceReferenceRequestHandler;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.resource.ResourceReference;
+
+/**
+ * Contains logging data for resource reference requests.
+ *
+ * @author Emond Papegaaij
+ */
+public class ResourceReferenceLogData extends ResourceLogData
+{
+ private static final long serialVersionUID = 1L;
+
+ private final Class<? extends ResourceReference> resourceReferenceClass;
+ private final Class<?> scope;
+ private final PageParameters pageParameters;
+
+ /**
+ * Construct.
+ *
+ * @param refHandler
+ */
+ public ResourceReferenceLogData(ResourceReferenceRequestHandler refHandler)
+ {
+ super(refHandler.getResourceReference().getName(), refHandler.getLocale(),
+ refHandler.getStyle(), refHandler.getVariation());
+ resourceReferenceClass = refHandler.getResourceReference().getClass();
+ scope = refHandler.getResourceReference().getScope();
+ pageParameters = refHandler.getPageParameters();
+ }
+
+ /**
+ * @return resourceReferenceClass
+ */
+ public final Class<? extends ResourceReference> getResourceReferenceClass()
+ {
+ return resourceReferenceClass;
+ }
+
+ /**
+ * @return scope
+ */
+ public final Class<?> getScope()
+ {
+ return scope;
+ }
+
+ /***
+ * @return pageParameters
+ */
+ public final PageParameters getPageParameters()
+ {
+ return pageParameters;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder("{");
+ fillToString(sb);
+ sb.append(",resourceReferenceClass=");
+ sb.append(getResourceReferenceClass().getName());
+ sb.append(",scope=");
+ sb.append(getScope() == null ? "null" : getScope().getName());
+ sb.append(",pageParameters={");
+ sb.append(getPageParameters());
+ sb.append("}}");
+ return sb.toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/1257c03d/wicket-core/src/main/java/org/apache/wicket/core/request/handler/logger/ResourceStreamLogData.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/handler/logger/ResourceStreamLogData.java b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/logger/ResourceStreamLogData.java
new file mode 100644
index 0000000..088fec8
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/logger/ResourceStreamLogData.java
@@ -0,0 +1,109 @@
+/*
+ * 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.core.request.handler.logger;
+
+import org.apache.wicket.request.handler.resource.ResourceLogData;
+import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler;
+import org.apache.wicket.request.resource.ContentDisposition;
+import org.apache.wicket.util.resource.IResourceStream;
+
+/**
+ * Contains logging data for resource stream requests, handled by
+ * {@link ResourceStreamRequestHandler}.
+ *
+ * @author Emond Papegaaij
+ */
+public class ResourceStreamLogData extends ResourceLogData
+{
+ private static final long serialVersionUID = 1L;
+
+ private final Class<? extends IResourceStream> resourceStreamClass;
+ private final ContentDisposition contentDisposition;
+ private final String contentType;
+
+ /**
+ * Construct.
+ *
+ * @param streamHandler
+ */
+ public ResourceStreamLogData(ResourceStreamRequestHandler streamHandler)
+ {
+ super(streamHandler.getFileName(), null, null, null);
+ contentDisposition = streamHandler.getContentDisposition();
+ resourceStreamClass = null;
+ contentType = null;
+ }
+
+ /**
+ * Construct.
+ *
+ * @param streamHandler
+ * @param stream
+ */
+ public ResourceStreamLogData(ResourceStreamRequestHandler streamHandler, IResourceStream stream)
+ {
+ super(streamHandler.getFileName(), stream.getLocale(), stream.getStyle(),
+ stream.getVariation());
+ contentDisposition = streamHandler.getContentDisposition();
+ resourceStreamClass = stream.getClass();
+ contentType = stream.getContentType();
+ }
+
+ /**
+ * Returns the class of the resource stream.
+ *
+ * @return The class of the resource stream.
+ */
+ public final Class<? extends IResourceStream> getResourceStreamClass()
+ {
+ return resourceStreamClass;
+ }
+
+ /**
+ * @return contentDisposition.
+ */
+ public final ContentDisposition getContentDisposition()
+ {
+ return contentDisposition;
+ }
+
+ /**
+ * @return contentType
+ */
+ public final String getContentType()
+ {
+ return contentType;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder("{");
+ fillToString(sb);
+ sb.append(",contentDisposition=");
+ sb.append(getContentDisposition());
+ if (getResourceStreamClass() != null)
+ {
+ sb.append(",resourceStreamClass=");
+ sb.append(getResourceStreamClass().getName());
+ sb.append(",contentType=");
+ sb.append(getContentType());
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/1257c03d/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/AbstractBookmarkableMapper.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/AbstractBookmarkableMapper.java b/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/AbstractBookmarkableMapper.java
new file mode 100644
index 0000000..0f65fb0
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/AbstractBookmarkableMapper.java
@@ -0,0 +1,414 @@
+/*
+ * 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.core.request.mapper;
+
+import org.apache.wicket.RequestListenerInterface;
+import org.apache.wicket.core.request.handler.BookmarkableListenerInterfaceRequestHandler;
+import org.apache.wicket.core.request.handler.BookmarkablePageRequestHandler;
+import org.apache.wicket.core.request.handler.ListenerInterfaceRequestHandler;
+import org.apache.wicket.core.request.handler.PageAndComponentProvider;
+import org.apache.wicket.core.request.handler.PageProvider;
+import org.apache.wicket.core.request.handler.RenderPageRequestHandler;
+import org.apache.wicket.request.IRequestHandler;
+import org.apache.wicket.request.IRequestHandlerDelegate;
+import org.apache.wicket.request.IRequestMapper;
+import org.apache.wicket.request.Request;
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.component.IRequestablePage;
+import org.apache.wicket.request.http.WebRequest;
+import org.apache.wicket.request.mapper.info.ComponentInfo;
+import org.apache.wicket.request.mapper.info.PageComponentInfo;
+import org.apache.wicket.request.mapper.info.PageInfo;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.util.lang.Args;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract encoder for Bookmarkable, Hybrid and BookmarkableListenerInterface URLs.
+ *
+ * @author Matej Knopp
+ */
+public abstract class AbstractBookmarkableMapper extends AbstractComponentMapper
+{
+ private static Logger logger = LoggerFactory.getLogger(AbstractBookmarkableMapper.class);
+
+ /**
+ * 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 IRequestablePage> 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 IRequestablePage> pageClass, PageParameters pageParameters)
+ {
+ Args.notNull(pageClass, "pageClass");
+
+ this.pageComponentInfo = pageComponentInfo;
+ this.pageParameters = cleanPageParameters(pageParameters);
+
+ this.pageClass = pageClass;
+ }
+
+ /**
+ * Cleans the original parameters from entries used by Wicket internals.
+ *
+ * @param originalParameters
+ * the current request's non-modified parameters
+ * @return all parameters but Wicket internal ones
+ */
+ private PageParameters cleanPageParameters(final PageParameters originalParameters)
+ {
+ PageParameters cleanParameters = null;
+ if (originalParameters != null)
+ {
+ cleanParameters = new PageParameters(originalParameters);
+
+ // WICKET-4038: Ajax related parameters are set by wicket-ajax.js when needed.
+ // They shouldn't be propagated to the next requests
+ cleanParameters.remove(WebRequest.PARAM_AJAX);
+ cleanParameters.remove(WebRequest.PARAM_AJAX_BASE_URL);
+ cleanParameters.remove(WebRequest.PARAM_AJAX_REQUEST_ANTI_CACHE);
+
+ if (cleanParameters.isEmpty())
+ {
+ cleanParameters = null;
+ }
+ }
+ return cleanParameters;
+ }
+
+ /**
+ * @return PageComponentInfo instance or <code>null</code>
+ */
+ public PageComponentInfo getPageComponentInfo()
+ {
+ return pageComponentInfo;
+ }
+
+ /**
+ * @return page class
+ */
+ public Class<? extends IRequestablePage> getPageClass()
+ {
+ return pageClass;
+ }
+
+ /**
+ * @return PageParameters instance (never <code>null</code>)
+ */
+ public PageParameters getPageParameters()
+ {
+ return pageParameters;
+ }
+ }
+
+ /**
+ * Construct.
+ */
+ public AbstractBookmarkableMapper()
+ {
+ }
+
+ /**
+ * 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>
+ * For 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 IRequestMapper#getCompatibilityScore(Request)
+ */
+ @Override
+ public abstract int getCompatibilityScore(Request request);
+
+ /**
+ * Creates a {@code IRequestHandler} that processes a bookmarkable request.
+ *
+ * @param pageClass
+ * @param pageParameters
+ * @return a {@code IRequestHandler} capable of processing the bookmarkable request.
+ */
+ protected IRequestHandler processBookmarkable(Class<? extends IRequestablePage> pageClass,
+ PageParameters pageParameters)
+ {
+ PageProvider provider = new PageProvider(pageClass, pageParameters);
+ provider.setPageSource(getContext());
+ return new RenderPageRequestHandler(provider);
+ }
+
+ /**
+ * Creates a {@code IRequestHandler} that processes a hybrid request. When the page identified
+ * by {@code pageInfo} was not available, the request should be treated as a bookmarkable
+ * request.
+ *
+ * @param pageInfo
+ * @param pageClass
+ * @param pageParameters
+ * @param renderCount
+ * @return a {@code IRequestHandler} capable of processing the hybrid request.
+ */
+ protected IRequestHandler processHybrid(PageInfo pageInfo,
+ Class<? extends IRequestablePage> pageClass, PageParameters pageParameters,
+ Integer renderCount)
+ {
+ PageProvider provider = new PageProvider(pageInfo.getPageId(), pageClass, pageParameters,
+ renderCount);
+ provider.setPageSource(getContext());
+ return new RenderPageRequestHandler(provider);
+ }
+
+ /**
+ * Creates a {@code IRequestHandler} that processes a listener request.
+ *
+ * @param pageComponentInfo
+ * @param pageClass
+ * @param pageParameters
+ * @return a {@code IRequestHandler} that invokes the listener interface
+ */
+ protected IRequestHandler processListener(PageComponentInfo pageComponentInfo,
+ Class<? extends IRequestablePage> pageClass, PageParameters pageParameters)
+ {
+ PageInfo pageInfo = pageComponentInfo.getPageInfo();
+ ComponentInfo componentInfo = pageComponentInfo.getComponentInfo();
+ Integer renderCount = null;
+ RequestListenerInterface listenerInterface = null;
+
+ if (componentInfo != null)
+ {
+ renderCount = componentInfo.getRenderCount();
+ 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.getBehaviorId());
+ }
+ else
+ {
+ if (logger.isWarnEnabled())
+ {
+ if (componentInfo != null)
+ {
+ logger.warn("Unknown listener interface '{}'",
+ componentInfo.getListenerInterface());
+ }
+ else
+ {
+ logger.warn("Cannot extract the listener interface for PageComponentInfo: '{}'" +
+ pageComponentInfo);
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * @see org.apache.wicket.request.IRequestMapper#mapRequest(org.apache.wicket.request.Request)
+ */
+ @Override
+ public IRequestHandler 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 IRequestablePage> 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 checkPageInstance(IRequestablePage page)
+ {
+ return page != null && checkPageClass(page.getClass());
+ }
+
+ protected boolean checkPageClass(Class<? extends IRequestablePage> pageClass)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Url mapHandler(IRequestHandler requestHandler)
+ {
+ // TODO see if we can refactor this to remove dependency on instanceof checks below and
+ // eliminate the need for IRequestHandlerDelegate
+ while (requestHandler instanceof IRequestHandlerDelegate)
+ {
+ requestHandler = ((IRequestHandlerDelegate)requestHandler).getDelegateHandler();
+ }
+
+ 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 bookmarkable URL
+
+ RenderPageRequestHandler handler = (RenderPageRequestHandler)requestHandler;
+
+ if (!checkPageClass(handler.getPageClass()))
+ {
+ return null;
+ }
+
+ if (handler.getPageProvider().isNewPageInstance())
+ {
+ // no existing page instance available, don't bother creating new page instance
+ PageInfo info = new PageInfo();
+ UrlInfo urlInfo = new UrlInfo(new PageComponentInfo(info, null),
+ handler.getPageClass(), handler.getPageParameters());
+
+ return buildUrl(urlInfo);
+ }
+
+ IRequestablePage page = handler.getPage();
+
+ if (checkPageInstance(page) &&
+ (!pageMustHaveBeenCreatedBookmarkable() || page.wasCreatedBookmarkable()))
+ {
+ PageInfo info = null;
+ if (!page.isPageStateless())
+ {
+ info = new PageInfo(page.getPageId());
+ }
+ 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;
+ Class<? extends IRequestablePage> pageClass = handler.getPageClass();
+
+ if (!checkPageClass(pageClass))
+ {
+ return null;
+ }
+
+ Integer renderCount = null;
+ if (handler.getListenerInterface().isIncludeRenderCount())
+ {
+ renderCount = handler.getRenderCount();
+ }
+
+ PageInfo pageInfo = new PageInfo(handler.getPageId());
+ ComponentInfo componentInfo = new ComponentInfo(renderCount,
+ requestListenerInterfaceToString(handler.getListenerInterface()),
+ handler.getComponentPath(), handler.getBehaviorIndex());
+
+ UrlInfo urlInfo = new UrlInfo(new PageComponentInfo(pageInfo, componentInfo),
+ pageClass, handler.getPageParameters());
+ return buildUrl(urlInfo);
+ }
+
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/1257c03d/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/AbstractComponentMapper.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/AbstractComponentMapper.java b/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/AbstractComponentMapper.java
new file mode 100644
index 0000000..fd75b4b
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/AbstractComponentMapper.java
@@ -0,0 +1,157 @@
+/*
+ * 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.core.request.mapper;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.RequestListenerInterface;
+import org.apache.wicket.request.IRequestMapper;
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.Url.QueryParameter;
+import org.apache.wicket.request.component.IRequestablePage;
+import org.apache.wicket.request.mapper.AbstractMapper;
+import org.apache.wicket.request.mapper.info.PageComponentInfo;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.core.util.lang.WicketObjects;
+import org.apache.wicket.util.string.Strings;
+
+/**
+ * Convenience class for implementing page/components related encoders.
+ *
+ * @author Matej Knopp
+ */
+public abstract class AbstractComponentMapper extends AbstractMapper implements IRequestMapper
+{
+ /**
+ * Construct.
+ */
+ public AbstractComponentMapper()
+ {
+ }
+
+ protected IMapperContext getContext()
+ {
+ return Application.get().getMapperContext();
+ }
+
+ /**
+ * Converts the specified listener interface to String.
+ *
+ * @param listenerInterface
+ * @return listenerInterface name as string
+ */
+ protected String requestListenerInterfaceToString(RequestListenerInterface listenerInterface)
+ {
+ Args.notNull(listenerInterface, "listenerInterface");
+
+ return getContext().requestListenerInterfaceToString(listenerInterface);
+ }
+
+ /**
+ * Creates listener interface from the specified string
+ *
+ * @param interfaceName
+ * @return listener interface
+ */
+ protected RequestListenerInterface requestListenerInterfaceFromString(String interfaceName)
+ {
+ Args.notEmpty(interfaceName, "interfaceName");
+
+ return getContext().requestListenerInterfaceFromString(interfaceName);
+ }
+
+ /**
+ * 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(final Url url)
+ {
+ if (url == null)
+ {
+ throw new IllegalStateException("Argument 'url' may not be null.");
+ }
+ else
+ {
+ for (QueryParameter queryParameter : url.getQueryParameters())
+ {
+ if (Strings.isEmpty(queryParameter.getValue()))
+ {
+ PageComponentInfo pageComponentInfo = PageComponentInfo.parse(queryParameter.getName());
+ if (pageComponentInfo != null)
+ {
+ return pageComponentInfo;
+ }
+ }
+ }
+ }
+ 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)
+ {
+ Args.notNull(url, "url");
+
+ 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 IRequestablePage> getPageClass(String name)
+ {
+ Args.notEmpty(name, "name");
+
+ return WicketObjects.resolveClass(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Removes the first query parameter only if {@link PageComponentInfo#parse(String)} returns
+ * non-null instance
+ */
+ @Override
+ protected void removeMetaParameter(final Url urlCopy)
+ {
+ String pageComponentInfoCandidate = urlCopy.getQueryParameters().get(0).getName();
+ if (PageComponentInfo.parse(pageComponentInfoCandidate) != null)
+ {
+ urlCopy.getQueryParameters().remove(0);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/1257c03d/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/AbstractResourceReferenceMapper.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/AbstractResourceReferenceMapper.java b/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/AbstractResourceReferenceMapper.java
new file mode 100644
index 0000000..65e5efb
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/AbstractResourceReferenceMapper.java
@@ -0,0 +1,171 @@
+/*
+ * 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.core.request.mapper;
+
+import java.util.Locale;
+
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.Url.QueryParameter;
+import org.apache.wicket.request.resource.ResourceReference;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.string.Strings;
+
+/**
+ * Base class for encoding and decoding {@link ResourceReference}s
+ *
+ * @author Matej Knopp
+ */
+public abstract class AbstractResourceReferenceMapper extends AbstractComponentMapper
+{
+
+ protected final String encodeResourceReferenceAttributes(
+ ResourceReference.UrlAttributes attributes)
+ {
+ if (attributes == null ||
+ (attributes.getLocale() == null && attributes.getStyle() == null && attributes.getVariation() == null))
+ {
+ return null;
+ }
+ else
+ {
+ StringBuilder res = new StringBuilder();
+ if (attributes.getLocale() != null)
+ {
+ res.append(attributes.getLocale().toString());
+ }
+ boolean styleEmpty = Strings.isEmpty(attributes.getStyle());
+ if (!styleEmpty)
+ {
+ res.append('-');
+ res.append(attributes.getStyle());
+ }
+ if (!Strings.isEmpty(attributes.getVariation()))
+ {
+ if (styleEmpty)
+ {
+ res.append("--");
+ }
+ else
+ {
+ res.append('-');
+ }
+ res.append(attributes.getVariation());
+ }
+ return res.toString();
+ }
+ }
+
+ private static String nonEmpty(String s)
+ {
+ if (Strings.isEmpty(s))
+ {
+ return null;
+ }
+ else
+ {
+ return s;
+ }
+ }
+
+ protected final ResourceReference.UrlAttributes decodeResourceReferenceAttributes(
+ String attributes)
+ {
+ Locale locale = null;
+ String style = null;
+ String variation = null;
+
+ if (!Strings.isEmpty(attributes))
+ {
+ String split[] = attributes.split("-", 3);
+ locale = parseLocale(split[0]);
+ if (split.length == 2)
+ {
+ style = nonEmpty(split[1]);
+ }
+ else if (split.length == 3)
+ {
+ style = nonEmpty(split[1]);
+ variation = nonEmpty(split[2]);
+ }
+ }
+ return new ResourceReference.UrlAttributes(locale, style, variation);
+ }
+
+ 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)
+ {
+ String encoded = encodeResourceReferenceAttributes(reference.getUrlAttributes());
+ if (!Strings.isEmpty(encoded))
+ {
+ url.getQueryParameters().add(new Url.QueryParameter(encoded, ""));
+ }
+ }
+
+ protected ResourceReference.UrlAttributes getResourceReferenceAttributes(Url url)
+ {
+ Args.notNull(url, "url");
+
+ if (url.getQueryParameters().size() > 0)
+ {
+ QueryParameter param = url.getQueryParameters().get(0);
+ if (Strings.isEmpty(param.getValue()))
+ {
+ return decodeResourceReferenceAttributes(param.getName());
+ }
+ }
+ return new ResourceReference.UrlAttributes(null, null, null);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * Remove the first parameter because it brings meta information like locale
+ */
+ @Override
+ protected void removeMetaParameter(final Url urlCopy)
+ {
+ urlCopy.getQueryParameters().remove(0);
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/1257c03d/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/BasicResourceReferenceMapper.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/BasicResourceReferenceMapper.java b/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/BasicResourceReferenceMapper.java
new file mode 100755
index 0000000..6c0317d
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/BasicResourceReferenceMapper.java
@@ -0,0 +1,256 @@
+/*
+ * 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.core.request.mapper;
+
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.apache.wicket.request.IRequestHandler;
+import org.apache.wicket.request.Request;
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.handler.resource.ResourceReferenceRequestHandler;
+import org.apache.wicket.request.mapper.parameter.IPageParametersEncoder;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.resource.IResource;
+import org.apache.wicket.request.resource.MetaInfStaticResourceReference;
+import org.apache.wicket.request.resource.ResourceReference;
+import org.apache.wicket.request.resource.caching.IResourceCachingStrategy;
+import org.apache.wicket.request.resource.caching.IStaticCacheableResource;
+import org.apache.wicket.request.resource.caching.ResourceUrl;
+import org.apache.wicket.util.IProvider;
+import org.apache.wicket.core.util.lang.WicketObjects;
+import org.apache.wicket.util.string.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Generic {@link ResourceReference} encoder that encodes and decodes non-mounted
+ * {@link ResourceReference}s.
+ * <p>
+ * Decodes and encodes the following URLs:
+ *
+ * <pre>
+ * /wicket/resource/org.apache.wicket.ResourceScope/name
+ * /wicket/resource/org.apache.wicket.ResourceScope/name?en
+ * /wicket/resource/org.apache.wicket.ResourceScope/name?-style
+ * /wicket/resource/org.apache.wicket.ResourceScope/resource/name.xyz?en_EN-style
+ * </pre>
+ *
+ * @author Matej Knopp
+ * @author igor.vaynberg
+ * @author Peter Ertl
+ */
+class BasicResourceReferenceMapper extends AbstractResourceReferenceMapper
+{
+ private static final Logger log = LoggerFactory.getLogger(BasicResourceReferenceMapper.class);
+
+ private final IPageParametersEncoder pageParametersEncoder;
+
+ /** resource caching strategy */
+ private final IProvider<? extends IResourceCachingStrategy> cachingStrategy;
+
+ /**
+ * Construct.
+ *
+ * @param pageParametersEncoder
+ * @param cachingStrategy
+ */
+ public BasicResourceReferenceMapper(IPageParametersEncoder pageParametersEncoder,
+ IProvider<? extends IResourceCachingStrategy> cachingStrategy)
+ {
+ this.pageParametersEncoder = pageParametersEncoder;
+ this.cachingStrategy = cachingStrategy;
+ }
+
+ @Override
+ public IRequestHandler mapRequest(Request request)
+ {
+ Url url = request.getUrl();
+
+ // extract the PageParameters from URL if there are any
+ PageParameters pageParameters = extractPageParameters(request, url.getSegments().size(),
+ pageParametersEncoder);
+
+ if (url.getSegments().size() >= 4 &&
+ urlStartsWith(url, getContext().getNamespace(), getContext().getResourceIdentifier()))
+ {
+ String className = url.getSegments().get(2);
+ StringBuilder name = new StringBuilder();
+ int segmentsSize = url.getSegments().size();
+ for (int i = 3; i < segmentsSize; ++i)
+ {
+ String segment = url.getSegments().get(i);
+
+ // skip possibly malicious segments
+ if (segment.contains("/"))
+ {
+ return null;
+ }
+
+ // remove caching information
+ if (i + 1 == segmentsSize && Strings.isEmpty(segment) == false)
+ {
+ // The filename + parameters eventually contain caching
+ // related information which needs to be removed
+ ResourceUrl resourceUrl = new ResourceUrl(segment, pageParameters);
+ getCachingStrategy().undecorateUrl(resourceUrl);
+ segment = resourceUrl.getFileName();
+
+ if (Strings.isEmpty(segment))
+ {
+ throw new IllegalStateException("caching strategy returned empty name for " + resourceUrl);
+ }
+ }
+ if (name.length() > 0)
+ {
+ name.append("/");
+ }
+ name.append(segment);
+ }
+
+ ResourceReference.UrlAttributes attributes = getResourceReferenceAttributes(url);
+
+ Class<?> scope = resolveClass(className);
+
+ if (scope != null && scope.getPackage() != null)
+ {
+ ResourceReference res = getContext().getResourceReferenceRegistry()
+ .getResourceReference(scope, name.toString(), attributes.getLocale(),
+ attributes.getStyle(), attributes.getVariation(), true, true);
+
+ if (res != null)
+ {
+ return new ResourceReferenceRequestHandler(res, pageParameters);
+ }
+ }
+ }
+ return null;
+ }
+
+ private IResourceCachingStrategy getCachingStrategy()
+ {
+ return cachingStrategy.get();
+ }
+
+ protected Class<?> resolveClass(String name)
+ {
+ return WicketObjects.resolveClass(name);
+ }
+
+ protected String getClassName(Class<?> scope)
+ {
+ return scope.getName();
+ }
+
+ @Override
+ public Url mapHandler(IRequestHandler requestHandler)
+ {
+ if (requestHandler instanceof ResourceReferenceRequestHandler)
+ {
+ ResourceReferenceRequestHandler referenceRequestHandler = (ResourceReferenceRequestHandler)requestHandler;
+ ResourceReference reference = referenceRequestHandler.getResourceReference();
+
+ Url url;
+
+ if (reference instanceof MetaInfStaticResourceReference)
+ {
+ url = ((MetaInfStaticResourceReference)reference).mapHandler(referenceRequestHandler);
+ // if running on Servlet 3.0 engine url is not null
+ if (url != null)
+ {
+ return url;
+ }
+ // otherwise it has to be served by the standard wicket way
+ }
+
+ url = new Url();
+
+ List<String> segments = url.getSegments();
+ segments.add(getContext().getNamespace());
+ segments.add(getContext().getResourceIdentifier());
+ segments.add(getClassName(reference.getScope()));
+
+ // setup resource parameters
+ PageParameters parameters = referenceRequestHandler.getPageParameters();
+
+ if (parameters == null)
+ {
+ parameters = new PageParameters();
+ }
+ else
+ {
+ parameters = new PageParameters(parameters);
+
+ // need to remove indexed parameters otherwise the URL won't be able to decode
+ parameters.clearIndexed();
+ }
+ encodeResourceReferenceAttributes(url, reference);
+
+ StringTokenizer tokens = new StringTokenizer(reference.getName(), "/");
+
+ while (tokens.hasMoreTokens())
+ {
+ String token = tokens.nextToken();
+
+ // on the last component of the resource path
+ if (tokens.hasMoreTokens() == false && Strings.isEmpty(token) == false)
+ {
+ final IResource resource = reference.getResource();
+
+ // apply caching if required
+ if (resource instanceof IStaticCacheableResource)
+ {
+ // add caching related information to filename + query parameters
+ final IStaticCacheableResource cacheable = (IStaticCacheableResource)resource;
+ final ResourceUrl resourceUrl = new ResourceUrl(token, parameters);
+ getCachingStrategy().decorateUrl(resourceUrl, cacheable);
+ token = resourceUrl.getFileName();
+
+ if (Strings.isEmpty(token))
+ {
+ throw new IllegalStateException("caching strategy returned empty name for " + resource);
+ }
+ }
+ }
+ segments.add(token);
+ }
+
+ if (parameters.isEmpty() == false)
+ {
+ url = encodePageParameters(url, parameters, pageParametersEncoder);
+ }
+
+ return url;
+ }
+ return null;
+ }
+
+ @Override
+ public int getCompatibilityScore(Request request)
+ {
+ Url url = request.getUrl();
+
+ int score = -1;
+ if (url.getSegments().size() >= 4 &&
+ urlStartsWith(url, getContext().getNamespace(), getContext().getResourceIdentifier()))
+ {
+ score = 1;
+ }
+
+ return score;
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/1257c03d/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/BookmarkableMapper.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/BookmarkableMapper.java b/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/BookmarkableMapper.java
new file mode 100644
index 0000000..22ff3a7
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/BookmarkableMapper.java
@@ -0,0 +1,154 @@
+/*
+ * 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.core.request.mapper;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.request.Request;
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.component.IRequestablePage;
+import org.apache.wicket.request.mapper.info.PageComponentInfo;
+import org.apache.wicket.request.mapper.parameter.IPageParametersEncoder;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.mapper.parameter.PageParametersEncoder;
+import org.apache.wicket.util.lang.Args;
+
+/**
+ * Decodes and encodes the following URLs:
+ *
+ * <pre>
+ * Page Class - Render (BookmarkablePageRequestHandler)
+ * /wicket/bookmarkable/org.apache.wicket.MyPage
+ * (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
+ *
+ * Page Instance - Bookmarkable Listener (BookmarkableListenerInterfaceRequestHandler)
+ * /wicket/bookmarkable/org.apache.wicket.MyPage?2-click-foo-bar-baz
+ * /wicket/bookmarkable/org.apache.wicket.MyPage?2-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 IPageParametersEncoder pageParametersEncoder;
+
+ /**
+ * Construct.
+ *
+ * @param pageParametersEncoder
+ */
+ public BookmarkableMapper(IPageParametersEncoder pageParametersEncoder)
+ {
+ Args.notNull(pageParametersEncoder, "pageParametersEncoder");
+
+ this.pageParametersEncoder = pageParametersEncoder;
+ }
+
+ /**
+ * Construct.
+ */
+ public BookmarkableMapper()
+ {
+ this(new PageParametersEncoder());
+ }
+
+ /**
+ * @see org.apache.wicket.request.mapper.AbstractBookmarkableMapper#buildUrl(org.apache.wicket.request.mapper.AbstractBookmarkableMapper.UrlInfo)
+ */
+ @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);
+ }
+
+ /**
+ * @see org.apache.wicket.request.mapper.AbstractBookmarkableMapper#parseRequest(org.apache.wicket.request.Request)
+ */
+ @Override
+ protected UrlInfo parseRequest(Request request)
+ {
+ if (Application.exists())
+ {
+ if (Application.get().getSecuritySettings().getEnforceMounts())
+ {
+ return null;
+ }
+ }
+
+ Url url = request.getUrl();
+ if (matches(url))
+ {
+ // 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 IRequestablePage> pageClass = getPageClass(className);
+
+ if (pageClass != null && IRequestablePage.class.isAssignableFrom(pageClass))
+ {
+
+ // extract the PageParameters from URL if there are any
+ PageParameters pageParameters = extractPageParameters(request, 3,
+ pageParametersEncoder);
+
+ return new UrlInfo(info, pageClass, pageParameters);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @see org.apache.wicket.request.mapper.AbstractBookmarkableMapper#pageMustHaveBeenCreatedBookmarkable()
+ */
+ @Override
+ protected boolean pageMustHaveBeenCreatedBookmarkable()
+ {
+ return true;
+ }
+
+ /**
+ * @see org.apache.wicket.request.mapper.AbstractBookmarkableMapper#getCompatibilityScore(org.apache.wicket.request.Request)
+ */
+ @Override
+ public int getCompatibilityScore(Request request)
+ {
+ int score = 0;
+ Url url = request.getUrl();
+ if (matches(url))
+ {
+ score = Integer.MAX_VALUE;
+ }
+ return score;
+ }
+
+ private boolean matches(final Url url)
+ {
+ return (url.getSegments().size() >= 3 && urlStartsWith(url, getContext().getNamespace(),
+ getContext().getBookmarkableIdentifier()));
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/1257c03d/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/BufferedResponseMapper.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/BufferedResponseMapper.java b/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/BufferedResponseMapper.java
new file mode 100644
index 0000000..f04e674
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/BufferedResponseMapper.java
@@ -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.core.request.mapper;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.Session;
+import org.apache.wicket.protocol.http.BufferedWebResponse;
+import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.request.IRequestCycle;
+import org.apache.wicket.request.IRequestHandler;
+import org.apache.wicket.request.IRequestMapper;
+import org.apache.wicket.request.Request;
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.core.request.handler.BufferedResponseRequestHandler;
+import org.apache.wicket.session.ISessionStore;
+import org.apache.wicket.util.string.Strings;
+
+/**
+ * Encoder that intercepts requests for which there is already stored buffer with rendered data.
+ *
+ * @author Matej Knopp
+ */
+public class BufferedResponseMapper implements IRequestMapper
+{
+ /**
+ * Construct.
+ */
+ public BufferedResponseMapper()
+ {
+ }
+
+ /**
+ * @return the current session id for stateful pages and <code>null</code> for stateless pages
+ * and non-http threads
+ */
+ protected String getSessionId()
+ {
+ String sessionId = null;
+
+ if (Application.exists() && RequestCycle.get() != null)
+ {
+ ISessionStore sessionStore = Application.get().getSessionStore();
+ IRequestCycle requestCycle = RequestCycle.get();
+ Session session = sessionStore.lookup(requestCycle.getRequest());
+ if (session != null)
+ {
+ sessionId = session.getId();
+ }
+ }
+
+ return sessionId;
+ }
+
+ protected boolean hasBufferedResponse(Url url)
+ {
+ String sessionId = getSessionId();
+ boolean hasResponse = false;
+ if (Strings.isEmpty(sessionId) == false)
+ {
+ hasResponse = WebApplication.get().hasBufferedResponse(sessionId, url);
+ }
+ return hasResponse;
+ }
+
+ protected BufferedWebResponse getAndRemoveBufferedResponse(Url url)
+ {
+ String sessionId = getSessionId();
+ BufferedWebResponse response = null;
+ if (Strings.isEmpty(sessionId) == false)
+ {
+ response = WebApplication.get().getAndRemoveBufferedResponse(sessionId, url);
+ }
+ return response;
+ }
+
+ 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;
+ }
+ }
+
+ /**
+ * @see org.apache.wicket.request.IRequestMapper#mapRequest(org.apache.wicket.request.Request)
+ */
+ @Override
+ public IRequestHandler mapRequest(Request request)
+ {
+ request = getRequest(request);
+
+ BufferedWebResponse response = getAndRemoveBufferedResponse(request.getUrl());
+ if (response != null)
+ {
+ return new BufferedResponseRequestHandler(response);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * @see org.apache.wicket.request.IRequestMapper#mapHandler(org.apache.wicket.request.IRequestHandler)
+ */
+ @Override
+ public Url mapHandler(IRequestHandler requestHandler)
+ {
+ return null;
+ }
+
+ /**
+ * @see org.apache.wicket.request.IRequestMapper#getCompatibilityScore(org.apache.wicket.request.Request)
+ */
+ @Override
+ public int getCompatibilityScore(Request request)
+ {
+ request = getRequest(request);
+
+ if (hasBufferedResponse(request.getUrl()))
+ {
+ return Integer.MAX_VALUE;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/1257c03d/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/CryptoMapper.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/CryptoMapper.java b/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/CryptoMapper.java
new file mode 100755
index 0000000..40b0efa
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/CryptoMapper.java
@@ -0,0 +1,286 @@
+/*
+ * 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.core.request.mapper;
+
+import java.util.List;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.request.IRequestHandler;
+import org.apache.wicket.request.IRequestMapper;
+import org.apache.wicket.request.Request;
+import org.apache.wicket.request.Url;
+import org.apache.wicket.util.IProvider;
+import org.apache.wicket.util.crypt.ICrypt;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.string.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Request mapper that encrypts urls generated by another mapper. The original URL (both segments
+ * and parameters) is encrypted and is represented as URL segment. To be able to handle relative
+ * URLs for images in .css file the same amount of URL segments that the original URL had are
+ * appended to the encrypted URL. Each segment has a precise 5 character value, calculated using a
+ * checksum. This helps in calculating the relative distance from the original URL. When a URL is
+ * returned by the browser, we iterate through these checksummed placeholder URL segments. If the
+ * segment matches the expected checksum, then the segment it deemed to be the corresponding segment
+ * in the encrypted URL. If the segment does not match the expected checksum, then the segment is
+ * deemed a plain text sibling of the corresponding segment in the encrypted URL, and all subsequent
+ * segments are considered plain text children of the current segment.
+ *
+ *
+ * @author igor.vaynberg
+ * @author Jesse Long
+ * @author svenmeier
+ */
+public class CryptoMapper implements IRequestMapper
+{
+ private static final Logger log = LoggerFactory.getLogger(CryptoMapper.class);
+
+ private final IRequestMapper wrappedMapper;
+ private final IProvider<ICrypt> cryptProvider;
+
+ /**
+ * Construct.
+ *
+ * @param wrappedMapper
+ * the non-crypted request mapper
+ * @param application
+ * the current application
+ */
+ public CryptoMapper(final IRequestMapper wrappedMapper, final Application application)
+ {
+ this(wrappedMapper, new ApplicationCryptProvider(application));
+ }
+
+ /**
+ * Construct.
+ *
+ * @param wrappedMapper
+ * the non-crypted request mapper
+ * @param cryptProvider
+ * the custom crypt provider
+ */
+ public CryptoMapper(final IRequestMapper wrappedMapper, final IProvider<ICrypt> cryptProvider)
+ {
+ this.wrappedMapper = Args.notNull(wrappedMapper, "wrappedMapper");
+ this.cryptProvider = Args.notNull(cryptProvider, "cryptProvider");
+ }
+
+ @Override
+ public int getCompatibilityScore(final Request request)
+ {
+ return 0;
+ }
+
+ @Override
+ public Url mapHandler(final IRequestHandler requestHandler)
+ {
+ final Url url = wrappedMapper.mapHandler(requestHandler);
+
+ if (url == null)
+ {
+ return null;
+ }
+
+ return encryptUrl(url);
+ }
+
+ @Override
+ public IRequestHandler mapRequest(final Request request)
+ {
+ Url url = decryptUrl(request, request.getUrl());
+
+ if (url == null)
+ {
+ return null;
+ }
+
+ return wrappedMapper.mapRequest(request.cloneWithUrl(url));
+ }
+
+ /**
+ * @return the {@link ICrypt} implementation that may be used to encrypt/decrypt {@link Url}'s
+ * segments and/or query string
+ */
+ protected final ICrypt getCrypt()
+ {
+ return cryptProvider.get();
+ }
+
+ /**
+ * @return the wrapped root request mapper
+ */
+ protected final IRequestMapper getWrappedMapper()
+ {
+ return wrappedMapper;
+ }
+
+ private Url encryptUrl(final Url url)
+ {
+ if (url.getSegments().isEmpty() && url.getQueryParameters().isEmpty())
+ {
+ return url;
+ }
+ String encryptedUrlString = getCrypt().encryptUrlSafe(url.toString());
+
+ Url encryptedUrl = new Url(url.getCharset());
+ encryptedUrl.getSegments().add(encryptedUrlString);
+
+ int numberOfSegments = url.getSegments().size();
+ HashedSegmentGenerator generator = new HashedSegmentGenerator(encryptedUrlString);
+ for (int segNo = 0; segNo < numberOfSegments; segNo++)
+ {
+ encryptedUrl.getSegments().add(generator.next());
+ }
+ return encryptedUrl;
+ }
+
+ private Url decryptUrl(final Request request, final Url encryptedUrl)
+ {
+ if (encryptedUrl.getSegments().isEmpty())
+ {
+ return encryptedUrl;
+ }
+
+ List<String> encryptedSegments = encryptedUrl.getSegments();
+ if (encryptedSegments.size() < 1)
+ {
+ return null;
+ }
+
+ Url url = new Url(request.getCharset());
+ try
+ {
+ String encryptedUrlString = encryptedSegments.get(0);
+ if (Strings.isEmpty(encryptedUrlString))
+ {
+ return null;
+ }
+
+ String decryptedUrl = getCrypt().decryptUrlSafe(encryptedUrlString);
+ if (decryptedUrl == null)
+ {
+ return null;
+ }
+ Url originalUrl = Url.parse(decryptedUrl, request.getCharset());
+
+ int originalNumberOfSegments = originalUrl.getSegments().size();
+ int encryptedNumberOfSegments = encryptedUrl.getSegments().size();
+
+ HashedSegmentGenerator generator = new HashedSegmentGenerator(encryptedUrlString);
+ int segNo = 1;
+ for (; segNo < encryptedNumberOfSegments; segNo++)
+ {
+ if (segNo > originalNumberOfSegments)
+ {
+ break;
+ }
+
+ String next = generator.next();
+ String encryptedSegment = encryptedSegments.get(segNo);
+ if (!next.equals(encryptedSegment))
+ {
+ break;
+ }
+
+ // unmodified segment
+ url.getSegments().add(originalUrl.getSegments().get(segNo - 1));
+ }
+ for (; segNo < encryptedNumberOfSegments; segNo++)
+ {
+ // modified or additional segment
+ url.getSegments().add(encryptedUrl.getSegments().get(segNo));
+ }
+
+ url.getQueryParameters().addAll(originalUrl.getQueryParameters());
+ }
+ catch (Exception e)
+ {
+ log.error("Error decrypting URL", e);
+ url = null;
+ }
+
+ return url;
+ }
+
+ private static class ApplicationCryptProvider implements IProvider<ICrypt>
+ {
+ private final Application application;
+
+ public ApplicationCryptProvider(final Application application)
+ {
+ this.application = application;
+ }
+
+ @Override
+ public ICrypt get()
+ {
+ return application.getSecuritySettings().getCryptFactory().newCrypt();
+ }
+ }
+
+ /**
+ * A generator of hashed segments.
+ */
+ private static class HashedSegmentGenerator
+ {
+ private char[] characters;
+
+ private int hash = 0;
+
+ public HashedSegmentGenerator(String string)
+ {
+ characters = string.toCharArray();
+ }
+
+ /**
+ * Generate the next segment
+ *
+ * @return segment
+ */
+ public String next()
+ {
+ char a = characters[Math.abs(hash % characters.length)];
+ hash++;
+ char b = characters[Math.abs(hash % characters.length)];
+ hash++;
+ char c = characters[Math.abs(hash % characters.length)];
+
+ String segment = "" + a + b + c;
+ hash = hashString(segment);
+
+ segment += String.format("%02x", Math.abs(hash % 256));
+ hash = hashString(segment);
+
+ return segment;
+ }
+
+ private int hashString(final String str)
+ {
+ int hash = 97;
+
+ for (char c : str.toCharArray())
+ {
+ int i = c;
+ hash = 47 * hash + i;
+ }
+
+ return hash;
+ }
+ }
+}
\ No newline at end of file