001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.core.request.mapper; 018 019import java.util.List; 020import java.util.StringTokenizer; 021import java.util.function.Supplier; 022 023import org.apache.wicket.core.util.lang.WicketObjects; 024import org.apache.wicket.request.IRequestHandler; 025import org.apache.wicket.request.Request; 026import org.apache.wicket.request.Url; 027import org.apache.wicket.request.handler.resource.ResourceReferenceRequestHandler; 028import org.apache.wicket.request.mapper.parameter.IPageParametersEncoder; 029import org.apache.wicket.request.mapper.parameter.PageParameters; 030import org.apache.wicket.request.resource.IResource; 031import org.apache.wicket.request.resource.MetaInfStaticResourceReference; 032import org.apache.wicket.request.resource.ResourceReference; 033import org.apache.wicket.request.resource.ResourceReferenceRegistry; 034import org.apache.wicket.request.resource.caching.IResourceCachingStrategy; 035import org.apache.wicket.request.resource.caching.IStaticCacheableResource; 036import org.apache.wicket.request.resource.caching.ResourceUrl; 037import org.apache.wicket.resource.ResourceUtil; 038import org.apache.wicket.resource.bundles.ResourceBundleReference; 039import org.apache.wicket.util.lang.Args; 040import org.apache.wicket.util.lang.Checks; 041import org.apache.wicket.util.string.Strings; 042import org.slf4j.Logger; 043import org.slf4j.LoggerFactory; 044 045/** 046 * Generic {@link ResourceReference} encoder that encodes and decodes non-mounted 047 * {@link ResourceReference}s. 048 * <p> 049 * Decodes and encodes the following URLs: 050 * 051 * <pre> 052 * /wicket/resource/org.apache.wicket.ResourceScope/name 053 * /wicket/resource/org.apache.wicket.ResourceScope/name?en 054 * /wicket/resource/org.apache.wicket.ResourceScope/name?-style 055 * /wicket/resource/org.apache.wicket.ResourceScope/resource/name.xyz?en_EN-style 056 * </pre> 057 * 058 * @author Matej Knopp 059 * @author igor.vaynberg 060 * @author Peter Ertl 061 */ 062public class BasicResourceReferenceMapper extends AbstractResourceReferenceMapper 063{ 064 private static final Logger log = LoggerFactory.getLogger(BasicResourceReferenceMapper.class); 065 066 protected final IPageParametersEncoder pageParametersEncoder; 067 068 /** resource caching strategy */ 069 protected final Supplier<? extends IResourceCachingStrategy> cachingStrategy; 070 071 /** 072 * Construct. 073 * 074 * @param pageParametersEncoder 075 * @param cachingStrategy 076 */ 077 public BasicResourceReferenceMapper(IPageParametersEncoder pageParametersEncoder, 078 Supplier<? extends IResourceCachingStrategy> cachingStrategy) 079 { 080 this.pageParametersEncoder = Args.notNull(pageParametersEncoder, "pageParametersEncoder"); 081 this.cachingStrategy = cachingStrategy; 082 } 083 084 @Override 085 public IRequestHandler mapRequest(Request request) 086 { 087 Url url = request.getUrl(); 088 089 if (canBeHandled(url)) 090 { 091 final int segmentsSize = url.getSegments().size(); 092 093 // extract the PageParameters from URL if there are any 094 PageParameters pageParameters = extractPageParameters(request, segmentsSize, 095 pageParametersEncoder); 096 if (pageParameters != null) 097 { 098 pageParameters.setLocale(resolveLocale()); 099 } 100 101 String className = url.getSegments().get(2); 102 StringBuilder name = new StringBuilder(segmentsSize * 2); 103 104 for (int i = 3; i < segmentsSize; ++i) 105 { 106 String segment = url.getSegments().get(i); 107 108 // ignore invalid segments 109 if (segment.indexOf('/') > -1) 110 { 111 return null; 112 } 113 114 // remove caching information 115 if (i + 1 == segmentsSize && Strings.isEmpty(segment) == false) 116 { 117 // The filename + parameters eventually contain caching 118 // related information which needs to be removed 119 ResourceUrl resourceUrl = new ResourceUrl(segment, pageParameters); 120 getCachingStrategy().undecorateUrl(resourceUrl); 121 segment = resourceUrl.getFileName(); 122 123 Checks.notEmpty(segment, "Caching strategy returned empty name for '%s'", resourceUrl); 124 } 125 if (name.length() > 0) 126 { 127 name.append('/'); 128 } 129 name.append(segment); 130 } 131 132 ResourceReference.UrlAttributes attributes = ResourceUtil.decodeResourceReferenceAttributes(url); 133 134 Class<?> scope = resolveClass(className); 135 136 if (scope != null && scope.getPackage() != null) 137 { 138 ResourceReference res = getContext().getResourceReferenceRegistry() 139 .getResourceReference(scope, name.toString(), attributes.getLocale(), 140 attributes.getStyle(), attributes.getVariation(), true, true); 141 142 if (res != null) 143 { 144 return new ResourceReferenceRequestHandler(res, pageParameters); 145 } 146 } 147 } 148 return null; 149 } 150 151 protected final IResourceCachingStrategy getCachingStrategy() 152 { 153 return cachingStrategy.get(); 154 } 155 156 protected Class<?> resolveClass(String name) 157 { 158 return WicketObjects.resolveClass(name); 159 } 160 161 protected String getClassName(Class<?> scope) 162 { 163 return scope.getName(); 164 } 165 166 @Override 167 public Url mapHandler(IRequestHandler requestHandler) 168 { 169 if (requestHandler instanceof ResourceReferenceRequestHandler) 170 { 171 ResourceReferenceRequestHandler referenceRequestHandler = (ResourceReferenceRequestHandler)requestHandler; 172 ResourceReference reference = referenceRequestHandler.getResourceReference(); 173 174 Url url; 175 176 while (reference instanceof ResourceBundleReference) 177 { 178 // unwrap the bundle to render the url for the actual reference 179 reference = ((ResourceBundleReference)reference).getBundleReference(); 180 } 181 182 if (reference instanceof MetaInfStaticResourceReference) 183 { 184 url = ((MetaInfStaticResourceReference)reference).mapHandler(referenceRequestHandler); 185 // if running on Servlet 3.0 engine url is not null 186 if (url != null) 187 { 188 return url; 189 } 190 // otherwise it has to be served by the standard wicket way 191 } 192 193 if (reference.canBeRegistered()) 194 { 195 ResourceReferenceRegistry resourceReferenceRegistry = getContext().getResourceReferenceRegistry(); 196 resourceReferenceRegistry.registerResourceReference(reference); 197 } 198 199 url = new Url(); 200 201 List<String> segments = url.getSegments(); 202 segments.add(getContext().getNamespace()); 203 segments.add(getContext().getResourceIdentifier()); 204 segments.add(getClassName(reference.getScope())); 205 206 // setup resource parameters 207 PageParameters parameters = new PageParameters(referenceRequestHandler.getPageParameters()); 208 // need to remove indexed parameters otherwise the URL won't be able to decode 209 parameters.clearIndexed(); 210 211 ResourceUtil.encodeResourceReferenceAttributes(url, reference); 212 213 StringTokenizer tokens = new StringTokenizer(reference.getName(), "/"); 214 215 while (tokens.hasMoreTokens()) 216 { 217 String token = tokens.nextToken(); 218 219 // on the last component of the resource path 220 if (tokens.hasMoreTokens() == false && Strings.isEmpty(token) == false) 221 { 222 final IResource resource = reference.getResource(); 223 224 // is resource supposed to be cached? 225 if (resource instanceof IStaticCacheableResource) 226 { 227 final IStaticCacheableResource cacheable = (IStaticCacheableResource)resource; 228 229 // is caching enabled? 230 if(cacheable.isCachingEnabled()) 231 { 232 // apply caching scheme to resource url 233 final ResourceUrl resourceUrl = new ResourceUrl(token, parameters); 234 getCachingStrategy().decorateUrl(resourceUrl, cacheable); 235 token = resourceUrl.getFileName(); 236 237 Checks.notEmpty(token, "Caching strategy returned empty name for '%s'", resource); 238 } 239 } 240 } 241 segments.add(token); 242 } 243 244 if (parameters.isEmpty() == false) 245 { 246 url = encodePageParameters(url, parameters, pageParametersEncoder); 247 } 248 249 return url; 250 } 251 return null; 252 } 253 254 @Override 255 public int getCompatibilityScore(Request request) 256 { 257 Url url = request.getUrl(); 258 259 int score = -1; 260 if (canBeHandled(url)) 261 { 262 score = 1; 263 } 264 265 return score; 266 } 267 268 /** 269 * Checks whether the passed Url can be handled by this mapper 270 * 271 * @param url 272 * the Url to check 273 * @return {@code true} - if the Url can be handled, {@code false} - otherwise 274 */ 275 protected boolean canBeHandled(final Url url) 276 { 277 List<String> segments = url.getSegments(); 278 return (segments.size() >= 4 && 279 urlStartsWith(url, getContext().getNamespace(), getContext().getResourceIdentifier()) && 280 Strings.isEmpty(segments.get(3)) == false 281 ); 282 283 } 284}