001/*
002 * Copyright 2002-2018 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;
025import javax.servlet.http.HttpServletRequest;
026
027import org.aopalliance.intercept.MethodInterceptor;
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030
031import org.springframework.aop.framework.ProxyFactory;
032import org.springframework.aop.target.EmptyTargetSource;
033import org.springframework.beans.factory.NoSuchBeanDefinitionException;
034import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
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.objenesis.ObjenesisException;
047import org.springframework.objenesis.SpringObjenesis;
048import org.springframework.util.AntPathMatcher;
049import org.springframework.util.Assert;
050import org.springframework.util.ObjectUtils;
051import org.springframework.util.PathMatcher;
052import org.springframework.util.ReflectionUtils;
053import org.springframework.util.ReflectionUtils.MethodFilter;
054import org.springframework.util.StringUtils;
055import org.springframework.web.bind.annotation.RequestMapping;
056import org.springframework.web.context.WebApplicationContext;
057import org.springframework.web.context.request.RequestAttributes;
058import org.springframework.web.context.request.RequestContextHolder;
059import org.springframework.web.context.request.ServletRequestAttributes;
060import org.springframework.web.method.HandlerMethod;
061import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
062import org.springframework.web.method.support.CompositeUriComponentsContributor;
063import org.springframework.web.servlet.DispatcherServlet;
064import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
065import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
066import org.springframework.web.util.UriComponents;
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(UriComponentsBuilder builder,
174                        Class<?> controllerType) {
175
176                builder = getBaseUrlToUse(builder);
177                String mapping = getTypeRequestMapping(controllerType);
178                return builder.path(mapping);
179        }
180
181        /**
182         * Create a {@link UriComponentsBuilder} from the mapping of a controller
183         * method and an array of method argument values. This method delegates
184         * to {@link #fromMethod(Class, Method, Object...)}.
185         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
186         * and "X-Forwarded-*" headers if found. See class-level docs.
187         * @param controllerType the controller
188         * @param methodName the method name
189         * @param args the argument values
190         * @return a UriComponentsBuilder instance, never {@code null}
191         * @throws IllegalArgumentException if there is no matching or
192         * if there is more than one matching method
193         */
194        public static UriComponentsBuilder fromMethodName(Class<?> controllerType,
195                        String methodName, Object... args) {
196
197                Method method = getMethod(controllerType, methodName, args);
198                return fromMethodInternal(null, controllerType, method, args);
199        }
200
201        /**
202         * An alternative to {@link #fromMethodName(Class, String, Object...)} that
203         * accepts a {@code UriComponentsBuilder} representing the base URL. This is
204         * useful when using MvcUriComponentsBuilder outside the context of processing
205         * a request or to apply a custom baseUrl not matching the current request.
206         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
207         * and "X-Forwarded-*" headers if found. See class-level docs.
208         * @param builder the builder for the base URL; the builder will be cloned
209         * and therefore not modified and may be re-used for further calls.
210         * @param controllerType the controller
211         * @param methodName the method name
212         * @param args the argument values
213         * @return a UriComponentsBuilder instance, never {@code null}
214         * @throws IllegalArgumentException if there is no matching or
215         * if there is more than one matching method
216         */
217        public static UriComponentsBuilder fromMethodName(UriComponentsBuilder builder,
218                        Class<?> controllerType, String methodName, Object... args) {
219
220                Method method = getMethod(controllerType, methodName, args);
221                return fromMethodInternal(builder, controllerType, method, args);
222        }
223
224        /**
225         * Create a {@link UriComponentsBuilder} by invoking a "mock" controller method.
226         * The controller method and the supplied argument values are then used to
227         * delegate to {@link #fromMethod(Class, Method, Object...)}.
228         * <p>For example, given this controller:
229         * <pre class="code">
230         * &#064;RequestMapping("/people/{id}/addresses")
231         * class AddressController {
232         *
233         *   &#064;RequestMapping("/{country}")
234         *   public HttpEntity<Void> getAddressesForCountry(&#064;PathVariable String country) { ... }
235         *
236         *   &#064;RequestMapping(value="/", method=RequestMethod.POST)
237         *   public void addAddress(Address address) { ... }
238         * }
239         * </pre>
240         * A UriComponentsBuilder can be created:
241         * <pre class="code">
242         * // Inline style with static import of "MvcUriComponentsBuilder.on"
243         *
244         * MvcUriComponentsBuilder.fromMethodCall(
245         *              on(AddressController.class).getAddressesForCountry("US")).buildAndExpand(1);
246         *
247         * // Longer form useful for repeated invocation (and void controller methods)
248         *
249         * AddressController controller = MvcUriComponentsBuilder.on(AddressController.class);
250         * controller.addAddress(null);
251         * builder = MvcUriComponentsBuilder.fromMethodCall(controller);
252         * controller.getAddressesForCountry("US")
253         * builder = MvcUriComponentsBuilder.fromMethodCall(controller);
254         * </pre>
255         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
256         * and "X-Forwarded-*" headers if found. See class-level docs.
257         * @param info either the value returned from a "mock" controller
258         * invocation or the "mock" controller itself after an invocation
259         * @return a UriComponents instance
260         */
261        public static UriComponentsBuilder fromMethodCall(Object info) {
262                Assert.isInstanceOf(MethodInvocationInfo.class, info, "MethodInvocationInfo required");
263                MethodInvocationInfo invocationInfo = (MethodInvocationInfo) info;
264                Class<?> controllerType = invocationInfo.getControllerType();
265                Method method = invocationInfo.getControllerMethod();
266                Object[] arguments = invocationInfo.getArgumentValues();
267                return fromMethodInternal(null, controllerType, method, arguments);
268        }
269
270        /**
271         * An alternative to {@link #fromMethodCall(Object)} that accepts a
272         * {@code UriComponentsBuilder} representing the base URL. This is useful
273         * when using MvcUriComponentsBuilder outside the context of processing a
274         * request or to apply a custom baseUrl not matching the current request.
275         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
276         * and "X-Forwarded-*" headers if found. See class-level docs.
277         * @param builder the builder for the base URL; the builder will be cloned
278         * and therefore not modified and may be re-used for further calls.
279         * @param info either the value returned from a "mock" controller
280         * invocation or the "mock" controller itself after an invocation
281         * @return a UriComponents instance
282         */
283        public static UriComponentsBuilder fromMethodCall(UriComponentsBuilder builder, Object info) {
284                Assert.isInstanceOf(MethodInvocationInfo.class, info, "MethodInvocationInfo required");
285                MethodInvocationInfo invocationInfo = (MethodInvocationInfo) info;
286                Class<?> controllerType = invocationInfo.getControllerType();
287                Method method = invocationInfo.getControllerMethod();
288                Object[] arguments = invocationInfo.getArgumentValues();
289                return fromMethodInternal(builder, controllerType, method, arguments);
290        }
291
292        /**
293         * Create a URL from the name of a Spring MVC controller method's request mapping.
294         * <p>The configured
295         * {@link org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy
296         * HandlerMethodMappingNamingStrategy} determines the names of controller
297         * method request mappings at startup. By default all mappings are assigned
298         * a name based on the capital letters of the class name, followed by "#" as
299         * separator, and then the method name. For example "PC#getPerson"
300         * for a class named PersonController with method getPerson. In case the
301         * naming convention does not produce unique results, an explicit name may
302         * be assigned through the name attribute of the {@code @RequestMapping}
303         * annotation.
304         * <p>This is aimed primarily for use in view rendering technologies and EL
305         * expressions. The Spring URL tag library registers this method as a function
306         * called "mvcUrl".
307         * <p>For example, given this controller:
308         * <pre class="code">
309         * &#064;RequestMapping("/people")
310         * class PersonController {
311         *
312         *   &#064;RequestMapping("/{id}")
313         *   public HttpEntity<Void> getPerson(&#064;PathVariable String id) { ... }
314         *
315         * }
316         * </pre>
317         *
318         * A JSP can prepare a URL to the controller method as follows:
319         *
320         * <pre class="code">
321         * <%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
322         *
323         * &lt;a href="${s:mvcUrl('PC#getPerson').arg(0,"123").build()}"&gt;Get Person&lt;/a&gt;
324         * </pre>
325         * <p>Note that it's not necessary to specify all arguments. Only the ones
326         * required to prepare the URL, mainly {@code @RequestParam} and {@code @PathVariable}).
327         *
328         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
329         * and "X-Forwarded-*" headers if found. See class-level docs.
330         *
331         * @param mappingName the mapping name
332         * @return a builder to prepare the URI String
333         * @throws IllegalArgumentException if the mapping name is not found or
334         * if there is no unique match
335         * @since 4.1
336         */
337        public static MethodArgumentBuilder fromMappingName(String mappingName) {
338                return fromMappingName(null, mappingName);
339        }
340
341        /**
342         * An alternative to {@link #fromMappingName(String)} that accepts a
343         * {@code UriComponentsBuilder} representing the base URL. This is useful
344         * when using MvcUriComponentsBuilder outside the context of processing a
345         * request or to apply a custom baseUrl not matching the current request.
346         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
347         * and "X-Forwarded-*" headers if found. See class-level docs.
348         * @param builder the builder for the base URL; the builder will be cloned
349         * and therefore not modified and may be re-used for further calls.
350         * @param name the mapping name
351         * @return a builder to prepare the URI String
352         * @throws IllegalArgumentException if the mapping name is not found or
353         * if there is no unique match
354         * @since 4.2
355         */
356        public static MethodArgumentBuilder fromMappingName(UriComponentsBuilder builder, String name) {
357                RequestMappingInfoHandlerMapping handlerMapping = getRequestMappingInfoHandlerMapping();
358                List<HandlerMethod> handlerMethods = handlerMapping.getHandlerMethodsForMappingName(name);
359                if (handlerMethods == null) {
360                        throw new IllegalArgumentException("Mapping mappingName not found: " + name);
361                }
362                if (handlerMethods.size() != 1) {
363                        throw new IllegalArgumentException("No unique match for mapping mappingName " +
364                                        name + ": " + handlerMethods);
365                }
366                HandlerMethod handlerMethod = handlerMethods.get(0);
367                Class<?> controllerType = handlerMethod.getBeanType();
368                Method method = handlerMethod.getMethod();
369                return new MethodArgumentBuilder(builder, controllerType, method);
370        }
371
372        /**
373         * Create a {@link UriComponentsBuilder} from the mapping of a controller method
374         * and an array of method argument values. The array of values  must match the
375         * signature of the controller method. Values for {@code @RequestParam} and
376         * {@code @PathVariable} are used for building the URI (via implementations of
377         * {@link org.springframework.web.method.support.UriComponentsContributor
378         * UriComponentsContributor}) while remaining argument values are ignored and
379         * can be {@code null}.
380         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
381         * and "X-Forwarded-*" headers if found. See class-level docs.
382         * @param controllerType the controller type
383         * @param method the controller method
384         * @param args argument values for the controller method
385         * @return a UriComponentsBuilder instance, never {@code null}
386         * @since 4.2
387         */
388        public static UriComponentsBuilder fromMethod(Class<?> controllerType, Method method, Object... args) {
389                return fromMethodInternal(null, controllerType, method, args);
390        }
391
392        /**
393         * An alternative to {@link #fromMethod(Class, Method, Object...)}
394         * that accepts a {@code UriComponentsBuilder} representing the base URL.
395         * This is useful when using MvcUriComponentsBuilder outside the context of
396         * processing a request or to apply a custom baseUrl not matching the
397         * current request.
398         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
399         * and "X-Forwarded-*" headers if found. See class-level docs.
400         * @param baseUrl the builder for the base URL; the builder will be cloned
401         * and therefore not modified and may be re-used for further calls.
402         * @param controllerType the controller type
403         * @param method the controller method
404         * @param args argument values for the controller method
405         * @return a UriComponentsBuilder instance (never {@code null})
406         * @since 4.2
407         */
408        public static UriComponentsBuilder fromMethod(UriComponentsBuilder baseUrl,
409                        Class<?> controllerType, Method method, Object... args) {
410
411                return fromMethodInternal(baseUrl,
412                                (controllerType != null ? controllerType : method.getDeclaringClass()), method, args);
413        }
414
415        /**
416         * @see #fromMethod(Class, Method, Object...)
417         * @see #fromMethod(UriComponentsBuilder, Class, Method, Object...)
418         * @deprecated as of 4.2, this is deprecated in favor of the overloaded
419         * method that also accepts a controllerType argument
420         */
421        @Deprecated
422        public static UriComponentsBuilder fromMethod(Method method, Object... args) {
423                return fromMethodInternal(null, method.getDeclaringClass(), method, args);
424        }
425
426        private static UriComponentsBuilder fromMethodInternal(UriComponentsBuilder baseUrl,
427                        Class<?> controllerType, Method method, Object... args) {
428
429                baseUrl = getBaseUrlToUse(baseUrl);
430                String typePath = getTypeRequestMapping(controllerType);
431                String methodPath = getMethodRequestMapping(method);
432                String path = pathMatcher.combine(typePath, methodPath);
433                baseUrl.path(path);
434                UriComponents uriComponents = applyContributors(baseUrl, method, args);
435                return UriComponentsBuilder.newInstance().uriComponents(uriComponents);
436        }
437
438        private static UriComponentsBuilder getBaseUrlToUse(UriComponentsBuilder baseUrl) {
439                if (baseUrl != null) {
440                        return baseUrl.cloneBuilder();
441                }
442                else {
443                        return ServletUriComponentsBuilder.fromCurrentServletMapping();
444                }
445        }
446
447        private static String getTypeRequestMapping(Class<?> controllerType) {
448                Assert.notNull(controllerType, "'controllerType' must not be null");
449                RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(controllerType, RequestMapping.class);
450                if (requestMapping == null) {
451                        return "/";
452                }
453                String[] paths = requestMapping.path();
454                if (ObjectUtils.isEmpty(paths) || StringUtils.isEmpty(paths[0])) {
455                        return "/";
456                }
457                if (paths.length > 1 && logger.isWarnEnabled()) {
458                        logger.warn("Multiple paths on controller " + controllerType.getName() + ", using first one");
459                }
460                return paths[0];
461        }
462
463        private static String getMethodRequestMapping(Method method) {
464                Assert.notNull(method, "'method' must not be null");
465                RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
466                if (requestMapping == null) {
467                        throw new IllegalArgumentException("No @RequestMapping on: " + method.toGenericString());
468                }
469                String[] paths = requestMapping.path();
470                if (ObjectUtils.isEmpty(paths) || StringUtils.isEmpty(paths[0])) {
471                        return "/";
472                }
473                if (paths.length > 1 && logger.isWarnEnabled()) {
474                        logger.warn("Multiple paths on method " + method.toGenericString() + ", using first one");
475                }
476                return paths[0];
477        }
478
479        private static Method getMethod(Class<?> controllerType, final String methodName, final Object... args) {
480                MethodFilter selector = new MethodFilter() {
481                        @Override
482                        public boolean matches(Method method) {
483                                String name = method.getName();
484                                int argLength = method.getParameterTypes().length;
485                                return (name.equals(methodName) && argLength == args.length);
486                        }
487                };
488                Set<Method> methods = MethodIntrospector.selectMethods(controllerType, selector);
489                if (methods.size() == 1) {
490                        return methods.iterator().next();
491                }
492                else if (methods.size() > 1) {
493                        throw new IllegalArgumentException(String.format(
494                                        "Found two methods named '%s' accepting arguments %s in controller %s: [%s]",
495                                        methodName, Arrays.asList(args), controllerType.getName(), methods));
496                }
497                else {
498                        throw new IllegalArgumentException("No method named '" + methodName + "' with " + args.length +
499                                        " arguments found in controller " + controllerType.getName());
500                }
501        }
502
503        private static UriComponents applyContributors(UriComponentsBuilder builder, Method method, Object... args) {
504                CompositeUriComponentsContributor contributor = getConfiguredUriComponentsContributor();
505                if (contributor == null) {
506                        logger.debug("Using default CompositeUriComponentsContributor");
507                        contributor = defaultUriComponentsContributor;
508                }
509
510                int paramCount = method.getParameterTypes().length;
511                int argCount = args.length;
512                if (paramCount != argCount) {
513                        throw new IllegalArgumentException("Number of method parameters " + paramCount +
514                                        " does not match number of argument values " + argCount);
515                }
516
517                final Map<String, Object> uriVars = new HashMap<String, Object>();
518                for (int i = 0; i < paramCount; i++) {
519                        MethodParameter param = new SynthesizingMethodParameter(method, i);
520                        param.initParameterNameDiscovery(parameterNameDiscoverer);
521                        contributor.contributeMethodArgument(param, args[i], builder, uriVars);
522                }
523
524                // We may not have all URI var values, expand only what we have
525                return builder.build().expand(new UriComponents.UriTemplateVariables() {
526                        @Override
527                        public Object getValue(String name) {
528                                return uriVars.containsKey(name) ? uriVars.get(name) : UriComponents.UriTemplateVariables.SKIP_VALUE;
529                        }
530                });
531        }
532
533        private static CompositeUriComponentsContributor getConfiguredUriComponentsContributor() {
534                WebApplicationContext wac = getWebApplicationContext();
535                if (wac == null) {
536                        return null;
537                }
538                try {
539                        return wac.getBean(MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME, CompositeUriComponentsContributor.class);
540                }
541                catch (NoSuchBeanDefinitionException ex) {
542                        if (logger.isDebugEnabled()) {
543                                logger.debug("No CompositeUriComponentsContributor bean with name '" +
544                                                MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME + "'");
545                        }
546                        return null;
547                }
548        }
549
550        private static RequestMappingInfoHandlerMapping getRequestMappingInfoHandlerMapping() {
551                WebApplicationContext wac = getWebApplicationContext();
552                Assert.notNull(wac, "Cannot lookup handler method mappings without WebApplicationContext");
553                try {
554                        return wac.getBean(RequestMappingInfoHandlerMapping.class);
555                }
556                catch (NoUniqueBeanDefinitionException ex) {
557                        throw new IllegalStateException("More than one RequestMappingInfoHandlerMapping beans found", ex);
558                }
559                catch (NoSuchBeanDefinitionException ex) {
560                        throw new IllegalStateException("No RequestMappingInfoHandlerMapping bean", ex);
561                }
562        }
563
564        private static WebApplicationContext getWebApplicationContext() {
565                RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
566                if (requestAttributes == null) {
567                        logger.debug("No request bound to the current thread: not in a DispatcherServlet request?");
568                        return null;
569                }
570
571                HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
572                WebApplicationContext wac = (WebApplicationContext)
573                                request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE);
574                if (wac == null) {
575                        logger.debug("No WebApplicationContext found: not in a DispatcherServlet request?");
576                        return null;
577                }
578                return wac;
579        }
580
581        /**
582         * Return a "mock" controller instance. When an {@code @RequestMapping} method
583         * on the controller is invoked, the supplied argument values are remembered
584         * and the result can then be used to create a {@code UriComponentsBuilder}
585         * via {@link #fromMethodCall(Object)}.
586         * <p>Note that this is a shorthand version of {@link #controller(Class)} intended
587         * for inline use (with a static import), for example:
588         * <pre class="code">
589         * MvcUriComponentsBuilder.fromMethodCall(on(FooController.class).getFoo(1)).build();
590         * </pre>
591         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
592         * and "X-Forwarded-*" headers if found. See class-level docs.
593         *
594         * @param controllerType the target controller
595         */
596        public static <T> T on(Class<T> controllerType) {
597                return controller(controllerType);
598        }
599
600        /**
601         * Return a "mock" controller instance. When an {@code @RequestMapping} method
602         * on the controller is invoked, the supplied argument values are remembered
603         * and the result can then be used to create {@code UriComponentsBuilder} via
604         * {@link #fromMethodCall(Object)}.
605         * <p>This is a longer version of {@link #on(Class)}. It is needed with controller
606         * methods returning void as well for repeated invocations.
607         * <pre class="code">
608         * FooController fooController = controller(FooController.class);
609         *
610         * fooController.saveFoo(1, null);
611         * builder = MvcUriComponentsBuilder.fromMethodCall(fooController);
612         *
613         * fooController.saveFoo(2, null);
614         * builder = MvcUriComponentsBuilder.fromMethodCall(fooController);
615         * </pre>
616         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
617         * and "X-Forwarded-*" headers if found. See class-level docs.
618         * @param controllerType the target controller
619         */
620        public static <T> T controller(Class<T> controllerType) {
621                Assert.notNull(controllerType, "'controllerType' must not be null");
622                return initProxy(controllerType, new ControllerMethodInvocationInterceptor(controllerType));
623        }
624
625        @SuppressWarnings("unchecked")
626        private static <T> T initProxy(Class<?> type, ControllerMethodInvocationInterceptor interceptor) {
627                if (type.isInterface()) {
628                        ProxyFactory factory = new ProxyFactory(EmptyTargetSource.INSTANCE);
629                        factory.addInterface(type);
630                        factory.addInterface(MethodInvocationInfo.class);
631                        factory.addAdvice(interceptor);
632                        return (T) factory.getProxy();
633                }
634
635                else {
636                        Enhancer enhancer = new Enhancer();
637                        enhancer.setSuperclass(type);
638                        enhancer.setInterfaces(new Class<?>[] {MethodInvocationInfo.class});
639                        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
640                        enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class);
641
642                        Class<?> proxyClass = enhancer.createClass();
643                        Object proxy = null;
644
645                        if (objenesis.isWorthTrying()) {
646                                try {
647                                        proxy = objenesis.newInstance(proxyClass, enhancer.getUseCache());
648                                }
649                                catch (ObjenesisException ex) {
650                                        logger.debug("Unable to instantiate controller proxy using Objenesis, " +
651                                                        "falling back to regular construction", ex);
652                                }
653                        }
654
655                        if (proxy == null) {
656                                try {
657                                        proxy = proxyClass.newInstance();
658                                }
659                                catch (Throwable ex) {
660                                        throw new IllegalStateException("Unable to instantiate controller proxy using Objenesis, " +
661                                                        "and regular controller instantiation via default constructor fails as well", ex);
662                                }
663                        }
664
665                        ((Factory) proxy).setCallbacks(new Callback[] {interceptor});
666                        return (T) proxy;
667                }
668        }
669
670        /**
671         * An alternative to {@link #fromController(Class)} for use with an instance
672         * of this class created via a call to {@link #relativeTo}.
673         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
674         * and "X-Forwarded-*" headers if found. See class-level docs.
675         * @since 4.2
676         */
677        public UriComponentsBuilder withController(Class<?> controllerType) {
678                return fromController(this.baseUrl, controllerType);
679        }
680
681        /**
682         * An alternative to {@link #fromMethodName(Class, String, Object...)}} for
683         * use with an instance of this class created via {@link #relativeTo}.
684         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
685         * and "X-Forwarded-*" headers if found. See class-level docs.
686         * @since 4.2
687         */
688        public UriComponentsBuilder withMethodName(Class<?> controllerType, String methodName, Object... args) {
689                return fromMethodName(this.baseUrl, controllerType, methodName, args);
690        }
691
692        /**
693         * An alternative to {@link #fromMethodCall(Object)} for use with an instance
694         * of this class created via {@link #relativeTo}.
695         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
696         * and "X-Forwarded-*" headers if found. See class-level docs.
697         * @since 4.2
698         */
699        public UriComponentsBuilder withMethodCall(Object invocationInfo) {
700                return fromMethodCall(this.baseUrl, invocationInfo);
701        }
702
703        /**
704         * An alternative to {@link #fromMappingName(String)} for use with an instance
705         * of this class created via {@link #relativeTo}.
706         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
707         * and "X-Forwarded-*" headers if found. See class-level docs.
708         * @since 4.2
709         */
710        public MethodArgumentBuilder withMappingName(String mappingName) {
711                return fromMappingName(this.baseUrl, mappingName);
712        }
713
714        /**
715         * An alternative to {@link #fromMethod(Class, Method, Object...)}
716         * for use with an instance of this class created via {@link #relativeTo}.
717         * <p><strong>Note:</strong> This method extracts values from "Forwarded"
718         * and "X-Forwarded-*" headers if found. See class-level docs.
719         * @since 4.2
720         */
721        public UriComponentsBuilder withMethod(Class<?> controllerType, Method method, Object... args) {
722                return fromMethod(this.baseUrl, controllerType, method, args);
723        }
724
725
726        public interface MethodInvocationInfo {
727
728                Class<?> getControllerType();
729
730                Method getControllerMethod();
731
732                Object[] getArgumentValues();
733        }
734
735
736        private static class ControllerMethodInvocationInterceptor
737                        implements org.springframework.cglib.proxy.MethodInterceptor, MethodInterceptor {
738
739                private static final Method getControllerMethod =
740                                ReflectionUtils.findMethod(MethodInvocationInfo.class, "getControllerMethod");
741
742                private static final Method getArgumentValues =
743                                ReflectionUtils.findMethod(MethodInvocationInfo.class, "getArgumentValues");
744
745                private static final Method getControllerType =
746                                ReflectionUtils.findMethod(MethodInvocationInfo.class, "getControllerType");
747
748                private final Class<?> controllerType;
749
750                private Method controllerMethod;
751
752                private Object[] argumentValues;
753
754                ControllerMethodInvocationInterceptor(Class<?> controllerType) {
755                        this.controllerType = controllerType;
756                }
757
758                @Override
759                public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) {
760                        if (getControllerType.equals(method)) {
761                                return this.controllerType;
762                        }
763                        else if (getControllerMethod.equals(method)) {
764                                return this.controllerMethod;
765                        }
766                        else if (getArgumentValues.equals(method)) {
767                                return this.argumentValues;
768                        }
769                        else if (ReflectionUtils.isObjectMethod(method)) {
770                                return ReflectionUtils.invokeMethod(method, obj, args);
771                        }
772                        else {
773                                this.controllerMethod = method;
774                                this.argumentValues = args;
775                                Class<?> returnType = method.getReturnType();
776                                try {
777                                        return (returnType == void.class ? null : returnType.cast(initProxy(returnType, this)));
778                                }
779                                catch (Throwable ex) {
780                                        throw new IllegalStateException(
781                                                        "Failed to create proxy for controller method return type: " + method, ex);
782                                }
783                        }
784                }
785
786                @Override
787                public Object invoke(org.aopalliance.intercept.MethodInvocation inv) throws Throwable {
788                        return intercept(inv.getThis(), inv.getMethod(), inv.getArguments(), null);
789                }
790        }
791
792
793        public static class MethodArgumentBuilder {
794
795                private final Class<?> controllerType;
796
797                private final Method method;
798
799                private final Object[] argumentValues;
800
801                private final UriComponentsBuilder baseUrl;
802
803                /**
804                 * @since 4.2
805                 */
806                public MethodArgumentBuilder(Class<?> controllerType, Method method) {
807                        this(null, controllerType, method);
808                }
809
810                /**
811                 * @since 4.2
812                 */
813                public MethodArgumentBuilder(UriComponentsBuilder baseUrl, Class<?> controllerType, Method method) {
814                        Assert.notNull(controllerType, "'controllerType' is required");
815                        Assert.notNull(method, "'method' is required");
816                        this.baseUrl = (baseUrl != null ? baseUrl : initBaseUrl());
817                        this.controllerType = controllerType;
818                        this.method = method;
819                        this.argumentValues = new Object[method.getParameterTypes().length];
820                        for (int i = 0; i < this.argumentValues.length; i++) {
821                                this.argumentValues[i] = null;
822                        }
823                }
824
825                /**
826                 * @deprecated as of 4.2, this is deprecated in favor of alternative constructors
827                 * that accept a controllerType argument
828                 */
829                @Deprecated
830                public MethodArgumentBuilder(Method method) {
831                        this(method.getDeclaringClass(), method);
832                }
833
834                private static UriComponentsBuilder initBaseUrl() {
835                        UriComponentsBuilder builder = ServletUriComponentsBuilder.fromCurrentServletMapping();
836                        return UriComponentsBuilder.fromPath(builder.build().getPath());
837                }
838
839                public MethodArgumentBuilder arg(int index, Object value) {
840                        this.argumentValues[index] = value;
841                        return this;
842                }
843
844                public String build() {
845                        return fromMethodInternal(this.baseUrl, this.controllerType, this.method,
846                                        this.argumentValues).build(false).encode().toUriString();
847                }
848
849                public String buildAndExpand(Object... uriVars) {
850                        return fromMethodInternal(this.baseUrl, this.controllerType, this.method,
851                                        this.argumentValues).build(false).expand(uriVars).encode().toString();
852                }
853        }
854
855}