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}