blob: 31aa97cf392cf2bf378648335794a079b691dc3c [file] [log] [blame]
/*
* 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(&quot;/images&quot;, 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());
}
}
}