001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.servlet.handler;
018
019import java.io.IOException;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.List;
023import java.util.Map;
024import javax.servlet.http.HttpServletRequest;
025import javax.servlet.http.HttpServletResponse;
026
027import org.springframework.beans.BeansException;
028import org.springframework.beans.factory.BeanFactoryUtils;
029import org.springframework.core.Ordered;
030import org.springframework.util.AntPathMatcher;
031import org.springframework.util.Assert;
032import org.springframework.util.PathMatcher;
033import org.springframework.web.HttpRequestHandler;
034import org.springframework.web.context.request.WebRequestInterceptor;
035import org.springframework.web.context.support.WebApplicationObjectSupport;
036import org.springframework.web.cors.CorsConfiguration;
037import org.springframework.web.cors.CorsConfigurationSource;
038import org.springframework.web.cors.CorsProcessor;
039import org.springframework.web.cors.CorsUtils;
040import org.springframework.web.cors.DefaultCorsProcessor;
041import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
042import org.springframework.web.servlet.HandlerExecutionChain;
043import org.springframework.web.servlet.HandlerInterceptor;
044import org.springframework.web.servlet.HandlerMapping;
045import org.springframework.web.util.UrlPathHelper;
046
047/**
048 * Abstract base class for {@link org.springframework.web.servlet.HandlerMapping}
049 * implementations. Supports ordering, a default handler, handler interceptors,
050 * including handler interceptors mapped by path patterns.
051 *
052 * <p>Note: This base class does <i>not</i> support exposure of the
053 * {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE}. Support for this attribute
054 * is up to concrete subclasses, typically based on request URL mappings.
055 *
056 * @author Juergen Hoeller
057 * @author Rossen Stoyanchev
058 * @since 07.04.2003
059 * @see #getHandlerInternal
060 * @see #setDefaultHandler
061 * @see #setAlwaysUseFullPath
062 * @see #setUrlDecode
063 * @see org.springframework.util.AntPathMatcher
064 * @see #setInterceptors
065 * @see org.springframework.web.servlet.HandlerInterceptor
066 */
067public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered {
068
069        private Object defaultHandler;
070
071        private UrlPathHelper urlPathHelper = new UrlPathHelper();
072
073        private PathMatcher pathMatcher = new AntPathMatcher();
074
075        private final List<Object> interceptors = new ArrayList<Object>();
076
077        private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<HandlerInterceptor>();
078
079        private final UrlBasedCorsConfigurationSource globalCorsConfigSource = new UrlBasedCorsConfigurationSource();
080
081        private CorsProcessor corsProcessor = new DefaultCorsProcessor();
082
083        private int order = Ordered.LOWEST_PRECEDENCE;  // default: same as non-Ordered
084
085
086        /**
087         * Set the default handler for this handler mapping.
088         * This handler will be returned if no specific mapping was found.
089         * <p>Default is {@code null}, indicating no default handler.
090         */
091        public void setDefaultHandler(Object defaultHandler) {
092                this.defaultHandler = defaultHandler;
093        }
094
095        /**
096         * Return the default handler for this handler mapping,
097         * or {@code null} if none.
098         */
099        public Object getDefaultHandler() {
100                return this.defaultHandler;
101        }
102
103        /**
104         * Set if URL lookup should always use the full path within the current servlet
105         * context. Else, the path within the current servlet mapping is used if applicable
106         * (that is, in the case of a ".../*" servlet mapping in web.xml).
107         * <p>Default is "false".
108         * @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
109         */
110        public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
111                this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
112                this.globalCorsConfigSource.setAlwaysUseFullPath(alwaysUseFullPath);
113        }
114
115        /**
116         * Set if context path and request URI should be URL-decoded. Both are returned
117         * <i>undecoded</i> by the Servlet API, in contrast to the servlet path.
118         * <p>Uses either the request encoding or the default encoding according
119         * to the Servlet spec (ISO-8859-1).
120         * @see org.springframework.web.util.UrlPathHelper#setUrlDecode
121         */
122        public void setUrlDecode(boolean urlDecode) {
123                this.urlPathHelper.setUrlDecode(urlDecode);
124                this.globalCorsConfigSource.setUrlDecode(urlDecode);
125        }
126
127        /**
128         * Set if ";" (semicolon) content should be stripped from the request URI.
129         * <p>The default value is {@code true}.
130         * @see org.springframework.web.util.UrlPathHelper#setRemoveSemicolonContent(boolean)
131         */
132        public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
133                this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent);
134                this.globalCorsConfigSource.setRemoveSemicolonContent(removeSemicolonContent);
135        }
136
137        /**
138         * Set the UrlPathHelper to use for resolution of lookup paths.
139         * <p>Use this to override the default UrlPathHelper with a custom subclass,
140         * or to share common UrlPathHelper settings across multiple HandlerMappings
141         * and MethodNameResolvers.
142         */
143        public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
144                Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
145                this.urlPathHelper = urlPathHelper;
146                this.globalCorsConfigSource.setUrlPathHelper(urlPathHelper);
147        }
148
149        /**
150         * Return the UrlPathHelper implementation to use for resolution of lookup paths.
151         */
152        public UrlPathHelper getUrlPathHelper() {
153                return urlPathHelper;
154        }
155
156        /**
157         * Set the PathMatcher implementation to use for matching URL paths
158         * against registered URL patterns. Default is AntPathMatcher.
159         * @see org.springframework.util.AntPathMatcher
160         */
161        public void setPathMatcher(PathMatcher pathMatcher) {
162                Assert.notNull(pathMatcher, "PathMatcher must not be null");
163                this.pathMatcher = pathMatcher;
164                this.globalCorsConfigSource.setPathMatcher(pathMatcher);
165        }
166
167        /**
168         * Return the PathMatcher implementation to use for matching URL paths
169         * against registered URL patterns.
170         */
171        public PathMatcher getPathMatcher() {
172                return this.pathMatcher;
173        }
174
175        /**
176         * Set the interceptors to apply for all handlers mapped by this handler mapping.
177         * <p>Supported interceptor types are HandlerInterceptor, WebRequestInterceptor, and MappedInterceptor.
178         * Mapped interceptors apply only to request URLs that match its path patterns.
179         * Mapped interceptor beans are also detected by type during initialization.
180         * @param interceptors array of handler interceptors
181         * @see #adaptInterceptor
182         * @see org.springframework.web.servlet.HandlerInterceptor
183         * @see org.springframework.web.context.request.WebRequestInterceptor
184         */
185        public void setInterceptors(Object... interceptors) {
186                this.interceptors.addAll(Arrays.asList(interceptors));
187        }
188
189        /**
190         * Set the "global" CORS configurations based on URL patterns. By default the first
191         * matching URL pattern is combined with the CORS configuration for the handler, if any.
192         * @since 4.2
193         */
194        public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) {
195                this.globalCorsConfigSource.setCorsConfigurations(corsConfigurations);
196        }
197
198        /**
199         * Get the "global" CORS configurations.
200         */
201        public Map<String, CorsConfiguration> getCorsConfigurations() {
202                return this.globalCorsConfigSource.getCorsConfigurations();
203        }
204
205        /**
206         * Configure a custom {@link CorsProcessor} to use to apply the matched
207         * {@link CorsConfiguration} for a request.
208         * <p>By default {@link DefaultCorsProcessor} is used.
209         * @since 4.2
210         */
211        public void setCorsProcessor(CorsProcessor corsProcessor) {
212                Assert.notNull(corsProcessor, "CorsProcessor must not be null");
213                this.corsProcessor = corsProcessor;
214        }
215
216        /**
217         * Return the configured {@link CorsProcessor}.
218         */
219        public CorsProcessor getCorsProcessor() {
220                return this.corsProcessor;
221        }
222
223        /**
224         * Specify the order value for this HandlerMapping bean.
225         * <p>The default value is {@code Ordered.LOWEST_PRECEDENCE}, meaning non-ordered.
226         * @see org.springframework.core.Ordered#getOrder()
227         */
228        public void setOrder(int order) {
229                this.order = order;
230        }
231
232        @Override
233        public int getOrder() {
234                return this.order;
235        }
236
237
238        /**
239         * Initializes the interceptors.
240         * @see #extendInterceptors(java.util.List)
241         * @see #initInterceptors()
242         */
243        @Override
244        protected void initApplicationContext() throws BeansException {
245                extendInterceptors(this.interceptors);
246                detectMappedInterceptors(this.adaptedInterceptors);
247                initInterceptors();
248        }
249
250        /**
251         * Extension hook that subclasses can override to register additional interceptors,
252         * given the configured interceptors (see {@link #setInterceptors}).
253         * <p>Will be invoked before {@link #initInterceptors()} adapts the specified
254         * interceptors into {@link HandlerInterceptor} instances.
255         * <p>The default implementation is empty.
256         * @param interceptors the configured interceptor List (never {@code null}), allowing
257         * to add further interceptors before as well as after the existing interceptors
258         */
259        protected void extendInterceptors(List<Object> interceptors) {
260        }
261
262        /**
263         * Detect beans of type {@link MappedInterceptor} and add them to the list
264         * of mapped interceptors.
265         * <p>This is called in addition to any {@link MappedInterceptor}s that may
266         * have been provided via {@link #setInterceptors}, by default adding all
267         * beans of type {@link MappedInterceptor} from the current context and its
268         * ancestors. Subclasses can override and refine this policy.
269         * @param mappedInterceptors an empty list to add to
270         */
271        protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
272                mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors(
273                                getApplicationContext(), MappedInterceptor.class, true, false).values());
274        }
275
276        /**
277         * Initialize the specified interceptors adapting
278         * {@link WebRequestInterceptor}s to {@link HandlerInterceptor}.
279         * @see #setInterceptors
280         * @see #adaptInterceptor
281         */
282        protected void initInterceptors() {
283                if (!this.interceptors.isEmpty()) {
284                        for (int i = 0; i < this.interceptors.size(); i++) {
285                                Object interceptor = this.interceptors.get(i);
286                                if (interceptor == null) {
287                                        throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
288                                }
289                                this.adaptedInterceptors.add(adaptInterceptor(interceptor));
290                        }
291                }
292        }
293
294        /**
295         * Adapt the given interceptor object to {@link HandlerInterceptor}.
296         * <p>By default, the supported interceptor types are
297         * {@link HandlerInterceptor} and {@link WebRequestInterceptor}. Each given
298         * {@link WebRequestInterceptor} is wrapped with
299         * {@link WebRequestHandlerInterceptorAdapter}.
300         * @param interceptor the interceptor
301         * @return the interceptor downcast or adapted to HandlerInterceptor
302         * @see org.springframework.web.servlet.HandlerInterceptor
303         * @see org.springframework.web.context.request.WebRequestInterceptor
304         * @see WebRequestHandlerInterceptorAdapter
305         */
306        protected HandlerInterceptor adaptInterceptor(Object interceptor) {
307                if (interceptor instanceof HandlerInterceptor) {
308                        return (HandlerInterceptor) interceptor;
309                }
310                else if (interceptor instanceof WebRequestInterceptor) {
311                        return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
312                }
313                else {
314                        throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
315                }
316        }
317
318        /**
319         * Return the adapted interceptors as {@link HandlerInterceptor} array.
320         * @return the array of {@link HandlerInterceptor HandlerInterceptor}s,
321         * or {@code null} if none
322         */
323        protected final HandlerInterceptor[] getAdaptedInterceptors() {
324                int count = this.adaptedInterceptors.size();
325                return (count > 0 ? this.adaptedInterceptors.toArray(new HandlerInterceptor[count]) : null);
326        }
327
328        /**
329         * Return all configured {@link MappedInterceptor}s as an array.
330         * @return the array of {@link MappedInterceptor}s, or {@code null} if none
331         */
332        protected final MappedInterceptor[] getMappedInterceptors() {
333                List<MappedInterceptor> mappedInterceptors = new ArrayList<MappedInterceptor>(this.adaptedInterceptors.size());
334                for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
335                        if (interceptor instanceof MappedInterceptor) {
336                                mappedInterceptors.add((MappedInterceptor) interceptor);
337                        }
338                }
339                int count = mappedInterceptors.size();
340                return (count > 0 ? mappedInterceptors.toArray(new MappedInterceptor[count]) : null);
341        }
342
343
344        /**
345         * Look up a handler for the given request, falling back to the default
346         * handler if no specific one is found.
347         * @param request current HTTP request
348         * @return the corresponding handler instance, or the default handler
349         * @see #getHandlerInternal
350         */
351        @Override
352        public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
353                Object handler = getHandlerInternal(request);
354                if (handler == null) {
355                        handler = getDefaultHandler();
356                }
357                if (handler == null) {
358                        return null;
359                }
360                // Bean name or resolved handler?
361                if (handler instanceof String) {
362                        String handlerName = (String) handler;
363                        handler = getApplicationContext().getBean(handlerName);
364                }
365
366                HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
367                if (CorsUtils.isCorsRequest(request)) {
368                        CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
369                        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
370                        CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
371                        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
372                }
373                return executionChain;
374        }
375
376        /**
377         * Look up a handler for the given request, returning {@code null} if no
378         * specific one is found. This method is called by {@link #getHandler};
379         * a {@code null} return value will lead to the default handler, if one is set.
380         * <p>On CORS pre-flight requests this method should return a match not for
381         * the pre-flight request but for the expected actual request based on the URL
382         * path, the HTTP methods from the "Access-Control-Request-Method" header, and
383         * the headers from the "Access-Control-Request-Headers" header thus allowing
384         * the CORS configuration to be obtained via {@link #getCorsConfigurations},
385         * <p>Note: This method may also return a pre-built {@link HandlerExecutionChain},
386         * combining a handler object with dynamically determined interceptors.
387         * Statically specified interceptors will get merged into such an existing chain.
388         * @param request current HTTP request
389         * @return the corresponding handler instance, or {@code null} if none found
390         * @throws Exception if there is an internal error
391         */
392        protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
393
394        /**
395         * Build a {@link HandlerExecutionChain} for the given handler, including
396         * applicable interceptors.
397         * <p>The default implementation builds a standard {@link HandlerExecutionChain}
398         * with the given handler, the common interceptors of the handler mapping, and any
399         * {@link MappedInterceptor MappedInterceptors} matching to the current request URL. Interceptors
400         * are added in the order they were registered. Subclasses may override this
401         * in order to extend/rearrange the list of interceptors.
402         * <p><b>NOTE:</b> The passed-in handler object may be a raw handler or a
403         * pre-built {@link HandlerExecutionChain}. This method should handle those
404         * two cases explicitly, either building a new {@link HandlerExecutionChain}
405         * or extending the existing chain.
406         * <p>For simply adding an interceptor in a custom subclass, consider calling
407         * {@code super.getHandlerExecutionChain(handler, request)} and invoking
408         * {@link HandlerExecutionChain#addInterceptor} on the returned chain object.
409         * @param handler the resolved handler instance (never {@code null})
410         * @param request current HTTP request
411         * @return the HandlerExecutionChain (never {@code null})
412         * @see #getAdaptedInterceptors()
413         */
414        protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
415                HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
416                                (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
417
418                String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
419                for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
420                        if (interceptor instanceof MappedInterceptor) {
421                                MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
422                                if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
423                                        chain.addInterceptor(mappedInterceptor.getInterceptor());
424                                }
425                        }
426                        else {
427                                chain.addInterceptor(interceptor);
428                        }
429                }
430                return chain;
431        }
432
433        /**
434         * Retrieve the CORS configuration for the given handler.
435         * @param handler the handler to check (never {@code null}).
436         * @param request the current request.
437         * @return the CORS configuration for the handler, or {@code null} if none
438         * @since 4.2
439         */
440        protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {
441                Object resolvedHandler = handler;
442                if (handler instanceof HandlerExecutionChain) {
443                        resolvedHandler = ((HandlerExecutionChain) handler).getHandler();
444                }
445                if (resolvedHandler instanceof CorsConfigurationSource) {
446                        return ((CorsConfigurationSource) resolvedHandler).getCorsConfiguration(request);
447                }
448                return null;
449        }
450
451        /**
452         * Update the HandlerExecutionChain for CORS-related handling.
453         * <p>For pre-flight requests, the default implementation replaces the selected
454         * handler with a simple HttpRequestHandler that invokes the configured
455         * {@link #setCorsProcessor}.
456         * <p>For actual requests, the default implementation inserts a
457         * HandlerInterceptor that makes CORS-related checks and adds CORS headers.
458         * @param request the current request
459         * @param chain the handler chain
460         * @param config the applicable CORS configuration (possibly {@code null})
461         * @since 4.2
462         */
463        protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
464                        HandlerExecutionChain chain, CorsConfiguration config) {
465
466                if (CorsUtils.isPreFlightRequest(request)) {
467                        HandlerInterceptor[] interceptors = chain.getInterceptors();
468                        return new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
469                }
470                else {
471                        chain.addInterceptor(new CorsInterceptor(config));
472                        return chain;
473                }
474        }
475
476
477        private class PreFlightHandler implements HttpRequestHandler, CorsConfigurationSource {
478
479                private final CorsConfiguration config;
480
481                public PreFlightHandler(CorsConfiguration config) {
482                        this.config = config;
483                }
484
485                @Override
486                public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
487                        corsProcessor.processRequest(this.config, request, response);
488                }
489
490                @Override
491                public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
492                        return this.config;
493                }
494        }
495
496
497        private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource {
498
499                private final CorsConfiguration config;
500
501                public CorsInterceptor(CorsConfiguration config) {
502                        this.config = config;
503                }
504
505                @Override
506                public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
507                                throws Exception {
508
509                        return corsProcessor.processRequest(this.config, request, response);
510                }
511
512                @Override
513                public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
514                        return this.config;
515                }
516        }
517
518}