/* * 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.function.Supplier; import org.apache.wicket.core.request.handler.ListenerRequestHandler; import org.apache.wicket.request.IRequestHandler; 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.ComponentInfo; import org.apache.wicket.request.mapper.info.PageComponentInfo; import org.apache.wicket.request.mapper.info.PageInfo; 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; import org.apache.wicket.util.reference.ClassReference; import org.apache.wicket.util.string.Strings; /** * Encoder for mounted URL. The mount path can contain parameter placeholders, i.e. * <code>/mount/${foo}/path</code>. In that case the appropriate segment from the URL will be * accessible as named parameter "foo" in the {@link PageParameters}. Similarly when the URL is * constructed, the second segment will contain the value of the "foo" named page parameter. * Optional parameters are denoted by using a # instead of $: <code>/mount/#{foo}/path/${bar}</code> * has an optional {@code foo} parameter, a fixed {@code /path/} part and a required {@code bar} * parameter. When in doubt, parameters are matched from left to right, where required parameters * are matched before optional parameters, and optional parameters eager (from left to right). * <p> * Decodes and encodes the following URLs: * * <pre> * Page Class - Render (BookmarkablePageRequestHandler for mounted pages) * /mount/point * (these will redirect to hybrid alternative if page is not stateless) * * IPage Instance - Render Hybrid (RenderPageRequestHandler for mounted pages) * /mount/point?2 * * IPage Instance - Bookmarkable Listener (BookmarkableListenerRequestHandler for mounted pages) * /mount/point?2-click-foo-bar-baz * /mount/point?2-5.click.1-foo-bar-baz (1 is behavior index, 5 is render count) * (these will redirect to hybrid if page is not stateless) * </pre> * * @author Matej Knopp */ public class MountedMapper extends AbstractBookmarkableMapper { /** bookmarkable page class. */ private final Supplier<Class<? extends IRequestablePage>> pageClassProvider; /** * Construct. * * @param mountPath * @param pageClass */ public MountedMapper(String mountPath, Class<? extends IRequestablePage> pageClass) { this(mountPath, pageClass, new PageParametersEncoder()); } /** * Construct. * * @param mountPath * @param pageClassProvider */ public MountedMapper(String mountPath, Supplier<Class<? extends IRequestablePage>> pageClassProvider) { this(mountPath, pageClassProvider, new PageParametersEncoder()); } /** * Construct. * * @param mountPath * @param pageClass * @param pageParametersEncoder */ public MountedMapper(String mountPath, Class<? extends IRequestablePage> pageClass, IPageParametersEncoder pageParametersEncoder) { this(mountPath, new ClassReference(pageClass), pageParametersEncoder); } /** * Construct. * * @param mountPath * @param pageClassProvider * @param pageParametersEncoder */ public MountedMapper(String mountPath, Supplier<Class<? extends IRequestablePage>> pageClassProvider, IPageParametersEncoder pageParametersEncoder) { super(mountPath, pageParametersEncoder); Args.notNull(pageClassProvider, "pageClassProvider"); this.pageClassProvider = pageClassProvider; } @Override protected UrlInfo parseRequest(Request request) { Url url = request.getUrl(); // when redirect to buffer/render is active and redirectFromHomePage returns true // check mounted class against the home page class. if it matches let wicket redirect // to the mounted URL if (redirectFromHomePage() && checkHomePage(url)) { return new UrlInfo(null, getContext().getHomePageClass(), newPageParameters()); } // check if the URL starts with the proper segments else if (urlStartsWithMountedSegments(url)) { // try to extract page and component information from URL PageComponentInfo info = getPageComponentInfo(url); Class<? extends IRequestablePage> pageClass = getPageClass(); PageParameters pageParameters = extractPageParameters(request, url); return new UrlInfo(info, pageClass, pageParameters); } else { return null; } } protected PageParameters newPageParameters() { return new PageParameters(); } @Override public Url mapHandler(IRequestHandler requestHandler) { Url url = super.mapHandler(requestHandler); if (url == null && requestHandler instanceof ListenerRequestHandler && getRecreateMountedPagesAfterExpiry()) { ListenerRequestHandler handler = (ListenerRequestHandler)requestHandler; IRequestablePage page = handler.getPage(); if (checkPageInstance(page)) { Integer renderCount = null; if (handler.includeRenderCount()) { renderCount = page.getRenderCount(); } String componentPath = handler.getComponentPath(); PageInfo pageInfo = getPageInfo(handler); ComponentInfo componentInfo = new ComponentInfo(renderCount, componentPath, handler.getBehaviorIndex()); PageComponentInfo pageComponentInfo = new PageComponentInfo(pageInfo, componentInfo); PageParameters parameters = new PageParameters(page.getPageParameters()); UrlInfo urlInfo = new UrlInfo(pageComponentInfo, page.getClass(), parameters.mergeWith(handler.getPageParameters())); url = buildUrl(urlInfo); } } return url; } /** * @see AbstractBookmarkableMapper#buildUrl(AbstractBookmarkableMapper.UrlInfo) */ @Override protected Url buildUrl(UrlInfo info) { Url url = new Url(); for (String s : mountSegments) { url.getSegments().add(s); } encodePageComponentInfo(url, info.getPageComponentInfo()); PageParameters copy = new PageParameters(info.getPageParameters()); if (setPlaceholders(copy, url) == false) { // mandatory parameter is not provided => cannot build Url return null; } return encodePageParameters(url, copy, pageParametersEncoder); } /** * Check if the URL is for home page and the home page class match mounted class. If so, * redirect to mounted URL. * * @param url * @return request handler or <code>null</code> */ private boolean checkHomePage(Url url) { if (url.getSegments().isEmpty() && url.getQueryParameters().isEmpty()) { // this is home page if (getPageClass().equals(getContext().getHomePageClass())) { return true; } } return false; } /** * If this method returns <code>true</code> and application home page class is same as the class * mounted with this encoder, request to home page will result in a redirect to the mounted * path. * * @return whether this encode should respond to home page request when home page class is same * as mounted class. */ protected boolean redirectFromHomePage() { return true; } /** * @see AbstractBookmarkableMapper#pageMustHaveBeenCreatedBookmarkable() */ @Override protected boolean pageMustHaveBeenCreatedBookmarkable() { return false; } /** * @see AbstractBookmarkableMapper#checkPageClass(java.lang.Class) */ @Override protected boolean checkPageClass(Class<? extends IRequestablePage> pageClass) { return pageClass.equals(this.getPageClass()); } private Class<? extends IRequestablePage> getPageClass() { return pageClassProvider.get(); } @Override public String toString() { return "MountedMapper [mountSegments=" + Strings.join("/", mountSegments) + "]"; } }