| /* |
| * 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 jakarta.servlet.http.HttpServletResponse; |
| |
| 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.request.handler.resource.ResourceReferenceRequestHandler; |
| import org.apache.wicket.request.http.flow.AbortWithHttpErrorCodeException; |
| import org.apache.wicket.request.mapper.parameter.INamedParameters; |
| 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.request.resource.IResource; |
| 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.resource.ResourceUtil; |
| import org.apache.wicket.util.lang.Args; |
| import org.apache.wicket.util.string.Strings; |
| |
| /** |
| * A {@link IRequestMapper} to mount resources to a custom mount path |
| * <ul> |
| * <li>maps indexed parameters to path segments</li> |
| * <li>maps named parameters to query string arguments or placeholder path segments</li> |
| * </ul> |
| * |
| * <strong>sample structure of url</strong> |
| * |
| * <pre> |
| * /myresources/${category}/images/[indexed-param-0]/[indexed-param-1]?[named-param-1=value]&[named-param-2=value2] |
| * </pre> |
| * |
| * <h4>sample usage</h4> |
| * |
| * in your wicket application's init() method use a statement like this |
| * <p/> |
| * |
| * <pre> |
| * mountResource("/images", new ImagesResourceReference())); |
| * </pre> |
| * |
| * Note: Mounted this way the resource reference has application scope, i.e. it is shared between |
| * all users of the application. It is recommended to not keep any state in it. |
| * |
| * @see org.apache.wicket.protocol.http.WebApplication#mountResource(String, |
| * org.apache.wicket.request.resource.ResourceReference) |
| * |
| * @author Peter Ertl |
| */ |
| public class ResourceMapper extends AbstractBookmarkableMapper |
| { |
| // encode page parameters into url + decode page parameters from url |
| private final IPageParametersEncoder parametersEncoder; |
| |
| // mount path (= segments) the resource is bound to |
| private final String[] mountSegments; |
| |
| // resource that the mapper links to |
| private final ResourceReference resourceReference; |
| |
| /** |
| * create a resource mapper for a resource |
| * |
| * @param path |
| * mount path for the resource |
| * @param resourceReference |
| * resource reference that should be linked to the mount path |
| * |
| * @see #ResourceMapper(String, org.apache.wicket.request.resource.ResourceReference, |
| * org.apache.wicket.request.mapper.parameter.IPageParametersEncoder) |
| */ |
| public ResourceMapper(String path, ResourceReference resourceReference) |
| { |
| this(path, resourceReference, new PageParametersEncoder()); |
| } |
| |
| /** |
| * create a resource mapper for a resource |
| * |
| * @param path |
| * mount path for the resource |
| * @param resourceReference |
| * resource reference that should be linked to the mount path |
| * @param encoder |
| * encoder for url parameters |
| */ |
| public ResourceMapper(String path, ResourceReference resourceReference, |
| IPageParametersEncoder encoder) |
| { |
| super(path, encoder); |
| Args.notNull(resourceReference, "resourceReference"); |
| |
| this.resourceReference = resourceReference; |
| mountSegments = getMountSegments(path); |
| parametersEncoder = encoder; |
| } |
| |
| @Override |
| public IRequestHandler mapRequest(final Request request) |
| { |
| final Url url = new Url(request.getUrl()); |
| |
| // now extract the page parameters from the request url |
| PageParameters parameters = extractPageParameters(request, mountSegments.length, |
| parametersEncoder); |
| if (parameters != null) |
| { |
| parameters.setLocale(resolveLocale()); |
| } |
| |
| // remove caching information from current request |
| removeCachingDecoration(url, parameters); |
| |
| // check if url matches mount path |
| if (urlStartsWith(url, mountSegments) == false) |
| { |
| return null; |
| } |
| |
| // check if there are placeholders in mount segments |
| for (int index = 0; index < mountSegments.length; ++index) |
| { |
| String placeholder = getPlaceholder(mountSegments[index]); |
| |
| if (placeholder != null) |
| { |
| // extract the parameter from URL |
| if (parameters == null) |
| { |
| parameters = newPageParameters(); |
| } |
| parameters.add(placeholder, url.getSegments().get(index), INamedParameters.Type.PATH); |
| } |
| } |
| return new ResourceReferenceRequestHandler(resourceReference, parameters); |
| } |
| |
| @Override |
| protected final UrlInfo parseRequest(final Request request) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| protected final Url buildUrl(final UrlInfo info) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| protected final boolean pageMustHaveBeenCreatedBookmarkable() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public int getCompatibilityScore(Request request) |
| { |
| Url originalUrl = new Url(request.getUrl()); |
| PageParameters parameters = extractPageParameters(request, mountSegments.length, parametersEncoder); |
| if (parameters != null) |
| { |
| parameters.setLocale(resolveLocale()); |
| } |
| removeCachingDecoration(originalUrl, parameters); |
| Request requestWithoutDecoration = request.cloneWithUrl(originalUrl); |
| |
| int score = super.getCompatibilityScore(requestWithoutDecoration); |
| if (score > 0) |
| { |
| score--; // pages always have priority over resources |
| } |
| else |
| { |
| score = -1; |
| } |
| return score; |
| } |
| |
| @Override |
| public Url mapHandler(IRequestHandler requestHandler) |
| { |
| if ((requestHandler instanceof ResourceReferenceRequestHandler) == false) |
| { |
| return null; |
| } |
| |
| ResourceReferenceRequestHandler handler = (ResourceReferenceRequestHandler)requestHandler; |
| |
| // see if request handler addresses the resource reference we serve |
| if (resourceReference.equals(handler.getResourceReference()) == false) |
| { |
| return null; |
| } |
| |
| Url url = new Url(); |
| |
| // add mount path segments |
| for (String segment : mountSegments) |
| { |
| url.getSegments().add(segment); |
| } |
| |
| // replace placeholder parameters |
| PageParameters parameters = newPageParameters(); |
| parameters.mergeWith(handler.getPageParameters()); |
| |
| for (int index = 0; index < mountSegments.length; ++index) |
| { |
| String placeholder = getPlaceholder(mountSegments[index]); |
| |
| if (placeholder != null) |
| { |
| url.getSegments().set(index, parameters.get(placeholder).toString("")); |
| parameters.remove(placeholder); |
| } |
| } |
| |
| // add caching information |
| addCachingDecoration(url, parameters); |
| |
| ResourceUtil.encodeResourceReferenceAttributes(url, resourceReference); |
| // create url |
| return encodePageParameters(url, parameters, parametersEncoder); |
| } |
| |
| protected IResourceCachingStrategy getCachingStrategy() |
| { |
| return Application.get().getResourceSettings().getCachingStrategy(); |
| } |
| |
| protected void addCachingDecoration(Url url, PageParameters parameters) |
| { |
| final List<String> segments = url.getSegments(); |
| final int lastSegmentAt = segments.size() - 1; |
| final String filename = segments.get(lastSegmentAt); |
| |
| if (Strings.isEmpty(filename) == false) |
| { |
| final IResource resource = resourceReference.getResource(); |
| |
| if (resource instanceof IStaticCacheableResource) |
| { |
| final IStaticCacheableResource cacheable = (IStaticCacheableResource)resource; |
| |
| if(cacheable.isCachingEnabled()) |
| { |
| final ResourceUrl cacheUrl = new ResourceUrl(filename, parameters); |
| |
| getCachingStrategy().decorateUrl(cacheUrl, cacheable); |
| |
| if (Strings.isEmpty(cacheUrl.getFileName())) |
| { |
| if (Application.exists() && Application.get().usesDeploymentConfig()) |
| { |
| throw new AbortWithHttpErrorCodeException(HttpServletResponse.SC_NOT_FOUND, |
| "caching strategy returned empty name for " + resource); |
| } |
| else |
| { |
| throw new IllegalStateException( |
| "caching strategy returned empty name for " + resource); |
| } |
| } |
| segments.set(lastSegmentAt, cacheUrl.getFileName()); |
| } |
| } |
| } |
| } |
| |
| protected void removeCachingDecoration(Url url, PageParameters parameters) |
| { |
| final List<String> segments = url.getSegments(); |
| |
| if (segments.isEmpty() == false) |
| { |
| // get filename (the last segment) |
| final int lastSegmentAt = segments.size() - 1; |
| String filename = segments.get(lastSegmentAt); |
| |
| // ignore requests with empty filename |
| if (Strings.isEmpty(filename)) |
| { |
| return; |
| } |
| |
| // create resource url from filename and query parameters |
| final ResourceUrl resourceUrl = new ResourceUrl(filename, parameters); |
| |
| // remove caching information from request |
| getCachingStrategy().undecorateUrl(resourceUrl); |
| |
| // check for broken caching strategy (this must never happen) |
| if (Strings.isEmpty(resourceUrl.getFileName())) |
| { |
| throw new IllegalStateException("caching strategy returned empty name for " + |
| resourceUrl); |
| } |
| |
| segments.set(lastSegmentAt, resourceUrl.getFileName()); |
| } |
| } |
| } |