summaryrefslogtreecommitdiff
path: root/spring-webmvc/src/main/java/org/springframework/web
diff options
context:
space:
mode:
authorEmmanuel Bourg <ebourg@apache.org>2016-08-03 19:55:01 +0200
committerEmmanuel Bourg <ebourg@apache.org>2016-08-03 19:55:01 +0200
commit75a721d1019da2a2fa86e24ff439df4a224e5b19 (patch)
tree2c44c00ce2c8641cccad177177e5682e187a17ea /spring-webmvc/src/main/java/org/springframework/web
parent9eaca6a06af3cbceb3754de19d477be770614265 (diff)
Imported Upstream version 4.3.2
Diffstat (limited to 'spring-webmvc/src/main/java/org/springframework/web')
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java23
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java11
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerInterceptor.java8
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java10
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java41
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java9
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java19
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java24
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java22
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceChainRegistration.java11
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistry.java10
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistry.java30
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java107
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java12
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerAdapter.java8
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerComposite.java11
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java13
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java28
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java13
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java27
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/BeanNameUrlHandlerMapping.java8
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerInterceptorAdapter.java7
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java187
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MatchableHandlerMapping.java42
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/RequestMatchResult.java67
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java5
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleServletPostProcessor.java10
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolver.java81
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java60
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java43
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/AbstractController.java29
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java6
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletForwardingController.java7
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletWrappingController.java25
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/WebContentInterceptor.java3
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java25
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtils.java8
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractMediaTypeExpression.java20
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java38
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/HeadersRequestCondition.java9
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java40
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestCondition.java35
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestCondition.java78
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java89
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java330
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java53
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/CompletionStageReturnValueHandler.java5
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultAdapter.java36
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java125
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java15
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java48
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewResponseBodyAdvice.java4
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ListenableFutureReturnValueHandler.java5
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMapMethodArgumentResolver.java36
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMethodArgumentResolver.java6
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java3
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java23
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java4
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolver.java59
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java91
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java45
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java159
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java20
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterAdapter.java39
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java81
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java12
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletCookieValueMethodArgumentResolver.java4
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java23
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolver.java59
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.java6
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandler.java12
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/AbstractUrlMethodNameResolver.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/InternalPathMethodNameResolver.java6
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MethodNameResolver.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MultiActionController.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/NoSuchRequestHandlingMethodException.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/ParameterMethodNameResolver.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/PropertiesMethodNameResolver.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AbstractControllerUrlHandlerMapping.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AnnotationControllerTypePredicate.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerBeanNameHandlerMapping.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerClassNameHandlerMapping.java7
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerTypePredicate.java6
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java12
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CssLinkResourceTransformer.java21
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java456
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceTransformer.java6
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilter.java8
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlProvider.java26
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java24
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractAnnotationConfigDispatcherServletInitializer.java5
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java17
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java18
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java109
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/CheckboxTag.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java6
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/DefaultRequestToViewNameTranslator.java26
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/InternalResourceViewResolver.java13
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java62
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java28
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerView.java10
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewResolver.java18
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewResolver.java18
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/JasperReportsMultiFormatView.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java62
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateViewResolver.java18
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/AbstractSpringPreparerFactory.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SimpleSpringPreparerFactory.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SpringBeanPreparerFactory.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/package-info.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfig.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfigurer.java5
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutView.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutViewResolver.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityToolboxView.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityViewResolver.java2
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java4
-rw-r--r--spring-webmvc/src/main/java/org/springframework/web/servlet/view/xslt/XsltView.java11
124 files changed, 2695 insertions, 1021 deletions
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
index 38d16f9e..e18f57e2 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
@@ -348,6 +348,7 @@ public class DispatcherServlet extends FrameworkServlet {
*/
public DispatcherServlet() {
super();
+ setDispatchOptionsRequest(true);
}
/**
@@ -391,6 +392,7 @@ public class DispatcherServlet extends FrameworkServlet {
*/
public DispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
+ setDispatchOptionsRequest(true);
}
@@ -970,13 +972,19 @@ public class DispatcherServlet extends FrameworkServlet {
catch (Exception ex) {
dispatchException = ex;
}
+ catch (Throwable err) {
+ // As of 4.3, we're processing Errors thrown from handler methods as well,
+ // making them available for @ExceptionHandler methods and other scenarios.
+ dispatchException = new NestedServletException("Handler dispatch failed", err);
+ }
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
- triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
+ triggerAfterCompletion(processedRequest, response, mappedHandler,
+ new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
@@ -1243,6 +1251,9 @@ public class DispatcherServlet extends FrameworkServlet {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
try {
+ if (mv.getStatus() != null) {
+ response.setStatus(mv.getStatus().value());
+ }
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
@@ -1299,16 +1310,6 @@ public class DispatcherServlet extends FrameworkServlet {
throw ex;
}
- private void triggerAfterCompletionWithError(HttpServletRequest request, HttpServletResponse response,
- HandlerExecutionChain mappedHandler, Throwable error) throws Exception {
-
- ServletException ex = new NestedServletException("Handler processing failed", error);
- if (mappedHandler != null) {
- mappedHandler.triggerAfterCompletion(request, response, ex);
- }
- throw ex;
- }
-
/**
* Restore the request attributes after an include.
* @param request current HTTP request
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java
index 0cf705a9..5b56e210 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java
@@ -426,9 +426,11 @@ public abstract class FrameworkServlet extends HttpServletBean implements Applic
/**
* Set whether this servlet should dispatch an HTTP OPTIONS request to
* the {@link #doService} method.
- * <p>Default is "false", applying {@link javax.servlet.http.HttpServlet}'s
- * default behavior (i.e. enumerating all standard HTTP request methods
- * as a response to the OPTIONS request).
+ * <p>Default in the {@code FrameworkServlet} is "false", applying
+ * {@link javax.servlet.http.HttpServlet}'s default behavior (i.e.enumerating
+ * all standard HTTP request methods as a response to the OPTIONS request).
+ * Note however that as of 4.3 the {@code DispatcherServlet} sets this
+ * property to "true" by default due to its built-in support for OPTIONS.
* <p>Turn this flag on if you prefer OPTIONS requests to go through the
* regular dispatching chain, just like other HTTP requests. This usually
* means that your controllers will receive those requests; make sure
@@ -836,7 +838,8 @@ public abstract class FrameworkServlet extends HttpServletBean implements Applic
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
- if (HttpMethod.PATCH.matches(request.getMethod())) {
+ HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
+ if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
processRequest(request, response);
}
else {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerInterceptor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerInterceptor.java
index 6f8e8036..e49eeafb 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerInterceptor.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerInterceptor.java
@@ -93,7 +93,7 @@ public interface HandlerInterceptor {
* @throws Exception in case of errors
*/
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
- throws Exception;
+ throws Exception;
/**
* Intercept the execution of a handler. Called after HandlerAdapter actually
@@ -114,7 +114,8 @@ public interface HandlerInterceptor {
* (can also be {@code null})
* @throws Exception in case of errors
*/
- void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
+ void postHandle(
+ HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
/**
@@ -136,7 +137,8 @@ public interface HandlerInterceptor {
* @param ex exception thrown on handler execution, if any
* @throws Exception in case of errors
*/
- void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
+ void afterCompletion(
+ HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java
index 80998d0f..2d0f9540 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -51,8 +51,8 @@ import javax.servlet.http.HttpServletResponse;
public interface LocaleResolver {
/**
- * Resolve the current locale via the given request. Can return a default locale as
- * fallback in any case.
+ * Resolve the current locale via the given request.
+ * Can return a default locale as fallback in any case.
* @param request the request to resolve the locale for
* @return the current locale (never {@code null})
*/
@@ -63,8 +63,8 @@ public interface LocaleResolver {
* @param request the request to be used for locale modification
* @param response the response to be used for locale modification
* @param locale the new locale, or {@code null} to clear the locale
- * @throws UnsupportedOperationException if the LocaleResolver implementation does not
- * support dynamic changing of the locale
+ * @throws UnsupportedOperationException if the LocaleResolver
+ * implementation does not support dynamic changing of the locale
*/
void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale);
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java
index d58ea7de..66fd7429 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -18,6 +18,7 @@ package org.springframework.web.servlet;
import java.util.Map;
+import org.springframework.http.HttpStatus;
import org.springframework.ui.ModelMap;
import org.springframework.util.CollectionUtils;
@@ -36,6 +37,7 @@ import org.springframework.util.CollectionUtils;
* @author Rod Johnson
* @author Juergen Hoeller
* @author Rob Harrop
+ * @author Rossen Stoyanchev
* @see DispatcherServlet
* @see ViewResolver
* @see HandlerAdapter#handle
@@ -49,6 +51,9 @@ public class ModelAndView {
/** Model Map */
private ModelMap model;
+ /** Optional HTTP status for the response */
+ private HttpStatus status;
+
/** Indicates whether or not this instance has been cleared with a call to {@link #clear()} */
private boolean cleared = false;
@@ -116,6 +121,24 @@ public class ModelAndView {
}
/**
+ * Creates new ModelAndView given a view name, model, and status.
+ * @param viewName name of the View to render, to be resolved
+ * by the DispatcherServlet's ViewResolver
+ * @param model Map of model names (Strings) to model objects
+ * (Objects). Model entries may not be {@code null}, but the
+ * model Map may be {@code null} if there is no model data.
+ * @param status an alternative status code to use for the response.
+ * @since 4.3
+ */
+ public ModelAndView(String viewName, Map<String, ?> model, HttpStatus status) {
+ this.view = viewName;
+ if (model != null) {
+ getModelMap().addAllAttributes(model);
+ }
+ this.status = status;
+ }
+
+ /**
* Convenient constructor to take a single model object.
* @param viewName name of the View to render, to be resolved
* by the DispatcherServlet's ViewResolver
@@ -215,6 +238,22 @@ public class ModelAndView {
return getModelMap();
}
+ /**
+ * Set the HTTP status to use for the response.
+ * @since 4.3
+ */
+ public void setStatus(HttpStatus status) {
+ this.status = status;
+ }
+
+ /**
+ * Return the configured HTTP status for the response, if any.
+ * @since 4.3
+ */
+ public HttpStatus getStatus() {
+ return this.status;
+ }
+
/**
* Add an attribute to the model.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
index 7c8cbcb4..dd80a15a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -286,6 +286,13 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
addResponseBodyAdvice(exceptionHandlerExceptionResolver);
+ if (argumentResolvers != null) {
+ exceptionHandlerExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
+ }
+ if (returnValueHandlers != null) {
+ exceptionHandlerExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
+ }
+
String methodExceptionResolverName = readerContext.registerWithGeneratedName(exceptionHandlerExceptionResolver);
RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java
index e6684136..fb787d1a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java
@@ -178,4 +178,23 @@ abstract class MvcNamespaceUtils {
return new RuntimeBeanReference(CORS_CONFIGURATION_BEAN_NAME);
}
+ /**
+ * Find the {@code ContentNegotiationManager} bean created by or registered
+ * with the {@code annotation-driven} element.
+ * @return a bean definition, bean reference, or null.
+ */
+ public static Object getContentNegotiationManager(ParserContext context) {
+ String name = AnnotationDrivenBeanDefinitionParser.HANDLER_MAPPING_BEAN_NAME;
+ if (context.getRegistry().containsBeanDefinition(name)) {
+ BeanDefinition handlerMappingBeanDef = context.getRegistry().getBeanDefinition(name);
+ return handlerMappingBeanDef.getPropertyValues().get("contentNegotiationManager");
+ }
+ name = AnnotationDrivenBeanDefinitionParser.CONTENT_NEGOTIATION_MANAGER_BEAN_NAME;
+ if (context.getRegistry().containsBeanDefinition(name)) {
+ return new RuntimeBeanReference(name);
+ }
+ return null;
+ }
+
+
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java
index f55e287a..e8059186 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -22,6 +22,7 @@ import java.util.concurrent.TimeUnit;
import org.w3c.dom.Element;
+import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -165,17 +166,19 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class);
resourceHandlerDef.setSource(source);
resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- resourceHandlerDef.getPropertyValues().add("locations", locations);
+
+ MutablePropertyValues values = resourceHandlerDef.getPropertyValues();
+ values.add("locations", locations);
String cacheSeconds = element.getAttribute("cache-period");
if (StringUtils.hasText(cacheSeconds)) {
- resourceHandlerDef.getPropertyValues().add("cacheSeconds", cacheSeconds);
+ values.add("cacheSeconds", cacheSeconds);
}
Element cacheControlElement = DomUtils.getChildElementByTagName(element, "cache-control");
if (cacheControlElement != null) {
CacheControl cacheControl = parseCacheControl(cacheControlElement);
- resourceHandlerDef.getPropertyValues().add("cacheControl", cacheControl);
+ values.add("cacheControl", cacheControl);
}
Element resourceChainElement = DomUtils.getChildElementByTagName(element, "resource-chain");
@@ -183,6 +186,11 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
parseResourceChain(resourceHandlerDef, parserContext, resourceChainElement, source);
}
+ Object manager = MvcNamespaceUtils.getContentNegotiationManager(parserContext);
+ if (manager != null) {
+ values.add("contentNegotiationManager", manager);
+ }
+
String beanName = parserContext.getReaderContext().generateBeanName(resourceHandlerDef);
parserContext.getRegistry().registerBeanDefinition(beanName, resourceHandlerDef);
parserContext.registerComponent(new BeanComponentDefinition(resourceHandlerDef, beanName));
@@ -242,6 +250,14 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
if (element.hasAttribute("s-maxage")) {
cacheControl = cacheControl.sMaxAge(Long.parseLong(element.getAttribute("s-maxage")), TimeUnit.SECONDS);
}
+ if (element.hasAttribute("stale-while-revalidate")) {
+ cacheControl = cacheControl.staleWhileRevalidate(
+ Long.parseLong(element.getAttribute("stale-while-revalidate")), TimeUnit.SECONDS);
+ }
+ if (element.hasAttribute("stale-if-error")) {
+ cacheControl = cacheControl.staleIfError(
+ Long.parseLong(element.getAttribute("stale-if-error")), TimeUnit.SECONDS);
+ }
return cacheControl;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java
index 06e645f5..a91c937d 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -22,7 +22,6 @@ import org.w3c.dom.Element;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.ManagedList;
@@ -39,7 +38,6 @@ import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver;
import org.springframework.web.servlet.view.script.ScriptTemplateViewResolver;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;
-import org.springframework.web.servlet.view.velocity.VelocityViewResolver;
/**
* Parse the {@code view-resolvers} MVC namespace element and register
@@ -69,6 +67,7 @@ public class ViewResolversBeanDefinitionParser implements BeanDefinitionParser {
public static final String VIEW_RESOLVER_BEAN_NAME = "mvcViewResolver";
+ @SuppressWarnings("deprecation")
public BeanDefinition parse(Element element, ParserContext context) {
Object source = context.extractSource(element);
context.pushContainingComponent(new CompositeComponentDefinition(element.getTagName(), source));
@@ -100,7 +99,7 @@ public class ViewResolversBeanDefinitionParser implements BeanDefinitionParser {
addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
}
else if ("velocity".equals(name)) {
- resolverBeanDef = new RootBeanDefinition(VelocityViewResolver.class);
+ resolverBeanDef = new RootBeanDefinition(org.springframework.web.servlet.view.velocity.VelocityViewResolver.class);
resolverBeanDef.getPropertyValues().add("suffix", ".vm");
addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
}
@@ -192,24 +191,11 @@ public class ViewResolversBeanDefinitionParser implements BeanDefinitionParser {
if (resolverElement.hasAttribute("use-not-acceptable")) {
values.add("useNotAcceptableStatusCode", resolverElement.getAttribute("use-not-acceptable"));
}
- Object manager = getContentNegotiationManager(context);
+ Object manager = MvcNamespaceUtils.getContentNegotiationManager(context);
if (manager != null) {
values.add("contentNegotiationManager", manager);
}
return beanDef;
}
- private Object getContentNegotiationManager(ParserContext context) {
- String name = AnnotationDrivenBeanDefinitionParser.HANDLER_MAPPING_BEAN_NAME;
- if (context.getRegistry().containsBeanDefinition(name)) {
- BeanDefinition handlerMappingBeanDef = context.getRegistry().getBeanDefinition(name);
- return handlerMappingBeanDef.getPropertyValues().get("contentNegotiationManager");
- }
- name = AnnotationDrivenBeanDefinitionParser.CONTENT_NEGOTIATION_MANAGER_BEAN_NAME;
- if (context.getRegistry().containsBeanDefinition(name)) {
- return new RuntimeBeanReference(name);
- }
- return null;
- }
-
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceChainRegistration.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceChainRegistration.java
index 92a75bed..93a21b89 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceChainRegistration.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceChainRegistration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -56,6 +56,8 @@ public class ResourceChainRegistration {
private boolean hasCssLinkTransformer;
+ private boolean hasWebjarsResolver;
+
public ResourceChainRegistration(boolean cacheResources) {
this(cacheResources, cacheResources ? new ConcurrentMapCache(DEFAULT_CACHE_NAME) : null);
@@ -84,6 +86,9 @@ public class ResourceChainRegistration {
else if (resolver instanceof PathResourceResolver) {
this.hasPathResolver = true;
}
+ else if (resolver instanceof WebJarsResourceResolver) {
+ this.hasWebjarsResolver = true;
+ }
return this;
}
@@ -104,7 +109,7 @@ public class ResourceChainRegistration {
protected List<ResourceResolver> getResourceResolvers() {
if (!this.hasPathResolver) {
List<ResourceResolver> result = new ArrayList<ResourceResolver>(this.resolvers);
- if (isWebJarsAssetLocatorPresent) {
+ if (isWebJarsAssetLocatorPresent && !this.hasWebjarsResolver) {
result.add(new WebJarsResourceResolver());
}
result.add(new PathResourceResolver());
@@ -114,7 +119,7 @@ public class ResourceChainRegistration {
}
protected List<ResourceTransformer> getResourceTransformers() {
- if (this.hasVersionResolver && !this.hasCssLinkTransformer) {
+ if (this.hasVersionResolver && !this.hasCssLinkTransformer) {
List<ResourceTransformer> result = new ArrayList<ResourceTransformer>(this.transformers);
boolean hasTransformers = !this.transformers.isEmpty();
boolean hasCaching = hasTransformers && this.transformers.get(0) instanceof CachingResourceTransformer;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistry.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistry.java
index f9145d4a..a4c7c02a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistry.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistry.java
@@ -27,6 +27,7 @@ import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.web.HttpRequestHandler;
+import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
@@ -55,15 +56,23 @@ public class ResourceHandlerRegistry {
private final ApplicationContext appContext;
+ private final ContentNegotiationManager contentNegotiationManager;
+
private final List<ResourceHandlerRegistration> registrations = new ArrayList<ResourceHandlerRegistration>();
private int order = Integer.MAX_VALUE -1;
public ResourceHandlerRegistry(ApplicationContext applicationContext, ServletContext servletContext) {
+ this(applicationContext, servletContext, null);
+ }
+
+ public ResourceHandlerRegistry(ApplicationContext applicationContext, ServletContext servletContext,
+ ContentNegotiationManager contentNegotiationManager) {
Assert.notNull(applicationContext, "ApplicationContext is required");
this.appContext = applicationContext;
this.servletContext = servletContext;
+ this.contentNegotiationManager = contentNegotiationManager;
}
@@ -113,6 +122,7 @@ public class ResourceHandlerRegistry {
ResourceHttpRequestHandler handler = registration.getRequestHandler();
handler.setServletContext(this.servletContext);
handler.setApplicationContext(this.appContext);
+ handler.setContentNegotiationManager(this.contentNegotiationManager);
try {
handler.afterPropertiesSet();
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistry.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistry.java
index c4fedda8..bc21e6e0 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistry.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistry.java
@@ -41,8 +41,6 @@ import org.springframework.web.servlet.view.script.ScriptTemplateConfigurer;
import org.springframework.web.servlet.view.script.ScriptTemplateViewResolver;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;
-import org.springframework.web.servlet.view.velocity.VelocityConfigurer;
-import org.springframework.web.servlet.view.velocity.VelocityViewResolver;
/**
* Assist with the configuration of a chain of
@@ -86,10 +84,8 @@ public class ViewResolverRegistry {
* Enable use of a {@link ContentNegotiatingViewResolver} to front all other
* configured view resolvers and select among all selected Views based on
* media types requested by the client (e.g. in the Accept header).
- *
* <p>If invoked multiple times the provided default views will be added to
* any other default views that may have been configured already.
- *
* @see ContentNegotiatingViewResolver#setDefaultViews
*/
public void enableContentNegotiation(View... defaultViews) {
@@ -100,7 +96,6 @@ public class ViewResolverRegistry {
* Enable use of a {@link ContentNegotiatingViewResolver} to front all other
* configured view resolvers and select among all selected Views based on
* media types requested by the client (e.g. in the Accept header).
- *
* <p>If invoked multiple times the provided default views will be added to
* any other default views that may have been configured already.
*
@@ -112,9 +107,8 @@ public class ViewResolverRegistry {
}
private void initContentNegotiatingViewResolver(View[] defaultViews) {
-
// ContentNegotiatingResolver in the registry: elevate its precedence!
- this.order = (this.order == null ? Ordered.HIGHEST_PRECEDENCE : this.order);
+ this.order = (this.order != null ? this.order : Ordered.HIGHEST_PRECEDENCE);
if (this.contentNegotiatingResolver != null) {
if (!ObjectUtils.isEmpty(defaultViews)) {
@@ -136,7 +130,6 @@ public class ViewResolverRegistry {
/**
* Register JSP view resolver using a default view name prefix of "/WEB-INF/"
* and a default suffix of ".jsp".
- *
* <p>When this method is invoked more than once, each call will register a
* new ViewResolver instance. Note that since it's not easy to determine
* if a JSP exists without forwarding to it, using multiple JSP-based view
@@ -149,7 +142,6 @@ public class ViewResolverRegistry {
/**
* Register JSP view resolver with the specified prefix and suffix.
- *
* <p>When this method is invoked more than once, each call will register a
* new ViewResolver instance. Note that since it's not easy to determine
* if a JSP exists without forwarding to it, using multiple JSP-based view
@@ -166,7 +158,6 @@ public class ViewResolverRegistry {
/**
* Register Tiles 3.x view resolver.
- *
* <p><strong>Note</strong> that you must also configure Tiles by adding a
* {@link org.springframework.web.servlet.view.tiles3.TilesConfigurer} bean.
*/
@@ -184,7 +175,6 @@ public class ViewResolverRegistry {
/**
* Register a FreeMarker view resolver with an empty default view name
* prefix and a default suffix of ".ftl".
- *
* <p><strong>Note</strong> that you must also configure FreeMarker by adding a
* {@link org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer} bean.
*/
@@ -203,12 +193,13 @@ public class ViewResolverRegistry {
/**
* Register Velocity view resolver with an empty default view name
* prefix and a default suffix of ".vm".
- *
* <p><strong>Note</strong> that you must also configure Velocity by adding a
* {@link org.springframework.web.servlet.view.velocity.VelocityConfigurer} bean.
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
+ @Deprecated
public UrlBasedViewResolverRegistration velocity() {
- if (this.applicationContext != null && !hasBeanOfType(VelocityConfigurer.class)) {
+ if (this.applicationContext != null && !hasBeanOfType(org.springframework.web.servlet.view.velocity.VelocityConfigurer.class)) {
throw new BeanInitializationException("In addition to a Velocity view resolver " +
"there must also be a single VelocityConfig bean in this web application context " +
"(or its parent): VelocityConfigurer is the usual implementation. " +
@@ -313,22 +304,23 @@ public class ViewResolverRegistry {
private static class TilesRegistration extends UrlBasedViewResolverRegistration {
- private TilesRegistration() {
+ public TilesRegistration() {
super(new TilesViewResolver());
}
}
private static class VelocityRegistration extends UrlBasedViewResolverRegistration {
- private VelocityRegistration() {
- super(new VelocityViewResolver());
+ @SuppressWarnings("deprecation")
+ public VelocityRegistration() {
+ super(new org.springframework.web.servlet.view.velocity.VelocityViewResolver());
getViewResolver().setSuffix(".vm");
}
}
private static class FreeMarkerRegistration extends UrlBasedViewResolverRegistration {
- private FreeMarkerRegistration() {
+ public FreeMarkerRegistration() {
super(new FreeMarkerViewResolver());
getViewResolver().setSuffix(".ftl");
}
@@ -336,7 +328,7 @@ public class ViewResolverRegistry {
private static class GroovyMarkupRegistration extends UrlBasedViewResolverRegistration {
- private GroovyMarkupRegistration() {
+ public GroovyMarkupRegistration() {
super(new GroovyMarkupViewResolver());
getViewResolver().setSuffix(".tpl");
}
@@ -344,7 +336,7 @@ public class ViewResolverRegistry {
private static class ScriptRegistration extends UrlBasedViewResolverRegistration {
- private ScriptRegistration() {
+ public ScriptRegistration() {
super(new ScriptTemplateViewResolver());
getViewResolver();
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
index 37976905..3a114972 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -199,6 +199,10 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
private ContentNegotiationManager contentNegotiationManager;
+ private List<HandlerMethodArgumentResolver> argumentResolvers;
+
+ private List<HandlerMethodReturnValueHandler> returnValueHandlers;
+
private List<HttpMessageConverter<?>> messageConverters;
private Map<String, CorsConfiguration> corsConfigurations;
@@ -403,7 +407,8 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
*/
@Bean
public HandlerMapping resourceHandlerMapping() {
- ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext, this.servletContext);
+ ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
+ this.servletContext, mvcContentNegotiationManager());
addResourceHandlers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
@@ -474,18 +479,12 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
*/
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
- List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>();
- addArgumentResolvers(argumentResolvers);
-
- List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
- addReturnValueHandlers(returnValueHandlers);
-
- RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
+ RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
adapter.setContentNegotiationManager(mvcContentNegotiationManager());
adapter.setMessageConverters(getMessageConverters());
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
- adapter.setCustomArgumentResolvers(argumentResolvers);
- adapter.setCustomReturnValueHandlers(returnValueHandlers);
+ adapter.setCustomArgumentResolvers(getArgumentResolvers());
+ adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
List<RequestBodyAdvice> requestBodyAdvices = new ArrayList<RequestBodyAdvice>();
@@ -513,6 +512,14 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
+ * Protected method for plugging in a custom sub-class of
+ * {@link RequestMappingHandlerAdapter}.
+ */
+ protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
+ return new RequestMappingHandlerAdapter();
+ }
+
+ /**
* Return the {@link ConfigurableWebBindingInitializer} to use for
* initializing all {@link WebDataBinder} instances.
*/
@@ -618,6 +625,20 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
+ * Provide access to the shared custom argument resolvers used by the
+ * {@link RequestMappingHandlerAdapter} and the
+ * {@link ExceptionHandlerExceptionResolver}. This method cannot be
+ * overridden, use {@link #addArgumentResolvers(List)} instead.
+ */
+ protected final List<HandlerMethodArgumentResolver> getArgumentResolvers() {
+ if (this.argumentResolvers == null) {
+ this.argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>();
+ addArgumentResolvers(this.argumentResolvers);
+ }
+ return this.argumentResolvers;
+ }
+
+ /**
* Add custom {@link HandlerMethodArgumentResolver}s to use in addition to
* the ones registered by default.
* <p>Custom argument resolvers are invoked before built-in resolvers
@@ -632,6 +653,20 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
+ * Provide access to the shared return value handlers used by the
+ * {@link RequestMappingHandlerAdapter} and the
+ * {@link ExceptionHandlerExceptionResolver}. This method cannot be
+ * overridden, use {@link #addReturnValueHandlers(List)} instead.
+ */
+ protected final List<HandlerMethodReturnValueHandler> getReturnValueHandlers() {
+ if (this.returnValueHandlers == null) {
+ this.returnValueHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
+ addReturnValueHandlers(this.returnValueHandlers);
+ }
+ return this.returnValueHandlers;
+ }
+
+ /**
* Add custom {@link HandlerMethodReturnValueHandler}s in addition to the
* ones registered by default.
* <p>Custom return value handlers are invoked before built-in ones except
@@ -679,6 +714,14 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
+ /**
+ * Override this method to extend or modify the list of converters after it
+ * has been configured. This may be useful for example to allow default
+ * converters to be registered and then insert a custom converter through
+ * this method.
+ * @param converters the list of configured converters to extend.
+ * @since 4.1.3
+ */
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
@@ -779,6 +822,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
addDefaultHandlerExceptionResolvers(exceptionResolvers);
}
+ extendHandlerExceptionResolvers(exceptionResolvers);
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
composite.setOrder(0);
composite.setExceptionResolvers(exceptionResolvers);
@@ -798,6 +842,17 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
+ * Override this method to extend or modify the list of
+ * {@link HandlerExceptionResolver}s after it has been configured. This may
+ * be useful for example to allow default resolvers to be registered and then
+ * insert a custom one through this method.
+ * @param exceptionResolvers the list of configured resolvers to extend.
+ * @since 4.3
+ */
+ protected void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
+ }
+
+ /**
* A method available to subclasses for adding default {@link HandlerExceptionResolver}s.
* <p>Adds the following exception resolvers:
* <ul>
@@ -810,26 +865,36 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
* </ul>
*/
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
- ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver = new ExceptionHandlerExceptionResolver();
- exceptionHandlerExceptionResolver.setContentNegotiationManager(mvcContentNegotiationManager());
- exceptionHandlerExceptionResolver.setMessageConverters(getMessageConverters());
+ ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
+ exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager());
+ exceptionHandlerResolver.setMessageConverters(getMessageConverters());
+ exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
+ exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
List<ResponseBodyAdvice<?>> interceptors = new ArrayList<ResponseBodyAdvice<?>>();
interceptors.add(new JsonViewResponseBodyAdvice());
- exceptionHandlerExceptionResolver.setResponseBodyAdvice(interceptors);
+ exceptionHandlerResolver.setResponseBodyAdvice(interceptors);
}
- exceptionHandlerExceptionResolver.setApplicationContext(this.applicationContext);
- exceptionHandlerExceptionResolver.afterPropertiesSet();
- exceptionResolvers.add(exceptionHandlerExceptionResolver);
+ exceptionHandlerResolver.setApplicationContext(this.applicationContext);
+ exceptionHandlerResolver.afterPropertiesSet();
+ exceptionResolvers.add(exceptionHandlerResolver);
- ResponseStatusExceptionResolver responseStatusExceptionResolver = new ResponseStatusExceptionResolver();
- responseStatusExceptionResolver.setMessageSource(this.applicationContext);
- exceptionResolvers.add(responseStatusExceptionResolver);
+ ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
+ responseStatusResolver.setMessageSource(this.applicationContext);
+ exceptionResolvers.add(responseStatusResolver);
exceptionResolvers.add(new DefaultHandlerExceptionResolver());
}
/**
+ * Protected method for plugging in a custom sub-class of
+ * {@link ExceptionHandlerExceptionResolver}.
+ */
+ protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() {
+ return new ExceptionHandlerExceptionResolver();
+ }
+
+ /**
* Register a {@link ViewResolverComposite} that contains a chain of view resolvers
* to use for view resolution.
* By default this resolver is ordered at 0 unless content negotiation view
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java
index 3e9c509f..6ab3eb41 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -131,6 +131,16 @@ public interface WebMvcConfigurer {
void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers);
/**
+ * A hook for extending or modifying the list of
+ * {@link HandlerExceptionResolver}s after it has been configured. This may
+ * be useful for example to allow default resolvers to be registered and then
+ * insert a custom one through this method.
+ * @param exceptionResolvers the list of configured resolvers to extend.
+ * @since 4.3
+ */
+ void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers);
+
+ /**
* Add Spring MVC lifecycle interceptors for pre- and post-processing of
* controller method invocations. Interceptors can be registered to apply
* to all requests or be limited to a subset of URL patterns.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerAdapter.java
index 90d4b6ae..1c9e3e0c 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerAdapter.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerAdapter.java
@@ -121,6 +121,14 @@ public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
* <p>This implementation is empty.
*/
@Override
+ public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>This implementation is empty.
+ */
+ @Override
public MessageCodesResolver getMessageCodesResolver() {
return null;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerComposite.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerComposite.java
index c88ba0ff..1b6d6e53 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerComposite.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerComposite.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -28,7 +28,7 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
/**
- * An {@link WebMvcConfigurer} implementation that delegates to other {@link WebMvcConfigurer} instances.
+ * A {@link WebMvcConfigurer} that delegates to one or more others.
*
* @author Rossen Stoyanchev
* @since 3.1
@@ -107,6 +107,13 @@ class WebMvcConfigurerComposite implements WebMvcConfigurer {
}
@Override
+ public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
+ for (WebMvcConfigurer delegate : this.delegates) {
+ delegate.configureHandlerExceptionResolvers(exceptionResolvers);
+ }
+ }
+
+ @Override
public void addInterceptors(InterceptorRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addInterceptors(registry);
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java
index 9ae1cb42..50997f55 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -17,6 +17,7 @@
package org.springframework.web.servlet.handler;
import java.util.Set;
+
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -128,13 +129,15 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti
Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
- // Log exception, both at debug log level and at warn level, if desired.
if (this.logger.isDebugEnabled()) {
this.logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
}
- logException(ex, request);
prepareResponse(ex, response);
- return doResolveException(request, response, handler, ex);
+ ModelAndView result = doResolveException(request, response, handler, ex);
+ if (result != null) {
+ logException(ex, request);
+ }
+ return result;
}
else {
return null;
@@ -194,7 +197,7 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti
* @return the log message to use
*/
protected String buildLogMessage(Exception ex, HttpServletRequest request) {
- return "Handler execution resulted in exception: " + ex;
+ return "Resolved exception caused by Handler execution: " + ex;
}
/**
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
index 1877f194..1af14127 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
@@ -27,21 +27,21 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.core.Ordered;
-import org.springframework.web.HttpRequestHandler;
-import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
-import org.springframework.web.cors.CorsProcessor;
-import org.springframework.web.cors.CorsConfiguration;
-import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;
+import org.springframework.web.HttpRequestHandler;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.context.support.WebApplicationObjectSupport;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.CorsProcessor;
+import org.springframework.web.cors.CorsUtils;
+import org.springframework.web.cors.DefaultCorsProcessor;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
-import org.springframework.web.cors.DefaultCorsProcessor;
-import org.springframework.web.cors.CorsUtils;
import org.springframework.web.util.UrlPathHelper;
/**
@@ -471,7 +471,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
- private class PreFlightHandler implements HttpRequestHandler {
+ private class PreFlightHandler implements HttpRequestHandler, CorsConfigurationSource {
private final CorsConfiguration config;
@@ -485,10 +485,15 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
corsProcessor.processRequest(this.config, request, response);
}
+
+ @Override
+ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
+ return this.config;
+ }
}
- private class CorsInterceptor extends HandlerInterceptorAdapter {
+ private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource {
private final CorsConfiguration config;
@@ -502,6 +507,11 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
return corsProcessor.processRequest(this.config, request, response);
}
+
+ @Override
+ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
+ return this.config;
+ }
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java
index d84d1601..65a8db26 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java
@@ -31,6 +31,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
+import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.MethodIntrospector;
@@ -230,7 +231,13 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
new MethodIntrospector.MetadataLookup<T>() {
@Override
public T inspect(Method method) {
- return getMappingForMethod(method, userType);
+ try {
+ return getMappingForMethod(method, userType);
+ }
+ catch (Throwable ex) {
+ throw new IllegalStateException("Invalid mapping on handler class [" +
+ userType.getName() + "]: " + method, ex);
+ }
}
});
@@ -238,7 +245,9 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
}
for (Map.Entry<Method, T> entry : methods.entrySet()) {
- registerHandlerMethod(handler, entry.getKey(), entry.getValue());
+ Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
+ T mapping = entry.getValue();
+ registerHandlerMethod(handler, invocableMethod, mapping);
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java
index 5d6406e5..51bed445 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -29,7 +29,6 @@ import org.springframework.beans.BeansException;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.HandlerExecutionChain;
-import org.springframework.web.servlet.HandlerMapping;
/**
* Abstract base class for URL-mapped {@link org.springframework.web.servlet.HandlerMapping}
@@ -50,7 +49,7 @@ import org.springframework.web.servlet.HandlerMapping;
* @author Arjen Poutsma
* @since 16.04.2003
*/
-public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
+public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
private Object rootHandler;
@@ -265,8 +264,8 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
* @see #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
*/
protected void exposePathWithinMapping(String bestMatchingPattern, String pathWithinMapping, HttpServletRequest request) {
- request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, bestMatchingPattern);
- request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping);
+ request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestMatchingPattern);
+ request.setAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping);
}
/**
@@ -276,7 +275,21 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
* @see #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
*/
protected void exposeUriTemplateVariables(Map<String, String> uriTemplateVariables, HttpServletRequest request) {
- request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
+ request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
+ }
+
+ @Override
+ public RequestMatchResult match(HttpServletRequest request, String pattern) {
+ String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
+ if (getPathMatcher().match(pattern, lookupPath)) {
+ return new RequestMatchResult(pattern, lookupPath, getPathMatcher());
+ }
+ else if (useTrailingSlashMatch()) {
+ if (!pattern.endsWith("/") && getPathMatcher().match(pattern + "/", lookupPath)) {
+ return new RequestMatchResult(pattern + "/", lookupPath, getPathMatcher());
+ }
+ }
+ return null;
}
/**
@@ -386,7 +399,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
exposePathWithinMapping(this.bestMatchingPattern, this.pathWithinMapping, request);
- request.setAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings());
+ request.setAttribute(INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings());
return true;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/BeanNameUrlHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/BeanNameUrlHandlerMapping.java
index 397c179b..44376cbd 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/BeanNameUrlHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/BeanNameUrlHandlerMapping.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -28,9 +28,9 @@ import org.springframework.util.StringUtils;
*
* <p>This is the default implementation used by the
* {@link org.springframework.web.servlet.DispatcherServlet}, along with
- * {@link org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping}
- * (on Java 5 and higher). Alternatively, {@link SimpleUrlHandlerMapping} allows for
- * customizing a handler mapping declaratively.
+ * {@link org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping}.
+ * Alternatively, {@link SimpleUrlHandlerMapping} allows for customizing a
+ * handler mapping declaratively.
*
* <p>The mapping is from URL to bean name. Thus an incoming URL "/foo" would map
* to a handler named "/foo", or to "/foo /foo2" in case of multiple mappings to
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerInterceptorAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerInterceptorAdapter.java
index 602886ba..b00d4689 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerInterceptorAdapter.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerInterceptorAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -23,7 +23,7 @@ import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
- * Abstract adapter class for the HandlerInterceptor interface,
+ * Abstract adapter class for the {@link AsyncHandlerInterceptor} interface,
* for simplified implementation of pre-only/post-only interceptors.
*
* @author Juergen Hoeller
@@ -36,7 +36,8 @@ public abstract class HandlerInterceptorAdapter implements AsyncHandlerIntercept
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
- throws Exception {
+ throws Exception {
+
return true;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java
new file mode 100644
index 00000000..1c7fa9b6
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2002-2016 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.servlet.handler;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PropertiesLoaderUtils;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.servlet.DispatcherServlet;
+import org.springframework.web.servlet.HandlerExecutionChain;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.HandlerMapping;
+
+/**
+ * Helper class to get information from the {@code HandlerMapping} that would
+ * serve a specific request.
+ *
+ * <p>Provides the following methods:
+ * <ul>
+ * <li>{@link #getMatchableHandlerMapping} &mdash; obtain a {@code HandlerMapping}
+ * to check request-matching criteria against.
+ * <li>{@link #getCorsConfiguration} &mdash; obtain the CORS configuration for the
+ * request.
+ * </ul>
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3.1
+ */
+public class HandlerMappingIntrospector implements CorsConfigurationSource {
+
+ private final List<HandlerMapping> handlerMappings;
+
+
+ /**
+ * Constructor that detects the configured {@code HandlerMapping}s in the
+ * given {@code ApplicationContext} or falls back on
+ * "DispatcherServlet.properties" like the {@code DispatcherServlet}.
+ */
+ public HandlerMappingIntrospector(ApplicationContext context) {
+ this.handlerMappings = initHandlerMappings(context);
+ }
+
+
+ private static List<HandlerMapping> initHandlerMappings(ApplicationContext context) {
+ Map<String, HandlerMapping> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
+ context, HandlerMapping.class, true, false);
+ if (!beans.isEmpty()) {
+ List<HandlerMapping> mappings = new ArrayList<HandlerMapping>(beans.values());
+ AnnotationAwareOrderComparator.sort(mappings);
+ return mappings;
+ }
+ return initDefaultHandlerMappings(context);
+ }
+
+ private static List<HandlerMapping> initDefaultHandlerMappings(ApplicationContext context) {
+ Properties props;
+ String path = "DispatcherServlet.properties";
+ try {
+ Resource resource = new ClassPathResource(path, DispatcherServlet.class);
+ props = PropertiesLoaderUtils.loadProperties(resource);
+ }
+ catch (IOException ex) {
+ throw new IllegalStateException("Could not load '" + path + "': " + ex.getMessage());
+ }
+
+ String value = props.getProperty(HandlerMapping.class.getName());
+ String[] names = StringUtils.commaDelimitedListToStringArray(value);
+ List<HandlerMapping> result = new ArrayList<HandlerMapping>(names.length);
+ for (String name : names) {
+ try {
+ Class<?> clazz = ClassUtils.forName(name, DispatcherServlet.class.getClassLoader());
+ Object mapping = context.getAutowireCapableBeanFactory().createBean(clazz);
+ result.add((HandlerMapping) mapping);
+ }
+ catch (ClassNotFoundException ex) {
+ throw new IllegalStateException("Could not find default HandlerMapping [" + name + "]");
+ }
+ }
+ return result;
+ }
+
+
+ /**
+ * Return the configured HandlerMapping's.
+ */
+ public List<HandlerMapping> getHandlerMappings() {
+ return this.handlerMappings;
+ }
+
+ /**
+ * Find the {@link HandlerMapping} that would handle the given request and
+ * return it as a {@link MatchableHandlerMapping} that can be used to test
+ * request-matching criteria.
+ * <p>If the matching HandlerMapping is not an instance of
+ * {@link MatchableHandlerMapping}, an IllegalStateException is raised.
+ * @param request the current request
+ * @return the resolved matcher, or {@code null}
+ * @throws Exception if any of the HandlerMapping's raise an exception
+ */
+ public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception {
+ HttpServletRequest wrapper = new RequestAttributeChangeIgnoringWrapper(request);
+ for (HandlerMapping handlerMapping : this.handlerMappings) {
+ Object handler = handlerMapping.getHandler(wrapper);
+ if (handler == null) {
+ continue;
+ }
+ if (handlerMapping instanceof MatchableHandlerMapping) {
+ return ((MatchableHandlerMapping) handlerMapping);
+ }
+ throw new IllegalStateException("HandlerMapping is not a MatchableHandlerMapping");
+ }
+ return null;
+ }
+
+ @Override
+ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
+ HttpServletRequest wrapper = new RequestAttributeChangeIgnoringWrapper(request);
+ for (HandlerMapping handlerMapping : this.handlerMappings) {
+ HandlerExecutionChain handler = null;
+ try {
+ handler = handlerMapping.getHandler(wrapper);
+ }
+ catch (Exception ex) {
+ // Ignore
+ }
+ if (handler == null) {
+ continue;
+ }
+ if (handler.getInterceptors() != null) {
+ for (HandlerInterceptor interceptor : handler.getInterceptors()) {
+ if (interceptor instanceof CorsConfigurationSource) {
+ return ((CorsConfigurationSource) interceptor).getCorsConfiguration(wrapper);
+ }
+ }
+ }
+ if (handler.getHandler() instanceof CorsConfigurationSource) {
+ return ((CorsConfigurationSource) handler.getHandler()).getCorsConfiguration(wrapper);
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Request wrapper that ignores request attribute changes.
+ */
+ private static class RequestAttributeChangeIgnoringWrapper extends HttpServletRequestWrapper {
+
+ public RequestAttributeChangeIgnoringWrapper(HttpServletRequest request) {
+ super(request);
+ }
+
+ @Override
+ public void setAttribute(String name, Object value) {
+ // Ignore attribute change
+ }
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MatchableHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MatchableHandlerMapping.java
new file mode 100644
index 00000000..af5788c7
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MatchableHandlerMapping.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2016 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.servlet.handler;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.web.servlet.HandlerMapping;
+
+/**
+ * Additional interface that a {@link HandlerMapping} can implement to expose
+ * a request matching API aligned with its internal request matching
+ * configuration and implementation.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3.1
+ * @see HandlerMappingIntrospector
+ */
+public interface MatchableHandlerMapping extends HandlerMapping {
+
+ /**
+ * Determine whether the given request matches the request criteria.
+ * @param request the current request
+ * @param pattern the pattern to match
+ * @return the result from request matching, or {@code null} if none
+ */
+ RequestMatchResult match(HttpServletRequest request, String pattern);
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/RequestMatchResult.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/RequestMatchResult.java
new file mode 100644
index 00000000..380be418
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/RequestMatchResult.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2016 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.servlet.handler;
+
+import java.util.Map;
+
+import org.springframework.util.Assert;
+import org.springframework.util.PathMatcher;
+
+/**
+ * Container for the result from request pattern matching via
+ * {@link MatchableHandlerMapping} with a method to further extract
+ * URI template variables from the pattern.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3.1
+ */
+public class RequestMatchResult {
+
+ private final String matchingPattern;
+
+ private final String lookupPath;
+
+ private final PathMatcher pathMatcher;
+
+
+ /**
+ * Create an instance with a matching pattern.
+ * @param matchingPattern the matching pattern, possibly not the same as the
+ * input pattern, e.g. inputPattern="/foo" and matchingPattern="/foo/".
+ * @param lookupPath the lookup path extracted from the request
+ * @param pathMatcher the PathMatcher used
+ */
+ public RequestMatchResult(String matchingPattern, String lookupPath, PathMatcher pathMatcher) {
+ Assert.hasText(matchingPattern, "'matchingPattern' is required");
+ Assert.hasText(lookupPath, "'lookupPath' is required");
+ Assert.notNull(pathMatcher, "'pathMatcher' is required");
+ this.matchingPattern = matchingPattern;
+ this.lookupPath = lookupPath;
+ this.pathMatcher = pathMatcher;
+ }
+
+
+ /**
+ * Extract URI template variables from the matching pattern as defined in
+ * {@link PathMatcher#extractUriTemplateVariables}.
+ * @return a map with URI template variables
+ */
+ public Map<String, String> extractUriTemplateVariables() {
+ return this.pathMatcher.extractUriTemplateVariables(this.matchingPattern, this.lookupPath);
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java
index b24363f1..7bf2c47e 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -46,6 +46,7 @@ public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionReso
/** The default name of the exception attribute: "exception". */
public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";
+
private Properties exceptionMappings;
private Class<?>[] excludedExceptions;
@@ -108,7 +109,7 @@ public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionReso
public void setStatusCodes(Properties statusCodes) {
for (Enumeration<?> enumeration = statusCodes.propertyNames(); enumeration.hasMoreElements();) {
String viewName = (String) enumeration.nextElement();
- Integer statusCode = new Integer(statusCodes.getProperty(viewName));
+ Integer statusCode = Integer.valueOf(statusCodes.getProperty(viewName));
this.statusCodes.put(viewName, statusCode);
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleServletPostProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleServletPostProcessor.java
index 49e2e3c8..d6fef3b6 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleServletPostProcessor.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleServletPostProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -18,7 +18,6 @@ package org.springframework.web.servlet.handler;
import java.util.Collections;
import java.util.Enumeration;
-import java.util.HashSet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
@@ -126,6 +125,11 @@ public class SimpleServletPostProcessor implements
}
}
+ @Override
+ public boolean requiresDestruction(Object bean) {
+ return (bean instanceof Servlet);
+ }
+
/**
* Internal implementation of the {@link ServletConfig} interface,
@@ -159,7 +163,7 @@ public class SimpleServletPostProcessor implements
@Override
public Enumeration<String> getInitParameterNames() {
- return Collections.enumeration(new HashSet<String>());
+ return Collections.enumeration(Collections.<String>emptySet());
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolver.java
index c28f686e..c8bfa23c 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -16,6 +16,9 @@
package org.springframework.web.servlet.i18n;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -31,14 +34,88 @@ import org.springframework.web.servlet.LocaleResolver;
* can only be changed through changing the client's locale settings.
*
* @author Juergen Hoeller
+ * @author Rossen Stoyanchev
* @since 27.02.2003
* @see javax.servlet.http.HttpServletRequest#getLocale()
*/
public class AcceptHeaderLocaleResolver implements LocaleResolver {
+ private final List<Locale> supportedLocales = new ArrayList<Locale>(4);
+
+ private Locale defaultLocale;
+
+
+ /**
+ * Configure supported locales to check against the requested locales
+ * determined via {@link HttpServletRequest#getLocales()}. If this is not
+ * configured then {@link HttpServletRequest#getLocale()} is used instead.
+ * @param locales the supported locales
+ * @since 4.3
+ */
+ public void setSupportedLocales(List<Locale> locales) {
+ this.supportedLocales.clear();
+ if (locales != null) {
+ this.supportedLocales.addAll(locales);
+ }
+ }
+
+ /**
+ * Return the configured list of supported locales.
+ * @since 4.3
+ */
+ public List<Locale> getSupportedLocales() {
+ return this.supportedLocales;
+ }
+
+ /**
+ * Configure a fixed default locale to fall back on if the request does not
+ * have an "Accept-Language" header.
+ * <p>By default this is not set in which case when there is "Accept-Language"
+ * header, the default locale for the server is used as defined in
+ * {@link HttpServletRequest#getLocale()}.
+ * @param defaultLocale the default locale to use
+ * @since 4.3
+ */
+ public void setDefaultLocale(Locale defaultLocale) {
+ this.defaultLocale = defaultLocale;
+ }
+
+ /**
+ * The configured default locale, if any.
+ * @since 4.3
+ */
+ public Locale getDefaultLocale() {
+ return this.defaultLocale;
+ }
+
+
@Override
public Locale resolveLocale(HttpServletRequest request) {
- return request.getLocale();
+ Locale defaultLocale = getDefaultLocale();
+ if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
+ return defaultLocale;
+ }
+ Locale locale = request.getLocale();
+ if (!isSupportedLocale(locale)) {
+ locale = findSupportedLocale(request, locale);
+ }
+ return locale;
+ }
+
+ private boolean isSupportedLocale(Locale locale) {
+ List<Locale> supportedLocales = getSupportedLocales();
+ return (supportedLocales.isEmpty() || supportedLocales.contains(locale));
+ }
+
+ private Locale findSupportedLocale(HttpServletRequest request, Locale fallback) {
+ Enumeration<Locale> requestLocales = request.getLocales();
+ while (requestLocales.hasMoreElements()) {
+ Locale locale = requestLocales.nextElement();
+ if (getSupportedLocales().contains(locale)) {
+ return locale;
+ }
+ }
+ return fallback;
}
@Override
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java
index 547a91fc..f216e9f6 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java
@@ -25,6 +25,7 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.SimpleLocaleContext;
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
+import org.springframework.lang.UsesJava7;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleContextResolver;
import org.springframework.web.servlet.LocaleResolver;
@@ -81,6 +82,8 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
public static final String DEFAULT_COOKIE_NAME = CookieLocaleResolver.class.getName() + ".LOCALE";
+ private boolean languageTagCompliant = false;
+
private Locale defaultLocale;
private TimeZone defaultTimeZone;
@@ -94,6 +97,30 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
setCookieName(DEFAULT_COOKIE_NAME);
}
+
+ /**
+ * Specify whether this resolver's cookies should be compliant with BCP 47
+ * language tags instead of Java's legacy locale specification format.
+ * The default is {@code false}.
+ * <p>Note: This mode requires JDK 7 or higher. Set this flag to {@code true}
+ * for BCP 47 compliance on JDK 7+ only.
+ * @since 4.3
+ * @see Locale#forLanguageTag(String)
+ * @see Locale#toLanguageTag()
+ */
+ public void setLanguageTagCompliant(boolean languageTagCompliant) {
+ this.languageTagCompliant = languageTagCompliant;
+ }
+
+ /**
+ * Return whether this resolver's cookies should be compliant with BCP 47
+ * language tags instead of Java's legacy locale specification format.
+ * @since 4.3
+ */
+ public boolean isLanguageTagCompliant() {
+ return this.languageTagCompliant;
+ }
+
/**
* Set a fixed Locale that this resolver will return if no cookie found.
*/
@@ -163,7 +190,7 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
localePart = value.substring(0, spaceIndex);
timeZonePart = value.substring(spaceIndex + 1);
}
- locale = (!"-".equals(localePart) ? StringUtils.parseLocaleString(localePart) : null);
+ locale = (!"-".equals(localePart) ? parseLocaleValue(localePart) : null);
if (timeZonePart != null) {
timeZone = StringUtils.parseTimeZoneString(timeZonePart);
}
@@ -193,7 +220,8 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
if (localeContext instanceof TimeZoneAwareLocaleContext) {
timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
}
- addCookie(response, (locale != null ? locale : "-") + (timeZone != null ? ' ' + timeZone.getID() : ""));
+ addCookie(response,
+ (locale != null ? toLocaleValue(locale) : "-") + (timeZone != null ? ' ' + timeZone.getID() : ""));
}
else {
removeCookie(response);
@@ -206,6 +234,34 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
/**
+ * Parse the given locale value coming from an incoming cookie.
+ * <p>The default implementation calls {@link StringUtils#parseLocaleString(String)}
+ * or JDK 7's {@link Locale#forLanguageTag(String)}, depending on the
+ * {@link #setLanguageTagCompliant "languageTagCompliant"} configuration property.
+ * @param locale the locale value to parse
+ * @return the corresponding {@code Locale} instance
+ * @since 4.3
+ */
+ @UsesJava7
+ protected Locale parseLocaleValue(String locale) {
+ return (isLanguageTagCompliant() ? Locale.forLanguageTag(locale) : StringUtils.parseLocaleString(locale));
+ }
+
+ /**
+ * Render the given locale as a text value for inclusion in a cookie.
+ * <p>The default implementation calls {@link Locale#toString()}
+ * or JDK 7's {@link Locale#toLanguageTag()}, depending on the
+ * {@link #setLanguageTagCompliant "languageTagCompliant"} configuration property.
+ * @param locale the locale to stringify
+ * @return a String representation for the given locale
+ * @since 4.3
+ */
+ @UsesJava7
+ protected String toLocaleValue(Locale locale) {
+ return (isLanguageTagCompliant() ? locale.toLanguageTag() : locale.toString());
+ }
+
+ /**
* Determine the default locale for the given request,
* Called if no locale cookie has been found.
* <p>The default implementation returns the specified default locale,
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java
index 099e4b46..7642bcd0 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java
@@ -16,6 +16,7 @@
package org.springframework.web.servlet.i18n;
+import java.util.Locale;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -23,6 +24,7 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.springframework.lang.UsesJava7;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
@@ -54,6 +56,8 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {
private boolean ignoreInvalidLocale = false;
+ private boolean languageTagCompliant = false;
+
/**
* Set the name of the parameter that contains a locale specification
@@ -104,6 +108,29 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {
return this.ignoreInvalidLocale;
}
+ /**
+ * Specify whether to parse request parameter values as BCP 47 language tags
+ * instead of Java's legacy locale specification format.
+ * The default is {@code false}.
+ * <p>Note: This mode requires JDK 7 or higher. Set this flag to {@code true}
+ * for BCP 47 compliance on JDK 7+ only.
+ * @since 4.3
+ * @see Locale#forLanguageTag(String)
+ * @see Locale#toLanguageTag()
+ */
+ public void setLanguageTagCompliant(boolean languageTagCompliant) {
+ this.languageTagCompliant = languageTagCompliant;
+ }
+
+ /**
+ * Return whether to use BCP 47 language tags instead of Java's legacy
+ * locale specification format.
+ * @since 4.3
+ */
+ public boolean isLanguageTagCompliant() {
+ return this.languageTagCompliant;
+ }
+
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
@@ -118,7 +145,7 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {
"No LocaleResolver found: not in a DispatcherServlet request?");
}
try {
- localeResolver.setLocale(request, response, StringUtils.parseLocaleString(newLocale));
+ localeResolver.setLocale(request, response, parseLocaleValue(newLocale));
}
catch (IllegalArgumentException ex) {
if (isIgnoreInvalidLocale()) {
@@ -147,4 +174,18 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {
return false;
}
+ /**
+ * Parse the given locale value as coming from a request parameter.
+ * <p>The default implementation calls {@link StringUtils#parseLocaleString(String)}
+ * or JDK 7's {@link Locale#forLanguageTag(String)}, depending on the
+ * {@link #setLanguageTagCompliant "languageTagCompliant"} configuration property.
+ * @param locale the locale value to parse
+ * @return the corresponding {@code Locale} instance
+ * @since 4.3
+ */
+ @UsesJava7
+ protected Locale parseLocaleValue(String locale) {
+ return (isLanguageTagCompliant() ? Locale.forLanguageTag(locale) : StringUtils.parseLocaleString(locale));
+ }
+
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/AbstractController.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/AbstractController.java
index 46fab982..c37e656a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/AbstractController.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/AbstractController.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -20,6 +20,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
+import org.springframework.http.HttpMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.support.WebContentGenerator;
import org.springframework.web.util.WebUtils;
@@ -87,6 +88,7 @@ import org.springframework.web.util.WebUtils;
*
* @author Rod Johnson
* @author Juergen Hoeller
+ * @author Rossen Stoyanchev
* @see WebContentInterceptor
*/
public abstract class AbstractController extends WebContentGenerator implements Controller {
@@ -95,6 +97,26 @@ public abstract class AbstractController extends WebContentGenerator implements
/**
+ * Create a new AbstractController which supports
+ * HTTP methods GET, HEAD and POST by default.
+ */
+ public AbstractController() {
+ this(true);
+ }
+
+ /**
+ * Create a new AbstractController.
+ * @param restrictDefaultSupportedMethods {@code true} if this
+ * controller should support HTTP methods GET, HEAD and POST by default,
+ * or {@code false} if it should be unrestricted
+ * @since 4.3
+ */
+ public AbstractController(boolean restrictDefaultSupportedMethods) {
+ super(restrictDefaultSupportedMethods);
+ }
+
+
+ /**
* Set if controller execution should be synchronized on the session,
* to serialize parallel invocations from the same client.
* <p>More specifically, the execution of the {@code handleRequestInternal}
@@ -129,6 +151,11 @@ public abstract class AbstractController extends WebContentGenerator implements
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception {
+ if (HttpMethod.OPTIONS.matches(request.getMethod())) {
+ response.setHeader("Allow", getAllowHeader());
+ return null;
+ }
+
// Delegate to WebContentGenerator for checking and preparing.
checkRequest(request);
prepareResponse(response);
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java
index bee18861..5d512887 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java
@@ -19,6 +19,7 @@ package org.springframework.web.servlet.mvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
@@ -43,6 +44,11 @@ public class ParameterizableViewController extends AbstractController {
private boolean statusOnly;
+ public ParameterizableViewController() {
+ super(false);
+ setSupportedMethods(HttpMethod.GET.name(), HttpMethod.HEAD.name());
+ }
+
/**
* Set a view name for the ModelAndView to return, to be resolved by the
* DispatcherServlet via a ViewResolver. Will override any pre-existing
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletForwardingController.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletForwardingController.java
index 50cf151b..3df9b642 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletForwardingController.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletForwardingController.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 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.
@@ -90,6 +90,11 @@ public class ServletForwardingController extends AbstractController implements B
private String beanName;
+ public ServletForwardingController() {
+ super(false);
+ }
+
+
/**
* Set the name of the servlet to forward to,
* i.e. the "servlet-name" of the target servlet in web.xml.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletWrappingController.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletWrappingController.java
index 8ca0ef0a..c16534ff 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletWrappingController.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/ServletWrappingController.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -78,13 +78,11 @@ import org.springframework.web.servlet.ModelAndView;
* @author Juergen Hoeller
* @since 1.1.1
* @see ServletForwardingController
- * @see org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor
- * @see org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
*/
public class ServletWrappingController extends AbstractController
implements BeanNameAware, InitializingBean, DisposableBean {
- private Class<?> servletClass;
+ private Class<? extends Servlet> servletClass;
private String servletName;
@@ -95,12 +93,17 @@ public class ServletWrappingController extends AbstractController
private Servlet servletInstance;
+ public ServletWrappingController() {
+ super(false);
+ }
+
+
/**
* Set the class of the servlet to wrap.
* Needs to implement {@code javax.servlet.Servlet}.
* @see javax.servlet.Servlet
*/
- public void setServletClass(Class<?> servletClass) {
+ public void setServletClass(Class<? extends Servlet> servletClass) {
this.servletClass = servletClass;
}
@@ -133,27 +136,23 @@ public class ServletWrappingController extends AbstractController
@Override
public void afterPropertiesSet() throws Exception {
if (this.servletClass == null) {
- throw new IllegalArgumentException("servletClass is required");
- }
- if (!Servlet.class.isAssignableFrom(this.servletClass)) {
- throw new IllegalArgumentException("servletClass [" + this.servletClass.getName() +
- "] needs to implement interface [javax.servlet.Servlet]");
+ throw new IllegalArgumentException("'servletClass' is required");
}
if (this.servletName == null) {
this.servletName = this.beanName;
}
- this.servletInstance = (Servlet) this.servletClass.newInstance();
+ this.servletInstance = this.servletClass.newInstance();
this.servletInstance.init(new DelegatingServletConfig());
}
/**
- * Invoke the the wrapped Servlet instance.
+ * Invoke the wrapped Servlet instance.
* @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
*/
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
- throws Exception {
+ throws Exception {
this.servletInstance.service(request, response);
return null;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/WebContentInterceptor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/WebContentInterceptor.java
index 038667f4..5e5810e5 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/WebContentInterceptor.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/WebContentInterceptor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -102,7 +102,6 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle
* <p>Only relevant for the "cacheMappings" setting.
* @see #setCacheMappings
* @see org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#setUrlPathHelper
- * @see org.springframework.web.servlet.mvc.multiaction.AbstractUrlMethodNameResolver#setUrlPathHelper
*/
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
index f3cdd873..2a26985c 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
@@ -96,8 +96,6 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.SessionAttributes;
-import org.springframework.web.bind.annotation.support.HandlerMethodInvoker;
-import org.springframework.web.bind.annotation.support.HandlerMethodResolver;
import org.springframework.web.bind.support.DefaultSessionAttributeStore;
import org.springframework.web.bind.support.SessionAttributeStore;
import org.springframework.web.bind.support.WebArgumentResolver;
@@ -110,9 +108,6 @@ import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
-import org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver;
-import org.springframework.web.servlet.mvc.multiaction.MethodNameResolver;
-import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.servlet.support.WebContentGenerator;
import org.springframework.web.util.UrlPathHelper;
@@ -164,7 +159,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
private PathMatcher pathMatcher = new AntPathMatcher();
- private MethodNameResolver methodNameResolver = new InternalPathMethodNameResolver();
+ private org.springframework.web.servlet.mvc.multiaction.MethodNameResolver methodNameResolver =
+ new org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver();
private WebBindingInitializer webBindingInitializer;
@@ -255,7 +251,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
* <p>Will only kick in when the handler method cannot be resolved uniquely
* through the annotation metadata already.
*/
- public void setMethodNameResolver(MethodNameResolver methodNameResolver) {
+ public void setMethodNameResolver(org.springframework.web.servlet.mvc.multiaction.MethodNameResolver methodNameResolver) {
this.methodNameResolver = methodNameResolver;
}
@@ -524,9 +520,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
/**
- * Servlet-specific subclass of {@link HandlerMethodResolver}.
+ * Servlet-specific subclass of {@code HandlerMethodResolver}.
*/
- private class ServletHandlerMethodResolver extends HandlerMethodResolver {
+ @SuppressWarnings("deprecation")
+ private class ServletHandlerMethodResolver extends org.springframework.web.bind.annotation.support.HandlerMethodResolver {
private final Map<Method, RequestMappingInfo> mappings = new HashMap<Method, RequestMappingInfo>();
@@ -674,7 +671,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
if (!allowedMethods.isEmpty()) {
throw new HttpRequestMethodNotSupportedException(request.getMethod(), StringUtils.toStringArray(allowedMethods));
}
- throw new NoSuchRequestHandlingMethodException(lookupPath, request.getMethod(), request.getParameterMap());
+ throw new org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException(
+ lookupPath, request.getMethod(), request.getParameterMap());
}
}
@@ -768,13 +766,14 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
/**
- * Servlet-specific subclass of {@link HandlerMethodInvoker}.
+ * Servlet-specific subclass of {@code HandlerMethodInvoker}.
*/
- private class ServletHandlerMethodInvoker extends HandlerMethodInvoker {
+ @SuppressWarnings("deprecation")
+ private class ServletHandlerMethodInvoker extends org.springframework.web.bind.annotation.support.HandlerMethodInvoker {
private boolean responseArgumentUsed = false;
- private ServletHandlerMethodInvoker(HandlerMethodResolver resolver) {
+ private ServletHandlerMethodInvoker(org.springframework.web.bind.annotation.support.HandlerMethodResolver resolver) {
super(resolver, webBindingInitializer, sessionAttributeStore, parameterNameDiscoverer,
customArgumentResolvers, messageConverters);
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtils.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtils.java
index 6c1288fc..331da205 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtils.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -30,6 +30,7 @@ import org.springframework.web.util.WebUtils;
*
* @author Juergen Hoeller
* @author Arjen Poutsma
+ * @author Rossen Stoyanchev
* @since 2.5.2
* @deprecated as of Spring 3.2, together with {@link DefaultAnnotationHandlerMapping},
* {@link AnnotationMethodHandlerAdapter}, and {@link AnnotationMethodHandlerExceptionResolver}.
@@ -43,11 +44,12 @@ abstract class ServletAnnotationMappingUtils {
* @param request the current HTTP request to check
*/
public static boolean checkRequestMethod(RequestMethod[] methods, HttpServletRequest request) {
- if (ObjectUtils.isEmpty(methods)) {
+ String inputMethod = request.getMethod();
+ if (ObjectUtils.isEmpty(methods) && !RequestMethod.OPTIONS.name().equals(inputMethod)) {
return true;
}
for (RequestMethod method : methods) {
- if (method.name().equals(request.getMethod())) {
+ if (method.name().equals(inputMethod)) {
return true;
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractMediaTypeExpression.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractMediaTypeExpression.java
index e6708f27..d3114c13 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractMediaTypeExpression.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractMediaTypeExpression.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -16,13 +16,10 @@
package org.springframework.web.servlet.mvc.condition;
-import javax.servlet.http.HttpServletRequest;
-
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.MediaType;
-import org.springframework.web.HttpMediaTypeException;
import org.springframework.web.bind.annotation.RequestMapping;
/**
@@ -33,7 +30,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
* @author Rossen Stoyanchev
* @since 3.1
*/
-abstract class AbstractMediaTypeExpression implements Comparable<AbstractMediaTypeExpression>, MediaTypeExpression {
+abstract class AbstractMediaTypeExpression implements MediaTypeExpression, Comparable<AbstractMediaTypeExpression> {
protected final Log logger = LogFactory.getLog(getClass());
@@ -70,19 +67,6 @@ abstract class AbstractMediaTypeExpression implements Comparable<AbstractMediaTy
}
- public final boolean match(HttpServletRequest request) {
- try {
- boolean match = matchMediaType(request);
- return (!this.isNegated ? match : !match);
- }
- catch (HttpMediaTypeException ex) {
- return false;
- }
- }
-
- protected abstract boolean matchMediaType(HttpServletRequest request) throws HttpMediaTypeException;
-
-
@Override
public int compareTo(AbstractMediaTypeExpression other) {
return MediaType.SPECIFICITY_COMPARATOR.compare(this.getMediaType(), other.getMediaType());
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java
index 92dd4e87..26fb5641 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java
index f53e34b9..e7a6af41 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -28,8 +28,10 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
+import org.springframework.web.HttpMediaTypeException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.cors.CorsUtils;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression;
/**
@@ -46,6 +48,9 @@ import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.Hea
*/
public final class ConsumesRequestCondition extends AbstractRequestCondition<ConsumesRequestCondition> {
+ private final static ConsumesRequestCondition PRE_FLIGHT_MATCH = new ConsumesRequestCondition();
+
+
private final List<ConsumeMediaTypeExpression> expressions;
@@ -160,13 +165,25 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
*/
@Override
public ConsumesRequestCondition getMatchingCondition(HttpServletRequest request) {
+ if (CorsUtils.isPreFlightRequest(request)) {
+ return PRE_FLIGHT_MATCH;
+ }
if (isEmpty()) {
return this;
}
- Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<ConsumeMediaTypeExpression>(expressions);
+ MediaType contentType;
+ try {
+ contentType = StringUtils.hasLength(request.getContentType()) ?
+ MediaType.parseMediaType(request.getContentType()) :
+ MediaType.APPLICATION_OCTET_STREAM;
+ }
+ catch (InvalidMediaTypeException ex) {
+ return null;
+ }
+ Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<ConsumeMediaTypeExpression>(this.expressions);
for (Iterator<ConsumeMediaTypeExpression> iterator = result.iterator(); iterator.hasNext();) {
ConsumeMediaTypeExpression expression = iterator.next();
- if (!expression.match(request)) {
+ if (!expression.match(contentType)) {
iterator.remove();
}
}
@@ -214,18 +231,9 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
super(mediaType, negated);
}
- @Override
- protected boolean matchMediaType(HttpServletRequest request) throws HttpMediaTypeNotSupportedException {
- try {
- MediaType contentType = StringUtils.hasLength(request.getContentType()) ?
- MediaType.parseMediaType(request.getContentType()) :
- MediaType.APPLICATION_OCTET_STREAM;
- return getMediaType().includes(contentType);
- }
- catch (InvalidMediaTypeException ex) {
- throw new HttpMediaTypeNotSupportedException(
- "Can't parse Content-Type [" + request.getContentType() + "]: " + ex.getMessage());
- }
+ public final boolean match(MediaType contentType) {
+ boolean match = getMediaType().includes(contentType);
+ return (!isNegated() ? match : !match);
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/HeadersRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/HeadersRequestCondition.java
index ffa47be0..aaa0847c 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/HeadersRequestCondition.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/HeadersRequestCondition.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -23,6 +23,7 @@ import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.cors.CorsUtils;
/**
* A logical conjunction (' && ') request condition that matches a request against
@@ -38,6 +39,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
*/
public final class HeadersRequestCondition extends AbstractRequestCondition<HeadersRequestCondition> {
+ private final static HeadersRequestCondition PRE_FLIGHT_MATCH = new HeadersRequestCondition();
+
+
private final Set<HeaderExpression> expressions;
@@ -105,6 +109,9 @@ public final class HeadersRequestCondition extends AbstractRequestCondition<Head
*/
@Override
public HeadersRequestCondition getMatchingCondition(HttpServletRequest request) {
+ if (CorsUtils.isPreFlightRequest(request)) {
+ return PRE_FLIGHT_MATCH;
+ }
for (HeaderExpression expression : expressions) {
if (!expression.match(request)) {
return null;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java
index bc99eae4..55931d4b 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -26,10 +26,12 @@ import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
+import org.springframework.web.HttpMediaTypeException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.ServletWebRequest;
+import org.springframework.web.cors.CorsUtils;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression;
/**
@@ -45,6 +47,11 @@ import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.Hea
*/
public final class ProducesRequestCondition extends AbstractRequestCondition<ProducesRequestCondition> {
+ private final static ProducesRequestCondition PRE_FLIGHT_MATCH = new ProducesRequestCondition();
+
+ private static final ProducesRequestCondition EMPTY_CONDITION = new ProducesRequestCondition();
+
+
private final List<ProduceMediaTypeExpression> MEDIA_TYPE_ALL_LIST =
Collections.singletonList(new ProduceMediaTypeExpression("*/*"));
@@ -176,17 +183,35 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
*/
@Override
public ProducesRequestCondition getMatchingCondition(HttpServletRequest request) {
+ if (CorsUtils.isPreFlightRequest(request)) {
+ return PRE_FLIGHT_MATCH;
+ }
if (isEmpty()) {
return this;
}
+ List<MediaType> acceptedMediaTypes;
+ try {
+ acceptedMediaTypes = getAcceptedMediaTypes(request);
+ }
+ catch (HttpMediaTypeException ex) {
+ return null;
+ }
Set<ProduceMediaTypeExpression> result = new LinkedHashSet<ProduceMediaTypeExpression>(expressions);
for (Iterator<ProduceMediaTypeExpression> iterator = result.iterator(); iterator.hasNext();) {
ProduceMediaTypeExpression expression = iterator.next();
- if (!expression.match(request)) {
+ if (!expression.match(acceptedMediaTypes)) {
iterator.remove();
}
}
- return (result.isEmpty()) ? null : new ProducesRequestCondition(result, this.contentNegotiationManager);
+ if (!result.isEmpty()) {
+ return new ProducesRequestCondition(result, this.contentNegotiationManager);
+ }
+ else if (acceptedMediaTypes.contains(MediaType.ALL)) {
+ return EMPTY_CONDITION;
+ }
+ else {
+ return null;
+ }
}
/**
@@ -295,9 +320,12 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
super(expression);
}
- @Override
- protected boolean matchMediaType(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
- List<MediaType> acceptedMediaTypes = getAcceptedMediaTypes(request);
+ public final boolean match(List<MediaType> acceptedMediaTypes) {
+ boolean match = matchMediaType(acceptedMediaTypes);
+ return (!isNegated() ? match : !match);
+ }
+
+ private boolean matchMediaType(List<MediaType> acceptedMediaTypes) {
for (MediaType acceptedMediaType : acceptedMediaTypes) {
if (getMediaType().isCompatibleWith(acceptedMediaType)) {
return true;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestCondition.java
index 1487381c..542e1a2d 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestCondition.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestCondition.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -18,27 +18,25 @@ package org.springframework.web.servlet.mvc.condition;
import javax.servlet.http.HttpServletRequest;
-import org.springframework.web.bind.annotation.RequestMapping;
-
/**
- * The contract for request conditions in Spring MVC's mapping infrastructure.
+ * Contract for request mapping conditions.
*
* <p>Request conditions can be combined via {@link #combine(Object)}, matched to
* a request via {@link #getMatchingCondition(HttpServletRequest)}, and compared
* to each other via {@link #compareTo(Object, HttpServletRequest)} to determine
- * which matches a request more closely.
+ * which is a closer match for a given request.
*
* @author Rossen Stoyanchev
* @author Arjen Poutsma
* @since 3.1
- * @param <T> the type of objects that this RequestCondition can be combined with and compared to
+ * @param <T> the type of objects that this RequestCondition can be combined
+ * with and compared to
*/
public interface RequestCondition<T> {
/**
- * Defines the rules for combining this condition (i.e. the current instance)
- * with another condition. For example combining type- and method-level
- * {@link RequestMapping} conditions.
+ * Combine this condition with another such as conditions from a
+ * type-level and method-level {@code @RequestMapping} annotation.
* @param other the condition to combine with.
* @return a request condition instance that is the result of combining
* the two condition instances.
@@ -46,18 +44,21 @@ public interface RequestCondition<T> {
T combine(T other);
/**
- * Checks if this condition matches the given request and returns a
- * potentially new request condition with content tailored to the
- * current request. For example a condition with URL patterns might
- * return a new condition that contains matching patterns sorted
- * with best matching patterns on top.
- * @return a condition instance in case of a match;
- * or {@code null} if there is no match
+ * Check if the condition matches the request returning a potentially new
+ * instance created for the current request. For example a condition with
+ * multiple URL patterns may return a new instance only with those patterns
+ * that match the request.
+ * <p>For CORS pre-flight requests, conditions should match to the would-be,
+ * actual request (e.g. URL pattern, query parameters, and the HTTP method
+ * from the "Access-Control-Request-Method" header). If a condition cannot
+ * be matched to a pre-flight request it should return an instance with
+ * empty content thus not causing a failure to match.
+ * @return a condition instance in case of a match or {@code null} otherwise.
*/
T getMatchingCondition(HttpServletRequest request);
/**
- * Compares this condition to another condition in the context of
+ * Compare this condition to another condition in the context of
* a specific request. This method assumes both instances have
* been obtained via {@link #getMatchingCondition(HttpServletRequest)}
* to ensure they have content relevant to current request only.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestCondition.java
index 6d18fcf2..b044ae5b 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestCondition.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestCondition.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -22,9 +22,13 @@ import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
+import javax.servlet.DispatcherType;
import javax.servlet.http.HttpServletRequest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.cors.CorsUtils;
/**
* A logical disjunction (' || ') request condition that matches a request
@@ -36,6 +40,10 @@ import org.springframework.web.bind.annotation.RequestMethod;
*/
public final class RequestMethodsRequestCondition extends AbstractRequestCondition<RequestMethodsRequestCondition> {
+ private static final RequestMethodsRequestCondition GET_CONDITION =
+ new RequestMethodsRequestCondition(RequestMethod.GET);
+
+
private final Set<RequestMethod> methods;
@@ -90,32 +98,55 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi
* Check if any of the HTTP request methods match the given request and
* return an instance that contains the matching HTTP request method only.
* @param request the current request
- * @return the same instance if the condition is empty, a new condition with
- * the matched request method, or {@code null} if no request methods match
+ * @return the same instance if the condition is empty (unless the request
+ * method is HTTP OPTIONS), a new condition with the matched request method,
+ * or {@code null} if there is no match or the condition is empty and the
+ * request method is OPTIONS.
*/
@Override
public RequestMethodsRequestCondition getMatchingCondition(HttpServletRequest request) {
- if (this.methods.isEmpty()) {
- return this;
+ if (CorsUtils.isPreFlightRequest(request)) {
+ return matchPreFlight(request);
}
- RequestMethod incomingRequestMethod = getRequestMethod(request);
- if (incomingRequestMethod != null) {
- for (RequestMethod method : this.methods) {
- if (method.equals(incomingRequestMethod)) {
- return new RequestMethodsRequestCondition(method);
- }
+
+ if (getMethods().isEmpty()) {
+ if (RequestMethod.OPTIONS.name().equals(request.getMethod()) &&
+ !DispatcherType.ERROR.equals(request.getDispatcherType())) {
+
+ return null; // No implicit match for OPTIONS (we handle it)
}
+ return this;
}
- return null;
+
+ return matchRequestMethod(request.getMethod());
}
- private RequestMethod getRequestMethod(HttpServletRequest request) {
- try {
- return RequestMethod.valueOf(request.getMethod());
+ /**
+ * On a pre-flight request match to the would-be, actual request.
+ * Hence empty conditions is a match, otherwise try to match to the HTTP
+ * method in the "Access-Control-Request-Method" header.
+ */
+ private RequestMethodsRequestCondition matchPreFlight(HttpServletRequest request) {
+ if (getMethods().isEmpty()) {
+ return this;
}
- catch (IllegalArgumentException ex) {
- return null;
+ String expectedMethod = request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
+ return matchRequestMethod(expectedMethod);
+ }
+
+ private RequestMethodsRequestCondition matchRequestMethod(String httpMethodValue) {
+ HttpMethod httpMethod = HttpMethod.resolve(httpMethodValue);
+ if (httpMethod != null) {
+ for (RequestMethod method : getMethods()) {
+ if (httpMethod.matches(method.name())) {
+ return new RequestMethodsRequestCondition(method);
+ }
+ }
+ if (httpMethod == HttpMethod.HEAD && getMethods().contains(RequestMethod.GET)) {
+ return GET_CONDITION;
+ }
}
+ return null;
}
/**
@@ -131,7 +162,18 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi
*/
@Override
public int compareTo(RequestMethodsRequestCondition other, HttpServletRequest request) {
- return (other.methods.size() - this.methods.size());
+ if (other.methods.size() != this.methods.size()) {
+ return other.methods.size() - this.methods.size();
+ }
+ else if (this.methods.size() == 1) {
+ if (this.methods.contains(RequestMethod.HEAD) && other.methods.contains(RequestMethod.GET)) {
+ return -1;
+ }
+ else if (this.methods.contains(RequestMethod.GET) && other.methods.contains(RequestMethod.HEAD)) {
+ return 1;
+ }
+ }
+ return 0;
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java
index 395ecb9c..6ecc7687 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -19,12 +19,11 @@ package org.springframework.web.servlet.mvc.method;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
-import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.cors.CorsUtils;
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
@@ -36,7 +35,7 @@ import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondit
import org.springframework.web.util.UrlPathHelper;
/**
- * Encapsulates the following request mapping conditions:
+ * A {@link RequestCondition} that consists of the following other conditions:
* <ol>
* <li>{@link PatternsRequestCondition}
* <li>{@link RequestMethodsRequestCondition}
@@ -215,15 +214,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
if (methods == null || params == null || headers == null || consumes == null || produces == null) {
- if (CorsUtils.isPreFlightRequest(request)) {
- methods = getAccessControlRequestMethodCondition(request);
- if (methods == null || params == null) {
- return null;
- }
- }
- else {
- return null;
- }
+ return null;
}
PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
@@ -241,22 +232,6 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
}
/**
- * Return a matching RequestMethodsRequestCondition based on the expected
- * HTTP method specified in a CORS pre-flight request.
- */
- private RequestMethodsRequestCondition getAccessControlRequestMethodCondition(HttpServletRequest request) {
- String expectedMethod = request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
- if (StringUtils.hasText(expectedMethod)) {
- for (RequestMethod method : getMethodsCondition().getMethods()) {
- if (expectedMethod.equalsIgnoreCase(method.name())) {
- return new RequestMethodsRequestCondition(method);
- }
- }
- }
- return null;
- }
-
- /**
* Compares "this" info (i.e. the current instance) with another info in the context of a request.
* <p>Note: It is assumed both instances have been obtained via
* {@link #getMatchingCondition(HttpServletRequest)} to ensure they have conditions with
@@ -264,7 +239,15 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
*/
@Override
public int compareTo(RequestMappingInfo other, HttpServletRequest request) {
- int result = this.patternsCondition.compareTo(other.getPatternsCondition(), request);
+ int result;
+ // Automatic vs explicit HTTP HEAD mapping
+ if (HttpMethod.HEAD.matches(request.getMethod())) {
+ result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
+ if (result != 0) {
+ return result;
+ }
+ }
+ result = this.patternsCondition.compareTo(other.getPatternsCondition(), request);
if (result != 0) {
return result;
}
@@ -284,6 +267,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
if (result != 0) {
return result;
}
+ // Implicit (no method) vs explicit HTTP method mappings
result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
if (result != 0) {
return result;
@@ -537,13 +521,25 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
private ContentNegotiationManager contentNegotiationManager;
/**
- * Set a custom UrlPathHelper to use for the PatternsRequestCondition.
- * <p>By default this is not set.
+ * @deprecated as of Spring 4.2.8, in favor of {@link #setUrlPathHelper}
*/
+ @Deprecated
public void setPathHelper(UrlPathHelper pathHelper) {
this.urlPathHelper = pathHelper;
}
+ /**
+ * Set a custom UrlPathHelper to use for the PatternsRequestCondition.
+ * <p>By default this is not set.
+ * @since 4.2.8
+ */
+ public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
+ this.urlPathHelper = urlPathHelper;
+ }
+
+ /**
+ * Return a custom UrlPathHelper to use for the PatternsRequestCondition, if any.
+ */
public UrlPathHelper getUrlPathHelper() {
return this.urlPathHelper;
}
@@ -556,24 +552,30 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
this.pathMatcher = pathMatcher;
}
+ /**
+ * Return a custom PathMatcher to use for the PatternsRequestCondition, if any.
+ */
public PathMatcher getPathMatcher() {
return this.pathMatcher;
}
/**
- * Whether to apply trailing slash matching in PatternsRequestCondition.
+ * Set whether to apply trailing slash matching in PatternsRequestCondition.
* <p>By default this is set to 'true'.
*/
public void setTrailingSlashMatch(boolean trailingSlashMatch) {
this.trailingSlashMatch = trailingSlashMatch;
}
+ /**
+ * Return whether to apply trailing slash matching in PatternsRequestCondition.
+ */
public boolean useTrailingSlashMatch() {
return this.trailingSlashMatch;
}
/**
- * Whether to apply suffix pattern matching in PatternsRequestCondition.
+ * Set whether to apply suffix pattern matching in PatternsRequestCondition.
* <p>By default this is set to 'true'.
* @see #setRegisteredSuffixPatternMatch(boolean)
*/
@@ -581,14 +583,17 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
this.suffixPatternMatch = suffixPatternMatch;
}
+ /**
+ * Return whether to apply suffix pattern matching in PatternsRequestCondition.
+ */
public boolean useSuffixPatternMatch() {
return this.suffixPatternMatch;
}
/**
- * Whether suffix pattern matching should be restricted to registered
+ * Set whether suffix pattern matching should be restricted to registered
* file extensions only. Setting this property also sets
- * suffixPatternMatch=true and requires that a
+ * {@code suffixPatternMatch=true} and requires that a
* {@link #setContentNegotiationManager} is also configured in order to
* obtain the registered file extensions.
*/
@@ -597,6 +602,10 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
this.suffixPatternMatch = (registeredSuffixPatternMatch || this.suffixPatternMatch);
}
+ /**
+ * Return whether suffix pattern matching should be restricted to registered
+ * file extensions only.
+ */
public boolean useRegisteredSuffixPatternMatch() {
return this.registeredSuffixPatternMatch;
}
@@ -617,10 +626,14 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
* Set the ContentNegotiationManager to use for the ProducesRequestCondition.
* <p>By default this is not set.
*/
- public void setContentNegotiationManager(ContentNegotiationManager manager) {
- this.contentNegotiationManager = manager;
+ public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
+ this.contentNegotiationManager = contentNegotiationManager;
}
+ /**
+ * Return the ContentNegotiationManager to use for the ProducesRequestCondition,
+ * if any.
+ */
public ContentNegotiationManager getContentNegotiationManager() {
return this.contentNegotiationManager;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java
index 5c18540a..2c57650d 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -16,10 +16,10 @@
package org.springframework.web.servlet.mvc.method;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
-import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -29,6 +29,8 @@ import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
@@ -43,7 +45,6 @@ import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
import org.springframework.web.servlet.mvc.condition.NameValueExpression;
-import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
import org.springframework.web.util.WebUtils;
/**
@@ -56,6 +57,19 @@ import org.springframework.web.util.WebUtils;
*/
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
+ private static final Method HTTP_OPTIONS_HANDLE_METHOD;
+
+ static {
+ try {
+ HTTP_OPTIONS_HANDLE_METHOD = HttpOptionsHandler.class.getMethod("handle");
+ }
+ catch (NoSuchMethodException ex) {
+ // Should never happen
+ throw new IllegalStateException("Failed to retrieve internal handler method for HTTP OPTIONS", ex);
+ }
+ }
+
+
protected RequestMappingInfoHandlerMapping() {
setHandlerMethodMappingNamingStrategy(new RequestMappingInfoHandlerMethodMappingNamingStrategy());
}
@@ -167,116 +181,284 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
}
/**
- * Iterate all RequestMappingInfos once again, look if any match by URL at
- * least and raise exceptions accordingly.
+ * Iterate all RequestMappingInfo's once again, look if any match by URL at
+ * least and raise exceptions according to what doesn't match.
* @throws HttpRequestMethodNotSupportedException if there are matches by URL
* but not by HTTP method
* @throws HttpMediaTypeNotAcceptableException if there are matches by URL
* but not by consumable/producible media types
*/
@Override
- protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> requestMappingInfos,
- String lookupPath, HttpServletRequest request) throws ServletException {
+ protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> infos, String lookupPath,
+ HttpServletRequest request) throws ServletException {
- Set<String> allowedMethods = new LinkedHashSet<String>(4);
+ PartialMatchHelper helper = new PartialMatchHelper(infos, request);
- Set<RequestMappingInfo> patternMatches = new HashSet<RequestMappingInfo>();
- Set<RequestMappingInfo> patternAndMethodMatches = new HashSet<RequestMappingInfo>();
+ if (helper.isEmpty()) {
+ return null;
+ }
+
+ if (helper.hasMethodsMismatch()) {
+ Set<String> methods = helper.getAllowedMethods();
+ if (HttpMethod.OPTIONS.matches(request.getMethod())) {
+ HttpOptionsHandler handler = new HttpOptionsHandler(methods);
+ return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);
+ }
+ throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods);
+ }
- for (RequestMappingInfo info : requestMappingInfos) {
- if (info.getPatternsCondition().getMatchingCondition(request) != null) {
- patternMatches.add(info);
- if (info.getMethodsCondition().getMatchingCondition(request) != null) {
- patternAndMethodMatches.add(info);
+ if (helper.hasConsumesMismatch()) {
+ Set<MediaType> mediaTypes = helper.getConsumableMediaTypes();
+ MediaType contentType = null;
+ if (StringUtils.hasLength(request.getContentType())) {
+ try {
+ contentType = MediaType.parseMediaType(request.getContentType());
}
- else {
- for (RequestMethod method : info.getMethodsCondition().getMethods()) {
- allowedMethods.add(method.name());
- }
+ catch (InvalidMediaTypeException ex) {
+ throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
}
+ throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<MediaType>(mediaTypes));
}
- if (patternMatches.isEmpty()) {
- return null;
+ if (helper.hasProducesMismatch()) {
+ Set<MediaType> mediaTypes = helper.getProducibleMediaTypes();
+ throw new HttpMediaTypeNotAcceptableException(new ArrayList<MediaType>(mediaTypes));
}
- else if (patternAndMethodMatches.isEmpty() && !allowedMethods.isEmpty()) {
- throw new HttpRequestMethodNotSupportedException(request.getMethod(), allowedMethods);
+
+ if (helper.hasParamsMismatch()) {
+ List<String[]> conditions = helper.getParamConditions();
+ throw new UnsatisfiedServletRequestParameterException(conditions, request.getParameterMap());
}
- Set<MediaType> consumableMediaTypes;
- Set<MediaType> producibleMediaTypes;
- List<String[]> paramConditions;
+ return null;
+ }
+
+
+ /**
+ * Aggregate all partial matches and expose methods checking across them.
+ */
+ private static class PartialMatchHelper {
+
+ private final List<PartialMatch> partialMatches = new ArrayList<PartialMatch>();
- if (patternAndMethodMatches.isEmpty()) {
- consumableMediaTypes = getConsumableMediaTypes(request, patternMatches);
- producibleMediaTypes = getProducibleMediaTypes(request, patternMatches);
- paramConditions = getRequestParams(request, patternMatches);
+ public PartialMatchHelper(Set<RequestMappingInfo> infos, HttpServletRequest request) {
+ for (RequestMappingInfo info : infos) {
+ if (info.getPatternsCondition().getMatchingCondition(request) != null) {
+ this.partialMatches.add(new PartialMatch(info, request));
+ }
+ }
}
- else {
- consumableMediaTypes = getConsumableMediaTypes(request, patternAndMethodMatches);
- producibleMediaTypes = getProducibleMediaTypes(request, patternAndMethodMatches);
- paramConditions = getRequestParams(request, patternAndMethodMatches);
+
+ /**
+ * Whether there any partial matches.
+ */
+ public boolean isEmpty() {
+ return this.partialMatches.isEmpty();
}
- if (!consumableMediaTypes.isEmpty()) {
- MediaType contentType = null;
- if (StringUtils.hasLength(request.getContentType())) {
- try {
- contentType = MediaType.parseMediaType(request.getContentType());
+ /**
+ * Any partial matches for "methods"?
+ */
+ public boolean hasMethodsMismatch() {
+ for (PartialMatch match : this.partialMatches) {
+ if (match.hasMethodsMatch()) {
+ return false;
}
- catch (InvalidMediaTypeException ex) {
- throw new HttpMediaTypeNotSupportedException(ex.getMessage());
+ }
+ return true;
+ }
+
+ /**
+ * Any partial matches for "methods" and "consumes"?
+ */
+ public boolean hasConsumesMismatch() {
+ for (PartialMatch match : this.partialMatches) {
+ if (match.hasConsumesMatch()) {
+ return false;
}
}
- throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<MediaType>(consumableMediaTypes));
+ return true;
}
- else if (!producibleMediaTypes.isEmpty()) {
- throw new HttpMediaTypeNotAcceptableException(new ArrayList<MediaType>(producibleMediaTypes));
+
+ /**
+ * Any partial matches for "methods", "consumes", and "produces"?
+ */
+ public boolean hasProducesMismatch() {
+ for (PartialMatch match : this.partialMatches) {
+ if (match.hasProducesMatch()) {
+ return false;
+ }
+ }
+ return true;
}
- else if (!CollectionUtils.isEmpty(paramConditions)) {
- throw new UnsatisfiedServletRequestParameterException(paramConditions, request.getParameterMap());
+
+ /**
+ * Any partial matches for "methods", "consumes", "produces", and "params"?
+ */
+ public boolean hasParamsMismatch() {
+ for (PartialMatch match : this.partialMatches) {
+ if (match.hasParamsMatch()) {
+ return false;
+ }
+ }
+ return true;
}
- else {
- return null;
+
+ /**
+ * Return declared HTTP methods.
+ */
+ public Set<String> getAllowedMethods() {
+ Set<String> result = new LinkedHashSet<String>();
+ for (PartialMatch match : this.partialMatches) {
+ for (RequestMethod method : match.getInfo().getMethodsCondition().getMethods()) {
+ result.add(method.name());
+ }
+ }
+ return result;
}
- }
- private Set<MediaType> getConsumableMediaTypes(HttpServletRequest request, Set<RequestMappingInfo> partialMatches) {
- Set<MediaType> result = new HashSet<MediaType>();
- for (RequestMappingInfo partialMatch : partialMatches) {
- if (partialMatch.getConsumesCondition().getMatchingCondition(request) == null) {
- result.addAll(partialMatch.getConsumesCondition().getConsumableMediaTypes());
+ /**
+ * Return declared "consumable" types but only among those that also
+ * match the "methods" condition.
+ */
+ public Set<MediaType> getConsumableMediaTypes() {
+ Set<MediaType> result = new LinkedHashSet<MediaType>();
+ for (PartialMatch match : this.partialMatches) {
+ if (match.hasMethodsMatch()) {
+ result.addAll(match.getInfo().getConsumesCondition().getConsumableMediaTypes());
+ }
}
+ return result;
}
- return result;
- }
- private Set<MediaType> getProducibleMediaTypes(HttpServletRequest request, Set<RequestMappingInfo> partialMatches) {
- Set<MediaType> result = new HashSet<MediaType>();
- for (RequestMappingInfo partialMatch : partialMatches) {
- if (partialMatch.getProducesCondition().getMatchingCondition(request) == null) {
- result.addAll(partialMatch.getProducesCondition().getProducibleMediaTypes());
+ /**
+ * Return declared "producible" types but only among those that also
+ * match the "methods" and "consumes" conditions.
+ */
+ public Set<MediaType> getProducibleMediaTypes() {
+ Set<MediaType> result = new LinkedHashSet<MediaType>();
+ for (PartialMatch match : this.partialMatches) {
+ if (match.hasConsumesMatch()) {
+ result.addAll(match.getInfo().getProducesCondition().getProducibleMediaTypes());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Return declared "params" conditions but only among those that also
+ * match the "methods", "consumes", and "params" conditions.
+ */
+ public List<String[]> getParamConditions() {
+ List<String[]> result = new ArrayList<String[]>();
+ for (PartialMatch match : this.partialMatches) {
+ if (match.hasProducesMatch()) {
+ Set<NameValueExpression<String>> set = match.getInfo().getParamsCondition().getExpressions();
+ if (!CollectionUtils.isEmpty(set)) {
+ int i = 0;
+ String[] array = new String[set.size()];
+ for (NameValueExpression<String> expression : set) {
+ array[i++] = expression.toString();
+ }
+ result.add(array);
+ }
+ }
+ }
+ return result;
+ }
+
+
+ /**
+ * Container for a RequestMappingInfo that matches the URL path at least.
+ */
+ private static class PartialMatch {
+
+ private final RequestMappingInfo info;
+
+ private final boolean methodsMatch;
+
+ private final boolean consumesMatch;
+
+ private final boolean producesMatch;
+
+ private final boolean paramsMatch;
+
+ /**
+ * @param info RequestMappingInfo that matches the URL path.
+ * @param request the current request
+ */
+ public PartialMatch(RequestMappingInfo info, HttpServletRequest request) {
+ this.info = info;
+ this.methodsMatch = (info.getMethodsCondition().getMatchingCondition(request) != null);
+ this.consumesMatch = (info.getConsumesCondition().getMatchingCondition(request) != null);
+ this.producesMatch = (info.getProducesCondition().getMatchingCondition(request) != null);
+ this.paramsMatch = (info.getParamsCondition().getMatchingCondition(request) != null);
+ }
+
+ public RequestMappingInfo getInfo() {
+ return this.info;
+ }
+
+ public boolean hasMethodsMatch() {
+ return this.methodsMatch;
+ }
+
+ public boolean hasConsumesMatch() {
+ return (hasMethodsMatch() && this.consumesMatch);
+ }
+
+ public boolean hasProducesMatch() {
+ return (hasConsumesMatch() && this.producesMatch);
+ }
+
+ public boolean hasParamsMatch() {
+ return (hasProducesMatch() && this.paramsMatch);
+ }
+
+ @Override
+ public String toString() {
+ return this.info.toString();
}
}
- return result;
}
- private List<String[]> getRequestParams(HttpServletRequest request, Set<RequestMappingInfo> partialMatches) {
- List<String[]> result = new ArrayList<String[]>();
- for (RequestMappingInfo partialMatch : partialMatches) {
- ParamsRequestCondition condition = partialMatch.getParamsCondition();
- Set<NameValueExpression<String>> expressions = condition.getExpressions();
- if (!CollectionUtils.isEmpty(expressions) && condition.getMatchingCondition(request) == null) {
- int i = 0;
- String[] array = new String[expressions.size()];
- for (NameValueExpression<String> expression : expressions) {
- array[i++] = expression.toString();
+
+ /**
+ * Default handler for HTTP OPTIONS.
+ */
+ private static class HttpOptionsHandler {
+
+ private final HttpHeaders headers = new HttpHeaders();
+
+ public HttpOptionsHandler(Set<String> declaredMethods) {
+ this.headers.setAllow(initAllowedHttpMethods(declaredMethods));
+ }
+
+ private static Set<HttpMethod> initAllowedHttpMethods(Set<String> declaredMethods) {
+ Set<HttpMethod> result = new LinkedHashSet<HttpMethod>(declaredMethods.size());
+ if (declaredMethods.isEmpty()) {
+ for (HttpMethod method : HttpMethod.values()) {
+ if (!HttpMethod.TRACE.equals(method)) {
+ result.add(method);
+ }
+ }
+ }
+ else {
+ boolean hasHead = declaredMethods.contains("HEAD");
+ for (String method : declaredMethods) {
+ result.add(HttpMethod.valueOf(method));
+ if (!hasHead && "GET".equals(method)) {
+ result.add(HttpMethod.HEAD);
+ }
}
- result.add(array);
}
+ return result;
+ }
+
+ public HttpHeaders handle() {
+ return this.headers;
}
- return result;
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java
index 296caaf8..c0510172 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java
@@ -44,7 +44,6 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.accept.ContentNegotiationManager;
-import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
@@ -123,12 +122,9 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
}
private static PathExtensionContentNegotiationStrategy initPathStrategy(ContentNegotiationManager manager) {
- for (ContentNegotiationStrategy strategy : manager.getStrategies()) {
- if (strategy instanceof PathExtensionContentNegotiationStrategy) {
- return (PathExtensionContentNegotiationStrategy) strategy;
- }
- }
- return new PathExtensionContentNegotiationStrategy();
+ Class<PathExtensionContentNegotiationStrategy> clazz = PathExtensionContentNegotiationStrategy.class;
+ PathExtensionContentNegotiationStrategy strategy = manager.getStrategy(clazz);
+ return (strategy != null ? strategy : new PathExtensionContentNegotiationStrategy());
}
@@ -169,13 +165,26 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
- Class<?> valueType = getReturnValueType(value, returnType);
- Type declaredType = getGenericType(returnType);
+ Object outputValue;
+ Class<?> valueType;
+ Type declaredType;
+
+ if (value instanceof CharSequence) {
+ outputValue = value.toString();
+ valueType = String.class;
+ declaredType = String.class;
+ }
+ else {
+ outputValue = value;
+ valueType = getReturnValueType(outputValue, returnType);
+ declaredType = getGenericType(returnType);
+ }
+
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
- if (value != null && producibleMediaTypes.isEmpty()) {
+ if (outputValue != null && producibleMediaTypes.isEmpty()) {
throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
}
@@ -188,7 +197,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
}
}
if (compatibleMediaTypes.isEmpty()) {
- if (value != null) {
+ if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
return;
@@ -213,17 +222,17 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter instanceof GenericHttpMessageConverter) {
- if (((GenericHttpMessageConverter<T>) messageConverter).canWrite(
+ if (((GenericHttpMessageConverter) messageConverter).canWrite(
declaredType, valueType, selectedMediaType)) {
- value = (T) getAdvice().beforeBodyWrite(value, returnType, selectedMediaType,
+ outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
inputMessage, outputMessage);
- if (value != null) {
+ if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
- ((GenericHttpMessageConverter<T>) messageConverter).write(
- value, declaredType, selectedMediaType, outputMessage);
+ ((GenericHttpMessageConverter) messageConverter).write(
+ outputValue, declaredType, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
- logger.debug("Written [" + value + "] as \"" + selectedMediaType +
+ logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + messageConverter + "]");
}
}
@@ -231,14 +240,14 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
}
}
else if (messageConverter.canWrite(valueType, selectedMediaType)) {
- value = (T) getAdvice().beforeBodyWrite(value, returnType, selectedMediaType,
+ outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
inputMessage, outputMessage);
- if (value != null) {
+ if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
- ((HttpMessageConverter<T>) messageConverter).write(value, selectedMediaType, outputMessage);
+ ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
- logger.debug("Written [" + value + "] as \"" + selectedMediaType +
+ logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + messageConverter + "]");
}
}
@@ -247,7 +256,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
}
}
- if (value != null) {
+ if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/CompletionStageReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/CompletionStageReturnValueHandler.java
index 6543b76c..c3c7b1d0 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/CompletionStageReturnValueHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/CompletionStageReturnValueHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -34,7 +34,10 @@ import org.springframework.web.method.support.ModelAndViewContainer;
*
* @author Sebastien Deleuze
* @since 4.2
+ * @deprecated as of 4.3 {@link DeferredResultMethodReturnValueHandler} supports
+ * CompletionStage return values via an adapter mechanism.
*/
+@Deprecated
@UsesJava8
public class CompletionStageReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultAdapter.java
new file mode 100644
index 00000000..a743b09e
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultAdapter.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-2016 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.servlet.mvc.method.annotation;
+
+import org.springframework.web.context.request.async.DeferredResult;
+
+/**
+ * Contract to adapt a single-value async return value to {@code DeferredResult}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public interface DeferredResultAdapter {
+
+ /**
+ * Create a {@code DeferredResult} for the given return value.
+ * @param returnValue the return value (never {@code null})
+ * @return the DeferredResult
+ */
+ DeferredResult<?> adaptToDeferredResult(Object returnValue);
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java
index 147e6aff..789606dc 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/DeferredResultMethodReturnValueHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -16,7 +16,17 @@
package org.springframework.web.servlet.mvc.method.annotation;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletionStage;
+import java.util.function.BiFunction;
+
import org.springframework.core.MethodParameter;
+import org.springframework.lang.UsesJava8;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.concurrent.ListenableFuture;
+import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.WebAsyncUtils;
@@ -24,21 +34,56 @@ import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandl
import org.springframework.web.method.support.ModelAndViewContainer;
/**
- * Handles return values of type {@link DeferredResult}.
+ * Handler for return values of type {@link DeferredResult}, {@link ListenableFuture},
+ * {@link CompletionStage} and any other async type with a {@link #getAdapterMap()
+ * registered adapter}.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public class DeferredResultMethodReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
+ private final Map<Class<?>, DeferredResultAdapter> adapterMap;
+
+
+ public DeferredResultMethodReturnValueHandler() {
+ this.adapterMap = new HashMap<Class<?>, DeferredResultAdapter>(5);
+ this.adapterMap.put(DeferredResult.class, new SimpleDeferredResultAdapter());
+ this.adapterMap.put(ListenableFuture.class, new ListenableFutureAdapter());
+ if (ClassUtils.isPresent("java.util.concurrent.CompletionStage", getClass().getClassLoader())) {
+ this.adapterMap.put(CompletionStage.class, new CompletionStageAdapter());
+ }
+ }
+
+
+ /**
+ * Return the map with {@code DeferredResult} adapters.
+ * <p>By default the map contains adapters for {@code DeferredResult}, which
+ * simply downcasts, {@link ListenableFuture}, and {@link CompletionStage}.
+ * @return the map of adapters
+ */
+ public Map<Class<?>, DeferredResultAdapter> getAdapterMap() {
+ return this.adapterMap;
+ }
+
+ private DeferredResultAdapter getAdapterFor(Class<?> type) {
+ for (Class<?> adapteeType : getAdapterMap().keySet()) {
+ if (adapteeType.isAssignableFrom(type)) {
+ return getAdapterMap().get(adapteeType);
+ }
+ }
+ return null;
+ }
+
+
@Override
public boolean supportsReturnType(MethodParameter returnType) {
- return DeferredResult.class.isAssignableFrom(returnType.getParameterType());
+ return (getAdapterFor(returnType.getParameterType()) != null);
}
@Override
public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {
- return (returnValue != null && returnValue instanceof DeferredResult);
+ return (returnValue != null && (getAdapterFor(returnValue.getClass()) != null));
}
@Override
@@ -50,8 +95,76 @@ public class DeferredResultMethodReturnValueHandler implements AsyncHandlerMetho
return;
}
- DeferredResult<?> deferredResult = (DeferredResult<?>) returnValue;
- WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer);
+ DeferredResultAdapter adapter = getAdapterFor(returnValue.getClass());
+ Assert.notNull(adapter);
+ DeferredResult<?> result = adapter.adaptToDeferredResult(returnValue);
+ WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(result, mavContainer);
+ }
+
+
+ /**
+ * Adapter for {@code DeferredResult} return values.
+ */
+ private static class SimpleDeferredResultAdapter implements DeferredResultAdapter {
+
+ @Override
+ public DeferredResult<?> adaptToDeferredResult(Object returnValue) {
+ Assert.isInstanceOf(DeferredResult.class, returnValue);
+ return (DeferredResult<?>) returnValue;
+ }
+ }
+
+
+ /**
+ * Adapter for {@code ListenableFuture} return values.
+ */
+ private static class ListenableFutureAdapter implements DeferredResultAdapter {
+
+ @Override
+ public DeferredResult<?> adaptToDeferredResult(Object returnValue) {
+ Assert.isInstanceOf(ListenableFuture.class, returnValue);
+ final DeferredResult<Object> result = new DeferredResult<Object>();
+ ((ListenableFuture<?>) returnValue).addCallback(new ListenableFutureCallback<Object>() {
+ @Override
+ public void onSuccess(Object value) {
+ result.setResult(value);
+ }
+ @Override
+ public void onFailure(Throwable ex) {
+ result.setErrorResult(ex);
+ }
+ });
+ return result;
+ }
+ }
+
+
+ /**
+ * Adapter for {@code CompletionStage} return values.
+ */
+ @UsesJava8
+ private static class CompletionStageAdapter implements DeferredResultAdapter {
+
+ @Override
+ public DeferredResult<?> adaptToDeferredResult(Object returnValue) {
+ Assert.isInstanceOf(CompletionStage.class, returnValue);
+ final DeferredResult<Object> result = new DeferredResult<Object>();
+ @SuppressWarnings("unchecked")
+ CompletionStage<?> future = (CompletionStage<?>) returnValue;
+ future.handle(new BiFunction<Object, Throwable, Object>() {
+ @Override
+ public Object apply(Object value, Throwable ex) {
+ if (ex != null) {
+ result.setErrorResult(ex);
+ }
+ else {
+ result.setResult(value);
+ }
+ return null;
+ }
+ });
+ return result;
+ }
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java
index 572f0b7c..8e554773 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java
@@ -65,6 +65,7 @@ import org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionRes
* {@link #setArgumentResolvers} and {@link #setReturnValueHandlers(List)}.
*
* @author Rossen Stoyanchev
+ * @author Juergen Hoeller
* @since 3.1
*/
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
@@ -294,6 +295,10 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
+ // Annotation-based argument resolution
+ resolvers.add(new SessionAttributeMethodArgumentResolver());
+ resolvers.add(new RequestAttributeMethodArgumentResolver());
+
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
@@ -364,7 +369,15 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
if (logger.isDebugEnabled()) {
logger.debug("Invoking @ExceptionHandler method: " + exceptionHandlerMethod);
}
- exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
+ Throwable cause = exception.getCause();
+ if (cause != null) {
+ // Expose cause as provided argument as well
+ exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
+ }
+ else {
+ // Otherwise, just the given exception as-is
+ exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
+ }
}
catch (Exception invocationEx) {
if (logger.isDebugEnabled()) {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java
index 7741b1c4..6545d4a8 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -19,7 +19,10 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
@@ -162,15 +165,27 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
Assert.isInstanceOf(HttpEntity.class, returnValue);
HttpEntity<?> responseEntity = (HttpEntity<?>) returnValue;
+ HttpHeaders outputHeaders = outputMessage.getHeaders();
HttpHeaders entityHeaders = responseEntity.getHeaders();
+ if (outputHeaders.containsKey(HttpHeaders.VARY) && entityHeaders.containsKey(HttpHeaders.VARY)) {
+ List<String> values = getVaryRequestHeadersToAdd(outputHeaders, entityHeaders);
+ if (!values.isEmpty()) {
+ outputHeaders.setVary(values);
+ }
+ }
if (!entityHeaders.isEmpty()) {
- outputMessage.getHeaders().putAll(entityHeaders);
+ for (Map.Entry<String, List<String>> entry : entityHeaders.entrySet()) {
+ if (!outputHeaders.containsKey(entry.getKey())) {
+ outputHeaders.put(entry.getKey(), entry.getValue());
+ }
+ }
}
- Object body = responseEntity.getBody();
if (responseEntity instanceof ResponseEntity) {
- outputMessage.setStatusCode(((ResponseEntity<?>) responseEntity).getStatusCode());
- if (HttpMethod.GET == inputMessage.getMethod() && isResourceNotModified(inputMessage, outputMessage)) {
+ outputMessage.getServletResponse().setStatus(((ResponseEntity<?>) responseEntity).getStatusCodeValue());
+ HttpMethod method = inputMessage.getMethod();
+ boolean isGetOrHead = (HttpMethod.GET == method || HttpMethod.HEAD == method);
+ if (isGetOrHead && isResourceNotModified(inputMessage, outputMessage)) {
outputMessage.setStatusCode(HttpStatus.NOT_MODIFIED);
// Ensure headers are flushed, no body should be written.
outputMessage.flush();
@@ -180,12 +195,33 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
}
// Try even with null body. ResponseBodyAdvice could get involved.
- writeWithMessageConverters(body, returnType, inputMessage, outputMessage);
+ writeWithMessageConverters(responseEntity.getBody(), returnType, inputMessage, outputMessage);
// Ensure headers are flushed even if no body was written.
outputMessage.flush();
}
+ private List<String> getVaryRequestHeadersToAdd(HttpHeaders responseHeaders, HttpHeaders entityHeaders) {
+ if (!responseHeaders.containsKey(HttpHeaders.VARY)) {
+ return entityHeaders.getVary();
+ }
+ List<String> entityHeadersVary = entityHeaders.getVary();
+ List<String> result = new ArrayList<String>(entityHeadersVary);
+ for (String header : responseHeaders.get(HttpHeaders.VARY)) {
+ for (String existing : StringUtils.tokenizeToStringArray(header, ",")) {
+ if ("*".equals(existing)) {
+ return Collections.emptyList();
+ }
+ for (String value : entityHeadersVary) {
+ if (value.equalsIgnoreCase(existing)) {
+ result.remove(value);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
private boolean isResourceNotModified(ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) {
List<String> ifNoneMatch = inputMessage.getHeaders().getIfNoneMatch();
long ifModifiedSince = inputMessage.getHeaders().getIfModifiedSince();
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewResponseBodyAdvice.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewResponseBodyAdvice.java
index 3ed6bb8f..2cb56bc2 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewResponseBodyAdvice.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewResponseBodyAdvice.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -47,7 +47,7 @@ public class JsonViewResponseBodyAdvice extends AbstractMappingJacksonResponseBo
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
- return (super.supports(returnType, converterType) && returnType.getMethodAnnotation(JsonView.class) != null);
+ return super.supports(returnType, converterType) && returnType.hasMethodAnnotation(JsonView.class);
}
@Override
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ListenableFutureReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ListenableFutureReturnValueHandler.java
index 18d25a4f..d4a8fe62 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ListenableFutureReturnValueHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ListenableFutureReturnValueHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -31,7 +31,10 @@ import org.springframework.web.method.support.ModelAndViewContainer;
*
* @author Rossen Stoyanchev
* @since 4.1
+ * @deprecated as of 4.3 {@link DeferredResultMethodReturnValueHandler} supports
+ * ListenableFuture return values via an adapter mechanism.
*/
+@Deprecated
public class ListenableFutureReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {
@Override
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMapMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMapMethodArgumentResolver.java
index 59a8e987..212a8269 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMapMethodArgumentResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMapMethodArgumentResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -17,9 +17,11 @@
package org.springframework.web.servlet.mvc.method.annotation;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import org.springframework.core.MethodParameter;
+import org.springframework.core.ResolvableType;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@@ -68,23 +70,37 @@ public class MatrixVariableMapMethodArgumentResolver implements HandlerMethodArg
return Collections.emptyMap();
}
+ MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
String pathVariable = parameter.getParameterAnnotation(MatrixVariable.class).pathVar();
if (!pathVariable.equals(ValueConstants.DEFAULT_NONE)) {
- MultiValueMap<String, String> map = matrixVariables.get(pathVariable);
- return (map != null) ? map : Collections.emptyMap();
+ MultiValueMap<String, String> mapForPathVariable = matrixVariables.get(pathVariable);
+ if (mapForPathVariable == null) {
+ return Collections.emptyMap();
+ }
+ map.putAll(mapForPathVariable);
}
-
- MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
- for (MultiValueMap<String, String> vars : matrixVariables.values()) {
- for (String name : vars.keySet()) {
- for (String value : vars.get(name)) {
- map.add(name, value);
+ else {
+ for (MultiValueMap<String, String> vars : matrixVariables.values()) {
+ for (String name : vars.keySet()) {
+ for (String value : vars.get(name)) {
+ map.add(name, value);
+ }
}
}
}
- return map;
+ return (isSingleValueMap(parameter) ? map.toSingleValueMap() : map);
+ }
+
+ private boolean isSingleValueMap(MethodParameter parameter) {
+ if (!MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) {
+ ResolvableType[] genericTypes = ResolvableType.forMethodParameter(parameter).getGenerics();
+ if (genericTypes.length == 2) {
+ return !List.class.isAssignableFrom(genericTypes[1].getRawClass());
+ }
+ }
+ return false;
}
} \ No newline at end of file
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMethodArgumentResolver.java
index 6e971176..1dd9c354 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMethodArgumentResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MatrixVariableMethodArgumentResolver.java
@@ -54,7 +54,7 @@ public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMeth
if (!parameter.hasParameterAnnotation(MatrixVariable.class)) {
return false;
}
- if (Map.class.isAssignableFrom(parameter.getParameterType())) {
+ if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
String variableName = parameter.getParameterAnnotation(MatrixVariable.class).name();
return StringUtils.hasText(variableName);
}
@@ -90,7 +90,7 @@ public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMeth
for (MultiValueMap<String, String> params : pathParameters.values()) {
if (params.containsKey(name)) {
if (found) {
- String paramType = parameter.getParameterType().getName();
+ String paramType = parameter.getNestedParameterType().getName();
throw new ServletRequestBindingException(
"Found more than one match for URI path parameter '" + name +
"' for parameter type [" + paramType + "]. Use 'pathVar' attribute to disambiguate.");
@@ -115,7 +115,7 @@ public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMeth
@Override
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
throw new ServletRequestBindingException("Missing matrix variable '" + name +
- "' for method parameter of type " + parameter.getParameterType().getSimpleName());
+ "' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java
index a848a970..10ea4028 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -98,6 +98,7 @@ public class ModelAndViewMethodReturnValueHandler implements HandlerMethodReturn
}
}
}
+ mavContainer.setStatus(mav.getStatus());
mavContainer.addAllAttributes(mav.getModel());
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java
index 25018ff9..e9e302a5 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java
@@ -38,8 +38,8 @@ import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.core.DefaultParameterNameDiscoverer;
-import org.springframework.core.MethodParameter;
import org.springframework.core.MethodIntrospector;
+import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.SynthesizingMethodParameter;
@@ -118,7 +118,7 @@ public class MvcUriComponentsBuilder {
* @see #fromMethodName(Class, String, Object...)
* @see #fromMethodCall(Object)
* @see #fromMappingName(String)
- * @see #fromMethod(java.lang.reflect.Method, Object...)
+ * @see #fromMethod(Class, Method, Object...)
*/
protected MvcUriComponentsBuilder(UriComponentsBuilder baseUrl) {
Assert.notNull(baseUrl, "'baseUrl' is required");
@@ -168,7 +168,7 @@ public class MvcUriComponentsBuilder {
/**
* Create a {@link UriComponentsBuilder} from the mapping of a controller
* method and an array of method argument values. This method delegates
- * to {@link #fromMethod(java.lang.reflect.Method, Object...)}.
+ * to {@link #fromMethod(Class, Method, Object...)}.
* @param controllerType the controller
* @param methodName the method name
* @param args the argument values
@@ -207,7 +207,7 @@ public class MvcUriComponentsBuilder {
/**
* Create a {@link UriComponentsBuilder} by invoking a "mock" controller method.
* The controller method and the supplied argument values are then used to
- * delegate to {@link #fromMethod(java.lang.reflect.Method, Object...)}.
+ * delegate to {@link #fromMethod(Class, Method, Object...)}.
* <p>For example, given this controller:
* <pre class="code">
* &#064;RequestMapping("/people/{id}/addresses")
@@ -304,7 +304,7 @@ public class MvcUriComponentsBuilder {
* <p>Note that it's not necessary to specify all arguments. Only the ones
* required to prepare the URL, mainly {@code @RequestParam} and {@code @PathVariable}).
* @param mappingName the mapping name
- * @return a builder to to prepare the URI String
+ * @return a builder to prepare the URI String
* @throws IllegalArgumentException if the mapping name is not found or
* if there is no unique match
* @since 4.1
@@ -321,7 +321,7 @@ public class MvcUriComponentsBuilder {
* @param builder the builder for the base URL; the builder will be cloned
* and therefore not modified and may be re-used for further calls.
* @param name the mapping name
- * @return a builder to to prepare the URI String
+ * @return a builder to prepare the URI String
* @throws IllegalArgumentException if the mapping name is not found or
* if there is no unique match
* @since 4.2
@@ -361,7 +361,7 @@ public class MvcUriComponentsBuilder {
}
/**
- * An alternative to {@link #fromMethod(java.lang.reflect.Method, Object...)}
+ * An alternative to {@link #fromMethod(Class, Method, Object...)}
* that accepts a {@code UriComponentsBuilder} representing the base URL.
* This is useful when using MvcUriComponentsBuilder outside the context of
* processing a request or to apply a custom baseUrl not matching the
@@ -557,8 +557,7 @@ public class MvcUriComponentsBuilder {
* on the controller is invoked, the supplied argument values are remembered
* and the result can then be used to create a {@code UriComponentsBuilder}
* via {@link #fromMethodCall(Object)}.
- * <p>
- * Note that this is a shorthand version of {@link #controller(Class)} intended
+ * <p>Note that this is a shorthand version of {@link #controller(Class)} intended
* for inline use (with a static import), for example:
* <pre class="code">
* MvcUriComponentsBuilder.fromMethodCall(on(FooController.class).getFoo(1)).build();
@@ -574,8 +573,7 @@ public class MvcUriComponentsBuilder {
* on the controller is invoked, the supplied argument values are remembered
* and the result can then be used to create {@code UriComponentsBuilder} via
* {@link #fromMethodCall(Object)}.
- * <p>
- * This is a longer version of {@link #on(Class)}. It is needed with controller
+ * <p>This is a longer version of {@link #on(Class)}. It is needed with controller
* methods returning void as well for repeated invocations.
* <pre class="code">
* FooController fooController = controller(FooController.class);
@@ -627,7 +625,7 @@ public class MvcUriComponentsBuilder {
try {
proxy = proxyClass.newInstance();
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new IllegalStateException("Unable to instantiate controller proxy using Objenesis, " +
"and regular controller instantiation via default constructor fails as well", ex);
}
@@ -778,7 +776,6 @@ public class MvcUriComponentsBuilder {
}
/**
- * @see #MethodArgumentBuilder(Class, Method)
* @deprecated as of 4.2, this is deprecated in favor of alternative constructors
* that accept a controllerType argument
*/
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java
index 0475e03b..d7b1cb52 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java
@@ -74,7 +74,7 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod
if (!parameter.hasParameterAnnotation(PathVariable.class)) {
return false;
}
- if (Map.class.isAssignableFrom(parameter.getParameterType())) {
+ if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
String paramName = parameter.getParameterAnnotation(PathVariable.class).value();
return StringUtils.hasText(paramName);
}
@@ -121,7 +121,7 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod
public void contributeMethodArgument(MethodParameter parameter, Object value,
UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
- if (Map.class.isAssignableFrom(parameter.getParameterType())) {
+ if (Map.class.isAssignableFrom(parameter.getNestedParameterType())) {
return;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolver.java
new file mode 100644
index 00000000..def67fde
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestAttributeMethodArgumentResolver.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-2016 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.servlet.mvc.method.annotation;
+
+import javax.servlet.ServletException;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.web.bind.ServletRequestBindingException;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.ValueConstants;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver;
+
+/**
+ * Resolves method arguments annotated with an @{@link RequestAttribute}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public class RequestAttributeMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return parameter.hasParameterAnnotation(RequestAttribute.class);
+ }
+
+ @Override
+ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
+ RequestAttribute ann = parameter.getParameterAnnotation(RequestAttribute.class);
+ return new NamedValueInfo(ann.name(), ann.required(), ValueConstants.DEFAULT_NONE);
+ }
+
+ @Override
+ protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request){
+ return request.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
+ }
+
+ @Override
+ protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
+ throw new ServletRequestBindingException("Missing request attribute '" + name +
+ "' of type " + parameter.getNestedParameterType().getSimpleName());
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
index ce031bd5..a48efb1b 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
@@ -48,7 +48,6 @@ import org.springframework.http.converter.support.AllEncompassingFormHttpMessage
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.ui.ModelMap;
import org.springframework.util.Assert;
-import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.web.accept.ContentNegotiationManager;
@@ -117,10 +116,6 @@ import org.springframework.web.util.WebUtils;
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
- private static final boolean completionStagePresent = ClassUtils.isPresent(
- "java.util.concurrent.CompletionStage", RequestMappingHandlerAdapter.class.getClassLoader());
-
-
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
private HandlerMethodArgumentResolverComposite argumentResolvers;
@@ -599,6 +594,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
+ resolvers.add(new SessionAttributeMethodArgumentResolver());
+ resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
@@ -638,6 +635,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
+ resolvers.add(new SessionAttributeMethodArgumentResolver());
+ resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
@@ -673,10 +672,6 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
handlers.add(new CallableMethodReturnValueHandler());
handlers.add(new DeferredResultMethodReturnValueHandler());
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
- handlers.add(new ListenableFutureReturnValueHandler());
- if (completionStagePresent) {
- handlers.add(new CompletionStageReturnValueHandler());
- }
// Annotation-based return value types
handlers.add(new ModelAttributeMethodProcessor(false));
@@ -795,46 +790,50 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
+ try {
+ WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
+ ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
+
+ ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
+ invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
+ invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
+ invocableMethod.setDataBinderFactory(binderFactory);
+ invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
+
+ ModelAndViewContainer mavContainer = new ModelAndViewContainer();
+ mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
+ modelFactory.initModel(webRequest, mavContainer, invocableMethod);
+ mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
+
+ AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
+ asyncWebRequest.setTimeout(this.asyncRequestTimeout);
+
+ WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
+ asyncManager.setTaskExecutor(this.taskExecutor);
+ asyncManager.setAsyncWebRequest(asyncWebRequest);
+ asyncManager.registerCallableInterceptors(this.callableInterceptors);
+ asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
+
+ if (asyncManager.hasConcurrentResult()) {
+ Object result = asyncManager.getConcurrentResult();
+ mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
+ asyncManager.clearConcurrentResult();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Found concurrent result value [" + result + "]");
+ }
+ invocableMethod = invocableMethod.wrapConcurrentResult(result);
+ }
- WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
- ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
-
- ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
- invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
- invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
- invocableMethod.setDataBinderFactory(binderFactory);
- invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
-
- ModelAndViewContainer mavContainer = new ModelAndViewContainer();
- mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
- modelFactory.initModel(webRequest, mavContainer, invocableMethod);
- mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
-
- AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
- asyncWebRequest.setTimeout(this.asyncRequestTimeout);
-
- WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
- asyncManager.setTaskExecutor(this.taskExecutor);
- asyncManager.setAsyncWebRequest(asyncWebRequest);
- asyncManager.registerCallableInterceptors(this.callableInterceptors);
- asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
-
- if (asyncManager.hasConcurrentResult()) {
- Object result = asyncManager.getConcurrentResult();
- mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
- asyncManager.clearConcurrentResult();
- if (logger.isDebugEnabled()) {
- logger.debug("Found concurrent result value [" + result + "]");
+ invocableMethod.invokeAndHandle(webRequest, mavContainer);
+ if (asyncManager.isConcurrentHandlingStarted()) {
+ return null;
}
- invocableMethod = invocableMethod.wrapConcurrentResult(result);
- }
- invocableMethod.invokeAndHandle(webRequest, mavContainer);
- if (asyncManager.isConcurrentHandlingStarted()) {
- return null;
+ return getModelAndView(mavContainer, modelFactory, webRequest);
+ }
+ finally {
+ webRequest.requestCompleted();
}
-
- return getModelAndView(mavContainer, modelFactory, webRequest);
}
/**
@@ -934,7 +933,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
return null;
}
ModelMap model = mavContainer.getModel();
- ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);
+ ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
index 5ffb6aaf..b4abdc16 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -20,10 +20,11 @@ import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
+import java.util.Set;
+import javax.servlet.http.HttpServletRequest;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.annotation.AnnotatedElementUtils;
-import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -34,6 +35,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.MatchableHandlerMapping;
+import org.springframework.web.servlet.handler.RequestMatchResult;
import org.springframework.web.servlet.mvc.condition.AbstractRequestCondition;
import org.springframework.web.servlet.mvc.condition.CompositeRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
@@ -51,7 +54,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi
* @since 3.1
*/
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
- implements EmbeddedValueResolverAware {
+ implements MatchableHandlerMapping, EmbeddedValueResolverAware {
private boolean useSuffixPatternMatch = true;
@@ -109,13 +112,13 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
- this.embeddedValueResolver = resolver;
+ this.embeddedValueResolver = resolver;
}
@Override
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
- this.config.setPathHelper(getUrlPathHelper());
+ this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
@@ -168,8 +171,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
*/
@Override
protected boolean isHandler(Class<?> beanType) {
- return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
- (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
+ return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
+ AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
/**
@@ -276,6 +279,18 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
}
@Override
+ public RequestMatchResult match(HttpServletRequest request, String pattern) {
+ RequestMappingInfo info = RequestMappingInfo.paths(pattern).options(this.config).build();
+ RequestMappingInfo matchingInfo = info.getMatchingCondition(request);
+ if (matchingInfo == null) {
+ return null;
+ }
+ Set<String> patterns = matchingInfo.getPatternsCondition().getPatterns();
+ String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
+ return new RequestMatchResult(patterns.iterator().next(), lookupPath, getPathMatcher());
+ }
+
+ @Override
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getBeanType(), CrossOrigin.class);
@@ -314,19 +329,19 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
return;
}
for (String origin : annotation.origins()) {
- config.addAllowedOrigin(origin);
+ config.addAllowedOrigin(resolveCorsAnnotationValue(origin));
}
for (RequestMethod method : annotation.methods()) {
config.addAllowedMethod(method.name());
}
for (String header : annotation.allowedHeaders()) {
- config.addAllowedHeader(header);
+ config.addAllowedHeader(resolveCorsAnnotationValue(header));
}
for (String header : annotation.exposedHeaders()) {
- config.addExposedHeader(header);
+ config.addExposedHeader(resolveCorsAnnotationValue(header));
}
- String allowCredentials = annotation.allowCredentials();
+ String allowCredentials = resolveCorsAnnotationValue(annotation.allowCredentials());
if ("true".equalsIgnoreCase(allowCredentials)) {
config.setAllowCredentials(true);
}
@@ -334,8 +349,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
config.setAllowCredentials(false);
}
else if (!allowCredentials.isEmpty()) {
- throw new IllegalStateException("@CrossOrigin's allowCredentials value must be \"true\", \"false\", "
- + "or an empty string (\"\"); current value is [" + allowCredentials + "].");
+ throw new IllegalStateException("@CrossOrigin's allowCredentials value must be \"true\", \"false\", " +
+ "or an empty string (\"\"): current value is [" + allowCredentials + "]");
}
if (annotation.maxAge() >= 0 && config.getMaxAge() == null) {
@@ -343,4 +358,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
}
}
+ private String resolveCorsAnnotationValue(String value) {
+ return (this.embeddedValueResolver != null ? this.embeddedValueResolver.resolveStringValue(value) : value);
+ }
+
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java
index ba34c2e4..d78d77de 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java
@@ -16,19 +16,15 @@
package org.springframework.web.servlet.mvc.method.annotation;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.Part;
-import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.UsesJava8;
-import org.springframework.util.Assert;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
@@ -40,12 +36,10 @@ import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartFile;
-import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
+import org.springframework.web.multipart.support.MultipartResolutionDelegate;
import org.springframework.web.multipart.support.RequestPartServletServerHttpRequest;
-import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
-import org.springframework.web.util.WebUtils;
/**
* Resolves the following method arguments:
@@ -64,12 +58,13 @@ import org.springframework.web.util.WebUtils;
* it is derived from the name of the method argument.
*
* <p>Automatic validation may be applied if the argument is annotated with
- * {@code @javax.validation.Valid}. In case of validation failure, a
- * {@link MethodArgumentNotValidException} is raised and a 400 response status
- * code returned if {@link DefaultHandlerExceptionResolver} is configured.
+ * {@code @javax.validation.Valid}. In case of validation failure, a {@link MethodArgumentNotValidException}
+ * is raised and a 400 response status code returned if
+ * {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver} is configured.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
+ * @author Juergen Hoeller
* @since 3.1
*/
public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {
@@ -106,18 +101,10 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
return true;
}
else {
- if (parameter.hasParameterAnnotation(RequestParam.class)){
- return false;
- }
- else if (MultipartFile.class == parameter.getParameterType()) {
- return true;
- }
- else if ("javax.servlet.http.Part".equals(parameter.getParameterType().getName())) {
- return true;
- }
- else {
+ if (parameter.hasParameterAnnotation(RequestParam.class)) {
return false;
}
+ return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
}
}
@@ -126,87 +113,58 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
NativeWebRequest request, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
- assertIsMultipartRequest(servletRequest);
- MultipartHttpServletRequest multipartRequest =
- WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
-
- String partName = getPartName(parameter);
- Class<?> paramType = parameter.getParameterType();
- boolean optional = paramType.getName().equals("java.util.Optional");
- if (optional) {
- parameter = new MethodParameter(parameter);
- parameter.increaseNestingLevel();
- paramType = parameter.getNestedParameterType();
- }
+ RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
+ boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());
- Object arg;
+ String name = getPartName(parameter, requestPart);
+ parameter = parameter.nestedIfOptional();
+ Object arg = null;
- if (MultipartFile.class == paramType) {
- Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
- arg = multipartRequest.getFile(partName);
- }
- else if (isMultipartFileCollection(parameter)) {
- Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
- arg = multipartRequest.getFiles(partName);
- }
- else if (isMultipartFileArray(parameter)) {
- Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
- List<MultipartFile> files = multipartRequest.getFiles(partName);
- arg = files.toArray(new MultipartFile[files.size()]);
- }
- else if ("javax.servlet.http.Part".equals(paramType.getName())) {
- assertIsMultipartRequest(servletRequest);
- arg = servletRequest.getPart(partName);
- }
- else if (isPartCollection(parameter)) {
- assertIsMultipartRequest(servletRequest);
- arg = new ArrayList<Object>(servletRequest.getParts());
- }
- else if (isPartArray(parameter)) {
- assertIsMultipartRequest(servletRequest);
- arg = RequestPartResolver.resolvePart(servletRequest);
+ Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
+ if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
+ arg = mpArg;
}
else {
try {
- HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, partName);
+ HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name);
arg = readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType());
- WebDataBinder binder = binderFactory.createBinder(request, arg, partName);
+ WebDataBinder binder = binderFactory.createBinder(request, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
- mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + partName, binder.getBindingResult());
+ mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
catch (MissingServletRequestPartException ex) {
- // handled below
- arg = null;
+ if (isRequired) {
+ throw ex;
+ }
+ }
+ catch (MultipartException ex) {
+ if (isRequired) {
+ throw ex;
+ }
}
}
- RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
- boolean isRequired = ((requestPart == null || requestPart.required()) && !optional);
-
if (arg == null && isRequired) {
- throw new MissingServletRequestPartException(partName);
+ if (!MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
+ throw new MultipartException("Current request is not a multipart request");
+ }
+ else {
+ throw new MissingServletRequestPartException(name);
+ }
}
- if (optional) {
+ if (parameter.isOptional()) {
arg = OptionalResolver.resolveValue(arg);
}
return arg;
}
- private static void assertIsMultipartRequest(HttpServletRequest request) {
- String contentType = request.getContentType();
- if (contentType == null || !contentType.toLowerCase().startsWith("multipart/")) {
- throw new MultipartException("The current request is not a multipart request");
- }
- }
-
- private String getPartName(MethodParameter methodParam) {
- RequestPart requestPart = methodParam.getParameterAnnotation(RequestPart.class);
+ private String getPartName(MethodParameter methodParam, RequestPart requestPart) {
String partName = (requestPart != null ? requestPart.name() : "");
if (partName.length() == 0) {
partName = methodParam.getParameterName();
@@ -219,49 +177,6 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
return partName;
}
- private boolean isMultipartFileCollection(MethodParameter methodParam) {
- Class<?> collectionType = getCollectionParameterType(methodParam);
- return MultipartFile.class == collectionType;
- }
-
- private boolean isMultipartFileArray(MethodParameter methodParam) {
- Class<?> paramType = methodParam.getNestedParameterType().getComponentType();
- return MultipartFile.class == paramType;
- }
-
- private boolean isPartCollection(MethodParameter methodParam) {
- Class<?> collectionType = getCollectionParameterType(methodParam);
- return (collectionType != null && "javax.servlet.http.Part".equals(collectionType.getName()));
- }
-
- private boolean isPartArray(MethodParameter methodParam) {
- Class<?> paramType = methodParam.getNestedParameterType().getComponentType();
- return (paramType != null && "javax.servlet.http.Part".equals(paramType.getName()));
- }
-
- private Class<?> getCollectionParameterType(MethodParameter methodParam) {
- Class<?> paramType = methodParam.getNestedParameterType();
- if (Collection.class == paramType || List.class.isAssignableFrom(paramType)){
- Class<?> valueType = GenericCollectionTypeResolver.getCollectionParameterType(methodParam);
- if (valueType != null) {
- return valueType;
- }
- }
- return null;
- }
-
-
- /**
- * Inner class to avoid hard-coded dependency on Servlet 3.0 Part type...
- */
- private static class RequestPartResolver {
-
- public static Object resolvePart(HttpServletRequest servletRequest) throws Exception {
- Collection<Part> parts = servletRequest.getParts();
- return parts.toArray(new Part[parts.size()]);
- }
- }
-
/**
* Inner class to avoid hard-coded dependency on Java 8 Optional type...
@@ -270,7 +185,11 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
private static class OptionalResolver {
public static Object resolveValue(Object value) {
- return Optional.ofNullable(value);
+ if (value == null || (value instanceof Collection && ((Collection) value).isEmpty()) ||
+ (value instanceof Object[] && ((Object[]) value).length == 0)) {
+ return Optional.empty();
+ }
+ return Optional.of(value);
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java
index bc187962..e8de9e85 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -19,15 +19,17 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
+
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.Conventions;
import org.springframework.core.MethodParameter;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
@@ -108,8 +110,8 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
@Override
public boolean supportsReturnType(MethodParameter returnType) {
- return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null ||
- returnType.getMethodAnnotation(ResponseBody.class) != null);
+ return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
+ returnType.hasMethodAnnotation(ResponseBody.class));
}
/**
@@ -146,7 +148,7 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
Object arg = readWithMessageConverters(inputMessage, methodParam, paramType);
if (arg == null) {
- if (methodParam.getParameterAnnotation(RequestBody.class).required()) {
+ if (checkRequired(methodParam)) {
throw new HttpMessageNotReadableException("Required request body is missing: " +
methodParam.getMethod().toGenericString());
}
@@ -154,15 +156,21 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
return arg;
}
+ protected boolean checkRequired(MethodParameter methodParam) {
+ return methodParam.getParameterAnnotation(RequestBody.class).required();
+ }
+
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
+ ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
+ ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
- writeWithMessageConverters(returnValue, returnType, webRequest);
+ writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterAdapter.java
new file mode 100644
index 00000000..fc827cc2
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterAdapter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2016 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.servlet.mvc.method.annotation;
+
+import org.springframework.http.server.ServerHttpResponse;
+
+/**
+ * Contract to adapt streaming async types to {@code ResponseBodyEmitter}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public interface ResponseBodyEmitterAdapter {
+
+ /**
+ * Obtain a {@code ResponseBodyEmitter} for the given return value.
+ * If the return is the body {@code ResponseEntity} then the given
+ * {@code ServerHttpResponse} contains its status and headers.
+ * @param returnValue the return value (never {@code null})
+ * @param response the response
+ * @return the return value adapted to a {@code ResponseBodyEmitter}
+ */
+ ResponseBodyEmitter adaptToEmitter(Object returnValue, ServerHttpResponse response);
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java
index 9a216058..264ca73c 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -18,8 +18,9 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.io.OutputStream;
+import java.util.HashMap;
import java.util.List;
-
+import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -44,8 +45,9 @@ import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandl
import org.springframework.web.method.support.ModelAndViewContainer;
/**
- * Supports return values of type {@link ResponseBodyEmitter} and also
- * {@code ResponseEntity<ResponseBodyEmitter>}.
+ * Handler for return values of type {@link ResponseBodyEmitter} (and the
+ * {@code ResponseEntity<ResponseBodyEmitter>} sub-class) as well as any other
+ * async type with a {@link #getAdapterMap() registered adapter}.
*
* @author Rossen Stoyanchev
* @since 4.2
@@ -54,36 +56,63 @@ public class ResponseBodyEmitterReturnValueHandler implements AsyncHandlerMethod
private static final Log logger = LogFactory.getLog(ResponseBodyEmitterReturnValueHandler.class);
+
private final List<HttpMessageConverter<?>> messageConverters;
+ private final Map<Class<?>, ResponseBodyEmitterAdapter> adapterMap;
+
public ResponseBodyEmitterReturnValueHandler(List<HttpMessageConverter<?>> messageConverters) {
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
this.messageConverters = messageConverters;
+ this.adapterMap = new HashMap<Class<?>, ResponseBodyEmitterAdapter>(3);
+ this.adapterMap.put(ResponseBodyEmitter.class, new SimpleResponseBodyEmitterAdapter());
+ }
+
+
+ /**
+ * Return the map with {@code ResponseBodyEmitter} adapters.
+ * By default the map contains a single adapter {@code ResponseBodyEmitter}
+ * that simply downcasts the return value.
+ * @return the map of adapters
+ */
+ public Map<Class<?>, ResponseBodyEmitterAdapter> getAdapterMap() {
+ return this.adapterMap;
+ }
+
+ private ResponseBodyEmitterAdapter getAdapterFor(Class<?> type) {
+ if (type != null) {
+ for (Class<?> adapteeType : getAdapterMap().keySet()) {
+ if (adapteeType.isAssignableFrom(type)) {
+ return getAdapterMap().get(adapteeType);
+ }
+ }
+ }
+ return null;
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
- if (ResponseBodyEmitter.class.isAssignableFrom(returnType.getParameterType())) {
- return true;
+ Class<?> bodyType;
+ if (ResponseEntity.class.isAssignableFrom(returnType.getParameterType())) {
+ bodyType = ResolvableType.forMethodParameter(returnType).getGeneric(0).resolve();
}
- else if (ResponseEntity.class.isAssignableFrom(returnType.getParameterType())) {
- Class<?> bodyType = ResolvableType.forMethodParameter(returnType).getGeneric(0).resolve();
- return (bodyType != null && ResponseBodyEmitter.class.isAssignableFrom(bodyType));
+ else {
+ bodyType = returnType.getParameterType();
}
- return false;
+ return (getAdapterFor(bodyType) != null);
}
@Override
public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {
if (returnValue != null) {
- if (returnValue instanceof ResponseBodyEmitter) {
- return true;
+ Object adaptFrom = returnValue;
+ if (returnValue instanceof ResponseEntity) {
+ adaptFrom = ((ResponseEntity) returnValue).getBody();
}
- else if (returnValue instanceof ResponseEntity) {
- Object body = ((ResponseEntity) returnValue).getBody();
- return (body != null && body instanceof ResponseBodyEmitter);
+ if (adaptFrom != null) {
+ return (getAdapterFor(adaptFrom.getClass()) != null);
}
}
return false;
@@ -101,13 +130,14 @@ public class ResponseBodyEmitterReturnValueHandler implements AsyncHandlerMethod
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
ServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
- if (ResponseEntity.class.isAssignableFrom(returnValue.getClass())) {
+ if (returnValue instanceof ResponseEntity) {
ResponseEntity<?> responseEntity = (ResponseEntity<?>) returnValue;
- outputMessage.setStatusCode(responseEntity.getStatusCode());
+ response.setStatus(responseEntity.getStatusCodeValue());
outputMessage.getHeaders().putAll(responseEntity.getHeaders());
returnValue = responseEntity.getBody();
if (returnValue == null) {
mavContainer.setRequestHandled(true);
+ outputMessage.flush();
return;
}
}
@@ -115,8 +145,9 @@ public class ResponseBodyEmitterReturnValueHandler implements AsyncHandlerMethod
ServletRequest request = webRequest.getNativeRequest(ServletRequest.class);
ShallowEtagHeaderFilter.disableContentCaching(request);
- Assert.isInstanceOf(ResponseBodyEmitter.class, returnValue);
- ResponseBodyEmitter emitter = (ResponseBodyEmitter) returnValue;
+ ResponseBodyEmitterAdapter adapter = getAdapterFor(returnValue.getClass());
+ Assert.notNull(adapter);
+ ResponseBodyEmitter emitter = adapter.adaptToEmitter(returnValue, outputMessage);
emitter.extendResponse(outputMessage);
// Commit the response and wrap to ignore further header changes
@@ -133,6 +164,18 @@ public class ResponseBodyEmitterReturnValueHandler implements AsyncHandlerMethod
/**
+ * Adapter for {@code ResponseBodyEmitter} return values.
+ */
+ private static class SimpleResponseBodyEmitterAdapter implements ResponseBodyEmitterAdapter {
+
+ @Override
+ public ResponseBodyEmitter adaptToEmitter(Object returnValue, ServerHttpResponse response) {
+ Assert.isInstanceOf(ResponseBodyEmitter.class, returnValue);
+ return (ResponseBodyEmitter) returnValue;
+ }
+ }
+
+ /**
* ResponseBodyEmitter.Handler that writes with HttpMessageConverter's.
*/
private class HttpMessageConvertingHandler implements ResponseBodyEmitter.Handler {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java
index 0be4cb9b..267f1959 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java
@@ -46,7 +46,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;
-import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
import org.springframework.web.util.WebUtils;
/**
@@ -99,8 +98,9 @@ public abstract class ResponseEntityExceptionHandler {
* @param ex the target exception
* @param request the current request
*/
+ @SuppressWarnings("deprecation")
@ExceptionHandler({
- NoSuchRequestHandlingMethodException.class,
+ org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException.class,
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
HttpMediaTypeNotAcceptableException.class,
@@ -118,9 +118,9 @@ public abstract class ResponseEntityExceptionHandler {
})
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) {
HttpHeaders headers = new HttpHeaders();
- if (ex instanceof NoSuchRequestHandlingMethodException) {
+ if (ex instanceof org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) {
HttpStatus status = HttpStatus.NOT_FOUND;
- return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, headers, status, request);
+ return handleNoSuchRequestHandlingMethod((org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) ex, headers, status, request);
}
else if (ex instanceof HttpRequestMethodNotSupportedException) {
HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
@@ -213,8 +213,10 @@ public abstract class ResponseEntityExceptionHandler {
* @param status the selected response status
* @param request the current request
* @return a {@code ResponseEntity} instance
+ * @deprecated as of 4.3, along with {@link org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException}
*/
- protected ResponseEntity<Object> handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex,
+ @Deprecated
+ protected ResponseEntity<Object> handleNoSuchRequestHandlingMethod(org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
pageNotFoundLogger.warn(ex.getMessage());
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletCookieValueMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletCookieValueMethodArgumentResolver.java
index 8c51d5d8..cf174ea1 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletCookieValueMethodArgumentResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletCookieValueMethodArgumentResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -52,7 +52,7 @@ public class ServletCookieValueMethodArgumentResolver extends AbstractCookieValu
protected Object resolveName(String cookieName, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName);
- if (Cookie.class.isAssignableFrom(parameter.getParameterType())) {
+ if (Cookie.class.isAssignableFrom(parameter.getNestedParameterType())) {
return cookieValue;
}
else if (cookieValue != null) {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java
index f843a581..d8c19445 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java
@@ -24,6 +24,7 @@ import java.util.concurrent.Callable;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
+import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpStatus;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@@ -82,6 +83,9 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
private void initResponseStatus() {
ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class);
+ if (annotation == null) {
+ annotation = AnnotatedElementUtils.findMergedAnnotation(getBeanType(), ResponseStatus.class);
+ }
if (annotation != null) {
this.responseStatus = annotation.code();
this.responseReason = annotation.reason();
@@ -238,6 +242,14 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
return ServletInvocableHandlerMethod.this.getMethodAnnotation(annotationType);
}
+
+ /**
+ * Bridge to controller method-level annotations.
+ */
+ @Override
+ public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) {
+ return ServletInvocableHandlerMethod.this.hasMethodAnnotation(annotationType);
+ }
}
@@ -258,6 +270,12 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric(0);
}
+ public ConcurrentResultMethodParameter(ConcurrentResultMethodParameter original) {
+ super(original);
+ this.returnValue = original.returnValue;
+ this.returnType = original.returnType;
+ }
+
@Override
public Class<?> getParameterType() {
if (this.returnValue != null) {
@@ -273,6 +291,11 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
public Type getGenericParameterType() {
return this.returnType.getType();
}
+
+ @Override
+ public ConcurrentResultMethodParameter clone() {
+ return new ConcurrentResultMethodParameter(this);
+ }
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolver.java
new file mode 100644
index 00000000..7c7d0811
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SessionAttributeMethodArgumentResolver.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-2016 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.servlet.mvc.method.annotation;
+
+import javax.servlet.ServletException;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.web.bind.ServletRequestBindingException;
+import org.springframework.web.bind.annotation.SessionAttribute;
+import org.springframework.web.bind.annotation.ValueConstants;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver;
+
+/**
+ * Resolves method arguments annotated with an @{@link SessionAttribute}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public class SessionAttributeMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return parameter.hasParameterAnnotation(SessionAttribute.class);
+ }
+
+ @Override
+ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
+ SessionAttribute ann = parameter.getParameterAnnotation(SessionAttribute.class);
+ return new NamedValueInfo(ann.name(), ann.required(), ValueConstants.DEFAULT_NONE);
+ }
+
+ @Override
+ protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request){
+ return request.getAttribute(name, RequestAttributes.SCOPE_SESSION);
+ }
+
+ @Override
+ protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
+ throw new ServletRequestBindingException("Missing session attribute '" + name +
+ "' of type " + parameter.getNestedParameterType().getSimpleName());
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.java
index 4bc3267e..72b283f0 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -38,6 +38,8 @@ public class SseEmitter extends ResponseBodyEmitter {
static final MediaType TEXT_PLAIN = new MediaType("text", "plain", Charset.forName("UTF-8"));
+ static final MediaType UTF8_TEXT_EVENTSTREAM = new MediaType("text", "event-stream", Charset.forName("UTF-8"));
+
/**
* Create a new SseEmitter instance.
@@ -65,7 +67,7 @@ public class SseEmitter extends ResponseBodyEmitter {
HttpHeaders headers = outputMessage.getHeaders();
if (headers.getContentType() == null) {
- headers.setContentType(new MediaType("text", "event-stream"));
+ headers.setContentType(UTF8_TEXT_EVENTSTREAM);
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandler.java
index 40852899..a3b64612 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBodyReturnValueHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.springframework.web.servlet.mvc.method.annotation;
import java.io.OutputStream;
import java.util.concurrent.Callable;
-
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -33,7 +33,6 @@ import org.springframework.web.filter.ShallowEtagHeaderFilter;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
-
/**
* Supports return values of type
* {@link org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody}
@@ -68,14 +67,14 @@ public class StreamingResponseBodyReturnValueHandler implements HandlerMethodRet
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
ServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
- if (ResponseEntity.class.isAssignableFrom(returnValue.getClass())) {
+ if (returnValue instanceof ResponseEntity) {
ResponseEntity<?> responseEntity = (ResponseEntity<?>) returnValue;
- outputMessage.setStatusCode(responseEntity.getStatusCode());
+ response.setStatus(responseEntity.getStatusCodeValue());
outputMessage.getHeaders().putAll(responseEntity.getHeaders());
-
returnValue = responseEntity.getBody();
if (returnValue == null) {
mavContainer.setRequestHandled(true);
+ outputMessage.flush();
return;
}
}
@@ -97,7 +96,6 @@ public class StreamingResponseBodyReturnValueHandler implements HandlerMethodRet
private final StreamingResponseBody streamingBody;
-
public StreamingResponseBodyTask(OutputStream outputStream, StreamingResponseBody streamingBody) {
this.outputStream = outputStream;
this.streamingBody = streamingBody;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/AbstractUrlMethodNameResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/AbstractUrlMethodNameResolver.java
index 3ee748d4..0cc4fd1d 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/AbstractUrlMethodNameResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/AbstractUrlMethodNameResolver.java
@@ -34,7 +34,9 @@ import org.springframework.web.util.UrlPathHelper;
*
* @author Juergen Hoeller
* @since 14.01.2004
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
public abstract class AbstractUrlMethodNameResolver implements MethodNameResolver {
/** Logger available to subclasses */
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/InternalPathMethodNameResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/InternalPathMethodNameResolver.java
index f81e806b..4f0845e2 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/InternalPathMethodNameResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/InternalPathMethodNameResolver.java
@@ -35,7 +35,9 @@ import org.springframework.web.util.WebUtils;
*
* @author Rod Johnson
* @author Juergen Hoeller
-*/
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
+ */
+@Deprecated
public class InternalPathMethodNameResolver extends AbstractUrlMethodNameResolver {
private String prefix = "";
@@ -97,7 +99,7 @@ public class InternalPathMethodNameResolver extends AbstractUrlMethodNameResolve
/**
* Extract the handler method name from the given request URI.
- * Delegates to {@code WebUtils.extractViewNameFromUrlPath(String)}.
+ * Delegates to {@code WebUtils.extractFilenameFromUrlPath(String)}.
* @param uri the request URI (e.g. "/index.html")
* @return the extracted URI filename (e.g. "index")
* @see org.springframework.web.util.WebUtils#extractFilenameFromUrlPath
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MethodNameResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MethodNameResolver.java
index d67f3019..8d4751f8 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MethodNameResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MethodNameResolver.java
@@ -28,7 +28,9 @@ import javax.servlet.http.HttpServletRequest;
*
* @author Rod Johnson
* @see MultiActionController#setMethodNameResolver
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
public interface MethodNameResolver {
/**
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MultiActionController.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MultiActionController.java
index 188c9667..6ef9b7bf 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MultiActionController.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/MultiActionController.java
@@ -127,7 +127,9 @@ import org.springframework.web.servlet.mvc.LastModified;
* @see ParameterMethodNameResolver
* @see org.springframework.web.servlet.mvc.LastModified#getLastModified
* @see org.springframework.web.bind.ServletRequestDataBinder
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
public class MultiActionController extends AbstractController implements LastModified {
/** Suffix for last-modified methods */
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/NoSuchRequestHandlingMethodException.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/NoSuchRequestHandlingMethodException.java
index 1038c800..7d7d547f 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/NoSuchRequestHandlingMethodException.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/NoSuchRequestHandlingMethodException.java
@@ -30,7 +30,9 @@ import org.springframework.web.util.UrlPathHelper;
* @author Rod Johnson
* @author Juergen Hoeller
* @see MethodNameResolver#getHandlerMethodName(javax.servlet.http.HttpServletRequest)
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
@SuppressWarnings("serial")
public class NoSuchRequestHandlingMethodException extends ServletException {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/ParameterMethodNameResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/ParameterMethodNameResolver.java
index ff1b4547..55971724 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/ParameterMethodNameResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/ParameterMethodNameResolver.java
@@ -79,7 +79,9 @@ import org.springframework.web.util.WebUtils;
* @see #setMethodParamNames
* @see #setLogicalMappings
* @see #setDefaultMethodName
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
public class ParameterMethodNameResolver implements MethodNameResolver {
/**
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/PropertiesMethodNameResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/PropertiesMethodNameResolver.java
index 0f458a9e..8bc14b06 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/PropertiesMethodNameResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/multiaction/PropertiesMethodNameResolver.java
@@ -45,7 +45,9 @@ import org.springframework.util.PathMatcher;
* @author Juergen Hoeller
* @see java.util.Properties
* @see org.springframework.util.AntPathMatcher
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
public class PropertiesMethodNameResolver extends AbstractUrlMethodNameResolver
implements InitializingBean {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AbstractControllerUrlHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AbstractControllerUrlHandlerMapping.java
index 7893bc59..5604ed32 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AbstractControllerUrlHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AbstractControllerUrlHandlerMapping.java
@@ -31,7 +31,9 @@ import org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMappin
* @since 2.5.3
* @see ControllerClassNameHandlerMapping
* @see ControllerBeanNameHandlerMapping
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
public abstract class AbstractControllerUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
private ControllerTypePredicate predicate = new AnnotationControllerTypePredicate();
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AnnotationControllerTypePredicate.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AnnotationControllerTypePredicate.java
index ef0d574d..d9c3cfae 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AnnotationControllerTypePredicate.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/AnnotationControllerTypePredicate.java
@@ -25,7 +25,9 @@ import org.springframework.stereotype.Controller;
*
* @author Juergen Hoeller
* @since 2.5.3
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
class AnnotationControllerTypePredicate extends ControllerTypePredicate {
@Override
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerBeanNameHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerBeanNameHandlerMapping.java
index dc88ef32..90fa2159 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerBeanNameHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerBeanNameHandlerMapping.java
@@ -37,7 +37,9 @@ import org.springframework.util.StringUtils;
* @since 2.5.3
* @see ControllerClassNameHandlerMapping
* @see org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
public class ControllerBeanNameHandlerMapping extends AbstractControllerUrlHandlerMapping {
private String urlPrefix = "";
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerClassNameHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerClassNameHandlerMapping.java
index 752554cc..b47545dd 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerClassNameHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerClassNameHandlerMapping.java
@@ -18,7 +18,6 @@ package org.springframework.web.servlet.mvc.support;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
-import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
/**
* Implementation of {@link org.springframework.web.servlet.HandlerMapping} that
@@ -36,7 +35,7 @@ import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
* <li>{@code HomeController} -> {@code /home*}</li>
* </ul>
*
- * <p>For {@link MultiActionController MultiActionControllers} and {@code @Controller}
+ * <p>For {@code MultiActionController MultiActionControllers} and {@code @Controller}
* beans, a similar mapping is registered, except that all sub-paths are registered
* using the trailing wildcard pattern {@code /*}. For example:
* <ul>
@@ -44,7 +43,7 @@ import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
* <li>{@code CatalogController} -> {@code /catalog}, {@code /catalog/*}</li>
* </ul>
*
- * <p>For {@link MultiActionController} it is often useful to use
+ * <p>For {@code MultiActionController} it is often useful to use
* this mapping strategy in conjunction with the
* {@link org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver}.
*
@@ -56,7 +55,9 @@ import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
* @since 2.0
* @see org.springframework.web.servlet.mvc.Controller
* @see org.springframework.web.servlet.mvc.multiaction.MultiActionController
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
public class ControllerClassNameHandlerMapping extends AbstractControllerUrlHandlerMapping {
/**
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerTypePredicate.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerTypePredicate.java
index 33571f46..20f690d4 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerTypePredicate.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/ControllerTypePredicate.java
@@ -17,22 +17,24 @@
package org.springframework.web.servlet.mvc.support;
import org.springframework.web.servlet.mvc.Controller;
-import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
/**
* Internal helper class that identifies controller types.
*
* @author Juergen Hoeller
* @since 2.5.3
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
*/
+@Deprecated
class ControllerTypePredicate {
public boolean isControllerType(Class<?> beanClass) {
return Controller.class.isAssignableFrom(beanClass);
}
+ @SuppressWarnings("deprecation")
public boolean isMultiActionControllerType(Class<?> beanClass) {
- return MultiActionController.class.isAssignableFrom(beanClass);
+ return org.springframework.web.servlet.mvc.multiaction.MultiActionController.class.isAssignableFrom(beanClass);
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java
index 77290e56..6dea3daa 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java
@@ -49,7 +49,6 @@ import org.springframework.web.multipart.support.MissingServletRequestPartExcept
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
-import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
/**
* Default implementation of the {@link org.springframework.web.servlet.HandlerExceptionResolver
@@ -101,13 +100,14 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
@Override
+ @SuppressWarnings("deprecation")
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
try {
- if (ex instanceof NoSuchRequestHandlingMethodException) {
- return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, request, response,
- handler);
+ if (ex instanceof org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) {
+ return handleNoSuchRequestHandlingMethod((org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) ex,
+ request, response, handler);
}
else if (ex instanceof HttpRequestMethodNotSupportedException) {
return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request,
@@ -180,8 +180,10 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
* at the time of the exception (for example, if multipart resolution failed)
* @return an empty ModelAndView indicating the exception was handled
* @throws IOException potentially thrown from response.sendError()
+ * @deprecated as of 4.3, along with {@link org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException}
*/
- protected ModelAndView handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex,
+ @Deprecated
+ protected ModelAndView handleNoSuchRequestHandlingMethod(org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException ex,
HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
pageNotFoundLogger.warn(ex.getMessage());
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CssLinkResourceTransformer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CssLinkResourceTransformer.java
index 4380246e..fa884f7a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CssLinkResourceTransformer.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CssLinkResourceTransformer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -49,12 +49,11 @@ import org.springframework.util.StringUtils;
*/
public class CssLinkResourceTransformer extends ResourceTransformerSupport {
- private static final Log logger = LogFactory.getLog(CssLinkResourceTransformer.class);
-
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+ private static final Log logger = LogFactory.getLog(CssLinkResourceTransformer.class);
- private final List<CssLinkParser> linkParsers = new ArrayList<CssLinkParser>();
+ private final List<CssLinkParser> linkParsers = new ArrayList<CssLinkParser>(2);
public CssLinkResourceTransformer() {
@@ -81,7 +80,7 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
byte[] bytes = FileCopyUtils.copyToByteArray(resource.getInputStream());
String content = new String(bytes, DEFAULT_CHARSET);
- Set<CssLinkInfo> infos = new HashSet<CssLinkInfo>(5);
+ Set<CssLinkInfo> infos = new HashSet<CssLinkInfo>(8);
for (CssLinkParser parser : this.linkParsers) {
parser.parseLink(content, infos);
}
@@ -123,17 +122,16 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
private boolean hasScheme(String link) {
int schemeIndex = link.indexOf(":");
- return (schemeIndex > 0 && !link.substring(0, schemeIndex).contains("/"))
- || link.indexOf("//") == 0;
+ return (schemeIndex > 0 && !link.substring(0, schemeIndex).contains("/")) || link.indexOf("//") == 0;
}
- protected static interface CssLinkParser {
+ protected interface CssLinkParser {
void parseLink(String content, Set<CssLinkInfo> linkInfos);
-
}
+
protected static abstract class AbstractCssLinkParser implements CssLinkParser {
/**
@@ -189,6 +187,7 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
}
+
private static class ImportStatementCssLinkParser extends AbstractCssLinkParser {
@Override
@@ -208,6 +207,7 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
}
}
+
private static class UrlFunctionCssLinkParser extends AbstractCssLinkParser {
@Override
@@ -229,8 +229,7 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
private final int end;
-
- private CssLinkInfo(int start, int end) {
+ public CssLinkInfo(int start, int end) {
this.start = start;
this.end = end;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java
index fd25c306..cf9c020e 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java
@@ -16,17 +16,13 @@
package org.springframework.web.servlet.resource;
-import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
-import javax.activation.FileTypeMap;
-import javax.activation.MimetypesFileTypeMap;
+import javax.servlet.ServletContext;
import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -34,21 +30,27 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
-import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.ResourceRegion;
import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRange;
import org.springframework.http.MediaType;
+import org.springframework.http.converter.ResourceHttpMessageConverter;
+import org.springframework.http.converter.ResourceRegionHttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
-import org.springframework.util.MimeTypeUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils;
-import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
+import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpRequestHandler;
+import org.springframework.web.accept.ContentNegotiationManager;
+import org.springframework.web.accept.ContentNegotiationManagerFactoryBean;
+import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
@@ -56,49 +58,45 @@ import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.support.WebContentGenerator;
/**
- * {@link HttpRequestHandler} that serves static resources optimized for superior browser performance
- * (according to the guidelines of Page Speed, YSlow, etc.) by allowing for flexible cache settings
- * ({@linkplain #setCacheSeconds "cacheSeconds" property}, last-modified support).
+ * {@code HttpRequestHandler} that serves static resources in an optimized way
+ * according to the guidelines of Page Speed, YSlow, etc.
*
- * <p>The {@linkplain #setLocations "locations" property} takes a list of Spring {@link Resource}
- * locations from which static resources are allowed to be served by this handler. For a given request,
- * the list of locations will be consulted in order for the presence of the requested resource, and the
- * first found match will be written to the response, with a HTTP Caching headers
- * set as configured. The handler also properly evaluates the {@code Last-Modified} header
- * (if present) so that a {@code 304} status code will be returned as appropriate, avoiding unnecessary
- * overhead for resources that are already cached by the client. The use of {@code Resource} locations
- * allows resource requests to easily be mapped to locations other than the web application root.
- * For example, resources could be served from a classpath location such as
- * "classpath:/META-INF/public-web-resources/", allowing convenient packaging and serving of resources
- * such as a JavaScript library from within jar files.
+ * <p>The {@linkplain #setLocations "locations"} property takes a list of Spring
+ * {@link Resource} locations from which static resources are allowed to
+ * be served by this handler. Resources could be served from a classpath location,
+ * e.g. "classpath:/META-INF/public-web-resources/", allowing convenient packaging
+ * and serving of resources such as .js, .css, and others in jar files.
*
- * <p>To ensure that users with a primed browser cache get the latest changes to application-specific
- * resources upon deployment of new versions of the application, it is recommended that a version
- * string is used in the URL mapping pattern that selects this handler. Such patterns can be easily
- * parameterized using Spring EL. See the reference manual for further examples of this approach.
+ * <p>This request handler may also be configured with a
+ * {@link #setResourceResolvers(List) resourcesResolver} and
+ * {@link #setResourceTransformers(List) resourceTransformer} chains to support
+ * arbitrary resolution and transformation of resources being served. By default a
+ * {@link PathResourceResolver} simply finds resources based on the configured
+ * "locations". An application can configure additional resolvers and
+ * transformers such as the {@link VersionResourceResolver} which can resolve
+ * and prepare URLs for resources with a version in the URL.
*
- * <p>For various front-end needs &mdash; such as ensuring that users with a primed browser cache
- * get the latest changes, or serving variations of resources (e.g., minified versions) &mdash;
- * {@link org.springframework.web.servlet.resource.ResourceResolver}s can be configured.
- *
- * <p>This handler can be configured through use of a
- * {@link org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry}
- * or the {@code <mvc:resources/>} XML configuration element.
+ * <p>This handler also properly evaluates the {@code Last-Modified} header (if
+ * present) so that a {@code 304} status code will be returned as appropriate,
+ * avoiding unnecessary overhead for resources that are already cached by the
+ * client.
*
* @author Keith Donald
* @author Jeremy Grelle
* @author Juergen Hoeller
* @author Arjen Poutsma
* @author Brian Clozel
+ * @author Rossen Stoyanchev
* @since 3.0.4
*/
public class ResourceHttpRequestHandler extends WebContentGenerator
implements HttpRequestHandler, InitializingBean, CorsConfigurationSource {
- private static final Log logger = LogFactory.getLog(ResourceHttpRequestHandler.class);
+ // Servlet 3.1 setContentLengthLong(long) available?
+ private static final boolean contentLengthLongAvailable =
+ ClassUtils.hasMethod(ServletResponse.class, "setContentLengthLong", long.class);
- private static final boolean jafPresent = ClassUtils.isPresent(
- "javax.activation.FileTypeMap", ResourceHttpRequestHandler.class.getClassLoader());
+ private static final Log logger = LogFactory.getLog(ResourceHttpRequestHandler.class);
private final List<Resource> locations = new ArrayList<Resource>(4);
@@ -107,17 +105,24 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
private final List<ResourceTransformer> resourceTransformers = new ArrayList<ResourceTransformer>(4);
+ private ResourceHttpMessageConverter resourceHttpMessageConverter;
+
+ private ResourceRegionHttpMessageConverter resourceRegionHttpMessageConverter;
+
+ private ContentNegotiationManager contentNegotiationManager;
+
+ private final ContentNegotiationManagerFactoryBean cnmFactoryBean = new ContentNegotiationManagerFactoryBean();
+
private CorsConfiguration corsConfiguration;
public ResourceHttpRequestHandler() {
- super(METHOD_GET, METHOD_HEAD);
- this.resourceResolvers.add(new PathResourceResolver());
+ super(HttpMethod.GET.name(), HttpMethod.HEAD.name());
}
/**
- * Set a {@code List} of {@code Resource} paths to use as sources
+ * Set the {@code List} of {@code Resource} paths to use as sources
* for serving static resources.
*/
public void setLocations(List<Resource> locations) {
@@ -126,6 +131,10 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
this.locations.addAll(locations);
}
+ /**
+ * Return the {@code List} of {@code Resource} paths to use as sources
+ * for serving static resources.
+ */
public List<Resource> getLocations() {
return this.locations;
}
@@ -167,15 +176,87 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
return this.resourceTransformers;
}
+ /**
+ * Configure the {@link ResourceHttpMessageConverter} to use.
+ * <p>By default a {@link ResourceHttpMessageConverter} will be configured.
+ * @since 4.3
+ */
+ public void setResourceHttpMessageConverter(ResourceHttpMessageConverter resourceHttpMessageConverter) {
+ this.resourceHttpMessageConverter = resourceHttpMessageConverter;
+ }
+
+ /**
+ * Return the list of configured resource converters.
+ * @since 4.3
+ */
+ public ResourceHttpMessageConverter getResourceHttpMessageConverter() {
+ return this.resourceHttpMessageConverter;
+ }
+
+ /**
+ * Configure the {@link ResourceRegionHttpMessageConverter} to use.
+ * <p>By default a {@link ResourceRegionHttpMessageConverter} will be configured.
+ * @since 4.3
+ */
+ public void setResourceRegionHttpMessageConverter(ResourceRegionHttpMessageConverter resourceRegionHttpMessageConverter) {
+ this.resourceRegionHttpMessageConverter = resourceRegionHttpMessageConverter;
+ }
+
+ /**
+ * Return the list of configured resource region converters.
+ * @since 4.3
+ */
+ public ResourceRegionHttpMessageConverter getResourceRegionHttpMessageConverter() {
+ return this.resourceRegionHttpMessageConverter;
+ }
+
+ /**
+ * Configure a {@code ContentNegotiationManager} to determine the media types
+ * for resources being served. If the manager contains a path
+ * extension strategy it will be used to look up the file extension
+ * of resources being served via
+ * {@link PathExtensionContentNegotiationStrategy#getMediaTypeForResource
+ * getMediaTypeForResource}. If that fails the check is then expanded
+ * to use any configured content negotiation strategy against the request.
+ * <p>By default a {@link ContentNegotiationManagerFactoryBean} with default
+ * settings is used to create the manager. See the Javadoc of
+ * {@code ContentNegotiationManagerFactoryBean} for details
+ * @param contentNegotiationManager the manager to use
+ * @since 4.3
+ */
+ public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
+ this.contentNegotiationManager = contentNegotiationManager;
+ }
+
+ /**
+ * Return the specified content negotiation manager.
+ * @since 4.3
+ */
+ public ContentNegotiationManager getContentNegotiationManager() {
+ return this.contentNegotiationManager;
+ }
+
+ /**
+ * Specify the CORS configuration for resources served by this handler.
+ * <p>By default this is not set in which allows cross-origin requests.
+ */
public void setCorsConfiguration(CorsConfiguration corsConfiguration) {
this.corsConfiguration = corsConfiguration;
}
+ /**
+ * Return the specified CORS configuration.
+ */
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
return this.corsConfiguration;
}
+ @Override
+ protected void initServletContext(ServletContext servletContext) {
+ this.cnmFactoryBean.setServletContext(servletContext);
+ }
+
@Override
public void afterPropertiesSet() throws Exception {
@@ -183,7 +264,20 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
logger.warn("Locations list is empty. No resources will be served unless a " +
"custom ResourceResolver is configured as an alternative to PathResourceResolver.");
}
+ if (this.resourceResolvers.isEmpty()) {
+ this.resourceResolvers.add(new PathResourceResolver());
+ }
initAllowedLocations();
+ if (this.contentNegotiationManager == null) {
+ this.cnmFactoryBean.afterPropertiesSet();
+ this.contentNegotiationManager = this.cnmFactoryBean.getObject();
+ }
+ if (this.resourceHttpMessageConverter == null) {
+ this.resourceHttpMessageConverter = new ResourceHttpMessageConverter();
+ }
+ if(this.resourceRegionHttpMessageConverter == null) {
+ this.resourceRegionHttpMessageConverter = new ResourceRegionHttpMessageConverter();
+ }
}
/**
@@ -206,7 +300,6 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
}
}
-
/**
* Processes a resource request.
* <p>Checks for the existence of the requested resource in the configured list of locations.
@@ -223,10 +316,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
- // Supported methods and required session
- checkRequest(request);
-
- // Check whether a matching resource exists
+ // For very general mappings (e.g. "/") we need to check 404 first
Resource resource = getResource(request);
if (resource == null) {
logger.trace("No matching resource found - returning 404");
@@ -234,6 +324,14 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
return;
}
+ if (HttpMethod.OPTIONS.matches(request.getMethod())) {
+ response.setHeader("Allow", getAllowHeader());
+ return;
+ }
+
+ // Supported methods and required session
+ checkRequest(request);
+
// Header phase
if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {
logger.trace("Resource not modified - returning 304");
@@ -244,7 +342,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
prepareResponse(response);
// Check the media type for the resource
- MediaType mediaType = getMediaType(resource);
+ MediaType mediaType = getMediaType(request, resource);
if (mediaType != null) {
if (logger.isTraceEnabled()) {
logger.trace("Determined media type '" + mediaType + "' for " + resource);
@@ -263,12 +361,30 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
return;
}
+ ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
if (request.getHeader(HttpHeaders.RANGE) == null) {
setHeaders(response, resource, mediaType);
- writeContent(response, resource);
+ this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
}
else {
- writePartialContent(request, response, resource, mediaType);
+ response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
+ ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request);
+ try {
+ List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
+ response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+ if(httpRanges.size() == 1) {
+ ResourceRegion resourceRegion = httpRanges.get(0).toResourceRegion(resource);
+ this.resourceRegionHttpMessageConverter.write(resourceRegion, mediaType, outputMessage);
+ }
+ else {
+ this.resourceRegionHttpMessageConverter.write(
+ HttpRange.toResourceRegions(httpRanges, resource), mediaType, outputMessage);
+ }
+ }
+ catch (IllegalArgumentException ex) {
+ response.setHeader("Content-Range", "bytes */" + resource.contentLength());
+ response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+ }
}
}
@@ -384,26 +500,59 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
}
/**
- * Determine an appropriate media type for the given resource.
+ * Determine the media type for the given request and the resource matched
+ * to it. This implementation first tries to determine the MediaType based
+ * strictly on the file extension of the Resource via
+ * {@link PathExtensionContentNegotiationStrategy#getMediaTypeForResource}
+ * and then expands to check against the request via
+ * {@link ContentNegotiationManager#resolveMediaTypes}.
+ * @param request the current request
* @param resource the resource to check
* @return the corresponding media type, or {@code null} if none found
*/
- protected MediaType getMediaType(Resource resource) {
- MediaType mediaType = null;
- String mimeType = getServletContext().getMimeType(resource.getFilename());
- if (StringUtils.hasText(mimeType)) {
- mediaType = MediaType.parseMediaType(mimeType);
- }
- if (jafPresent && (mediaType == null || MediaType.APPLICATION_OCTET_STREAM.equals(mediaType))) {
- MediaType jafMediaType = ActivationMediaTypeFactory.getMediaType(resource.getFilename());
- if (jafMediaType != null && !MediaType.APPLICATION_OCTET_STREAM.equals(jafMediaType)) {
- mediaType = jafMediaType;
+ @SuppressWarnings("deprecation")
+ protected MediaType getMediaType(HttpServletRequest request, Resource resource) {
+ // For backwards compatibility
+ MediaType mediaType = getMediaType(resource);
+ if (mediaType != null) {
+ return mediaType;
+ }
+
+ Class<PathExtensionContentNegotiationStrategy> clazz = PathExtensionContentNegotiationStrategy.class;
+ PathExtensionContentNegotiationStrategy strategy = this.contentNegotiationManager.getStrategy(clazz);
+ if (strategy != null) {
+ mediaType = strategy.getMediaTypeForResource(resource);
+ }
+
+ if (mediaType == null) {
+ ServletWebRequest webRequest = new ServletWebRequest(request);
+ try {
+ List<MediaType> mediaTypes = getContentNegotiationManager().resolveMediaTypes(webRequest);
+ if (!mediaTypes.isEmpty()) {
+ mediaType = mediaTypes.get(0);
+ }
+ }
+ catch (HttpMediaTypeNotAcceptableException ex) {
+ // Ignore
}
}
+
return mediaType;
}
/**
+ * Determine an appropriate media type for the given resource.
+ * @param resource the resource to check
+ * @return the corresponding media type, or {@code null} if none found
+ * @deprecated as of 4.3 this method is deprecated; please override
+ * {@link #getMediaType(HttpServletRequest, Resource)} instead.
+ */
+ @Deprecated
+ protected MediaType getMediaType(Resource resource) {
+ return null;
+ }
+
+ /**
* Set headers on the given servlet response.
* Called for GET requests as well as HEAD requests.
* @param response current servlet response
@@ -414,9 +563,17 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
protected void setHeaders(HttpServletResponse response, Resource resource, MediaType mediaType) throws IOException {
long length = resource.contentLength();
if (length > Integer.MAX_VALUE) {
- throw new IOException("Resource content too long (beyond Integer.MAX_VALUE): " + resource);
+ if (contentLengthLongAvailable) {
+ response.setContentLengthLong(length);
+ }
+ else {
+ response.setHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(length));
+ }
}
- response.setContentLength((int) length);
+ else {
+ response.setContentLength((int) length);
+ }
+
if (mediaType != null) {
response.setContentType(mediaType.toString());
}
@@ -429,187 +586,10 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
}
- /**
- * Write the actual content out to the given servlet response,
- * streaming the resource's content.
- * @param response current servlet response
- * @param resource the identified resource (never {@code null})
- * @throws IOException in case of errors while writing the content
- */
- protected void writeContent(HttpServletResponse response, Resource resource) throws IOException {
- try {
- InputStream in = resource.getInputStream();
- try {
- StreamUtils.copy(in, response.getOutputStream());
- }
- catch (NullPointerException ex) {
- // ignore, see SPR-13620
- }
- finally {
- try {
- in.close();
- }
- catch (Throwable ex) {
- // ignore, see SPR-12999
- }
- }
- }
- catch (FileNotFoundException ex) {
- // ignore, see SPR-12999
- }
- }
-
- /**
- * Write parts of the resource as indicated by the request {@code Range} header.
- * @param request current servlet request
- * @param response current servlet response
- * @param resource the identified resource (never {@code null})
- * @param contentType the content type
- * @throws IOException in case of errors while writing the content
- */
- protected void writePartialContent(HttpServletRequest request, HttpServletResponse response,
- Resource resource, MediaType contentType) throws IOException {
-
- long length = resource.contentLength();
-
- List<HttpRange> ranges;
- try {
- HttpHeaders headers = new ServletServerHttpRequest(request).getHeaders();
- ranges = headers.getRange();
- }
- catch (IllegalArgumentException ex) {
- response.addHeader("Content-Range", "bytes */" + length);
- response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
- return;
- }
-
- response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
-
- if (ranges.size() == 1) {
- HttpRange range = ranges.get(0);
-
- long start = range.getRangeStart(length);
- long end = range.getRangeEnd(length);
- long rangeLength = end - start + 1;
-
- setHeaders(response, resource, contentType);
- response.addHeader("Content-Range", "bytes " + start + "-" + end + "/" + length);
- response.setContentLength((int) rangeLength);
-
- InputStream in = resource.getInputStream();
- try {
- copyRange(in, response.getOutputStream(), start, end);
- }
- finally {
- try {
- in.close();
- }
- catch (IOException ex) {
- // ignore
- }
- }
- }
- else {
- String boundaryString = MimeTypeUtils.generateMultipartBoundaryString();
- response.setContentType("multipart/byteranges; boundary=" + boundaryString);
-
- ServletOutputStream out = response.getOutputStream();
-
- for (HttpRange range : ranges) {
- long start = range.getRangeStart(length);
- long end = range.getRangeEnd(length);
-
- InputStream in = resource.getInputStream();
-
- // Writing MIME header.
- out.println();
- out.println("--" + boundaryString);
- if (contentType != null) {
- out.println("Content-Type: " + contentType);
- }
- out.println("Content-Range: bytes " + start + "-" + end + "/" + length);
- out.println();
-
- // Printing content
- copyRange(in, out, start, end);
- }
- out.println();
- out.print("--" + boundaryString + "--");
- }
- }
-
- private void copyRange(InputStream in, OutputStream out, long start, long end) throws IOException {
- long skipped = in.skip(start);
- if (skipped < start) {
- throw new IOException("Skipped only " + skipped + " bytes out of " + start + " required.");
- }
-
- long bytesToCopy = end - start + 1;
- byte buffer[] = new byte[StreamUtils.BUFFER_SIZE];
- while (bytesToCopy > 0) {
- int bytesRead = in.read(buffer);
- if (bytesRead <= bytesToCopy) {
- out.write(buffer, 0, bytesRead);
- bytesToCopy -= bytesRead;
- }
- else {
- out.write(buffer, 0, (int) bytesToCopy);
- bytesToCopy = 0;
- }
- if (bytesRead == -1) {
- break;
- }
- }
- }
-
@Override
public String toString() {
return "ResourceHttpRequestHandler [locations=" + getLocations() + ", resolvers=" + getResourceResolvers() + "]";
}
-
- /**
- * Inner class to avoid a hard-coded JAF dependency.
- */
- private static class ActivationMediaTypeFactory {
-
- private static final FileTypeMap fileTypeMap;
-
- static {
- fileTypeMap = loadFileTypeMapFromContextSupportModule();
- }
-
- private static FileTypeMap loadFileTypeMapFromContextSupportModule() {
- // See if we can find the extended mime.types from the context-support module...
- Resource mappingLocation = new ClassPathResource("org/springframework/mail/javamail/mime.types");
- if (mappingLocation.exists()) {
- InputStream inputStream = null;
- try {
- inputStream = mappingLocation.getInputStream();
- return new MimetypesFileTypeMap(inputStream);
- }
- catch (IOException ex) {
- // ignore
- }
- finally {
- if (inputStream != null) {
- try {
- inputStream.close();
- }
- catch (IOException ex) {
- // ignore
- }
- }
- }
- }
- return FileTypeMap.getDefaultFileTypeMap();
- }
-
- public static MediaType getMediaType(String filename) {
- String mediaType = fileTypeMap.getContentType(filename);
- return (StringUtils.hasText(mediaType) ? MediaType.parseMediaType(mediaType) : null);
- }
- }
-
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceTransformer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceTransformer.java
index fe941d83..de17faf1 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceTransformer.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceTransformer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -35,10 +35,10 @@ public interface ResourceTransformer {
* @param request the current request
* @param resource the resource to transform
* @param transformerChain the chain of remaining transformers to delegate to
- * @return the transformed resource, never {@code null}
+ * @return the transformed resource (never {@code null})
* @throws IOException if the transformation fails
*/
Resource transform(HttpServletRequest request, Resource resource, ResourceTransformerChain transformerChain)
throws IOException;
-} \ No newline at end of file
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilter.java
index 471ae154..901d0c5e 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilter.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilter.java
@@ -27,6 +27,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.filter.OncePerRequestFilter;
+import org.springframework.web.util.UrlPathHelper;
/**
* A filter that wraps the {@link HttpServletResponse} and overrides its
@@ -96,13 +97,14 @@ public class ResourceUrlEncodingFilter extends OncePerRequestFilter {
private void initLookupPath(ResourceUrlProvider urlProvider) {
if (this.indexLookupPath == null) {
- String requestUri = urlProvider.getPathHelper().getRequestUri(this.request);
- String lookupPath = urlProvider.getPathHelper().getLookupPathForRequest(this.request);
+ UrlPathHelper pathHelper = urlProvider.getUrlPathHelper();
+ String requestUri = pathHelper.getRequestUri(this.request);
+ String lookupPath = pathHelper.getLookupPathForRequest(this.request);
this.indexLookupPath = requestUri.lastIndexOf(lookupPath);
this.prefixLookupPath = requestUri.substring(0, this.indexLookupPath);
if ("/".equals(lookupPath) && !"/".equals(requestUri)) {
- String contextPath = urlProvider.getPathHelper().getContextPath(this.request);
+ String contextPath = pathHelper.getContextPath(this.request);
if (requestUri.equals(contextPath)) {
this.indexLookupPath = requestUri.length();
this.prefixLookupPath = requestUri;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlProvider.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlProvider.java
index 9ee0c5a8..f39e02ff 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlProvider.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -51,7 +51,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
protected final Log logger = LogFactory.getLog(getClass());
- private UrlPathHelper pathHelper = new UrlPathHelper();
+ private UrlPathHelper urlPathHelper = new UrlPathHelper();
private PathMatcher pathMatcher = new AntPathMatcher();
@@ -65,15 +65,24 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
* {@link #getForRequestUrl(javax.servlet.http.HttpServletRequest, String)}
* in order to derive the lookup path for a target request URL path.
*/
- public void setUrlPathHelper(UrlPathHelper pathHelper) {
- this.pathHelper = pathHelper;
+ public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
+ this.urlPathHelper = urlPathHelper;
}
/**
* Return the configured {@code UrlPathHelper}.
+ * @since 4.2.8
*/
+ public UrlPathHelper getUrlPathHelper() {
+ return this.urlPathHelper;
+ }
+
+ /**
+ * @deprecated as of Spring 4.2.8, in favor of {@link #getUrlPathHelper}
+ */
+ @Deprecated
public UrlPathHelper getPathHelper() {
- return this.pathHelper;
+ return this.urlPathHelper;
}
/**
@@ -135,6 +144,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
}
}
+
protected void detectResourceHandlers(ApplicationContext appContext) {
logger.debug("Looking for resource handler mappings");
@@ -158,7 +168,6 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
}
}
-
/**
* A variation on {@link #getForLookupPath(String)} that accepts a full request
* URL path (i.e. including context and servlet path) and returns the full request
@@ -181,8 +190,9 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
}
private int getLookupPathIndex(HttpServletRequest request) {
- String requestUri = getPathHelper().getRequestUri(request);
- String lookupPath = getPathHelper().getLookupPathForRequest(request);
+ UrlPathHelper pathHelper = getUrlPathHelper();
+ String requestUri = pathHelper.getRequestUri(request);
+ String lookupPath = pathHelper.getLookupPathForRequest(request);
return requestUri.indexOf(lookupPath);
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java
index 8d08bbd7..a76214c3 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -45,11 +45,29 @@ import org.springframework.core.io.Resource;
*/
public class WebJarsResourceResolver extends AbstractResourceResolver {
- private final static String WEBJARS_LOCATION = "META-INF/resources/webjars";
+ private final static String WEBJARS_LOCATION = "META-INF/resources/webjars/";
private final static int WEBJARS_LOCATION_LENGTH = WEBJARS_LOCATION.length();
- private final WebJarAssetLocator webJarAssetLocator = new WebJarAssetLocator();
+
+ private final WebJarAssetLocator webJarAssetLocator;
+
+
+ /**
+ * Create a {@code WebJarsResourceResolver} with a default {@code WebJarAssetLocator} instance.
+ */
+ public WebJarsResourceResolver() {
+ this(new WebJarAssetLocator());
+ }
+
+ /**
+ * Create a {@code WebJarsResourceResolver} with a custom {@code WebJarAssetLocator} instance,
+ * e.g. with a custom index.
+ * @since 4.3
+ */
+ public WebJarsResourceResolver(WebJarAssetLocator webJarAssetLocator) {
+ this.webJarAssetLocator = webJarAssetLocator;
+ }
@Override
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractAnnotationConfigDispatcherServletInitializer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractAnnotationConfigDispatcherServletInitializer.java
index fc897a10..20b2b9e4 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractAnnotationConfigDispatcherServletInitializer.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractAnnotationConfigDispatcherServletInitializer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -32,6 +32,9 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon
* Further template and customization methods are provided by
* {@link AbstractDispatcherServletInitializer}.
*
+ * <p>This is the preferred approach for applications that use Java-based
+ * Spring configuration.
+ *
* @author Arjen Poutsma
* @author Chris Beams
* @since 3.2
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java
index 9fae8679..df008b77 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -53,17 +53,18 @@ import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils;
/**
- * Context holder for request-specific state, like current web application context, current locale, current theme,
- * and potential binding errors. Provides easy access to localized messages and Errors instances.
+ * Context holder for request-specific state, like current web application context, current locale,
+ * current theme, and potential binding errors. Provides easy access to localized messages and
+ * Errors instances.
*
- * <p>Suitable for exposition to views, and usage within JSP's "useBean" tag, JSP scriptlets, JSTL EL, Velocity
- * templates, etc. Necessary for views that do not have access to the servlet request, like Velocity templates.
+ * <p>Suitable for exposition to views, and usage within JSP's "useBean" tag, JSP scriptlets, JSTL EL,
+ * etc. Necessary for views that do not have access to the servlet request, like FreeMarker templates.
*
* <p>Can be instantiated manually, or automatically exposed to views as model attribute via AbstractView's
* "requestContextAttribute" property.
*
- * <p>Will also work outside of DispatcherServlet requests, accessing the root WebApplicationContext and using
- * an appropriate fallback for the locale (the HttpServletRequest's primary locale).
+ * <p>Will also work outside of DispatcherServlet requests, accessing the root WebApplicationContext
+ * and using an appropriate fallback for the locale (the HttpServletRequest's primary locale).
*
* @author Juergen Hoeller
* @author Rossen Stoyanchev
@@ -467,7 +468,7 @@ public class RequestContext {
/**
* (De)activate default HTML escaping for messages and errors, for the scope of this RequestContext.
* <p>The default is the application-wide setting (the "defaultHtmlEscape" context-param in web.xml).
- * @see org.springframework.web.util.WebUtils#isDefaultHtmlEscape
+ * @see org.springframework.web.util.WebUtils#getDefaultHtmlEscape
*/
public void setDefaultHtmlEscape(boolean defaultHtmlEscape) {
this.defaultHtmlEscape = defaultHtmlEscape;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java
index ac6e0769..8c854917 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java
@@ -198,7 +198,7 @@ public abstract class RequestContextUtils {
* <p>Consider using {@link org.springframework.context.i18n.LocaleContextHolder#getTimeZone()}
* which will normally be populated with the same TimeZone: That method only
* differs in terms of its fallback to the system time zone if the LocaleResolver
- * hasn't provided provided a specific time zone (instead of this method's {@code null}).
+ * hasn't provided a specific time zone (instead of this method's {@code null}).
* @param request current HTTP request
* @return the current time zone for the given request, either from the
* TimeZoneAwareLocaleResolver or {@code null} if none associated
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java
index 07abc64b..367aed85 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java
@@ -16,6 +16,7 @@
package org.springframework.web.servlet.support;
+import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpRequest;
@@ -27,8 +28,8 @@ import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
+import org.springframework.web.util.UriUtils;
import org.springframework.web.util.UrlPathHelper;
-import org.springframework.web.util.WebUtils;
/**
* A UriComponentsBuilder that extracts information from the HttpServletRequest.
@@ -43,7 +44,6 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder {
/**
* Default constructor. Protected to prevent direct instantiation.
- *
* @see #fromContextPath(HttpServletRequest)
* @see #fromServletMapping(HttpServletRequest)
* @see #fromRequest(HttpServletRequest)
@@ -133,8 +133,15 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder {
}
private static String prependForwardedPrefix(HttpServletRequest request, String path) {
- String prefix = request.getHeader("X-Forwarded-Prefix");
- if (StringUtils.hasText(prefix)) {
+ String prefix = null;
+ Enumeration<String> names = request.getHeaderNames();
+ while (names.hasMoreElements()) {
+ String name = names.nextElement();
+ if ("X-Forwarded-Prefix".equalsIgnoreCase(name)) {
+ prefix = request.getHeader(name);
+ }
+ }
+ if (prefix != null) {
path = prefix + path;
}
return path;
@@ -211,8 +218,7 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder {
public String removePathExtension() {
String extension = null;
if (this.originalPath != null) {
- String filename = WebUtils.extractFullFilenameFromUrlPath(this.originalPath);
- extension = StringUtils.getFilenameExtension(filename);
+ extension = UriUtils.extractFileExtension(this.originalPath);
if (!StringUtils.isEmpty(extension)) {
int end = this.originalPath.length() - (extension.length() + 1);
replacePath(this.originalPath.substring(0, end));
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java
index 20cf89d2..6cf54cf2 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java
@@ -16,8 +16,10 @@
package org.springframework.web.servlet.support;
+import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashSet;
+import java.util.Collection;
+import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -26,6 +28,9 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.CacheControl;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -53,6 +58,7 @@ import org.springframework.web.context.support.WebApplicationObjectSupport;
* @author Rod Johnson
* @author Juergen Hoeller
* @author Brian Clozel
+ * @author Rossen Stoyanchev
* @see #setCacheSeconds
* @see #setCacheControl
* @see #setRequireSession
@@ -74,16 +80,27 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
protected static final String HEADER_CACHE_CONTROL = "Cache-Control";
+ /** Checking for Servlet 3.0+ HttpServletResponse.getHeaders(String) */
+ private static final boolean servlet3Present =
+ ClassUtils.hasMethod(HttpServletResponse.class, "getHeaders", String.class);
+
/** Set of supported HTTP methods */
private Set<String> supportedMethods;
+ private String allowHeader;
+
private boolean requireSession = false;
private CacheControl cacheControl;
private int cacheSeconds = -1;
+ private String[] varyByRequestHeaders;
+
+
+ // deprecated fields
+
/** Use HTTP 1.0 expires header? */
private boolean useExpiresHeader = false;
@@ -112,11 +129,12 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
*/
public WebContentGenerator(boolean restrictDefaultSupportedMethods) {
if (restrictDefaultSupportedMethods) {
- this.supportedMethods = new HashSet<String>(4);
+ this.supportedMethods = new LinkedHashSet<String>(4);
this.supportedMethods.add(METHOD_GET);
this.supportedMethods.add(METHOD_HEAD);
this.supportedMethods.add(METHOD_POST);
}
+ initAllowHeader();
}
/**
@@ -124,7 +142,7 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
* @param supportedMethods the supported HTTP methods for this content generator
*/
public WebContentGenerator(String... supportedMethods) {
- this.supportedMethods = new HashSet<String>(Arrays.asList(supportedMethods));
+ setSupportedMethods(supportedMethods);
}
@@ -140,6 +158,7 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
else {
this.supportedMethods = null;
}
+ initAllowHeader();
}
/**
@@ -149,6 +168,40 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
return StringUtils.toStringArray(this.supportedMethods);
}
+ private void initAllowHeader() {
+ Collection<String> allowedMethods;
+ if (this.supportedMethods == null) {
+ allowedMethods = new ArrayList<String>(HttpMethod.values().length - 1);
+ for (HttpMethod method : HttpMethod.values()) {
+ if (!HttpMethod.TRACE.equals(method)) {
+ allowedMethods.add(method.name());
+ }
+ }
+ }
+ else if (this.supportedMethods.contains(HttpMethod.OPTIONS.name())) {
+ allowedMethods = this.supportedMethods;
+ }
+ else {
+ allowedMethods = new ArrayList<String>(this.supportedMethods);
+ allowedMethods.add(HttpMethod.OPTIONS.name());
+
+ }
+ this.allowHeader = StringUtils.collectionToCommaDelimitedString(allowedMethods);
+ }
+
+ /**
+ * Return the "Allow" header value to use in response to an HTTP OPTIONS
+ * request based on the configured {@link #setSupportedMethods supported
+ * methods} also automatically adding "OPTIONS" to the list even if not
+ * present as a supported method. This means sub-classes don't have to
+ * explicitly list "OPTIONS" as a supported method as long as HTTP OPTIONS
+ * requests are handled before making a call to
+ * {@link #checkRequest(HttpServletRequest)}.
+ */
+ protected String getAllowHeader() {
+ return this.allowHeader;
+ }
+
/**
* Set whether a session should be required to handle requests.
*/
@@ -205,6 +258,29 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
}
/**
+ * Configure one or more request header names (e.g. "Accept-Language") to
+ * add to the "Vary" response header to inform clients that the response is
+ * subject to content negotiation and variances based on the value of the
+ * given request headers. The configured request header names are added only
+ * if not already present in the response "Vary" header.
+ * <p><strong>Note:</strong> This property is only supported on Servlet 3.0+
+ * which allows checking existing response header values.
+ * @param varyByRequestHeaders one or more request header names
+ * @since 4.3
+ */
+ public final void setVaryByRequestHeaders(String... varyByRequestHeaders) {
+ this.varyByRequestHeaders = varyByRequestHeaders;
+ }
+
+ /**
+ * Return the configured request header names for the "Vary" response header.
+ * @since 4.3
+ */
+ public final String[] getVaryByRequestHeaders() {
+ return this.varyByRequestHeaders;
+ }
+
+ /**
* Set whether to use the HTTP 1.0 expires header. Default is "false",
* as of 4.2.
* <p>Note: Cache headers will only get applied if caching is enabled
@@ -322,6 +398,11 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
else {
applyCacheSeconds(response, this.cacheSeconds);
}
+ if (servlet3Present && this.varyByRequestHeaders != null) {
+ for (String value : getVaryRequestHeadersToAdd(response)) {
+ response.addHeader("Vary", value);
+ }
+ }
}
/**
@@ -513,4 +594,26 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
}
}
+
+ private Collection<String> getVaryRequestHeadersToAdd(HttpServletResponse response) {
+ if (!response.containsHeader(HttpHeaders.VARY)) {
+ return Arrays.asList(getVaryByRequestHeaders());
+ }
+ Collection<String> result = new ArrayList<String>(getVaryByRequestHeaders().length);
+ Collections.addAll(result, getVaryByRequestHeaders());
+ for (String header : response.getHeaders(HttpHeaders.VARY)) {
+ for (String existing : StringUtils.tokenizeToStringArray(header, ",")) {
+ if ("*".equals(existing)) {
+ return Collections.emptyList();
+ }
+ for (String value : getVaryByRequestHeaders()) {
+ if (value.equalsIgnoreCase(existing)) {
+ result.remove(value);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/CheckboxTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/CheckboxTag.java
index ebde41ea..f0e619e9 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/CheckboxTag.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/CheckboxTag.java
@@ -38,7 +38,7 @@ import org.springframework.web.bind.WebDataBinder;
* the bound {@link Collection}.
* <h3>Approach Three</h3>
* For any other bound value type, the '{@code input(checkbox)}' is marked as 'checked'
- * if the the configured {@link #setValue(Object) value} is equal to the bound value.
+ * if the configured {@link #setValue(Object) value} is equal to the bound value.
*
* @author Rob Harrop
* @author Juergen Hoeller
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java
index 5aa9d232..d0b76ff9 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -147,7 +147,9 @@ public class FormTag extends AbstractHtmlElementTag {
* Set the name of the form attribute in the model.
* <p>May be a runtime expression.
* @see #setModelAttribute
+ * @deprecated as of Spring 4.3, in favor of {@link #setModelAttribute}
*/
+ @Deprecated
public void setCommandName(String commandName) {
this.modelAttribute = commandName;
}
@@ -155,7 +157,9 @@ public class FormTag extends AbstractHtmlElementTag {
/**
* Get the name of the form attribute in the model.
* @see #getModelAttribute
+ * @deprecated as of Spring 4.3, in favor of {@link #getModelAttribute}
*/
+ @Deprecated
protected String getCommandName() {
return this.modelAttribute;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/DefaultRequestToViewNameTranslator.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/DefaultRequestToViewNameTranslator.java
index dd8b1d39..1e42bb3b 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/DefaultRequestToViewNameTranslator.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/DefaultRequestToViewNameTranslator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -24,28 +24,28 @@ import org.springframework.web.servlet.RequestToViewNameTranslator;
import org.springframework.web.util.UrlPathHelper;
/**
- * {@link org.springframework.web.servlet.RequestToViewNameTranslator}
- * that simply transforms the URI of the incoming request into a view name.
+ * {@link RequestToViewNameTranslator} that simply transforms the URI of
+ * the incoming request into a view name.
*
- * <p>Can be explicitly defined as the "viewNameTranslator" bean in a
+ * <p>Can be explicitly defined as the {@code viewNameTranslator} bean in a
* {@link org.springframework.web.servlet.DispatcherServlet} context.
* Otherwise, a plain default instance will be used.
*
* <p>The default transformation simply strips leading and trailing slashes
* as well as the file extension of the URI, and returns the result as the
- * view name with the configured {@link #setPrefix "prefix"} and a
- * {@link #setSuffix "suffix"} added as appropriate.
+ * view name with the configured {@link #setPrefix prefix} and a
+ * {@link #setSuffix suffix} added as appropriate.
*
* <p>The stripping of the leading slash and file extension can be disabled
- * using the {@link #setStripLeadingSlash "stripLeadingSlash"} and
- * {@link #setStripExtension "stripExtension"} properties, respectively.
+ * using the {@link #setStripLeadingSlash stripLeadingSlash} and
+ * {@link #setStripExtension stripExtension} properties, respectively.
*
* <p>Find below some examples of request to view name translation.
- *
- * <pre class="code">http://localhost:8080/gamecast/display.html -> display
- * http://localhost:8080/gamecast/displayShoppingCart.html -> displayShoppingCart
- * http://localhost:8080/gamecast/admin/index.html -> admin/index
- * </pre>
+ * <ul>
+ * <li>{@code http://localhost:8080/gamecast/display.html} &raquo; {@code display}</li>
+ * <li>{@code http://localhost:8080/gamecast/displayShoppingCart.html} &raquo; {@code displayShoppingCart}</li>
+ * <li>{@code http://localhost:8080/gamecast/admin/index.html} &raquo; {@code admin/index}</li>
+ * </ul>
*
* @author Rob Harrop
* @author Juergen Hoeller
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/InternalResourceViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/InternalResourceViewResolver.java
index 679e8a95..13a70850 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/InternalResourceViewResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/InternalResourceViewResolver.java
@@ -66,6 +66,19 @@ public class InternalResourceViewResolver extends UrlBasedViewResolver {
setViewClass(viewClass);
}
+ /**
+ * A convenience constructor that allows for specifying {@link #setPrefix prefix}
+ * and {@link #setSuffix suffix} as constructor arguments.
+ * @param prefix the prefix that gets prepended to view names when building a URL
+ * @param suffix the suffix that gets appended to view names when building a URL
+ * @since 4.3
+ */
+ public InternalResourceViewResolver(String prefix, String suffix) {
+ this();
+ setPrefix(prefix);
+ setSuffix(suffix);
+ }
+
/**
* This resolver requires {@link InternalResourceView}.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java
index 0a188696..928e5ef9 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -104,6 +104,8 @@ public class RedirectView extends AbstractUrlBasedView implements SmartView {
private boolean propagateQueryParams = false;
+ private String[] hosts;
+
/**
* Constructor for use as a bean.
@@ -253,6 +255,28 @@ public class RedirectView extends AbstractUrlBasedView implements SmartView {
}
/**
+ * Configure one or more hosts associated with the application.
+ * All other hosts will be considered external hosts.
+ * <p>In effect, this property provides a way turn off encoding via
+ * {@link HttpServletResponse#encodeRedirectURL} for URLs that have a
+ * host and that host is not listed as a known host.
+ * <p>If not set (the default) all URLs are encoded through the response.
+ * @param hosts one or more application hosts
+ * @since 4.3
+ */
+ public void setHosts(String... hosts) {
+ this.hosts = hosts;
+ }
+
+ /**
+ * Return the configured application hosts.
+ * @since 4.3
+ */
+ public String[] getHosts() {
+ return this.hosts;
+ }
+
+ /**
* Returns "true" indicating this view performs a redirect.
*/
@Override
@@ -583,30 +607,56 @@ public class RedirectView extends AbstractUrlBasedView implements SmartView {
protected void sendRedirect(HttpServletRequest request, HttpServletResponse response,
String targetUrl, boolean http10Compatible) throws IOException {
- String encodedRedirectURL = response.encodeRedirectURL(targetUrl);
+ String encodedURL = (isRemoteHost(targetUrl) ? targetUrl : response.encodeRedirectURL(targetUrl));
if (http10Compatible) {
HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE);
if (this.statusCode != null) {
response.setStatus(this.statusCode.value());
- response.setHeader("Location", encodedRedirectURL);
+ response.setHeader("Location", encodedURL);
}
else if (attributeStatusCode != null) {
response.setStatus(attributeStatusCode.value());
- response.setHeader("Location", encodedRedirectURL);
+ response.setHeader("Location", encodedURL);
}
else {
// Send status code 302 by default.
- response.sendRedirect(encodedRedirectURL);
+ response.sendRedirect(encodedURL);
}
}
else {
HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl);
response.setStatus(statusCode.value());
- response.setHeader("Location", encodedRedirectURL);
+ response.setHeader("Location", encodedURL);
}
}
/**
+ * Whether the given targetUrl has a host that is a "foreign" system in which
+ * case {@link HttpServletResponse#encodeRedirectURL} will not be applied.
+ * This method returns {@code true} if the {@link #setHosts(String[])}
+ * property is configured and the target URL has a host that does not match.
+ * @param targetUrl the target redirect URL
+ * @return {@code true} the target URL has a remote host, {@code false} if it
+ * the URL does not have a host or the "host" property is not configured.
+ * @since 4.3
+ */
+ protected boolean isRemoteHost(String targetUrl) {
+ if (ObjectUtils.isEmpty(getHosts())) {
+ return false;
+ }
+ String targetHost = UriComponentsBuilder.fromUriString(targetUrl).build().getHost();
+ if (StringUtils.isEmpty(targetHost)) {
+ return false;
+ }
+ for (String host : getHosts()) {
+ if (targetHost.equals(host)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Determines the status code to use for HTTP 1.1 compatible requests.
* <p>The default implementation returns the {@link #setStatusCode(HttpStatus) statusCode}
* property if set, or the value of the {@link #RESPONSE_STATUS_ATTRIBUTE} attribute.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java
index 8bd441ea..9b09f8a9 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -20,6 +20,7 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
+import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeanUtils;
import org.springframework.core.Ordered;
@@ -112,6 +113,8 @@ public class UrlBasedViewResolver extends AbstractCachingViewResolver implements
private boolean redirectHttp10Compatible = true;
+ private String[] redirectHosts;
+
private String requestContextAttribute;
/** Map of static attributes, keyed by attribute name (String) */
@@ -254,6 +257,28 @@ public class UrlBasedViewResolver extends AbstractCachingViewResolver implements
}
/**
+ * Configure one or more hosts associated with the application.
+ * All other hosts will be considered external hosts.
+ * <p>In effect, this property provides a way turn off encoding on redirect
+ * via {@link HttpServletResponse#encodeRedirectURL} for URLs that have a
+ * host and that host is not listed as a known host.
+ * <p>If not set (the default) all URLs are encoded through the response.
+ * @param redirectHosts one or more application hosts
+ * @since 4.3
+ */
+ public void setRedirectHosts(String... redirectHosts) {
+ this.redirectHosts = redirectHosts;
+ }
+
+ /**
+ * Return the configured application hosts for redirect purposes.
+ * @since 4.3
+ */
+ public String[] getRedirectHosts() {
+ return this.redirectHosts;
+ }
+
+ /**
* Set the name of the RequestContext attribute for all views.
* @param requestContextAttribute name of the RequestContext attribute
* @see AbstractView#setRequestContextAttribute
@@ -435,6 +460,7 @@ public class UrlBasedViewResolver extends AbstractCachingViewResolver implements
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
+ view.setHosts(getRedirectHosts());
return applyLifecycleMethods(viewName, view);
}
// Check for special "forward:" prefix.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerView.java
index f5e260eb..00e678f1 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerView.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -20,7 +20,6 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
-import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import javax.servlet.GenericServlet;
@@ -42,6 +41,7 @@ import freemarker.ext.servlet.HttpRequestParametersHashModel;
import freemarker.ext.servlet.HttpSessionHashModel;
import freemarker.ext.servlet.ServletContextHashModel;
import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapperBuilder;
import freemarker.template.ObjectWrapper;
import freemarker.template.SimpleHash;
import freemarker.template.Template;
@@ -186,10 +186,10 @@ public class FreeMarkerView extends AbstractTemplateView {
* {@link ObjectWrapper#DEFAULT_WRAPPER default wrapper} if none specified.
* @see freemarker.template.Configuration#getObjectWrapper()
*/
- @SuppressWarnings("deprecation")
protected ObjectWrapper getObjectWrapper() {
ObjectWrapper ow = getConfiguration().getObjectWrapper();
- return (ow != null ? ow : ObjectWrapper.DEFAULT_WRAPPER);
+ return (ow != null ? ow :
+ new DefaultObjectWrapperBuilder(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS).build());
}
/**
@@ -405,7 +405,7 @@ public class FreeMarkerView extends AbstractTemplateView {
@Override
public Enumeration<String> getInitParameterNames() {
- return Collections.enumeration(new HashSet<String>());
+ return Collections.enumeration(Collections.<String>emptySet());
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewResolver.java
index c774ccf0..3768eb8a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerViewResolver.java
@@ -40,11 +40,29 @@ import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
*/
public class FreeMarkerViewResolver extends AbstractTemplateViewResolver {
+ /**
+ * Sets the default {@link #setViewClass view class} to {@link #requiredViewClass}:
+ * by default {@link FreeMarkerView}.
+ */
public FreeMarkerViewResolver() {
setViewClass(requiredViewClass());
}
/**
+ * A convenience constructor that allows for specifying {@link #setPrefix prefix}
+ * and {@link #setSuffix suffix} as constructor arguments.
+ * @param prefix the prefix that gets prepended to view names when building a URL
+ * @param suffix the suffix that gets appended to view names when building a URL
+ * @since 4.3
+ */
+ public FreeMarkerViewResolver(String prefix, String suffix) {
+ this();
+ setPrefix(prefix);
+ setSuffix(suffix);
+ }
+
+
+ /**
* Requires {@link FreeMarkerView}.
*/
@Override
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java
index 290d96c7..38eda981 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java
@@ -54,7 +54,7 @@ public class GroovyMarkupView extends AbstractTemplateView {
/**
* Set the MarkupTemplateEngine to use in this view.
- * <p>If not set, the engine is auto-detected by looking up up a single
+ * <p>If not set, the engine is auto-detected by looking up a single
* {@link GroovyMarkupConfig} bean in the web application context and using
* it to obtain the configured {@code MarkupTemplateEngine} instance.
* @see GroovyMarkupConfig
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewResolver.java
index 79c5f87a..197abdf2 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewResolver.java
@@ -38,10 +38,28 @@ import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
*/
public class GroovyMarkupViewResolver extends AbstractTemplateViewResolver {
+ /**
+ * Sets the default {@link #setViewClass view class} to {@link #requiredViewClass}:
+ * by default {@link GroovyMarkupView}.
+ */
public GroovyMarkupViewResolver() {
setViewClass(requiredViewClass());
}
+ /**
+ * A convenience constructor that allows for specifying {@link #setPrefix prefix}
+ * and {@link #setSuffix suffix} as constructor arguments.
+ * @param prefix the prefix that gets prepended to view names when building a URL
+ * @param suffix the suffix that gets appended to view names when building a URL
+ * @since 4.3
+ */
+ public GroovyMarkupViewResolver(String prefix, String suffix) {
+ this();
+ setPrefix(prefix);
+ setSuffix(suffix);
+ }
+
+
@Override
protected Class<?> requiredViewClass() {
return GroovyMarkupView.class;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/JasperReportsMultiFormatView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/JasperReportsMultiFormatView.java
index 7e9af07d..7821494d 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/JasperReportsMultiFormatView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/JasperReportsMultiFormatView.java
@@ -170,7 +170,7 @@ public class JasperReportsMultiFormatView extends AbstractJasperReportsView {
String format = (String) model.get(this.formatKey);
if (format == null) {
- throw new IllegalArgumentException("No format format found in model");
+ throw new IllegalArgumentException("No format found in model");
}
if (logger.isDebugEnabled()) {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java
index 34b75fa2..cf67ecc5 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java
@@ -38,7 +38,7 @@ import org.springframework.web.servlet.view.AbstractView;
* Abstract base class for Jackson based and content type independent
* {@link AbstractView} implementations.
*
- * <p>Compatible with Jackson 2.1 and higher.
+ * <p>Compatible with Jackson 2.6 and higher, as of Spring 4.3.
*
* @author Jeremy Grelle
* @author Arjen Poutsma
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java
index 2039e8c0..12a5baa0 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java
@@ -49,7 +49,7 @@ import org.springframework.web.servlet.View;
*
* <p>The default constructor uses the default configuration provided by {@link Jackson2ObjectMapperBuilder}.
*
- * <p>Compatible with Jackson 2.1 and higher.
+ * <p>Compatible with Jackson 2.6 and higher, as of Spring 4.3.
*
* @author Jeremy Grelle
* @author Arjen Poutsma
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java
index 908b273e..ac8298a8 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -18,13 +18,9 @@ package org.springframework.web.servlet.view.script;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.net.URL;
-import java.net.URLClassLoader;
import java.nio.charset.Charset;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import javax.script.Invocable;
import javax.script.ScriptEngine;
@@ -40,7 +36,6 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.core.NamedThreadLocal;
-import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.scripting.support.StandardScriptEvalException;
@@ -55,7 +50,7 @@ import org.springframework.web.servlet.view.AbstractUrlBasedView;
* An {@link AbstractUrlBasedView} subclass designed to run any template library
* based on a JSR-223 script engine.
*
- * <p>If not set, each property is auto-detected by looking up up a single
+ * <p>If not set, each property is auto-detected by looking up a single
* {@link ScriptTemplateConfig} bean in the web application context and using
* it to obtain the configured properties.
*
@@ -96,7 +91,7 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
private Charset charset;
- private String resourceLoaderPath;
+ private String[] resourceLoaderPaths;
private ResourceLoader resourceLoader;
@@ -184,7 +179,16 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
* See {@link ScriptTemplateConfigurer#setResourceLoaderPath(String)} documentation.
*/
public void setResourceLoaderPath(String resourceLoaderPath) {
- this.resourceLoaderPath = resourceLoaderPath;
+ String[] paths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath);
+ this.resourceLoaderPaths = new String[paths.length + 1];
+ this.resourceLoaderPaths[0] = "";
+ for (int i = 0; i < paths.length; i++) {
+ String path = paths[i];
+ if (!path.endsWith("/") && !path.endsWith(":")) {
+ path = path + "/";
+ }
+ this.resourceLoaderPaths[i + 1] = path;
+ }
}
@@ -214,12 +218,12 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
if (this.charset == null) {
this.charset = (viewConfig.getCharset() != null ? viewConfig.getCharset() : DEFAULT_CHARSET);
}
- if (this.resourceLoaderPath == null) {
- this.resourceLoaderPath = (viewConfig.getResourceLoaderPath() != null ?
- viewConfig.getResourceLoaderPath() : DEFAULT_RESOURCE_LOADER_PATH);
+ if (this.resourceLoaderPaths == null) {
+ String resourceLoaderPath = viewConfig.getResourceLoaderPath();
+ setResourceLoaderPath(resourceLoaderPath == null ? DEFAULT_RESOURCE_LOADER_PATH : resourceLoaderPath);
}
if (this.resourceLoader == null) {
- this.resourceLoader = new DefaultResourceLoader(createClassLoader());
+ this.resourceLoader = getApplicationContext();
}
if (this.sharedEngine == null && viewConfig.isSharedEngine() != null) {
this.sharedEngine = viewConfig.isSharedEngine();
@@ -282,10 +286,7 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
if (!ObjectUtils.isEmpty(this.scripts)) {
try {
for (String script : this.scripts) {
- Resource resource = this.resourceLoader.getResource(script);
- if (!resource.exists()) {
- throw new IllegalStateException("Script resource [" + script + "] not found");
- }
+ Resource resource = getResource(script);
engine.eval(new InputStreamReader(resource.getInputStream()));
}
}
@@ -295,26 +296,14 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
}
}
- protected ClassLoader createClassLoader() {
- String[] paths = StringUtils.commaDelimitedListToStringArray(this.resourceLoaderPath);
- List<URL> urls = new ArrayList<URL>();
- try {
- for (String path : paths) {
- Resource[] resources = getApplicationContext().getResources(path);
- if (resources.length > 0) {
- for (Resource resource : resources) {
- if (resource.exists()) {
- urls.add(resource.getURL());
- }
- }
- }
+ protected Resource getResource(String location) {
+ for (String path : this.resourceLoaderPaths) {
+ Resource resource = this.resourceLoader.getResource(path + location);
+ if (resource.exists()) {
+ return resource;
}
}
- catch (IOException ex) {
- throw new IllegalStateException("Cannot create class loader: " + ex.getMessage());
- }
- ClassLoader classLoader = getApplicationContext().getClassLoader();
- return (urls.size() > 0 ? new URLClassLoader(urls.toArray(new URL[urls.size()]), classLoader) : classLoader);
+ throw new IllegalStateException("Resource [" + location + "] not found");
}
protected ScriptTemplateConfig autodetectViewConfig() throws BeansException {
@@ -329,7 +318,6 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
}
}
-
@Override
protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
super.prepareResponse(request, response);
@@ -365,7 +353,7 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
}
protected String getTemplate(String path) throws IOException {
- Resource resource = this.resourceLoader.getResource(path);
+ Resource resource = getResource(path);
InputStreamReader reader = new InputStreamReader(resource.getInputStream(), this.charset);
return FileCopyUtils.copyToString(reader);
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateViewResolver.java
index 54f61470..168ce304 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateViewResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateViewResolver.java
@@ -35,10 +35,28 @@ import org.springframework.web.servlet.view.UrlBasedViewResolver;
*/
public class ScriptTemplateViewResolver extends UrlBasedViewResolver {
+ /**
+ * Sets the default {@link #setViewClass view class} to {@link #requiredViewClass}:
+ * by default {@link ScriptTemplateView}.
+ */
public ScriptTemplateViewResolver() {
setViewClass(requiredViewClass());
}
+ /**
+ * A convenience constructor that allows for specifying {@link #setPrefix prefix}
+ * and {@link #setSuffix suffix} as constructor arguments.
+ * @param prefix the prefix that gets prepended to view names when building a URL
+ * @param suffix the suffix that gets appended to view names when building a URL
+ * @since 4.3
+ */
+ public ScriptTemplateViewResolver(String prefix, String suffix) {
+ this();
+ setPrefix(prefix);
+ setSuffix(suffix);
+ }
+
+
@Override
protected Class<?> requiredViewClass() {
return ScriptTemplateView.class;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/AbstractSpringPreparerFactory.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/AbstractSpringPreparerFactory.java
index 93103388..f58dbed0 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/AbstractSpringPreparerFactory.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/AbstractSpringPreparerFactory.java
@@ -25,7 +25,7 @@ import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
/**
- * Abstract implementation of the Tiles3 {@link org.apache.tiles.preparer.PreparerFactory}
+ * Abstract implementation of the Tiles {@link org.apache.tiles.preparer.PreparerFactory}
* interface, obtaining the current Spring WebApplicationContext and delegating to
* {@link #getPreparer(String, org.springframework.web.context.WebApplicationContext)}.
*
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SimpleSpringPreparerFactory.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SimpleSpringPreparerFactory.java
index 457ace34..6efe19a8 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SimpleSpringPreparerFactory.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SimpleSpringPreparerFactory.java
@@ -27,7 +27,7 @@ import org.apache.tiles.preparer.factory.NoSuchPreparerException;
import org.springframework.web.context.WebApplicationContext;
/**
- * Tiles3 {@link org.apache.tiles.preparer.PreparerFactory} implementation
+ * Tiles {@link org.apache.tiles.preparer.PreparerFactory} implementation
* that expects preparer class names and builds preparer instances for those,
* creating them through the Spring ApplicationContext in order to apply
* Spring container callbacks and configured Spring BeanPostProcessors.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SpringBeanPreparerFactory.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SpringBeanPreparerFactory.java
index 0e252dc2..e04afdd2 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SpringBeanPreparerFactory.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/SpringBeanPreparerFactory.java
@@ -22,7 +22,7 @@ import org.apache.tiles.preparer.ViewPreparer;
import org.springframework.web.context.WebApplicationContext;
/**
- * Tiles3 {@link org.apache.tiles.preparer.PreparerFactory} implementation
+ * Tiles {@link org.apache.tiles.preparer.PreparerFactory} implementation
* that expects preparer bean names and obtains preparer beans from the
* Spring ApplicationContext. The full bean creation process will be in
* the control of the Spring application context in this case, allowing
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/package-info.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/package-info.java
index 845ac016..8934e376 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/package-info.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/tiles3/package-info.java
@@ -1,6 +1,6 @@
/**
* Support classes for the integration of
- * <a href="http://tiles.apache.org">Tiles3</a>
+ * <a href="http://tiles.apache.org">Tiles 3</a>
* (the standalone version of Tiles) as Spring web view technology.
* Contains a View implementation for Tiles definitions.
*/
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfig.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfig.java
index 9bffdadf..934883a3 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfig.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfig.java
@@ -26,7 +26,9 @@ import org.apache.velocity.app.VelocityEngine;
* @author Rod Johnson
* @see VelocityConfigurer
* @see VelocityView
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
+@Deprecated
public interface VelocityConfig {
/**
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfigurer.java
index 1dbd8a20..5f10a43a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfigurer.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityConfigurer.java
@@ -25,7 +25,6 @@ import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware;
-import org.springframework.ui.velocity.VelocityEngineFactory;
import org.springframework.web.context.ServletContextAware;
/**
@@ -69,8 +68,10 @@ import org.springframework.web.context.ServletContextAware;
* @see #setResourceLoaderPath
* @see #setVelocityEngine
* @see VelocityView
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
-public class VelocityConfigurer extends VelocityEngineFactory
+@Deprecated
+public class VelocityConfigurer extends org.springframework.ui.velocity.VelocityEngineFactory
implements VelocityConfig, InitializingBean, ResourceLoaderAware, ServletContextAware {
/** the name of the resource loader for Spring's bind macros */
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutView.java
index 65493af1..b5e3ae8a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutView.java
@@ -50,7 +50,9 @@ import org.springframework.core.NestedIOException;
* @see #setLayoutUrl
* @see #setLayoutKey
* @see #setScreenContentKey
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
+@Deprecated
public class VelocityLayoutView extends VelocityToolboxView {
/**
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutViewResolver.java
index 02124e0d..630dcc3e 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutViewResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityLayoutViewResolver.java
@@ -31,7 +31,9 @@ import org.springframework.web.servlet.view.AbstractUrlBasedView;
* @see #setLayoutUrl
* @see #setLayoutKey
* @see #setScreenContentKey
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
+@Deprecated
public class VelocityLayoutViewResolver extends VelocityViewResolver {
private String layoutUrl;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityToolboxView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityToolboxView.java
index ad54ffda..8513f9d5 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityToolboxView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityToolboxView.java
@@ -62,7 +62,9 @@ import org.springframework.util.ReflectionUtils;
* @see #initTool
* @see org.apache.velocity.tools.view.context.ViewContext
* @see org.apache.velocity.tools.view.context.ChainedContext
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
+@Deprecated
public class VelocityToolboxView extends VelocityView {
private String toolboxConfigLocation;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.java
index 23751a5d..936ee0d8 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.java
@@ -83,7 +83,9 @@ import org.springframework.web.util.NestedServletException;
* @see #setVelocityEngine
* @see VelocityConfig
* @see VelocityConfigurer
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
+@Deprecated
public class VelocityView extends AbstractTemplateView {
private Map<String, Class<?>> toolAttributes;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityViewResolver.java
index e812ec92..95191a4a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityViewResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityViewResolver.java
@@ -40,7 +40,9 @@ import org.springframework.web.servlet.view.AbstractUrlBasedView;
* @see #setDateToolAttribute
* @see #setNumberToolAttribute
* @see VelocityView
+ * @deprecated as of Spring 4.3, in favor of FreeMarker
*/
+@Deprecated
public class VelocityViewResolver extends AbstractTemplateViewResolver {
private String dateToolAttribute;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java
index dfcf59e5..ef40ba89 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 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.
@@ -36,7 +36,7 @@ import org.springframework.web.servlet.view.json.AbstractJackson2View;
*
* <p>The default constructor uses the default configuration provided by {@link Jackson2ObjectMapperBuilder}.
*
- * <p>Compatible with Jackson 2.1 and higher.
+ * <p>Compatible with Jackson 2.6 and higher, as of Spring 4.3.
*
* @author Sebastien Deleuze
* @since 4.1
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xslt/XsltView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xslt/XsltView.java
index 35587b78..8b1e1f82 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xslt/XsltView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xslt/XsltView.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -73,7 +73,7 @@ import org.springframework.web.util.WebUtils;
*/
public class XsltView extends AbstractUrlBasedView {
- private Class<?> transformerFactoryClass;
+ private Class<? extends TransformerFactory> transformerFactoryClass;
private String sourceKey;
@@ -97,8 +97,7 @@ public class XsltView extends AbstractUrlBasedView {
* <p>The default constructor of the specified class will be called
* to build the TransformerFactory for this view.
*/
- public void setTransformerFactoryClass(Class<?> transformerFactoryClass) {
- Assert.isAssignable(TransformerFactory.class, transformerFactoryClass);
+ public void setTransformerFactoryClass(Class<? extends TransformerFactory> transformerFactoryClass) {
this.transformerFactoryClass = transformerFactoryClass;
}
@@ -195,10 +194,10 @@ public class XsltView extends AbstractUrlBasedView {
* @see #setTransformerFactoryClass
* @see #getTransformerFactory()
*/
- protected TransformerFactory newTransformerFactory(Class<?> transformerFactoryClass) {
+ protected TransformerFactory newTransformerFactory(Class<? extends TransformerFactory> transformerFactoryClass) {
if (transformerFactoryClass != null) {
try {
- return (TransformerFactory) transformerFactoryClass.newInstance();
+ return transformerFactoryClass.newInstance();
}
catch (Exception ex) {
throw new TransformerFactoryConfigurationError(ex, "Could not instantiate TransformerFactory");