diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMap.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMap.java index 72f2155b116..0b7bc82ad03 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMap.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMap.java @@ -17,10 +17,10 @@ package org.springframework.web.servlet; import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import org.springframework.beans.BeanUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; /** * A FlashMap provides a way for one request to store attributes intended for @@ -35,7 +35,7 @@ import org.springframework.beans.BeanUtils; * recipient. On a redirect, the target URL is known and for example * {@code org.springframework.web.servlet.view.RedirectView} has the * opportunity to automatically update the current FlashMap with target - * URL information . + * URL information. * *

Annotated controllers will usually not use this type directly. * See {@code org.springframework.web.servlet.mvc.support.RedirectAttributes} @@ -46,13 +46,13 @@ import org.springframework.beans.BeanUtils; * * @see FlashMapManager */ -public class FlashMap extends HashMap implements Comparable { +public final class FlashMap extends HashMap implements Comparable { private static final long serialVersionUID = 1L; private String targetRequestPath; - private final Map targetRequestParams = new LinkedHashMap(); + private final MultiValueMap targetRequestParams = new LinkedMultiValueMap(); private long expirationStartTime; @@ -93,17 +93,15 @@ public class FlashMap extends HashMap implements Comparable params) { + public FlashMap addTargetRequestParams(MultiValueMap params) { if (params != null) { - for (String name : params.keySet()) { - Object value = params.get(name); - if ((value != null) && BeanUtils.isSimpleValueType(value.getClass())) { - this.targetRequestParams.put(name, value.toString()); + for (String key : params.keySet()) { + for (String value : params.get(key)) { + addTargetRequestParam(key, value); } } } @@ -111,19 +109,21 @@ public class FlashMap extends HashMap implements Comparable getTargetRequestParams() { + public MultiValueMap getTargetRequestParams() { return targetRequestParams; } @@ -174,11 +174,11 @@ public class FlashMap extends HashMap implements ComparableAnnotated controllers are most likely to store and access flash attributes - * through their model. + *

Annotated controllers will usually not use this FlashMap directly. * See {@code org.springframework.web.servlet.mvc.support.RedirectAttributes}. * * @author Rossen Stoyanchev diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java index 959fcf8291f..99fc6a40165 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java @@ -130,6 +130,7 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { methodAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); methodAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef); methodAdapterDef.getPropertyValues().add("messageConverters", messageConverters); + methodAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", true); if (argumentResolvers != null) { methodAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers); } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java index 8da51d8aeb6..322f5f6c032 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java @@ -73,42 +73,59 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; /** - * A base class that provides default configuration for Spring MVC applications by registering Spring MVC - * infrastructure components to be detected by the {@link DispatcherServlet}. Typically applications should not - * have to extend this class. A more likely place to start is to annotate an @{@link Configuration} - * class with @{@link EnableWebMvc} (see @{@link EnableWebMvc} and {@link WebMvcConfigurer} for details). + * A base class that provides default configuration for Spring MVC applications + * by registering Spring MVC infrastructure components to be detected by the + * {@link DispatcherServlet}. An application configuration class is not required + * to extend this class. A more likely place to start is to annotate + * an @{@link Configuration} class with @{@link EnableWebMvc} + * (see @{@link EnableWebMvc} and {@link WebMvcConfigurer} for details). * - *

If using @{@link EnableWebMvc} does not give you all you need, consider extending directly from this - * class. Remember to add @{@link Configuration} to your subclass and @{@link Bean} to any superclass - * @{@link Bean} methods you choose to override. + *

If the customization options available with use of @{@link EnableWebMvc} + * are not enough, consider extending directly from this class and override the + * appropriate methods. Remember to add @{@link Configuration} to your subclass + * and @{@link Bean} to any superclass @{@link Bean} methods you override. * *

This class registers the following {@link HandlerMapping}s:

* * *

Registers these {@link HandlerAdapter}s: *

* - *

Registers a {@link HandlerExceptionResolverComposite} with this chain of exception resolvers: + *

Registers a {@link HandlerExceptionResolverComposite} with this chain of + * exception resolvers: *

* *

Registers these other instances: *

* * @see EnableWebMvc @@ -137,7 +154,8 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Returns a {@link RequestMappingHandlerMapping} ordered at 0 for mapping requests to annotated controllers. + * Return a {@link RequestMappingHandlerMapping} ordered at 0 for mapping + * requests to annotated controllers. */ @Bean public RequestMappingHandlerMapping requestMappingHandlerMapping() { @@ -148,8 +166,9 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Provides access to the shared handler interceptors used to configure {@link HandlerMapping} instances with. - * This method cannot be overridden, use {@link #addInterceptors(InterceptorRegistry)} instead. + * Provide access to the shared handler interceptors used to configure + * {@link HandlerMapping} instances with. This method cannot be overridden, + * use {@link #addInterceptors(InterceptorRegistry)} instead. */ protected final Object[] getInterceptors() { if (interceptors == null) { @@ -162,15 +181,17 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Override this method to add Spring MVC interceptors for pre/post-processing of controller invocation. + * Override this method to add Spring MVC interceptors for + * pre- and post-processing of controller invocation. * @see InterceptorRegistry */ protected void addInterceptors(InterceptorRegistry registry) { } /** - * Returns a handler mapping ordered at 1 to map URL paths directly to view names. - * To configure view controllers, override {@link #addViewControllers(ViewControllerRegistry)}. + * Return a handler mapping ordered at 1 to map URL paths directly to + * view names. To configure view controllers, override + * {@link #addViewControllers}. */ @Bean public HandlerMapping viewControllerHandlerMapping() { @@ -191,7 +212,8 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Returns a {@link BeanNameUrlHandlerMapping} ordered at 2 to map URL paths to controller bean names. + * Return a {@link BeanNameUrlHandlerMapping} ordered at 2 to map URL + * paths to controller bean names. */ @Bean public BeanNameUrlHandlerMapping beanNameHandlerMapping() { @@ -202,8 +224,9 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Returns a handler mapping ordered at Integer.MAX_VALUE-1 with mapped resource handlers. - * To configure resource handling, override {@link #addResourceHandlers(ResourceHandlerRegistry)}. + * Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped + * resource handlers. To configure resource handling, override + * {@link #addResourceHandlers}. */ @Bean public HandlerMapping resourceHandlerMapping() { @@ -222,9 +245,9 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Returns a handler mapping ordered at Integer.MAX_VALUE with a mapped default servlet handler. - * To configure "default" Servlet handling, override - * {@link #configureDefaultServletHandling(DefaultServletHandlerConfigurer)}. + * Return a handler mapping ordered at Integer.MAX_VALUE with a mapped + * default servlet handler. To configure "default" Servlet handling, + * override {@link #configureDefaultServletHandling}. */ @Bean public HandlerMapping defaultServletHandlerMapping() { @@ -243,12 +266,13 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Returns a {@link RequestMappingHandlerAdapter} for processing requests through annotated controller methods. - * Consider overriding one of these other more fine-grained methods: + * Returns a {@link RequestMappingHandlerAdapter} for processing requests + * through annotated controller methods. Consider overriding one of these + * other more fine-grained methods: * */ @Bean @@ -268,34 +292,46 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw adapter.setWebBindingInitializer(webBindingInitializer); adapter.setCustomArgumentResolvers(argumentResolvers); adapter.setCustomReturnValueHandlers(returnValueHandlers); + adapter.setIgnoreDefaultModelOnRedirect(true); return adapter; } /** - * Add custom {@link HandlerMethodArgumentResolver}s to use in addition to the ones registered by default. - *

Custom argument resolvers are invoked before built-in resolvers except for those that rely on the presence - * of annotations (e.g. {@code @RequestParameter}, {@code @PathVariable}, etc.). The latter can be customized - * by configuring the {@link RequestMappingHandlerAdapter} directly. - * @param argumentResolvers the list of custom converters; initially an empty list. + * Add custom {@link HandlerMethodArgumentResolver}s to use in addition to + * the ones registered by default. + *

Custom argument resolvers are invoked before built-in resolvers + * except for those that rely on the presence of annotations (e.g. + * {@code @RequestParameter}, {@code @PathVariable}, etc.). + * The latter can be customized by configuring the + * {@link RequestMappingHandlerAdapter} directly. + * @param argumentResolvers the list of custom converters; + * initially an empty list. */ protected void addArgumentResolvers(List argumentResolvers) { } /** - * Add custom {@link HandlerMethodReturnValueHandler}s in addition to the ones registered by default. - *

Custom return value handlers are invoked before built-in ones except for those that rely on the presence - * of annotations (e.g. {@code @ResponseBody}, {@code @ModelAttribute}, etc.). The latter can be customized - * by configuring the {@link RequestMappingHandlerAdapter} directly. - * @param returnValueHandlers the list of custom handlers; initially an empty list. + * Add custom {@link HandlerMethodReturnValueHandler}s in addition to the + * ones registered by default. + *

Custom return value handlers are invoked before built-in ones except + * for those that rely on the presence of annotations (e.g. + * {@code @ResponseBody}, {@code @ModelAttribute}, etc.). + * The latter can be customized by configuring the + * {@link RequestMappingHandlerAdapter} directly. + * @param returnValueHandlers the list of custom handlers; + * initially an empty list. */ protected void addReturnValueHandlers(List returnValueHandlers) { } /** * Provides access to the shared {@link HttpMessageConverter}s used by the - * {@link RequestMappingHandlerAdapter} and the {@link ExceptionHandlerExceptionResolver}. - * This method cannot be overridden. Use {@link #configureMessageConverters(List)} instead. - * Also see {@link #addDefaultHttpMessageConverters(List)} that can be used to add default message converters. + * {@link RequestMappingHandlerAdapter} and the + * {@link ExceptionHandlerExceptionResolver}. + * This method cannot be overridden. + * Use {@link #configureMessageConverters(List)} instead. + * Also see {@link #addDefaultHttpMessageConverters(List)} that can be + * used to add default message converters. */ protected final List> getMessageConverters() { if (messageConverters == null) { @@ -309,17 +345,20 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Override this method to add custom {@link HttpMessageConverter}s to use with - * the {@link RequestMappingHandlerAdapter} and the {@link ExceptionHandlerExceptionResolver}. - * Adding converters to the list turns off the default converters that would otherwise be registered by default. - * Also see {@link #addDefaultHttpMessageConverters(List)} that can be used to add default message converters. - * @param converters a list to add message converters to; initially an empty list. + * Override this method to add custom {@link HttpMessageConverter}s to use + * with the {@link RequestMappingHandlerAdapter} and the + * {@link ExceptionHandlerExceptionResolver}. Adding converters to the + * list turns off the default converters that would otherwise be registered + * by default. Also see {@link #addDefaultHttpMessageConverters(List)} that + * can be used to add default message converters. + * @param converters a list to add message converters to; + * initially an empty list. */ protected void configureMessageConverters(List> converters) { } /** - * A method available to subclasses to add default {@link HttpMessageConverter}s. + * Override this method to add default {@link HttpMessageConverter}s. * @param messageConverters the list to add the default message converters to */ protected final void addDefaultHttpMessageConverters(List> messageConverters) { @@ -346,9 +385,9 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Returns a {@link FormattingConversionService} for use with annotated controller methods and the - * {@code spring:eval} JSP tag. Also see {@link #addFormatters(FormatterRegistry)} as an alternative - * to overriding this method. + * Returns a {@link FormattingConversionService} for use with annotated + * controller methods and the {@code spring:eval} JSP tag. + * Also see {@link #addFormatters} as an alternative to overriding this method. */ @Bean public FormattingConversionService mvcConversionService() { @@ -364,8 +403,9 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Returns {@link Validator} for validating {@code @ModelAttribute} and {@code @RequestBody} arguments of - * annotated controller methods. To configure a custom validation, override {@link #getValidator()}. + * Returns {@link Validator} for validating {@code @ModelAttribute} + * and {@code @RequestBody} arguments of annotated controller methods. + * To configure a custom validation, override {@link #getValidator()}. */ @Bean Validator mvcValidator() { @@ -404,7 +444,8 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Returns a {@link HttpRequestHandlerAdapter} for processing requests with {@link HttpRequestHandler}s. + * Returns a {@link HttpRequestHandlerAdapter} for processing requests + * with {@link HttpRequestHandler}s. */ @Bean public HttpRequestHandlerAdapter httpRequestHandlerAdapter() { @@ -412,7 +453,8 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Returns a {@link SimpleControllerHandlerAdapter} for processing requests with interface-based controllers. + * Returns a {@link SimpleControllerHandlerAdapter} for processing requests + * with interface-based controllers. */ @Bean public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() { @@ -420,8 +462,9 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Returns a {@link HandlerExceptionResolverComposite} that contains a list of exception resolvers. - * To customize the list of exception resolvers, override {@link #configureHandlerExceptionResolvers(List)}. + * Returns a {@link HandlerExceptionResolverComposite} that contains a list + * of exception resolvers. To customize the list of exception resolvers, + * override {@link #configureHandlerExceptionResolvers(List)}. */ @Bean HandlerExceptionResolver handlerExceptionResolver() throws Exception { @@ -439,21 +482,28 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Override this method to configure the list of {@link HandlerExceptionResolver}s to use. - * Adding resolvers to the list turns off the default resolvers that would otherwise be registered by default. - * Also see {@link #addDefaultHandlerExceptionResolvers(List)} that can be used to add the default exception resolvers. - * @param exceptionResolvers a list to add exception resolvers to; initially an empty list. + * Override this method to configure the list of + * {@link HandlerExceptionResolver}s to use. Adding resolvers to the list + * turns off the default resolvers that would otherwise be registered by + * default. Also see {@link #addDefaultHandlerExceptionResolvers(List)} + * that can be used to add the default exception resolvers. + * @param exceptionResolvers a list to add exception resolvers to; + * initially an empty list. */ protected void configureHandlerExceptionResolvers(List exceptionResolvers) { } /** - * A method available to subclasses for adding default {@link HandlerExceptionResolver}s. + * A method available to subclasses for adding default + * {@link HandlerExceptionResolver}s. *

Adds the following exception resolvers: *

*/ protected final void addDefaultHandlerExceptionResolvers(List exceptionResolvers) { diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java index ea3fbd0d847..fff6b623587 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java @@ -235,7 +235,7 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce return null; } - if (!mavContainer.isResolveView()) { + if (mavContainer.isRequestHandled()) { return new ModelAndView(); } else { diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java index 1db4cd6672b..4ce707ea9e8 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java @@ -93,7 +93,6 @@ import org.springframework.web.servlet.mvc.method.annotation.support.ServletRequ import org.springframework.web.servlet.mvc.method.annotation.support.ServletResponseMethodArgumentResolver; import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodReturnValueHandler; import org.springframework.web.servlet.mvc.support.RedirectAttributes; -import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; import org.springframework.web.servlet.support.RequestContextUtils; import org.springframework.web.util.WebUtils; @@ -147,7 +146,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore(); - private boolean alwaysUseRedirectAttributes; + private boolean ignoreDefaultModelOnRedirect = false; private final Map, SessionAttributesHandler> sessionAttributesHandlerCache = new ConcurrentHashMap, SessionAttributesHandler>(); @@ -334,19 +333,20 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i } /** - * By default a controller uses {@link Model} to select attributes for - * rendering and for redirecting. However, a controller can also use - * {@link RedirectAttributes} to select attributes before a redirect. - *

When this flag is set to {@code true}, {@link RedirectAttributes} - * becomes the only way to select attributes for a redirect. - * In other words, for a redirect a controller must use - * {@link RedirectAttributes} or no attributes will be used. - *

The default value is {@code false}, meaning the {@link Model} is - * used unless {@link RedirectAttributes} is used. + * A controller can use the "default" {@link Model} in rendering and + * redirect scenarios. Alternatively, it can use {@link RedirectAttributes} + * to provide attributes for a redirect scenario. + *

When this flag is set to {@code true}, the "default" model is never + * used in a redirect even if the controller method doesn't explicitly + * declare a RedirectAttributes argument. + *

When set to {@code false}, the "default" model may be used in a + * redirect if the controller method doesn't explicitly declare a + * RedirectAttributes argument. + *

The default setting is {@code false}. * @see RedirectAttributes */ - public void setAlwaysUseRedirectAttributes(boolean alwaysUseRedirectAttributes) { - this.alwaysUseRedirectAttributes = alwaysUseRedirectAttributes; + public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect) { + this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect; } public void setBeanFactory(BeanFactory beanFactory) { @@ -538,18 +538,14 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest, mavContainer, requestMappingMethod); + mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); - if (this.alwaysUseRedirectAttributes) { - DataBinder dataBinder = binderFactory.createBinder(webRequest, null, null); - mavContainer.setRedirectModel(new RedirectAttributesModelMap(dataBinder)); - } - SessionStatus sessionStatus = new SimpleSessionStatus(); requestMappingMethod.invokeAndHandle(webRequest, mavContainer, sessionStatus); modelFactory.updateModel(webRequest, mavContainer, sessionStatus); - if (!mavContainer.isResolveView()) { + if (mavContainer.isRequestHandled()) { return null; } else { diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java index 60d42f6d367..348be0654ac 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java @@ -75,7 +75,7 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { *

Return value handling may be skipped entirely when the method returns {@code null} (also possibly due * to a {@code void} return type) and one of the following additional conditions is true: *

    - *
  • A {@link HandlerMethodArgumentResolver} has set the {@link ModelAndViewContainer#setResolveView(boolean)} + *
  • A {@link HandlerMethodArgumentResolver} has set the {@link ModelAndViewContainer#setRequestHandled(boolean)} * flag to {@code false} -- e.g. method arguments providing access to the response. *
  • The request qualifies as "not modified" as defined in {@link ServletWebRequest#checkNotModified(long)} * and {@link ServletWebRequest#checkNotModified(String)}. In this case a response with "not modified" response @@ -98,13 +98,13 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { setResponseStatus((ServletWebRequest) request); if (returnValue == null) { - if (isRequestNotModified(request) || hasResponseStatus() || !mavContainer.isResolveView()) { - mavContainer.setResolveView(false); + if (isRequestNotModified(request) || hasResponseStatus() || mavContainer.isRequestHandled()) { + mavContainer.setRequestHandled(true); return; } } - mavContainer.setResolveView(true); + mavContainer.setRequestHandled(false); try { returnValueHandlers.handleReturnValue(returnValue, getReturnType(), mavContainer, request); diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java index 9e51874ca09..fb8a9d7e6f0 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java @@ -101,7 +101,7 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { - mavContainer.setResolveView(false); + mavContainer.setRequestHandled(true); if (returnValue == null) { return; diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewMethodReturnValueHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewMethodReturnValueHandler.java index 84f94f9dd68..f4ff0727e0b 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewMethodReturnValueHandler.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewMethodReturnValueHandler.java @@ -24,7 +24,7 @@ import org.springframework.web.servlet.ModelAndView; /** * Handles return values of type {@link ModelAndView} transferring their content to the {@link ModelAndViewContainer}. - * If the return value is {@code null}, the {@link ModelAndViewContainer#setResolveView(boolean)} flag is set to + * If the return value is {@code null}, the {@link ModelAndViewContainer#setRequestHandled(boolean)} flag is set to * {@code false} to indicate view resolution is not needed. * * @author Rossen Stoyanchev @@ -49,7 +49,7 @@ public class ModelAndViewMethodReturnValueHandler implements HandlerMethodReturn mavContainer.addAllAttributes(mav.getModel()); } else { - mavContainer.setResolveView(false); + mavContainer.setRequestHandled(true); } } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RedirectAttributesMethodArgumentResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RedirectAttributesMethodArgumentResolver.java index 16c70320bed..d8c950e482b 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RedirectAttributesMethodArgumentResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RedirectAttributesMethodArgumentResolver.java @@ -49,16 +49,10 @@ public class RedirectAttributesMethodArgumentResolver implements HandlerMethodAr ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { - - if (mavContainer.getRedirectModel() != null) { - return mavContainer.getRedirectModel(); - } - else { - DataBinder dataBinder = binderFactory.createBinder(webRequest, null, null); - ModelMap redirectAttributes = new RedirectAttributesModelMap(dataBinder); - mavContainer.setRedirectModel(redirectAttributes); - return redirectAttributes; - } + DataBinder dataBinder = binderFactory.createBinder(webRequest, null, null); + ModelMap redirectAttributes = new RedirectAttributesModelMap(dataBinder); + mavContainer.setRedirectModel(redirectAttributes); + return redirectAttributes; } } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java index 2ef1020f67e..0dd3f2e0bd7 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java @@ -98,7 +98,7 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException { - mavContainer.setResolveView(false); + mavContainer.setRequestHandled(true); if (returnValue != null) { writeWithMessageConverters(returnValue, returnType, webRequest); } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletResponseMethodArgumentResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletResponseMethodArgumentResolver.java index 2f4ce254023..23042cb3b61 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletResponseMethodArgumentResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletResponseMethodArgumentResolver.java @@ -54,7 +54,7 @@ public class ServletResponseMethodArgumentResolver implements HandlerMethodArgum /** * {@inheritDoc} - *

    Sets the {@link ModelAndViewContainer#setResolveView(boolean)} flag to {@code false} to indicate + *

    Sets the {@link ModelAndViewContainer#setRequestHandled(boolean)} flag to {@code false} to indicate * that the method signature provides access to the response. If subsequently the underlying method * returns {@code null}, view resolution will be bypassed. * @see ServletInvocableHandlerMethod#invokeAndHandle(NativeWebRequest, ModelAndViewContainer, Object...) @@ -64,7 +64,7 @@ public class ServletResponseMethodArgumentResolver implements HandlerMethodArgum NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws IOException { - mavContainer.setResolveView(false); + mavContainer.setRequestHandled(true); HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class); Class paramType = parameter.getParameterType(); diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java index 52604afad9f..4052952e6db 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandler.java @@ -61,14 +61,14 @@ public class ViewMethodReturnValueHandler implements HandlerMethodReturnValueHan String viewName = (String) returnValue; mavContainer.setViewName(viewName); if (isRedirectViewName(viewName)) { - mavContainer.setUseRedirectModel(); + mavContainer.setUseRedirectModel(true); } } else if (returnValue instanceof View){ View view = (View) returnValue; mavContainer.setView(view); if (isRedirectView(view)) { - mavContainer.setUseRedirectModel(); + mavContainer.setUseRedirectModel(true); } } else { @@ -95,7 +95,7 @@ public class ViewMethodReturnValueHandler implements HandlerMethodReturnValueHan * "false" otherwise. */ protected boolean isRedirectView(View view) { - if (SmartView.class.isAssignableFrom(view.getClass())) { + if (view instanceof SmartView) { return ((SmartView) view).isRedirectView(); } else { diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/DefaultFlashMapManager.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/DefaultFlashMapManager.java index c8b9f57b533..6b100f939cc 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/DefaultFlashMapManager.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/DefaultFlashMapManager.java @@ -27,13 +27,14 @@ import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.CollectionUtils; +import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.servlet.FlashMap; import org.springframework.web.servlet.FlashMapManager; import org.springframework.web.util.UrlPathHelper; /** - * A default {@link FlashMapManager} implementation keeps {@link FlashMap} + * A default {@link FlashMapManager} implementation that stores {@link FlashMap} * instances in the HTTP session. * * @author Rossen Stoyanchev @@ -122,9 +123,10 @@ public class DefaultFlashMapManager implements FlashMapManager { return false; } } - if (flashMap.getTargetRequestParams() != null) { - for (String paramName : flashMap.getTargetRequestParams().keySet()) { - if (!flashMap.getTargetRequestParams().get(paramName).equals(request.getParameter(paramName))) { + MultiValueMap params = flashMap.getTargetRequestParams(); + for (String key : params.keySet()) { + for (String value : params.get(key)) { + if (!value.equals(request.getParameter(key))) { return false; } } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/RedirectView.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/RedirectView.java index 0cbe8a8540e..dffa139b668 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/RedirectView.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/RedirectView.java @@ -43,6 +43,8 @@ import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.SmartView; import org.springframework.web.servlet.View; import org.springframework.web.servlet.support.RequestContextUtils; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriUtils; import org.springframework.web.util.WebUtils; @@ -240,11 +242,9 @@ public class RedirectView extends AbstractUrlBasedView implements SmartView { FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request); if (!CollectionUtils.isEmpty(flashMap)) { - String targetPath = WebUtils.extractUrlPath(targetUrl.toString()); - flashMap.setTargetRequestPath(targetPath); - if (this.exposeModelAttributes) { - flashMap.addTargetRequestParams(model); - } + UriComponents uriComponents = UriComponentsBuilder.fromUriString(targetUrl).build(); + flashMap.setTargetRequestPath(uriComponents.getPath()); + flashMap.addTargetRequestParams(uriComponents.getQueryParams()); } sendRedirect(request, response, targetUrl.toString(), this.http10Compatible); diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/FlashMapTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/FlashMapTests.java index 01d0a97a0de..6583b5d5c07 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/FlashMapTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/FlashMapTests.java @@ -21,6 +21,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.Test; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; /** * Test fixture for {@link FlashMap} tests. @@ -31,18 +33,17 @@ public class FlashMapTests { @Test public void isExpired() throws InterruptedException { + assertFalse(new FlashMap().isExpired()); + FlashMap flashMap = new FlashMap(); flashMap.startExpirationPeriod(0); - - Thread.sleep(1); + Thread.sleep(100); assertTrue(flashMap.isExpired()); } @Test public void notExpired() throws InterruptedException { - assertFalse(new FlashMap().isExpired()); - FlashMap flashMap = new FlashMap(); flashMap.startExpirationPeriod(10); Thread.sleep(100); @@ -71,4 +72,51 @@ public class FlashMapTests { assertEquals(0, flashMap1.compareTo(flashMap2)); } + @Test + public void addTargetRequestParamNullValue() { + FlashMap flashMap = new FlashMap(); + flashMap.addTargetRequestParam("text", "abc"); + flashMap.addTargetRequestParam("empty", " "); + flashMap.addTargetRequestParam("null", null); + + assertEquals(1, flashMap.getTargetRequestParams().size()); + assertEquals("abc", flashMap.getTargetRequestParams().getFirst("text")); + } + + @Test + public void addTargetRequestParamsNullValue() { + MultiValueMap params = new LinkedMultiValueMap(); + params.add("key", "abc"); + params.add("key", " "); + params.add("key", null); + + FlashMap flashMap = new FlashMap(); + flashMap.addTargetRequestParams(params); + + assertEquals(1, flashMap.getTargetRequestParams().size()); + assertEquals(1, flashMap.getTargetRequestParams().get("key").size()); + assertEquals("abc", flashMap.getTargetRequestParams().getFirst("key")); + } + + @Test + public void addTargetRequestParamNullKey() { + FlashMap flashMap = new FlashMap(); + flashMap.addTargetRequestParam(" ", "abc"); + flashMap.addTargetRequestParam(null, "abc"); + + assertTrue(flashMap.getTargetRequestParams().isEmpty()); + } + + @Test + public void addTargetRequestParamsNullKey() { + MultiValueMap params = new LinkedMultiValueMap(); + params.add(" ", "abc"); + params.add(null, " "); + + FlashMap flashMap = new FlashMap(); + flashMap.addTargetRequestParams(params); + + assertTrue(flashMap.getTargetRequestParams().isEmpty()); + } + } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java index c9c586240f7..b79e36adfa2 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java @@ -67,6 +67,7 @@ public class AnnotationDrivenBeanDefinitionParserTests { MessageCodesResolver resolver = ((ConfigurableWebBindingInitializer) initializer).getMessageCodesResolver(); assertNotNull(resolver); assertEquals(TestMessageCodesResolver.class, resolver.getClass()); + assertEquals(true, new DirectFieldAccessor(adapter).getPropertyValue("ignoreDefaultModelOnRedirect")); } @Test diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java index 08ae16db95d..c8977750b01 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java @@ -150,6 +150,8 @@ public class WebMvcConfigurationSupportTests { Validator validator = initializer.getValidator(); assertNotNull(validator); assertTrue(validator instanceof LocalValidatorFactoryBean); + + assertEquals(true, new DirectFieldAccessor(adapter).getPropertyValue("ignoreDefaultModelOnRedirect")); } @Test diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java index b3462ffd309..616e6de1b5e 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java @@ -103,7 +103,7 @@ public class RequestMappingHandlerAdapterTests { handlerAdapter.setArgumentResolvers(Arrays.asList(redirectAttributesResolver, modelResolver)); handlerAdapter.setReturnValueHandlers(Arrays.asList(viewHandler)); - handlerAdapter.setAlwaysUseRedirectAttributes(true); + handlerAdapter.setIgnoreDefaultModelOnRedirect(true); handlerAdapter.afterPropertiesSet(); request.setAttribute(FlashMapManager.OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); @@ -111,7 +111,7 @@ public class RequestMappingHandlerAdapterTests { HandlerMethod handlerMethod = handlerMethod(new RedirectAttributeHandler(), "handle", Model.class); ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod); - assertTrue("No redirect attributes added, model should be empty", mav.getModel().isEmpty()); + assertTrue("Without RedirectAttributes arg, model should be empty", mav.getModel().isEmpty()); } @Test diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java index dcd901c8d8f..34e98961519 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java @@ -71,8 +71,8 @@ public class ServletInvocableHandlerMethodTests { ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("responseStatus"); handlerMethod.invokeAndHandle(webRequest, mavContainer); - assertFalse("Null return value + @ResponseStatus should result in 'no view resolution'", - mavContainer.isResolveView()); + assertTrue("Null return value + @ResponseStatus should result in 'request handled'", + mavContainer.isRequestHandled()); assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatus()); assertEquals("400 Bad Request", response.getErrorMessage()); @@ -85,8 +85,8 @@ public class ServletInvocableHandlerMethodTests { ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("httpServletResponse", HttpServletResponse.class); handlerMethod.invokeAndHandle(webRequest, mavContainer); - assertFalse("Null return value + HttpServletResponse arg should result in 'no view resolution'", - mavContainer.isResolveView()); + assertTrue("Null return value + HttpServletResponse arg should result in 'request handled'", + mavContainer.isRequestHandled()); } @Test @@ -98,8 +98,8 @@ public class ServletInvocableHandlerMethodTests { ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("notModified"); handlerMethod.invokeAndHandle(webRequest, mavContainer); - assertFalse("Null return value + 'not modified' request should result in 'no view resolution'", - mavContainer.isResolveView()); + assertTrue("Null return value + 'not modified' request should result in 'request handled'", + mavContainer.isRequestHandled()); } @Test diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandlerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandlerTests.java index b29e8a701a6..acd37973db4 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandlerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandlerTests.java @@ -17,6 +17,7 @@ package org.springframework.web.servlet.mvc.method.annotation.support; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -70,7 +71,7 @@ public class DefaultMethodReturnValueHandlerTests { assertEquals("viewName", mavContainer.getViewName()); assertSame(testBean, mavContainer.getModel().get("modelAttrName")); - assertTrue(mavContainer.isResolveView()); + assertFalse(mavContainer.isRequestHandled()); } @Test(expected=UnsupportedOperationException.class) diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessorTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessorTests.java index f9a1b8c8dee..47467740cf2 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessorTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessorTests.java @@ -138,7 +138,7 @@ public class HttpEntityMethodProcessorTests { Object result = processor.resolveArgument(paramHttpEntity, mavContainer, webRequest, null); assertTrue(result instanceof HttpEntity); - assertTrue("The ResolveView flag shouldn't change", mavContainer.isResolveView()); + assertFalse("The requestHandled flag shouldn't change", mavContainer.isRequestHandled()); assertEquals("Invalid argument", body, ((HttpEntity) result).getBody()); verify(messageConverter); } @@ -179,7 +179,7 @@ public class HttpEntityMethodProcessorTests { processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest); - assertFalse(mavContainer.isResolveView()); + assertTrue(mavContainer.isRequestHandled()); verify(messageConverter); } @@ -197,7 +197,7 @@ public class HttpEntityMethodProcessorTests { processor.handleReturnValue(returnValue, returnTypeResponseEntityProduces, mavContainer, webRequest); - assertFalse(mavContainer.isResolveView()); + assertTrue(mavContainer.isRequestHandled()); verify(messageConverter); } @@ -245,7 +245,7 @@ public class HttpEntityMethodProcessorTests { processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest); - assertFalse(mavContainer.isResolveView()); + assertTrue(mavContainer.isRequestHandled()); assertEquals("headerValue", servletResponse.getHeader("headerName")); } @@ -264,7 +264,7 @@ public class HttpEntityMethodProcessorTests { processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest); - assertFalse(mavContainer.isResolveView()); + assertTrue(mavContainer.isRequestHandled()); assertEquals("headerValue", outputMessage.getValue().getHeaders().get("header").get(0)); verify(messageConverter); } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewMethodReturnValueHandlerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewMethodReturnValueHandlerTests.java index cffbc6630f3..4d88b14bbba 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewMethodReturnValueHandlerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewMethodReturnValueHandlerTests.java @@ -79,7 +79,7 @@ public class ModelAndViewMethodReturnValueHandlerTests { public void handleReturnValueNull() throws Exception { handler.handleReturnValue(null, getReturnValueParam("modelAndView"), mavContainer, webRequest); - assertFalse(mavContainer.isResolveView()); + assertTrue(mavContainer.isRequestHandled()); } private MethodParameter getReturnValueParam(String methodName) throws Exception { diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolverTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolverTests.java index 966b33e2091..231ce95b674 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolverTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolverTests.java @@ -245,7 +245,7 @@ public class RequestPartMethodArgumentResolverTests { Object actualValue = resolver.resolveArgument(parameter, mavContainer, webRequest, new ValidatingBinderFactory()); assertEquals("Invalid argument value", argValue, actualValue); - assertTrue("The ResolveView flag shouldn't change", mavContainer.isResolveView()); + assertFalse("The requestHandled flag shouldn't change", mavContainer.isRequestHandled()); verify(messageConverter); } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessorTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessorTests.java index e7e2712fefd..a01bc028bc8 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessorTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessorTests.java @@ -140,7 +140,7 @@ public class RequestResponseBodyMethodProcessorTests { Object result = processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, null); assertEquals("Invalid argument", body, result); - assertTrue("The ResolveView flag shouldn't change", mavContainer.isResolveView()); + assertFalse("The requestHandled flag shouldn't change", mavContainer.isRequestHandled()); verify(messageConverter); } @@ -211,7 +211,7 @@ public class RequestResponseBodyMethodProcessorTests { processor.handleReturnValue(body, returnTypeString, mavContainer, webRequest); - assertFalse("The ResolveView flag wasn't turned off", mavContainer.isResolveView()); + assertTrue("The requestHandled flag wasn't set", mavContainer.isRequestHandled()); verify(messageConverter); } @@ -228,7 +228,7 @@ public class RequestResponseBodyMethodProcessorTests { processor.handleReturnValue(body, returnTypeStringProduces, mavContainer, webRequest); - assertFalse(mavContainer.isResolveView()); + assertTrue(mavContainer.isRequestHandled()); verify(messageConverter); } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletRequestMethodArgumentResolverTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletRequestMethodArgumentResolverTests.java index 4bc0352ce29..8f9be5cbd59 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletRequestMethodArgumentResolverTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletRequestMethodArgumentResolverTests.java @@ -75,7 +75,7 @@ public class ServletRequestMethodArgumentResolverTests { assertTrue("ServletRequest not supported", isSupported); assertSame("Invalid result", servletRequest, result); - assertTrue("The ResolveView flag shouldn't change", mavContainer.isResolveView()); + assertFalse("The requestHandled flag shouldn't change", mavContainer.isRequestHandled()); } @Test @@ -89,7 +89,7 @@ public class ServletRequestMethodArgumentResolverTests { assertTrue("Session not supported", isSupported); assertSame("Invalid result", session, result); - assertTrue("The ResolveView flag shouldn't change", mavContainer.isResolveView()); + assertFalse("The requestHandled flag shouldn't change", mavContainer.isRequestHandled()); } @Test diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletResponseMethodArgumentResolverTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletResponseMethodArgumentResolverTests.java index f53ad820f0b..062ec1e6ff3 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletResponseMethodArgumentResolverTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletResponseMethodArgumentResolverTests.java @@ -67,7 +67,7 @@ public class ServletResponseMethodArgumentResolverTests { Object result = resolver.resolveArgument(servletResponseParameter, mavContainer, webRequest, null); assertSame("Invalid result", servletResponse, result); - assertFalse(mavContainer.isResolveView()); + assertTrue(mavContainer.isRequestHandled()); } @Test @@ -78,7 +78,7 @@ public class ServletResponseMethodArgumentResolverTests { Object result = resolver.resolveArgument(outputStreamParameter, mavContainer, webRequest, null); assertSame("Invalid result", servletResponse.getOutputStream(), result); - assertFalse(mavContainer.isResolveView()); + assertTrue(mavContainer.isRequestHandled()); } @Test @@ -89,7 +89,7 @@ public class ServletResponseMethodArgumentResolverTests { Object result = resolver.resolveArgument(writerParameter, mavContainer, webRequest, null); assertSame("Invalid result", servletResponse.getWriter(), result); - assertFalse(mavContainer.isResolveView()); + assertTrue(mavContainer.isRequestHandled()); } public void supportedParams(ServletResponse p0, OutputStream p1, Writer p2) { diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/support/DefaultFlashMapManagerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/support/DefaultFlashMapManagerTests.java index e8cfc5046ec..50cecefe955 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/support/DefaultFlashMapManagerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/support/DefaultFlashMapManagerTests.java @@ -163,9 +163,7 @@ public class DefaultFlashMapManagerTests { allMaps.add(flashMap); flashMap.startExpirationPeriod(0); } - - Thread.sleep(5); - + Thread.sleep(100); this.flashMapManager.requestStarted(this.request); assertEquals(0, allMaps.size()); @@ -197,7 +195,7 @@ public class DefaultFlashMapManagerTests { this.flashMapManager.setFlashMapTimeout(0); this.flashMapManager.requestCompleted(this.request); - Thread.sleep(1); + Thread.sleep(100); List allMaps = getFlashMaps(); diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java index 3cb570ceb3b..4f74c5c3928 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java @@ -129,7 +129,7 @@ public class RedirectViewTests { assertEquals("http://url.somewhere.com/path?id=1", response.getHeader("Location")); assertEquals("/path", flashMap.getTargetRequestPath()); - assertEquals(model, flashMap.getTargetRequestParams()); + assertEquals(model, flashMap.getTargetRequestParams().toSingleValueMap()); } @Test diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java index b8b2f578af2..24ab6e84a3b 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java @@ -211,7 +211,7 @@ public final class ModelFactory { this.sessionAttributesHandler.storeAttributes(request, mavContainer.getModel()); } - if (mavContainer.isResolveView()) { + if (!mavContainer.isRequestHandled()) { updateBindingResult(request, mavContainer.getModel()); } } diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolver.java b/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolver.java index fd16ad96c68..9dafb6d867a 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolver.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolver.java @@ -22,7 +22,8 @@ import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; /** - * Strategy interface for resolving method parameters into argument values in the context of a given request. + * Strategy interface for resolving method parameters into argument values in + * the context of a given request. * * @author Arjen Poutsma * @since 3.1 @@ -30,21 +31,27 @@ import org.springframework.web.context.request.NativeWebRequest; public interface HandlerMethodArgumentResolver { /** - * Whether the given {@linkplain MethodParameter method parameter} is supported by this resolver. + * Whether the given {@linkplain MethodParameter method parameter} is + * supported by this resolver. * * @param parameter the method parameter to check - * @return {@code true} if this resolver supports the supplied parameter; {@code false} otherwise + * @return {@code true} if this resolver supports the supplied parameter; + * {@code false} otherwise */ boolean supportsParameter(MethodParameter parameter); /** - * Resolves a method parameter into an argument value from a given request. A {@link ModelAndViewContainer} - * provides access to the model for the request. A {@link WebDataBinderFactory} provides a way to create - * a {@link WebDataBinder} instance when needed for data binding and type conversion purposes. + * Resolves a method parameter into an argument value from a given request. + * A {@link ModelAndViewContainer} provides access to the model for the + * request. A {@link WebDataBinderFactory} provides a way to create + * a {@link WebDataBinder} instance when needed for data binding and + * type conversion purposes. * - * @param parameter the method parameter to resolve. This parameter must have previously been passed to - * {@link #supportsParameter(org.springframework.core.MethodParameter)} and it must have returned {@code true} - * @param mavContainer the {@link ModelAndViewContainer} for the current request + * @param parameter the method parameter to resolve. This parameter must + * have previously been passed to + * {@link #supportsParameter(org.springframework.core.MethodParameter)} + * and it must have returned {@code true} + * @param mavContainer the ModelAndViewContainer for the current request * @param webRequest the current request * @param binderFactory a factory for creating {@link WebDataBinder} instances * @return the resolved argument value, or {@code null}. diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandler.java b/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandler.java index 9113228eb6e..8def3ac532b 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandler.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandler.java @@ -20,7 +20,8 @@ import org.springframework.core.MethodParameter; import org.springframework.web.context.request.NativeWebRequest; /** - * Strategy interface to handle the value returned from the invocation of a handler method . + * Strategy interface to handle the value returned from the invocation of a + * handler method . * * @author Arjen Poutsma * @since 3.1 @@ -28,24 +29,27 @@ import org.springframework.web.context.request.NativeWebRequest; public interface HandlerMethodReturnValueHandler { /** - * Whether the given {@linkplain MethodParameter method return type} is supported by this handler. + * Whether the given {@linkplain MethodParameter method return type} is + * supported by this handler. * * @param returnType the method return type to check - * @return {@code true} if this handler supports the supplied return type; {@code false} otherwise + * @return {@code true} if this handler supports the supplied return type; + * {@code false} otherwise */ boolean supportsReturnType(MethodParameter returnType); /** - * Handle the given return value by adding attributes to the model, setting the view (or view name), - * or by writing to the response. {@link HandlerMethodReturnValueHandler} implementations should also - * consider whether to set {@link ModelAndViewContainer#setResolveView(boolean)}, which is set to - * {@code true} by default and therefore needs to be set to {@code false} explicitly if view - * resolution is to be bypassed. + * Handle the given return value by adding attributes to the model and + * setting a view or setting the + * {@link ModelAndViewContainer#setRequestHandled} flag to {@code true} + * to indicate the response has been handled directly. * * @param returnValue the value returned from the handler method - * @param returnType the type of the return value. This type must have previously been passed to - * {@link #supportsReturnType(org.springframework.core.MethodParameter)} and it must have returned {@code true} - * @param mavContainer the {@link ModelAndViewContainer} for the current request + * @param returnType the type of the return value. This type must have + * previously been passed to + * {@link #supportsReturnType(org.springframework.core.MethodParameter)} + * and it must have returned {@code true} + * @param mavContainer the ModelAndViewContainer for the current request * @param webRequest the current request * @throws Exception if the return value handling results in an error */ diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java b/org.springframework.web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java index 86cd6d17b64..8efcd1a0b8d 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java @@ -28,14 +28,14 @@ import org.springframework.validation.support.BindingAwareModelMap; * {@link HandlerMethodReturnValueHandler}s during the course of invocation of * a controller method. * - *

    The {@link #setResolveView(boolean)} flag can be used to indicate that - * view resolution is not required (e.g. {@code @ResponseBody} method). + *

    The {@link #setRequestHandled} flag can be used to indicate the request + * has been handled directly and view resolution is not required. * - *

    A default {@link Model} is created at instantiation and used thereafter. - * The {@link #setRedirectModel(ModelMap)} method can be used to provide a - * separate model to use potentially in case of a redirect. - * The {@link #setUseRedirectModel()} can be used to enable use of the - * redirect model if the controller decides to redirect. + *

    A default {@link Model} is automatically created at instantiation. + * An alternate model instance may be provided via {@link #setRedirectModel} + * for use in a redirect scenario. When {@link #setUseRedirectModel} is set + * to {@code true} signalling a redirect scenario, the {@link #getModel()} + * returns the redirect model instead of the default model. * * @author Rossen Stoyanchev * @since 3.1 @@ -44,12 +44,14 @@ public class ModelAndViewContainer { private Object view; - private boolean resolveView = true; + private boolean requestHandled = false; private final ModelMap model = new BindingAwareModelMap(); private ModelMap redirectModel; + private boolean ignoreDefaultModelOnRedirect = false; + private boolean useRedirectModel = false; /** @@ -99,7 +101,7 @@ public class ModelAndViewContainer { } /** - * Whether view resolution is required or not. + * Signal a scenario where the request is handled directly. *

    A {@link HandlerMethodReturnValueHandler} may use this flag to * indicate the response has been fully handled and view resolution * is not required (e.g. {@code @ResponseBody}). @@ -109,54 +111,64 @@ public class ModelAndViewContainer { * a complete response depending on the method return value. *

    The default value is {@code true}. */ - public void setResolveView(boolean resolveView) { - this.resolveView = resolveView; + public void setRequestHandled(boolean requestHandled) { + this.requestHandled = requestHandled; } /** - * Whether view resolution is required or not. + * Whether the request is handled directly. */ - public boolean isResolveView() { - return this.resolveView; + public boolean isRequestHandled() { + return this.requestHandled; } /** - * Return the default model created at instantiation or the one provided - * via {@link #setRedirectModel(ModelMap)} as long as it has been enabled - * via {@link #setUseRedirectModel()}. + * Return the model to use. This is either the default model created at + * instantiation or the redirect model if {@link #setUseRedirectModel} + * is set to {@code true}. If a redirect model was never provided via + * {@link #setRedirectModel}, return the default model unless + * {@link #setIgnoreDefaultModelOnRedirect} is set to {@code true}. */ public ModelMap getModel() { - if ((this.redirectModel != null) && this.useRedirectModel) { + if (!this.useRedirectModel) { + return this.model; + } + else if (this.redirectModel != null) { return this.redirectModel; } else { - return this.model; + return this.ignoreDefaultModelOnRedirect ? new ModelMap() : this.model; } } /** - * Provide a model instance to use in case the controller redirects. - * Note that {@link #setUseRedirectModel()} must also be called in order - * to enable use of the redirect model. + * Provide a separate model instance to use in a redirect scenario. + * The provided additional model however is not used used unless + * {@link #setUseRedirectModel(boolean)} gets set to {@code true} to signal + * a redirect scenario. */ public void setRedirectModel(ModelMap redirectModel) { this.redirectModel = redirectModel; } /** - * Return the redirect model provided via - * {@link #setRedirectModel(ModelMap)} or {@code null} if not provided. + * When set to {@code true} the default model is never used in a redirect + * scenario. So if a redirect model is not available, an empty model is + * used instead. + *

    When set to {@code false} the default model can be used in a redirect + * scenario if a redirect model is not available. + *

    The default setting is {@code false}. */ - public ModelMap getRedirectModel() { - return this.redirectModel; + public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect) { + this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect; } /** - * Indicate that the redirect model provided via - * {@link #setRedirectModel(ModelMap)} should be used. + * Signal the conditions for using a redirect model are in place -- e.g. + * the controller has requested a redirect. */ - public void setUseRedirectModel() { - this.useRedirectModel = true; + public void setUseRedirectModel(boolean useRedirectModel) { + this.useRedirectModel = useRedirectModel; } /** @@ -210,7 +222,7 @@ public class ModelAndViewContainer { @Override public String toString() { StringBuilder sb = new StringBuilder("ModelAndViewContainer: "); - if (isResolveView()) { + if (!isRequestHandled()) { if (isViewReference()) { sb.append("reference to view with name '").append(this.view).append("'"); } @@ -220,7 +232,7 @@ public class ModelAndViewContainer { sb.append("; model is ").append(getModel()); } else { - sb.append("View resolution not required"); + sb.append("Request handled directly"); } return sb.toString(); } diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/WebUtils.java b/org.springframework.web/src/main/java/org/springframework/web/util/WebUtils.java index b2e2d222586..fa736be6a75 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/util/WebUtils.java +++ b/org.springframework.web/src/main/java/org/springframework/web/util/WebUtils.java @@ -724,29 +724,4 @@ public abstract class WebUtils { return urlPath.substring(begin, end); } - /** - * Extracts the path from the given URL by removing the query at the end - * and the scheme and authority in the front, if present. - * @param url a URL, never {@code null} - * @return the extracted URL path - */ - public static String extractUrlPath(String url) { - // Remove query/fragment - int end = url.indexOf('?'); - if (end == -1) { - end = url.indexOf('#'); - if (end == -1) { - end = url.length(); - } - } - url = url.substring(0, end); - // Remove scheme + authority - int start = url.indexOf("://"); - if (start != -1) { - start = url.indexOf('/', start + 3); - url = (start != -1 ) ? url.substring(start) : ""; - } - return url; - } - } diff --git a/org.springframework.web/src/test/java/org/springframework/web/method/support/ModelAndViewContainerTests.java b/org.springframework.web/src/test/java/org/springframework/web/method/support/ModelAndViewContainerTests.java new file mode 100644 index 00000000000..cd369258b9c --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/web/method/support/ModelAndViewContainerTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.method.support; + +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.ui.ModelMap; + +/** + * Test fixture for {@link ModelAndViewContainer}. + * + * @author Rossen Stoyanchev + * @since 3.1 + */ +public class ModelAndViewContainerTests { + + private ModelAndViewContainer mavContainer; + + @Before + public void setup() { + this.mavContainer = new ModelAndViewContainer(); + } + + @Test + public void getModel() { + this.mavContainer.addAttribute("name", "value"); + assertEquals(1, this.mavContainer.getModel().size()); + } + + @Test + public void getModelRedirectModel() { + ModelMap redirectModel = new ModelMap("name", "redirectValue"); + this.mavContainer.setRedirectModel(redirectModel); + this.mavContainer.addAttribute("name", "value"); + + assertEquals("Default model should be used if not in redirect scenario", + "value", this.mavContainer.getModel().get("name")); + + this.mavContainer.setUseRedirectModel(true); + + assertEquals("Redirect model should be used in redirect scenario", + "redirectValue", this.mavContainer.getModel().get("name")); + } + + @Test + public void getModelIgnoreDefaultModelOnRedirect() { + this.mavContainer.addAttribute("name", "value"); + this.mavContainer.setUseRedirectModel(true); + + assertEquals("Default model should be used since no redirect model was provided", + 1, this.mavContainer.getModel().size()); + + this.mavContainer.setIgnoreDefaultModelOnRedirect(true); + + assertEquals("Empty model should be returned if no redirect model is available", + 0, this.mavContainer.getModel().size()); + } + +} diff --git a/org.springframework.web/src/test/java/org/springframework/web/util/WebUtilsTests.java b/org.springframework.web/src/test/java/org/springframework/web/util/WebUtilsTests.java index cf33f29b640..616dc66686b 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/util/WebUtilsTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/util/WebUtilsTests.java @@ -64,15 +64,4 @@ public class WebUtilsTests { assertEquals("view.html", WebUtils.extractFullFilenameFromUrlPath("/products/view.html?param=/path/a.do")); } - @Test - public void extractUriPath() { - assertEquals("", WebUtils.extractUrlPath("http://example.com")); - assertEquals("/", WebUtils.extractUrlPath("http://example.com/")); - assertEquals("/rfc/rfc3986.txt", WebUtils.extractUrlPath("http://www.ietf.org/rfc/rfc3986.txt")); - assertEquals("/over/there", WebUtils.extractUrlPath("http://example.com/over/there?name=ferret#nose")); - assertEquals("/over/there", WebUtils.extractUrlPath("http://example.com/over/there#nose")); - assertEquals("/over/there", WebUtils.extractUrlPath("/over/there?name=ferret#nose")); - assertEquals("/over/there", WebUtils.extractUrlPath("/over/there?url=http://example.com")); - } - }