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.mvc.method.annotation;
018
019import java.lang.reflect.Method;
020import java.util.Arrays;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025
026import javax.servlet.http.HttpServletRequest;
027
028import org.aopalliance.intercept.MethodInterceptor;
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031
032import org.springframework.aop.framework.ProxyFactory;
033import org.springframework.aop.target.EmptyTargetSource;
034import org.springframework.beans.factory.NoSuchBeanDefinitionException;
035import org.springframework.cglib.core.SpringNamingPolicy;
036import org.springframework.cglib.proxy.Callback;
037import org.springframework.cglib.proxy.Enhancer;
038import org.springframework.cglib.proxy.Factory;
039import org.springframework.cglib.proxy.MethodProxy;
040import org.springframework.core.DefaultParameterNameDiscoverer;
041import org.springframework.core.MethodIntrospector;
042import org.springframework.core.MethodParameter;
043import org.springframework.core.ParameterNameDiscoverer;
044import org.springframework.core.annotation.AnnotatedElementUtils;
045import org.springframework.core.annotation.SynthesizingMethodParameter;
046import org.springframework.lang.Nullable;
047import org.springframework.objenesis.ObjenesisException;
048import org.springframework.objenesis.SpringObjenesis;
049import org.springframework.util.AntPathMatcher;
050import org.springframework.util.Assert;
051import org.springframework.util.ObjectUtils;
052import org.springframework.util.PathMatcher;
053import org.springframework.util.ReflectionUtils;
054import org.springframework.util.ReflectionUtils.MethodFilter;
055import org.springframework.util.StringUtils;
056import org.springframework.web.bind.annotation.RequestMapping;
057import org.springframework.web.context.WebApplicationContext;
058import org.springframework.web.context.request.RequestAttributes;
059import org.springframework.web.context.request.RequestContextHolder;
060import org.springframework.web.context.request.ServletRequestAttributes;
061import org.springframework.web.method.HandlerMethod;
062import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
063import org.springframework.web.method.support.CompositeUriComponentsContributor;
064import org.springframework.web.servlet.DispatcherServlet;
065import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
066import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
067import org.springframework.web.util.UriComponentsBuilder;
068
069/**
070 * Creates instances of {@link org.springframework.web.util.UriComponentsBuilder}
071 * by pointing to {@code @RequestMapping} methods on Spring MVC controllers.
072 *
073 * <p>There are several groups of methods:
074 * <ul>
075 * <li>Static {@code fromXxx(...)} methods to prepare links using information
076 * from the current request as determined by a call to
077 * {@link org.springframework.web.servlet.support.ServletUriComponentsBuilder#fromCurrentServletMapping()}.
078 * <li>Static {@code fromXxx(UriComponentsBuilder,...)} methods can be given
079 * a baseUrl when operating outside the context of a request.
080 * <li>Instance-based {@code withXxx(...)} methods where an instance of
081 * MvcUriComponentsBuilder is created with a baseUrl via
082 * {@link #relativeTo(org.springframework.web.util.UriComponentsBuilder)}.
083 * </ul>
084 *
085 * <p><strong>Note:</strong> This class uses values from "Forwarded"
086 * (<a href="https://tools.ietf.org/html/rfc7239">RFC 7239</a>),
087 * "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" headers,
088 * if present, in order to reflect the client-originated protocol and address.
089 * Consider using the {@code ForwardedHeaderFilter} in order to choose from a
090 * central place whether to extract and use, or to discard such headers.
091 * See the Spring Framework reference for more on this filter.
092 *
093 * @author Oliver Gierke
094 * @author Rossen Stoyanchev
095 * @author Sam Brannen
096 * @since 4.0
097 */
098public class MvcUriComponentsBuilder {
099
100        /**
101         * Well-known name for the {@link CompositeUriComponentsContributor} object in the bean factory.
102         */
103        public static final String MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME = "mvcUriComponentsContributor";
104
105
106        private static final Log logger = LogFactory.getLog(MvcUriComponentsBuilder.class);
107
108        private static final SpringObjenesis objenesis = new SpringObjenesis();
109
110        private static final PathMatcher pathMatcher = new AntPathMatcher();
111
112        private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
113
114        private static final CompositeUriComponentsContributor defaultUriComponentsContributor;
115
116        static {
117                defaultUriComponentsContributor = new CompositeUriComponentsContributor(
118                                new PathVariableMethodArgumentResolver(), new RequestParamMethodArgumentResolver(false));
119        }
120
121        private final UriComponentsBuilder baseUrl;
122
123
124        /**
125         * Default constructor. Protected to prevent direct instantiation.
126         * @see #fromController(Class)
127         * @see #fromMethodName(Class, String, Object...)
128         * @see #fromMethodCall(Object)
129         * @see #fromMappingName(String)
130         * @see #fromMethod(Class, Method, Object...)
131         */
132        protected MvcUriComponentsBuilder(UriComponentsBuilder baseUrl) {
133                Assert.notNull(baseUrl, "'baseUrl' is required");
134                this.baseUrl = baseUrl;
135        }
136
137
138        /**
139         * Create an instance of this class with a base URL. After that calls to one
140         * of the instance based {@code withXxx(...}} methods will create URLs relative
141         * to the given base URL.
142         */
143        public static MvcUriComponentsBuilder relativeTo(UriComponentsBuilder baseUrl) {
144                return new MvcUriComponentsBuilder(baseUrl);
145        }
146
147
148        /**
149         * Create a {@link UriComponentsBuilder} from the mapping of a controller class
150         * and current request information including Servlet mapping. If the controller
151         * contains multiple mappings, only the first one is used.
152         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
153         * and "X-Forwarded-*" headers if found. See class-level docs.
154         * @param controllerType the controller to build a URI for
155         * @return a UriComponentsBuilder instance (never {@code null})
156         */
157        public static UriComponentsBuilder fromController(Class<?> controllerType) {
158                return fromController(null, controllerType);
159        }
160
161        /**
162         * An alternative to {@link #fromController(Class)} that accepts a
163         * {@code UriComponentsBuilder} representing the base URL. This is useful
164         * when using MvcUriComponentsBuilder outside the context of processing a
165         * request or to apply a custom baseUrl not matching the current request.
166         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
167         * and "X-Forwarded-*" headers if found. See class-level docs.
168         * @param builder the builder for the base URL; the builder will be cloned
169         * and therefore not modified and may be re-used for further calls.
170         * @param controllerType the controller to build a URI for
171         * @return a UriComponentsBuilder instance (never {@code null})
172         */
173        public static UriComponentsBuilder fromController(@Nullable UriComponentsBuilder builder,
174                        Class<?> controllerType) {
175
176                builder = getBaseUrlToUse(builder);
177
178                // Externally configured prefix via PathConfigurer..
179                String prefix = getPathPrefix(controllerType);
180                builder.path(prefix);
181
182                String mapping = getClassMapping(controllerType);
183                builder.path(mapping);
184
185                return builder;
186        }
187
188        /**
189         * Create a {@link UriComponentsBuilder} from the mapping of a controller
190         * method and an array of method argument values. This method delegates
191         * to {@link #fromMethod(Class, Method, Object...)}.
192         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
193         * and "X-Forwarded-*" headers if found. See class-level docs.
194         * @param controllerType the controller
195         * @param methodName the method name
196         * @param args the argument values
197         * @return a UriComponentsBuilder instance, never {@code null}
198         * @throws IllegalArgumentException if there is no matching or
199         * if there is more than one matching method
200         */
201        public static UriComponentsBuilder fromMethodName(Class<?> controllerType,
202                        String methodName, Object... args) {
203
204                Method method = getMethod(controllerType, methodName, args);
205                return fromMethodInternal(null, controllerType, method, args);
206        }
207
208        /**
209         * An alternative to {@link #fromMethodName(Class, String, Object...)} that
210         * accepts a {@code UriComponentsBuilder} representing the base URL. This is
211         * useful when using MvcUriComponentsBuilder outside the context of processing
212         * a request or to apply a custom baseUrl not matching the current request.
213         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
214         * and "X-Forwarded-*" headers if found. See class-level docs.
215         * @param builder the builder for the base URL; the builder will be cloned
216         * and therefore not modified and may be re-used for further calls.
217         * @param controllerType the controller
218         * @param methodName the method name
219         * @param args the argument values
220         * @return a UriComponentsBuilder instance, never {@code null}
221         * @throws IllegalArgumentException if there is no matching or
222         * if there is more than one matching method
223         */
224        public static UriComponentsBuilder fromMethodName(UriComponentsBuilder builder,
225                        Class<?> controllerType, String methodName, Object... args) {
226
227                Method method = getMethod(controllerType, methodName, args);
228                return fromMethodInternal(builder, controllerType, method, args);
229        }
230
231        /**
232         * Create a {@link UriComponentsBuilder} from the mapping of a controller method
233         * and an array of method argument values. The array of values  must match the
234         * signature of the controller method. Values for {@code @RequestParam} and
235         * {@code @PathVariable} are used for building the URI (via implementations of
236         * {@link org.springframework.web.method.support.UriComponentsContributor
237         * UriComponentsContributor}) while remaining argument values are ignored and
238         * can be {@code null}.
239         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
240         * and "X-Forwarded-*" headers if found. See class-level docs.
241         * @param controllerType the controller type
242         * @param method the controller method
243         * @param args argument values for the controller method
244         * @return a UriComponentsBuilder instance, never {@code null}
245         * @since 4.2
246         */
247        public static UriComponentsBuilder fromMethod(Class<?> controllerType, Method method, Object... args) {
248                return fromMethodInternal(null, controllerType, method, args);
249        }
250
251        /**
252         * An alternative to {@link #fromMethod(Class, Method, Object...)}
253         * that accepts a {@code UriComponentsBuilder} representing the base URL.
254         * This is useful when using MvcUriComponentsBuilder outside the context of
255         * processing a request or to apply a custom baseUrl not matching the
256         * current request.
257         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
258         * and "X-Forwarded-*" headers if found. See class-level docs.
259         * @param baseUrl the builder for the base URL; the builder will be cloned
260         * and therefore not modified and may be re-used for further calls.
261         * @param controllerType the controller type
262         * @param method the controller method
263         * @param args argument values for the controller method
264         * @return a UriComponentsBuilder instance (never {@code null})
265         * @since 4.2
266         */
267        public static UriComponentsBuilder fromMethod(UriComponentsBuilder baseUrl,
268                        @Nullable Class<?> controllerType, Method method, Object... args) {
269
270                return fromMethodInternal(baseUrl,
271                                (controllerType != null ? controllerType : method.getDeclaringClass()), method, args);
272        }
273
274        /**
275         * Create a {@link UriComponentsBuilder} by invoking a "mock" controller method.
276         * The controller method and the supplied argument values are then used to
277         * delegate to {@link #fromMethod(Class, Method, Object...)}.
278         * <p>For example, given this controller:
279         * <pre class="code">
280         * &#064;RequestMapping("/people/{id}/addresses")
281         * class AddressController {
282         *
283         *   &#064;RequestMapping("/{country}")
284         *   public HttpEntity&lt;Void&gt; getAddressesForCountry(&#064;PathVariable String country) { ... }
285         *
286         *   &#064;RequestMapping(value="/", method=RequestMethod.POST)
287         *   public void addAddress(Address address) { ... }
288         * }
289         * </pre>
290         * A UriComponentsBuilder can be created:
291         * <pre class="code">
292         * // Inline style with static import of "MvcUriComponentsBuilder.on"
293         *
294         * MvcUriComponentsBuilder.fromMethodCall(
295         *              on(AddressController.class).getAddressesForCountry("US")).buildAndExpand(1);
296         *
297         * // Longer form useful for repeated invocation (and void controller methods)
298         *
299         * AddressController controller = MvcUriComponentsBuilder.on(AddressController.class);
300         * controller.addAddress(null);
301         * builder = MvcUriComponentsBuilder.fromMethodCall(controller);
302         * controller.getAddressesForCountry("US")
303         * builder = MvcUriComponentsBuilder.fromMethodCall(controller);
304         * </pre>
305         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
306         * and "X-Forwarded-*" headers if found. See class-level docs.
307         * @param info either the value returned from a "mock" controller
308         * invocation or the "mock" controller itself after an invocation
309         * @return a UriComponents instance
310         * @see #on(Class)
311         * @see #controller(Class)
312         */
313        public static UriComponentsBuilder fromMethodCall(Object info) {
314                Assert.isInstanceOf(MethodInvocationInfo.class, info, "MethodInvocationInfo required");
315                MethodInvocationInfo invocationInfo = (MethodInvocationInfo) info;
316                Class<?> controllerType = invocationInfo.getControllerType();
317                Method method = invocationInfo.getControllerMethod();
318                Object[] arguments = invocationInfo.getArgumentValues();
319                return fromMethodInternal(null, controllerType, method, arguments);
320        }
321
322        /**
323         * An alternative to {@link #fromMethodCall(Object)} that accepts a
324         * {@code UriComponentsBuilder} representing the base URL. This is useful
325         * when using MvcUriComponentsBuilder outside the context of processing a
326         * request or to apply a custom baseUrl not matching the current request.
327         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
328         * and "X-Forwarded-*" headers if found. See class-level docs.
329         * @param builder the builder for the base URL; the builder will be cloned
330         * and therefore not modified and may be re-used for further calls.
331         * @param info either the value returned from a "mock" controller
332         * invocation or the "mock" controller itself after an invocation
333         * @return a UriComponents instance
334         */
335        public static UriComponentsBuilder fromMethodCall(UriComponentsBuilder builder, Object info) {
336                Assert.isInstanceOf(MethodInvocationInfo.class, info, "MethodInvocationInfo required");
337                MethodInvocationInfo invocationInfo = (MethodInvocationInfo) info;
338                Class<?> controllerType = invocationInfo.getControllerType();
339                Method method = invocationInfo.getControllerMethod();
340                Object[] arguments = invocationInfo.getArgumentValues();
341                return fromMethodInternal(builder, controllerType, method, arguments);
342        }
343
344        /**
345         * Return a "mock" controller instance. When an {@code @RequestMapping} method
346         * on the controller is invoked, the supplied argument values are remembered
347         * and the result can then be used to create a {@code UriComponentsBuilder}
348         * via {@link #fromMethodCall(Object)}.
349         * <p>Note that this is a shorthand version of {@link #controller(Class)} intended
350         * for inline use (with a static import), for example:
351         * <pre class="code">
352         * MvcUriComponentsBuilder.fromMethodCall(on(FooController.class).getFoo(1)).build();
353         * </pre>
354         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
355         * and "X-Forwarded-*" headers if found. See class-level docs.
356         *
357         * @param controllerType the target controller
358         */
359        public static <T> T on(Class<T> controllerType) {
360                return controller(controllerType);
361        }
362
363        /**
364         * Return a "mock" controller instance. When an {@code @RequestMapping} method
365         * on the controller is invoked, the supplied argument values are remembered
366         * and the result can then be used to create {@code UriComponentsBuilder} via
367         * {@link #fromMethodCall(Object)}.
368         * <p>This is a longer version of {@link #on(Class)}. It is needed with controller
369         * methods returning void as well for repeated invocations.
370         * <pre class="code">
371         * FooController fooController = controller(FooController.class);
372         *
373         * fooController.saveFoo(1, null);
374         * builder = MvcUriComponentsBuilder.fromMethodCall(fooController);
375         *
376         * fooController.saveFoo(2, null);
377         * builder = MvcUriComponentsBuilder.fromMethodCall(fooController);
378         * </pre>
379         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
380         * and "X-Forwarded-*" headers if found. See class-level docs.
381         * @param controllerType the target controller
382         */
383        public static <T> T controller(Class<T> controllerType) {
384                Assert.notNull(controllerType, "'controllerType' must not be null");
385                return ControllerMethodInvocationInterceptor.initProxy(controllerType, null);
386        }
387
388        /**
389         * Create a URL from the name of a Spring MVC controller method's request mapping.
390         * <p>The configured
391         * {@link org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy
392         * HandlerMethodMappingNamingStrategy} determines the names of controller
393         * method request mappings at startup. By default all mappings are assigned
394         * a name based on the capital letters of the class name, followed by "#" as
395         * separator, and then the method name. For example "PC#getPerson"
396         * for a class named PersonController with method getPerson. In case the
397         * naming convention does not produce unique results, an explicit name may
398         * be assigned through the name attribute of the {@code @RequestMapping}
399         * annotation.
400         * <p>This is aimed primarily for use in view rendering technologies and EL
401         * expressions. The Spring URL tag library registers this method as a function
402         * called "mvcUrl".
403         * <p>For example, given this controller:
404         * <pre class="code">
405         * &#064;RequestMapping("/people")
406         * class PersonController {
407         *
408         *   &#064;RequestMapping("/{id}")
409         *   public HttpEntity&lt;Void&gt; getPerson(&#064;PathVariable String id) { ... }
410         *
411         * }
412         * </pre>
413         *
414         * A JSP can prepare a URL to the controller method as follows:
415         *
416         * <pre class="code">
417         * <%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
418         *
419         * &lt;a href="${s:mvcUrl('PC#getPerson').arg(0,"123").build()}"&gt;Get Person&lt;/a&gt;
420         * </pre>
421         * <p>Note that it's not necessary to specify all arguments. Only the ones
422         * required to prepare the URL, mainly {@code @RequestParam} and {@code @PathVariable}).
423         *
424         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
425         * and "X-Forwarded-*" headers if found. See class-level docs.
426         *
427         * @param mappingName the mapping name
428         * @return a builder to prepare the URI String
429         * @throws IllegalArgumentException if the mapping name is not found or
430         * if there is no unique match
431         * @since 4.1
432         */
433        public static MethodArgumentBuilder fromMappingName(String mappingName) {
434                return fromMappingName(null, mappingName);
435        }
436
437        /**
438         * An alternative to {@link #fromMappingName(String)} that accepts a
439         * {@code UriComponentsBuilder} representing the base URL. This is useful
440         * when using MvcUriComponentsBuilder outside the context of processing a
441         * request or to apply a custom baseUrl not matching the current request.
442         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
443         * and "X-Forwarded-*" headers if found. See class-level docs.
444         * @param builder the builder for the base URL; the builder will be cloned
445         * and therefore not modified and may be re-used for further calls.
446         * @param name the mapping name
447         * @return a builder to prepare the URI String
448         * @throws IllegalArgumentException if the mapping name is not found or
449         * if there is no unique match
450         * @since 4.2
451         */
452        public static MethodArgumentBuilder fromMappingName(@Nullable UriComponentsBuilder builder, String name) {
453                WebApplicationContext wac = getWebApplicationContext();
454                Assert.notNull(wac, "No WebApplicationContext. ");
455                Map<String, RequestMappingInfoHandlerMapping> map = wac.getBeansOfType(RequestMappingInfoHandlerMapping.class);
456                List<HandlerMethod> handlerMethods = null;
457                for (RequestMappingInfoHandlerMapping mapping : map.values()) {
458                        handlerMethods = mapping.getHandlerMethodsForMappingName(name);
459                        if (handlerMethods != null) {
460                                break;
461                        }
462                }
463                if (handlerMethods == null) {
464                        throw new IllegalArgumentException("Mapping not found: " + name);
465                }
466                else if (handlerMethods.size() != 1) {
467                        throw new IllegalArgumentException("No unique match for mapping " + name + ": " + handlerMethods);
468                }
469                else {
470                        HandlerMethod handlerMethod = handlerMethods.get(0);
471                        Class<?> controllerType = handlerMethod.getBeanType();
472                        Method method = handlerMethod.getMethod();
473                        return new MethodArgumentBuilder(builder, controllerType, method);
474                }
475        }
476
477
478        // Instance methods, relative to a base UriComponentsBuilder...
479
480        /**
481         * An alternative to {@link #fromController(Class)} for use with an instance
482         * of this class created via a call to {@link #relativeTo}.
483         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
484         * and "X-Forwarded-*" headers if found. See class-level docs.
485         * @since 4.2
486         */
487        public UriComponentsBuilder withController(Class<?> controllerType) {
488                return fromController(this.baseUrl, controllerType);
489        }
490
491        /**
492         * An alternative to {@link #fromMethodName(Class, String, Object...)}} for
493         * use with an instance of this class created via {@link #relativeTo}.
494         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
495         * and "X-Forwarded-*" headers if found. See class-level docs.
496         * @since 4.2
497         */
498        public UriComponentsBuilder withMethodName(Class<?> controllerType, String methodName, Object... args) {
499                return fromMethodName(this.baseUrl, controllerType, methodName, args);
500        }
501
502        /**
503         * An alternative to {@link #fromMethodCall(Object)} for use with an instance
504         * of this class created via {@link #relativeTo}.
505         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
506         * and "X-Forwarded-*" headers if found. See class-level docs.
507         * @since 4.2
508         */
509        public UriComponentsBuilder withMethodCall(Object invocationInfo) {
510                return fromMethodCall(this.baseUrl, invocationInfo);
511        }
512
513        /**
514         * An alternative to {@link #fromMappingName(String)} for use with an instance
515         * of this class created via {@link #relativeTo}.
516         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
517         * and "X-Forwarded-*" headers if found. See class-level docs.
518         * @since 4.2
519         */
520        public MethodArgumentBuilder withMappingName(String mappingName) {
521                return fromMappingName(this.baseUrl, mappingName);
522        }
523
524        /**
525         * An alternative to {@link #fromMethod(Class, Method, Object...)}
526         * for use with an instance of this class created via {@link #relativeTo}.
527         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
528         * and "X-Forwarded-*" headers if found. See class-level docs.
529         * @since 4.2
530         */
531        public UriComponentsBuilder withMethod(Class<?> controllerType, Method method, Object... args) {
532                return fromMethod(this.baseUrl, controllerType, method, args);
533        }
534
535
536        private static UriComponentsBuilder fromMethodInternal(@Nullable UriComponentsBuilder builder,
537                        Class<?> controllerType, Method method, Object... args) {
538
539                builder = getBaseUrlToUse(builder);
540
541                // Externally configured prefix via PathConfigurer..
542                String prefix = getPathPrefix(controllerType);
543                builder.path(prefix);
544
545                String typePath = getClassMapping(controllerType);
546                String methodPath = getMethodMapping(method);
547                String path = pathMatcher.combine(typePath, methodPath);
548                if (StringUtils.hasLength(path) && !path.startsWith("/")) {
549                        path = "/" + path;
550                }
551                builder.path(path);
552
553                return applyContributors(builder, method, args);
554        }
555
556        private static UriComponentsBuilder getBaseUrlToUse(@Nullable UriComponentsBuilder baseUrl) {
557                return baseUrl == null ?
558                                ServletUriComponentsBuilder.fromCurrentServletMapping() :
559                                baseUrl.cloneBuilder();
560        }
561
562        private static String getPathPrefix(Class<?> controllerType) {
563                WebApplicationContext wac = getWebApplicationContext();
564                if (wac != null) {
565                        Map<String, RequestMappingHandlerMapping> map = wac.getBeansOfType(RequestMappingHandlerMapping.class);
566                        for (RequestMappingHandlerMapping mapping : map.values()) {
567                                if (mapping.isHandler(controllerType)) {
568                                        String prefix = mapping.getPathPrefix(controllerType);
569                                        if (prefix != null) {
570                                                return prefix;
571                                        }
572                                }
573                        }
574                }
575                return "";
576        }
577
578        private static String getClassMapping(Class<?> controllerType) {
579                Assert.notNull(controllerType, "'controllerType' must not be null");
580                RequestMapping mapping = AnnotatedElementUtils.findMergedAnnotation(controllerType, RequestMapping.class);
581                if (mapping == null) {
582                        return "/";
583                }
584                String[] paths = mapping.path();
585                if (ObjectUtils.isEmpty(paths) || !StringUtils.hasLength(paths[0])) {
586                        return "/";
587                }
588                if (paths.length > 1 && logger.isTraceEnabled()) {
589                        logger.trace("Using first of multiple paths on " + controllerType.getName());
590                }
591                return paths[0];
592        }
593
594        private static String getMethodMapping(Method method) {
595                Assert.notNull(method, "'method' must not be null");
596                RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
597                if (requestMapping == null) {
598                        throw new IllegalArgumentException("No @RequestMapping on: " + method.toGenericString());
599                }
600                String[] paths = requestMapping.path();
601                if (ObjectUtils.isEmpty(paths) || !StringUtils.hasLength(paths[0])) {
602                        return "/";
603                }
604                if (paths.length > 1 && logger.isTraceEnabled()) {
605                        logger.trace("Using first of multiple paths on " + method.toGenericString());
606                }
607                return paths[0];
608        }
609
610        private static Method getMethod(Class<?> controllerType, final String methodName, final Object... args) {
611                MethodFilter selector = method -> {
612                        String name = method.getName();
613                        int argLength = method.getParameterCount();
614                        return (name.equals(methodName) && argLength == args.length);
615                };
616                Set<Method> methods = MethodIntrospector.selectMethods(controllerType, selector);
617                if (methods.size() == 1) {
618                        return methods.iterator().next();
619                }
620                else if (methods.size() > 1) {
621                        throw new IllegalArgumentException(String.format(
622                                        "Found two methods named '%s' accepting arguments %s in controller %s: [%s]",
623                                        methodName, Arrays.asList(args), controllerType.getName(), methods));
624                }
625                else {
626                        throw new IllegalArgumentException("No method named '" + methodName + "' with " + args.length +
627                                        " arguments found in controller " + controllerType.getName());
628                }
629        }
630
631        private static UriComponentsBuilder applyContributors(UriComponentsBuilder builder, Method method, Object... args) {
632                CompositeUriComponentsContributor contributor = getUriComponentsContributor();
633
634                int paramCount = method.getParameterCount();
635                int argCount = args.length;
636                if (paramCount != argCount) {
637                        throw new IllegalArgumentException("Number of method parameters " + paramCount +
638                                        " does not match number of argument values " + argCount);
639                }
640
641                final Map<String, Object> uriVars = new HashMap<>();
642                for (int i = 0; i < paramCount; i++) {
643                        MethodParameter param = new SynthesizingMethodParameter(method, i);
644                        param.initParameterNameDiscovery(parameterNameDiscoverer);
645                        contributor.contributeMethodArgument(param, args[i], builder, uriVars);
646                }
647
648                // This may not be all the URI variables, supply what we have so far..
649                return builder.uriVariables(uriVars);
650        }
651
652        private static CompositeUriComponentsContributor getUriComponentsContributor() {
653                WebApplicationContext wac = getWebApplicationContext();
654                if (wac != null) {
655                        try {
656                                return wac.getBean(MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME, CompositeUriComponentsContributor.class);
657                        }
658                        catch (NoSuchBeanDefinitionException ex) {
659                                // Ignore
660                        }
661                }
662                return defaultUriComponentsContributor;
663        }
664
665        @Nullable
666        private static WebApplicationContext getWebApplicationContext() {
667                RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
668                if (requestAttributes == null) {
669                        return null;
670                }
671                HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
672                String attributeName = DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE;
673                WebApplicationContext wac = (WebApplicationContext) request.getAttribute(attributeName);
674                if (wac == null) {
675                        return null;
676                }
677                return wac;
678        }
679
680
681
682        /**
683         * Method invocation information.
684         */
685        public interface MethodInvocationInfo {
686
687                /**
688                 * Return the controller types.
689                 */
690                Class<?> getControllerType();
691
692                /**
693                 * Return the controller method.
694                 */
695                Method getControllerMethod();
696
697                /**
698                 * Return the argument values.
699                 */
700                Object[] getArgumentValues();
701        }
702
703
704        private static class ControllerMethodInvocationInterceptor
705                        implements org.springframework.cglib.proxy.MethodInterceptor, MethodInterceptor, MethodInvocationInfo {
706
707                private final Class<?> controllerType;
708
709                @Nullable
710                private Method controllerMethod;
711
712                @Nullable
713                private Object[] argumentValues;
714
715                ControllerMethodInvocationInterceptor(Class<?> controllerType) {
716                        this.controllerType = controllerType;
717                }
718
719                @Override
720                @Nullable
721                public Object intercept(Object obj, Method method, Object[] args, @Nullable MethodProxy proxy) {
722                        if (method.getName().equals("getControllerType")) {
723                                return this.controllerType;
724                        }
725                        else if (method.getName().equals("getControllerMethod")) {
726                                return this.controllerMethod;
727                        }
728                        else if (method.getName().equals("getArgumentValues")) {
729                                return this.argumentValues;
730                        }
731                        else if (ReflectionUtils.isObjectMethod(method)) {
732                                return ReflectionUtils.invokeMethod(method, obj, args);
733                        }
734                        else {
735                                this.controllerMethod = method;
736                                this.argumentValues = args;
737                                Class<?> returnType = method.getReturnType();
738                                try {
739                                        return (returnType == void.class ? null : returnType.cast(initProxy(returnType, this)));
740                                }
741                                catch (Throwable ex) {
742                                        throw new IllegalStateException(
743                                                        "Failed to create proxy for controller method return type: " + method, ex);
744                                }
745                        }
746                }
747
748                @Override
749                @Nullable
750                public Object invoke(org.aopalliance.intercept.MethodInvocation inv) throws Throwable {
751                        return intercept(inv.getThis(), inv.getMethod(), inv.getArguments(), null);
752                }
753
754                @Override
755                public Class<?> getControllerType() {
756                        return this.controllerType;
757                }
758
759                @Override
760                public Method getControllerMethod() {
761                        Assert.state(this.controllerMethod != null, "Not initialized yet");
762                        return this.controllerMethod;
763                }
764
765                @Override
766                public Object[] getArgumentValues() {
767                        Assert.state(this.argumentValues != null, "Not initialized yet");
768                        return this.argumentValues;
769                }
770
771
772                @SuppressWarnings("unchecked")
773                private static <T> T initProxy(
774                                Class<?> controllerType, @Nullable ControllerMethodInvocationInterceptor interceptor) {
775
776                        interceptor = interceptor != null ?
777                                        interceptor : new ControllerMethodInvocationInterceptor(controllerType);
778
779                        if (controllerType == Object.class) {
780                                return (T) interceptor;
781                        }
782
783                        else if (controllerType.isInterface()) {
784                                ProxyFactory factory = new ProxyFactory(EmptyTargetSource.INSTANCE);
785                                factory.addInterface(controllerType);
786                                factory.addInterface(MethodInvocationInfo.class);
787                                factory.addAdvice(interceptor);
788                                return (T) factory.getProxy();
789                        }
790
791                        else {
792                                Enhancer enhancer = new Enhancer();
793                                enhancer.setSuperclass(controllerType);
794                                enhancer.setInterfaces(new Class<?>[] {MethodInvocationInfo.class});
795                                enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
796                                enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class);
797
798                                Class<?> proxyClass = enhancer.createClass();
799                                Object proxy = null;
800
801                                if (objenesis.isWorthTrying()) {
802                                        try {
803                                                proxy = objenesis.newInstance(proxyClass, enhancer.getUseCache());
804                                        }
805                                        catch (ObjenesisException ex) {
806                                                logger.debug("Failed to create controller proxy, falling back on default constructor", ex);
807                                        }
808                                }
809
810                                if (proxy == null) {
811                                        try {
812                                                proxy = ReflectionUtils.accessibleConstructor(proxyClass).newInstance();
813                                        }
814                                        catch (Throwable ex) {
815                                                throw new IllegalStateException(
816                                                                "Failed to create controller proxy or use default constructor", ex);
817                                        }
818                                }
819
820                                ((Factory) proxy).setCallbacks(new Callback[] {interceptor});
821                                return (T) proxy;
822                        }
823                }
824        }
825
826
827        /**
828         * Builder class to create URLs for method arguments.
829         */
830        public static class MethodArgumentBuilder {
831
832                private final Class<?> controllerType;
833
834                private final Method method;
835
836                private final Object[] argumentValues;
837
838                private final UriComponentsBuilder baseUrl;
839
840                /**
841                 * Create a new {@link MethodArgumentBuilder} instance.
842                 * @since 4.2
843                 */
844                public MethodArgumentBuilder(Class<?> controllerType, Method method) {
845                        this(null, controllerType, method);
846                }
847
848                /**
849                 * Create a new {@link MethodArgumentBuilder} instance.
850                 * @since 4.2
851                 */
852                public MethodArgumentBuilder(@Nullable UriComponentsBuilder baseUrl, Class<?> controllerType, Method method) {
853                        Assert.notNull(controllerType, "'controllerType' is required");
854                        Assert.notNull(method, "'method' is required");
855                        this.baseUrl = baseUrl != null ? baseUrl : UriComponentsBuilder.fromPath(getPath());
856                        this.controllerType = controllerType;
857                        this.method = method;
858                        this.argumentValues = new Object[method.getParameterCount()];
859                }
860
861                private static String getPath() {
862                        UriComponentsBuilder builder = ServletUriComponentsBuilder.fromCurrentServletMapping();
863                        String path = builder.build().getPath();
864                        return path != null ? path : "";
865                }
866
867                public MethodArgumentBuilder arg(int index, Object value) {
868                        this.argumentValues[index] = value;
869                        return this;
870                }
871
872                /**
873                 * Use this method only if you need to apply strong encoding to expanded
874                 * URI variables by quoting all characters with reserved meaning.
875                 * @since 5.0.8
876                 */
877                public MethodArgumentBuilder encode() {
878                        this.baseUrl.encode();
879                        return this;
880                }
881
882                public String build() {
883                        return fromMethodInternal(this.baseUrl, this.controllerType, this.method, this.argumentValues)
884                                        .build().encode().toUriString();
885                }
886
887                public String buildAndExpand(Object... uriVars) {
888                        return fromMethodInternal(this.baseUrl, this.controllerType, this.method, this.argumentValues)
889                                        .buildAndExpand(uriVars).encode().toString();
890                }
891        }
892
893}