📄 hybridurlcodingstrategy.java
字号:
/*
* 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.request.target.coding;
import java.lang.ref.WeakReference;
import org.apache.wicket.Application;
import org.apache.wicket.IRedirectListener;
import org.apache.wicket.IRequestTarget;
import org.apache.wicket.MetaDataKey;
import org.apache.wicket.Page;
import org.apache.wicket.PageParameters;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.Session;
import org.apache.wicket.protocol.http.PageExpiredException;
import org.apache.wicket.protocol.http.WebRequest;
import org.apache.wicket.protocol.http.request.WebRequestCodingStrategy;
import org.apache.wicket.request.RequestParameters;
import org.apache.wicket.request.target.component.BookmarkableListenerInterfaceRequestTarget;
import org.apache.wicket.request.target.component.BookmarkablePageRequestTarget;
import org.apache.wicket.request.target.component.PageRequestTarget;
import org.apache.wicket.request.target.component.listener.ListenerInterfaceRequestTarget;
import org.apache.wicket.util.string.AppendingStringBuffer;
import org.apache.wicket.util.string.Strings;
/**
* An URL coding strategy that encodes the mount point, page parameters and page instance
* information into the URL. The benefits compared to mounting page with
* {@link BookmarkablePageRequestTargetUrlCodingStrategy} are that the mount point is preserved even
* after invoking listener interfaces (thus you don't lose bookmarkability after clicking links) and
* that for ajax only pages the state is preserved on refresh.
* <p>
* The url with {@link HybridUrlCodingStrategy} looks like /mount/path/param1/value1.3. or
* /mount/path/param1/value1.3.2 where 3 is page Id and 2 is version number.
* <p>
* Also to preserve state on refresh with ajax-only pages the {@link HybridUrlCodingStrategy} does
* an immediate redirect after hitting bookmarkable URL, e.g. it immediately redirects from
* /mount/path to /mount/path.3 where 3 is the next page id. This preserves the page instance on
* subsequent page refresh.
*
* @author Matej Knopp
*/
public class HybridUrlCodingStrategy extends AbstractRequestTargetUrlCodingStrategy
{
/** bookmarkable page class. */
protected final WeakReference/* <Class> */pageClassRef;
private final boolean redirectOnBookmarkableRequest;
/**
* Construct.
*
* @param mountPath
* @param pageClass
* @param redirectOnBookmarkableRequest
* whether after hitting the page with URL in bookmarkable form it should be
* redirected to hybrid URL - needed for ajax to work properly after page refresh
*/
public HybridUrlCodingStrategy(String mountPath, Class pageClass,
boolean redirectOnBookmarkableRequest)
{
super(mountPath);
pageClassRef = new WeakReference(pageClass);
this.redirectOnBookmarkableRequest = redirectOnBookmarkableRequest;
}
/**
* Construct.
*
* @param mountPath
* @param pageClass
*/
public HybridUrlCodingStrategy(String mountPath, Class pageClass)
{
this(mountPath, pageClass, true);
}
/**
* Returns the amount of trailing slashes in the given string
*
* @param seq
* @return
*/
private int getTrailingSlashesCount(CharSequence seq)
{
int count = 0;
for (int i = seq.length() - 1; i >= 0; --i)
{
if (seq.charAt(i) == '/')
{
++count;
}
else
{
break;
}
}
return count;
}
/**
* Returns whether after hitting bookmarkable url the request should be redirected to a hybrid
* URL. This is recommended for pages with Ajax.
*
* @return
*/
protected boolean isRedirectOnBookmarkableRequest()
{
return redirectOnBookmarkableRequest;
}
/**
* Returns whether to redirect when there is pageMap specified in bookmarkable URL
*
* @return
*/
protected boolean alwaysRedirectWhenPageMapIsSpecified()
{
// returns true if the pageId is unique, so we can get rid of the
// pageMap name in the url this way
return Application.exists() &&
Application.get().getSessionSettings().isPageIdUniquePerSession();
}
/**
* @see org.apache.wicket.request.target.coding.IRequestTargetUrlCodingStrategy#decode(org.apache.wicket.request.RequestParameters)
*/
public IRequestTarget decode(RequestParameters requestParameters)
{
String parametersFragment = requestParameters.getPath().substring(getMountPath().length());
// try to extract page info
PageInfoExtraction extraction = extractPageInfo(parametersFragment);
PageInfo pageInfo = extraction.getPageInfo();
String pageMapName = pageInfo != null ? pageInfo.getPageMapName() : null;
Integer pageVersion = pageInfo != null ? pageInfo.getVersionNumber() : null;
Integer pageId = pageInfo != null ? pageInfo.getPageId() : null;
// decode parameters
PageParameters parameters = new PageParameters(decodeParameters(
extraction.getUrlAfterExtraction(), requestParameters.getParameters()));
if (requestParameters.getPageMapName() == null)
{
requestParameters.setPageMapName(pageMapName);
}
else
{
pageMapName = requestParameters.getPageMapName();
}
// do some extra work for checking whether this is a normal request to a
// bookmarkable page, or a request to a stateless page (in which case a
// wicket:interface parameter should be available
final String interfaceParameter = (String)parameters.remove(WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME);
// we need to remember the amount of trailing slashes after the redirect
// (otherwise we'll break relative urls)
int originalUrlTrailingSlashesCount = getTrailingSlashesCount(extraction.getUrlAfterExtraction());
boolean redirect = isRedirectOnBookmarkableRequest();
if (Strings.isEmpty(pageMapName) != true && alwaysRedirectWhenPageMapIsSpecified())
{
redirect = true;
}
if (interfaceParameter != null)
{
// stateless listener interface
WebRequestCodingStrategy.addInterfaceParameters(interfaceParameter, requestParameters);
return new BookmarkableListenerInterfaceRequestTarget(pageMapName,
(Class)pageClassRef.get(), parameters, requestParameters.getComponentPath(),
requestParameters.getInterfaceName(), requestParameters.getVersionNumber());
}
else if (pageId == null)
{
// bookmarkable page request
return new HybridBookmarkablePageRequestTarget(pageMapName, (Class)pageClassRef.get(),
parameters, originalUrlTrailingSlashesCount, redirect);
}
else
// hybrid url
{
Page page;
if (Strings.isEmpty(pageMapName) && Application.exists() &&
Application.get().getSessionSettings().isPageIdUniquePerSession())
{
page = Session.get().getPage(pageId.intValue(),
pageVersion != null ? pageVersion.intValue() : 0);
}
else
{
page = Session.get().getPage(pageMapName, "" + pageId,
pageVersion != null ? pageVersion.intValue() : 0);
}
// check if the found page match the required class
if (page != null && page.getClass().equals(pageClassRef.get()))
{
requestParameters.setInterfaceName(IRedirectListener.INTERFACE.getName());
RequestCycle.get().getRequest().setPage(page);
return new PageRequestTarget(page);
}
else
{
// we didn't find the page, act as bookmarkable page request -
// create new instance, but only if there is no callback to a non-existing page
if (requestParameters.getInterface() != null)
{
handleExpiredPage(pageMapName, (Class)pageClassRef.get(),
originalUrlTrailingSlashesCount, redirect);
}
return new HybridBookmarkablePageRequestTarget(pageMapName,
(Class)pageClassRef.get(), parameters, originalUrlTrailingSlashesCount,
redirect);
}
}
}
/**
* Handles the case where a non-bookmarkable url with a hybrid base refers to a page that is no
* longer in session. eg <code>/context/hybrid-mount.0.23?wicket:interface=...</code>. The
* default behavior is to throw a <code>PageExpiredException</code>.
*
* This method can be overwritten to, for example, return the user to a new instance of the
* bookmarkable page that was mounted using hybrid strategy - this, however, should only be used
* in cases where the page expects no page parameters because they are no longer available.
*
* @param pageMapName
* page map name this page is mounted in
* @param pageClass
* class of mounted page
* @param trailingSlashesCount
* count of trailing slsahes in the url
* @param redirect
* whether or not a redirect should be issued
* @return request target used to handle this situation
*/
protected IRequestTarget handleExpiredPage(String pageMapName, Class pageClass,
int trailingSlashesCount, boolean redirect)
{
throw new PageExpiredException(
"Request cannot be processed. The target page does not exist anymore.");
}
/**
* Returns the number of trailing slashes in the url when the page in request target was created
* or null if the number can't be determined.
*
* @param requestTarget
* @return
*/
private Integer getOriginalOriginalTrailingSlashesCount(IRequestTarget requestTarget)
{
if (requestTarget instanceof ListenerInterfaceRequestTarget)
{
ListenerInterfaceRequestTarget target = (ListenerInterfaceRequestTarget)requestTarget;
Page page = target.getPage();
return (Integer)page.getMetaData(ORIGINAL_TRAILING_SLASHES_COUNT_METADATA_KEY);
}
return null;
}
/**
* Extracts the PageParameters from given request target
*
* @param requestTarget
* @return
*/
private PageParameters getPageParameters(IRequestTarget requestTarget)
{
if (requestTarget instanceof BookmarkablePageRequestTarget)
{
BookmarkablePageRequestTarget target = (BookmarkablePageRequestTarget)requestTarget;
return target.getPageParameters();
}
else if (requestTarget instanceof ListenerInterfaceRequestTarget)
{
ListenerInterfaceRequestTarget target = (ListenerInterfaceRequestTarget)requestTarget;
Page page = target.getPage();
return getInitialPagePageParameters(page);
}
else
{
return null;
}
}
/**
* Extracts the PageInfo from given request target
*
* @param requestTarget
* @return
*/
private PageInfo getPageInfo(IRequestTarget requestTarget)
{
if (requestTarget instanceof BookmarkablePageRequestTarget)
{
BookmarkablePageRequestTarget target = (BookmarkablePageRequestTarget)requestTarget;
if (target.getPageMapName() != null)
{
return new PageInfo(null, null, target.getPageMapName());
}
else
{
return null;
}
}
else if (requestTarget instanceof ListenerInterfaceRequestTarget)
{
ListenerInterfaceRequestTarget target = (ListenerInterfaceRequestTarget)requestTarget;
Page page = target.getPage();
return new PageInfo(new Integer(page.getNumericId()), new Integer(
page.getCurrentVersionNumber()), page.getPageMapName());
}
else
{
return null;
}
}
/**
* Sets the initial page parameters for page instance. Use this only if you know what you are
* doing.
*
* @param page
* @param pageParameters
*/
public static void setInitialPageParameters(Page page, PageParameters pageParameters)
{
page.setMetaData(PAGE_PARAMETERS_META_DATA_KEY, pageParameters);
}
/**
* @param page
* @return
*/
public static PageParameters getInitialPagePageParameters(Page page)
{
return (PageParameters)page.getMetaData(PAGE_PARAMETERS_META_DATA_KEY);
}
/**
* Meta data key to store PageParameters in page instance. This is used to save the
* PageParameters that were used to create the page instance so that later they can be used when
* generating page URL
*/
public static final PageParametersMetaDataKey PAGE_PARAMETERS_META_DATA_KEY = new PageParametersMetaDataKey();
private static class PageParametersMetaDataKey extends MetaDataKey
{
private static final long serialVersionUID = 1L;
/**
* Construct.
*/
public PageParametersMetaDataKey()
{
super(PageParameters.class);
}
};
// used to store number of trailing slashes in page url (prior the PageInfo)
// part. This is necessary to maintain
// the exact number of slashes after page redirect, so that we don't break
// things that rely on URL depth
// (other mounted URLs)
private static final OriginalUrlTrailingSlashesCountMetaDataKey ORIGINAL_TRAILING_SLASHES_COUNT_METADATA_KEY = new OriginalUrlTrailingSlashesCountMetaDataKey();
private static class OriginalUrlTrailingSlashesCountMetaDataKey extends MetaDataKey
{
private static final long serialVersionUID = 1L;
/**
* Construct.
*/
public OriginalUrlTrailingSlashesCountMetaDataKey()
{
super(Integer.class);
}
}
/**
* Fix the amount of trailing slashes in the specified buffer.
*
* @param buffer
* @param desiredCount
*/
private void fixTrailingSlashes(AppendingStringBuffer buffer, int desiredCount)
{
int current = getTrailingSlashesCount(buffer);
if (current > desiredCount)
{
buffer.setLength(buffer.length() - (current - desiredCount));
}
else if (desiredCount > current)
{
int toAdd = desiredCount - current;
while (toAdd > 0)
{
buffer.append("/");
--toAdd;
}
}
}
/**
* @see org.apache.wicket.request.target.coding.IRequestTargetUrlCodingStrategy#encode(org.apache.wicket.IRequestTarget)
*/
public CharSequence encode(IRequestTarget requestTarget)
{
if (matches(requestTarget) == false)
{
throw new IllegalArgumentException("Unsupported request target type.");
}
PageParameters parameters = getPageParameters(requestTarget);
PageInfo pageInfo = getPageInfo(requestTarget);
final AppendingStringBuffer url = new AppendingStringBuffer(40);
url.append(getMountPath());
// there are cases where the parameters are null
if (parameters != null)
{
appendParameters(url, parameters);
}
// check whether we know if the initial URL ended with slash
Integer trailingSlashesCount = getOriginalOriginalTrailingSlashesCount(requestTarget);
if (trailingSlashesCount != null)
{
fixTrailingSlashes(url, trailingSlashesCount.intValue());
}
return addPageInfo(url.toString(), pageInfo);
}
/**
* @see org.apache.wicket.request.target.coding.IRequestTargetUrlCodingStrategy#matches(org.apache.wicket.IRequestTarget)
*/
public boolean matches(IRequestTarget requestTarget)
{
if (requestTarget instanceof BookmarkablePageRequestTarget)
{
BookmarkablePageRequestTarget target = (BookmarkablePageRequestTarget)requestTarget;
return target.getPageClass().equals(pageClassRef.get());
}
else if (requestTarget instanceof ListenerInterfaceRequestTarget)
{
ListenerInterfaceRequestTarget target = (ListenerInterfaceRequestTarget)requestTarget;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -