Merge branch '3.2.x' into master

Conflicts:
	gradle.properties
	spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java
	spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java
	spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java
	spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java
	spring-jdbc/src/main/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.java
	spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/AbstractLobHandler.java
	spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestWrapper.java
	spring-web/src/main/java/org/springframework/http/client/SimpleBufferingClientHttpRequest.java
	spring-web/src/main/java/org/springframework/http/converter/BufferedImageHttpMessageConverter.java
	spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java
This commit is contained in:
Chris Beams
2013-03-04 15:41:15 +01:00
1450 changed files with 17678 additions and 42998 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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,6 +26,7 @@ import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
@@ -39,11 +40,13 @@ import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.i18n.SimpleLocaleContext;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.ConfigurableWebEnvironment;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
@@ -638,7 +641,10 @@ public abstract class FrameworkServlet extends HttpServletBean {
// the context is refreshed; do it eagerly here to ensure servlet property sources
// are in place for use in any post-processing or initialization that occurs
// below prior to #refresh
wac.getEnvironment().initPropertySources(getServletContext(), getServletConfig());
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
@@ -861,10 +867,18 @@ public abstract class FrameworkServlet extends HttpServletBean {
return;
}
}
super.doOptions(request, response);
String allowedMethods = response.getHeader("Allow");
allowedMethods += ", " + RequestMethod.PATCH.name();
response.setHeader("Allow", allowedMethods);
// Use response wrapper for Servlet 2.5 compatibility where
// the getHeader() method does not exist
super.doOptions(request, new HttpServletResponseWrapper(response) {
@Override
public void setHeader(String name, String value) {
if("Allow".equals(name)) {
value = (StringUtils.hasLength(value) ? value + ", " : "") + RequestMethod.PATCH.name();
}
super.setHeader(name, value);
}
});
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -27,7 +27,6 @@ import javax.servlet.http.HttpServlet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
@@ -35,6 +34,7 @@ import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.io.Resource;
@@ -42,9 +42,8 @@ import org.springframework.core.io.ResourceEditor;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.context.ConfigurableWebEnvironment;
import org.springframework.web.context.support.StandardServletEnvironment;
import org.springframework.web.context.support.ServletContextResourceLoader;
import org.springframework.web.context.support.StandardServletEnvironment;
/**
* Simple extension of {@link javax.servlet.http.HttpServlet} which treats
@@ -91,7 +90,7 @@ public abstract class HttpServletBean extends HttpServlet
*/
private final Set<String> requiredProperties = new HashSet<String>();
private ConfigurableWebEnvironment environment;
private ConfigurableEnvironment environment;
/**
@@ -187,11 +186,11 @@ public abstract class HttpServletBean extends HttpServlet
/**
* {@inheritDoc}
* @throws IllegalArgumentException if environment is not assignable to
* {@code ConfigurableWebEnvironment}.
* {@code ConfigurableEnvironment}.
*/
public void setEnvironment(Environment environment) {
Assert.isInstanceOf(ConfigurableWebEnvironment.class, environment);
this.environment = (ConfigurableWebEnvironment)environment;
Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
this.environment = (ConfigurableEnvironment) environment;
}
/**
@@ -199,7 +198,7 @@ public abstract class HttpServletBean extends HttpServlet
* <p>If {@code null}, a new environment will be initialized via
* {@link #createEnvironment()}.
*/
public ConfigurableWebEnvironment getEnvironment() {
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = this.createEnvironment();
}
@@ -210,7 +209,7 @@ public abstract class HttpServletBean extends HttpServlet
* Create and return a new {@link StandardServletEnvironment}. Subclasses may override
* in order to configure the environment or specialize the environment type returned.
*/
protected ConfigurableWebEnvironment createEnvironment() {
protected ConfigurableEnvironment createEnvironment() {
return new StandardServletEnvironment();
}

View File

@@ -154,7 +154,6 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("order", 0);
handlerMappingDef.getPropertyValues().add("removeSemicolonContent", false);
handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
String methodMappingName = parserContext.getReaderContext().registerWithGeneratedName(handlerMappingDef);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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,7 @@ import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
/**
* Helps with configuring a options for asynchronous request processing.
* Helps with configuring options for asynchronous request processing.
*
* @author Rossen Stoyanchev
* @since 3.2
@@ -50,9 +50,9 @@ public class AsyncSupportConfigurer {
/**
* Set the default {@link AsyncTaskExecutor} to use when a controller method
* returns a {@link Callable}. Controller methods can override this default on
* a per-request basis by returning an {@link WebAsyncTask}.
* a per-request basis by returning a {@link WebAsyncTask}.
*
* <p>By default a {@link SimpleAsyncTaskExecutor} instance is used and it's
* <p>By default a {@link SimpleAsyncTaskExecutor} instance is used, and it's
* highly recommended to change that default in production since the simple
* executor does not re-use threads.
*
@@ -79,7 +79,7 @@ public class AsyncSupportConfigurer {
}
/**
* Configure lifecycle intercepters with callbacks around concurrent request
* Configure lifecycle interceptors with callbacks around concurrent request
* execution that starts when a controller returns a
* {@link java.util.concurrent.Callable}.
*
@@ -92,7 +92,7 @@ public class AsyncSupportConfigurer {
}
/**
* Configure lifecycle intercepters with callbacks around concurrent request
* Configure lifecycle interceptors with callbacks around concurrent request
* execution that starts when a controller returns a {@link DeferredResult}.
*
* @param interceptors the interceptors to register

View File

@@ -95,4 +95,4 @@ public class DefaultServletHandlerConfigurer {
return handlerMapping;
}
}
}

View File

@@ -108,4 +108,4 @@ public class ResourceHandlerRegistry {
return handlerMapping;
}
}
}

View File

@@ -74,4 +74,4 @@ public class ViewControllerRegistry {
return handlerMapping;
}
}
}

View File

@@ -192,7 +192,6 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping handlerMapping = new RequestMappingHandlerMapping();
handlerMapping.setOrder(0);
handlerMapping.setRemoveSemicolonContent(false);
handlerMapping.setInterceptors(getInterceptors());
handlerMapping.setContentNegotiationManager(mvcContentNegotiationManager());
return handlerMapping;

View File

@@ -128,4 +128,4 @@ public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
}
}

View File

@@ -127,6 +127,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
/**
* Set if ";" (semicolon) content should be stripped from the request URI.
* <p>The default value is {@code false}.
* @see org.springframework.web.util.UrlPathHelper#setRemoveSemicolonContent(boolean)
*/
public void setRemoveSemicolonContent(boolean removeSemicolonContent) {

View File

@@ -37,6 +37,7 @@ import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.HandlerMethodSelector;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.UrlPathHelper;
/**
* Abstract base class for {@link HandlerMapping} implementations that define a
@@ -61,6 +62,12 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
private final MultiValueMap<String, T> urlMap = new LinkedMultiValueMap<String, T>();
public AbstractHandlerMethodMapping() {
UrlPathHelper pathHelper = new UrlPathHelper();
pathHelper.setRemoveSemicolonContent(false);
setUrlPathHelper(pathHelper);
}
/**
* Whether to detect handler methods in beans in ancestor ApplicationContexts.
* <p>Default is "false": Only beans in the current ApplicationContext are
@@ -165,25 +172,17 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
* under the same mapping
*/
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
HandlerMethod handlerMethod;
if (handler instanceof String) {
String beanName = (String) handler;
handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method);
}
else {
handlerMethod = new HandlerMethod(handler, method);
}
HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
HandlerMethod oldHandlerMethod = handlerMethods.get(mapping);
if (oldHandlerMethod != null && !oldHandlerMethod.equals(handlerMethod)) {
throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + handlerMethod.getBean()
+ "' bean method \n" + handlerMethod + "\nto " + mapping + ": There is already '"
if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean()
+ "' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '"
+ oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
}
this.handlerMethods.put(mapping, handlerMethod);
this.handlerMethods.put(mapping, newHandlerMethod);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);
}
Set<String> patterns = getMappingPathPatterns(mapping);
@@ -194,6 +193,24 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
}
}
/**
* Create the HandlerMethod instance.
* @param handler either a bean name or an actual handler instance
* @param method the target method
* @return the created HandlerMethod
*/
protected HandlerMethod createHandlerMethod(Object handler, Method method) {
HandlerMethod handlerMethod;
if (handler instanceof String) {
String beanName = (String) handler;
handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method);
}
else {
handlerMethod = new HandlerMethod(handler, method);
}
return handlerMethod;
}
/**
* Extract and return the URL paths contained in a mapping.
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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,6 +19,9 @@ package org.springframework.web.servlet.mvc.annotation;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ResponseStatus;
@@ -32,9 +35,17 @@ import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
* <p>This exception resolver is enabled by default in the {@link org.springframework.web.servlet.DispatcherServlet}.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.0
*/
public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver {
public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver implements MessageSourceAware {
private MessageSource messageSource;
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
@@ -69,6 +80,9 @@ public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionRes
int statusCode = responseStatus.value().value();
String reason = responseStatus.reason();
if (this.messageSource != null) {
reason = this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale());
}
if (!StringUtils.hasLength(reason)) {
response.sendError(statusCode);
}

View File

@@ -107,4 +107,4 @@ abstract class AbstractMediaTypeExpression implements Comparable<AbstractMediaTy
return builder.toString();
}
}
}

View File

@@ -36,4 +36,4 @@ public interface MediaTypeExpression {
boolean isNegated();
}
}

View File

@@ -37,4 +37,4 @@ public interface NameValueExpression<T> {
boolean isNegated();
}
}

View File

@@ -42,9 +42,16 @@ import org.springframework.web.util.UrlPathHelper;
*/
public final class PatternsRequestCondition extends AbstractRequestCondition<PatternsRequestCondition> {
private static UrlPathHelper pathHelperNoSemicolonContent;
static {
pathHelperNoSemicolonContent = new UrlPathHelper();
pathHelperNoSemicolonContent.setRemoveSemicolonContent(true);
}
private final Set<String> patterns;
private final UrlPathHelper urlPathHelper;
private final UrlPathHelper pathHelper;
private final PathMatcher pathMatcher;
@@ -105,7 +112,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
List<String> fileExtensions) {
this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns));
this.urlPathHelper = urlPathHelper != null ? urlPathHelper : new UrlPathHelper();
this.pathHelper = urlPathHelper != null ? urlPathHelper : new UrlPathHelper();
this.pathMatcher = pathMatcher != null ? pathMatcher : new AntPathMatcher();
this.useSuffixPatternMatch = useSuffixPatternMatch;
this.useTrailingSlashMatch = useTrailingSlashMatch;
@@ -179,7 +186,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
else {
result.add("");
}
return new PatternsRequestCondition(result, this.urlPathHelper, this.pathMatcher, this.useSuffixPatternMatch,
return new PatternsRequestCondition(result, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch,
this.useTrailingSlashMatch, this.fileExtensions);
}
@@ -206,17 +213,24 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
if (this.patterns.isEmpty()) {
return this;
}
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
String lookupPath = this.pathHelper.getLookupPathForRequest(request);
String lookupPathNoSemicolonContent = (lookupPath.indexOf(';') != -1) ?
pathHelperNoSemicolonContent.getLookupPathForRequest(request) : null;
List<String> matches = new ArrayList<String>();
for (String pattern : patterns) {
String match = getMatchingPattern(pattern, lookupPath);
if (match == null && lookupPathNoSemicolonContent != null) {
match = getMatchingPattern(pattern, lookupPathNoSemicolonContent);
}
if (match != null) {
matches.add(match);
}
}
Collections.sort(matches, this.pathMatcher.getPatternComparator(lookupPath));
return matches.isEmpty() ? null :
new PatternsRequestCondition(matches, this.urlPathHelper, this.pathMatcher, this.useSuffixPatternMatch,
new PatternsRequestCondition(matches, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch,
this.useTrailingSlashMatch, this.fileExtensions);
}
@@ -225,7 +239,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
return pattern;
}
if (this.useSuffixPatternMatch) {
if (useSmartSuffixPatternMatch(pattern, lookupPath)) {
if (!this.fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) {
for (String extension : this.fileExtensions) {
if (this.pathMatcher.match(pattern + extension, lookupPath)) {
return pattern + extension;
@@ -251,14 +265,6 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
return null;
}
/**
* Whether to match by known file extensions. Return "true" if file extensions
* are configured, and the lookup path has a suffix.
*/
private boolean useSmartSuffixPatternMatch(String pattern, String lookupPath) {
return (!this.fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) ;
}
/**
* Compare the two conditions based on the URL patterns they contain.
* Patterns are compared one at a time, from top to bottom via
@@ -272,7 +278,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
* the best matches on top.
*/
public int compareTo(PatternsRequestCondition other, HttpServletRequest request) {
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
String lookupPath = this.pathHelper.getLookupPathForRequest(request);
Comparator<String> patternComparator = this.pathMatcher.getPatternComparator(lookupPath);
Iterator<String> iterator = patterns.iterator();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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,15 +28,19 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
import org.springframework.web.bind.annotation.RequestMethod;
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;
/**
@@ -100,7 +104,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);
if (isMatrixVariableContentAvailable()) {
request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, extractMatrixVariables(uriVariables));
request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, extractMatrixVariables(request, uriVariables));
}
if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
@@ -113,7 +117,9 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
return !getUrlPathHelper().shouldRemoveSemicolonContent();
}
private Map<String, MultiValueMap<String, String>> extractMatrixVariables(Map<String, String> uriVariables) {
private Map<String, MultiValueMap<String, String>> extractMatrixVariables(
HttpServletRequest request, Map<String, String> uriVariables) {
Map<String, MultiValueMap<String, String>> result = new LinkedHashMap<String, MultiValueMap<String, String>>();
for (Entry<String, String> uriVar : uriVariables.entrySet()) {
String uriVarValue = uriVar.getValue();
@@ -134,7 +140,8 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
uriVariables.put(uriVar.getKey(), uriVarValue.substring(0, semicolonIndex));
}
result.put(uriVar.getKey(), WebUtils.parseMatrixVariables(matrixVariables));
MultiValueMap<String, String> vars = WebUtils.parseMatrixVariables(matrixVariables);
result.put(uriVar.getKey(), getUrlPathHelper().decodeMatrixVariables(request, vars));
}
return result;
}
@@ -182,26 +189,38 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
Set<MediaType> consumableMediaTypes;
Set<MediaType> producibleMediaTypes;
Set<String> paramConditions;
if (patternAndMethodMatches.isEmpty()) {
consumableMediaTypes = getConsumableMediaTypes(request, patternMatches);
producibleMediaTypes = getProdicubleMediaTypes(request, patternMatches);
paramConditions = getRequestParams(request, patternMatches);
}
else {
consumableMediaTypes = getConsumableMediaTypes(request, patternAndMethodMatches);
producibleMediaTypes = getProdicubleMediaTypes(request, patternAndMethodMatches);
paramConditions = getRequestParams(request, patternAndMethodMatches);
}
if (!consumableMediaTypes.isEmpty()) {
MediaType contentType = null;
if (StringUtils.hasLength(request.getContentType())) {
contentType = MediaType.parseMediaType(request.getContentType());
try {
contentType = MediaType.parseMediaType(request.getContentType());
}
catch (IllegalArgumentException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
}
throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<MediaType>(consumableMediaTypes));
}
else if (!producibleMediaTypes.isEmpty()) {
throw new HttpMediaTypeNotAcceptableException(new ArrayList<MediaType>(producibleMediaTypes));
}
else if (!CollectionUtils.isEmpty(paramConditions)) {
String[] params = paramConditions.toArray(new String[paramConditions.size()]);
throw new UnsatisfiedServletRequestParameterException(params, request.getParameterMap());
}
else {
return null;
}
@@ -227,4 +246,18 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
return result;
}
private Set<String> getRequestParams(HttpServletRequest request, Set<RequestMappingInfo> partialMatches) {
for (RequestMappingInfo partialMatch : partialMatches) {
ParamsRequestCondition condition = partialMatch.getParamsCondition();
if (!CollectionUtils.isEmpty(condition.getExpressions()) && (condition.getMatchingCondition(request) == null)) {
Set<String> expressions = new HashSet<String>();
for (NameValueExpression expr : condition.getExpressions()) {
expressions.add(expr.toString());
}
return expressions;
}
}
return null;
}
}

View File

@@ -123,7 +123,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
}
}
if (compatibleMediaTypes.isEmpty()) {
throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes);
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -68,7 +68,7 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> parameterType = returnType.getParameterType();
return HttpEntity.class.equals(parameterType) || ResponseEntity.class.equals(parameterType);
return HttpEntity.class.isAssignableFrom(parameterType) || ResponseEntity.class.isAssignableFrom(parameterType);
}
public Object resolveArgument(

View File

@@ -67,4 +67,4 @@ public class PathVariableMapMethodArgumentResolver implements HandlerMethodArgum
}
}
}
}

View File

@@ -119,4 +119,4 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod
super(annotation.value(), true, ValueConstants.DEFAULT_NONE);
}
}
}
}

View File

@@ -879,4 +879,4 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
};
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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,9 +20,11 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.util.StringValueResolver;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.condition.AbstractRequestCondition;
@@ -46,7 +48,8 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi
* @author Rossen Stoyanchev
* @since 3.1
*/
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping {
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
implements EmbeddedValueResolverAware {
private boolean useSuffixPatternMatch = true;
@@ -58,6 +61,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
private final List<String> fileExtensions = new ArrayList<String>();
private StringValueResolver embeddedValueResolver;
/**
* Whether to use suffix pattern match (".*") when matching patterns to
@@ -101,6 +106,11 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
this.useTrailingSlashMatch = useTrailingSlashMatch;
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.embeddedValueResolver = resolver;
}
/**
* Set the {@link ContentNegotiationManager} to use to determine requested media types.
* If not set, the default constructor is used.
@@ -142,15 +152,15 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
* Return the file extensions to use for suffix pattern matching.
*/
public List<String> getFileExtensions() {
return fileExtensions;
return this.fileExtensions;
}
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
if (this.useRegisteredSuffixPatternMatch) {
this.fileExtensions.addAll(contentNegotiationManager.getAllFileExtensions());
}
super.afterPropertiesSet();
}
/**
@@ -227,8 +237,9 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
* Created a RequestMappingInfo from a RequestMapping annotation.
*/
private RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition<?> customCondition) {
String[] patterns = resolveEmbeddedValuesInPatterns(annotation.value());
return new RequestMappingInfo(
new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher(),
new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(),
this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions),
new RequestMethodsRequestCondition(annotation.method()),
new ParamsRequestCondition(annotation.params()),
@@ -238,4 +249,21 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
customCondition);
}
/**
* Resolve placeholder values in the given array of patterns.
* @return a new array with updated patterns
*/
protected String[] resolveEmbeddedValuesInPatterns(String[] patterns) {
if (this.embeddedValueResolver == null) {
return patterns;
}
else {
String[] resolvedPatterns = new String[patterns.length];
for (int i=0; i < patterns.length; i++) {
resolvedPatterns[i] = this.embeddedValueResolver.resolveStringValue(patterns[i]);
}
return resolvedPatterns;
}
}
}

View File

@@ -190,4 +190,4 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
}
}
}
}

View File

@@ -60,4 +60,4 @@ public class ServletCookieValueMethodArgumentResolver extends AbstractCookieValu
return null;
}
}
}
}

View File

@@ -153,4 +153,4 @@ public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodPr
servletBinder.bind(servletRequest);
}
}
}

View File

@@ -51,4 +51,4 @@ public class ServletWebArgumentResolverAdapter extends AbstractWebArgumentResolv
}
return null;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -50,14 +50,14 @@ import org.springframework.web.util.WebUtils;
*
* <p>Note that the second strategy also supports the use of submit buttons of
* type 'image'. That is, an image submit button named 'reset' will normally be
* submitted by the browser as two request paramters called 'reset.x', and
* 'reset.y'. When checking for the existence of a paramter from the
* submitted by the browser as two request parameters called 'reset.x', and
* 'reset.y'. When checking for the existence of a parameter from the
* {@code methodParamNames} list, to indicate that a specific method should
* be called, the code will look for request parameter in the "reset" form
* (exactly as spcified in the list), and in the "reset.x" form ('.x' appended to
* the name in the list). In this way it can handle both normal and image submit
* buttons. The actual method name resolved if there is a match will always be
* the bare form without the ".x".
* be called, the code will look for a request parameter in the "reset" form
* (exactly as specified in the list), and in the "reset.x" form ('.x' appended
* to the name in the list). In this way it can handle both normal and image
* submit buttons. The actual method name resolved, if there is a match, will
* always be the bare form without the ".x".
*
* <p><b>Note:</b> If both strategies are configured, i.e. both "paramName"
* and "methodParamNames" are specified, then both will be checked for any given
@@ -69,7 +69,7 @@ import org.springframework.web.util.WebUtils;
*
* <p>For both resolution strategies, the method name is of course coming from
* some sort of view code, (such as a JSP page). While this may be acceptable,
* it is sometimes desireable to treat this only as a 'logical' method name,
* it is sometimes desirable to treat this only as a 'logical' method name,
* with a further mapping to a 'real' method name. As such, an optional
* 'logical' mapping may be specified for this purpose.
*

View File

@@ -94,6 +94,11 @@ public abstract class AbstractDispatcherServletInitializer
ServletRegistration.Dynamic registration =
servletContext.addServlet(servletName, dispatcherServlet);
Assert.notNull(registration,
"Failed to register servlet with name '" + servletName + "'." +
"Check if there is another servlet registered under the same name.");
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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,6 +36,7 @@ import org.springframework.ui.context.ThemeSource;
import org.springframework.ui.context.support.ResourceBundleThemeSource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
@@ -61,6 +62,7 @@ import org.springframework.web.util.WebUtils;
* appropriate fallback for the locale (the HttpServletRequest's primary locale).
*
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @since 03.03.2003
* @see org.springframework.web.servlet.DispatcherServlet
* @see org.springframework.web.servlet.view.AbstractView#setRequestContextAttribute
@@ -233,8 +235,10 @@ public class RequestContext {
}
/**
* Determine the fallback locale for this context. <p>The default implementation checks for a JSTL locale attribute
* in request, session or application scope; if not found, returns the {@code HttpServletRequest.getLocale()}.
* Determine the fallback locale for this context.
* <p>The default implementation checks for a JSTL locale attribute in request,
* session or application scope; if not found, returns the
* {@code HttpServletRequest.getLocale()}.
* @return the fallback locale (never {@code null})
* @see javax.servlet.http.HttpServletRequest#getLocale()
*/
@@ -249,8 +253,8 @@ public class RequestContext {
}
/**
* Determine the fallback theme for this context. <p>The default implementation returns the default theme (with name
* "theme").
* Determine the fallback theme for this context.
* <p>The default implementation returns the default theme (with name "theme").
* @return the fallback theme (never {@code null})
*/
protected Theme getFallbackTheme() {
@@ -309,8 +313,8 @@ public class RequestContext {
}
/**
* Return the current theme (never {@code null}). <p>Resolved lazily for more efficiency when theme support is
* not being used.
* Return the current theme (never {@code null}).
* <p>Resolved lazily for more efficiency when theme support is not being used.
*/
public final Theme getTheme() {
if (this.theme == null) {
@@ -350,7 +354,8 @@ public class RequestContext {
/**
* Set the UrlPathHelper to use for context path and request URI decoding. Can be used to pass a shared
* UrlPathHelper instance in. <p>A default UrlPathHelper is always available.
* UrlPathHelper instance in.
* <p>A default UrlPathHelper is always available.
*/
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
@@ -359,7 +364,8 @@ public class RequestContext {
/**
* Return the UrlPathHelper used for context path and request URI decoding. Can be used to configure the current
* UrlPathHelper. <p>A default UrlPathHelper is always available.
* UrlPathHelper.
* <p>A default UrlPathHelper is always available.
*/
public UrlPathHelper getUrlPathHelper() {
return this.urlPathHelper;
@@ -376,8 +382,8 @@ public class RequestContext {
/**
* Return the context path of the original request, that is, the path that indicates the current web application.
* This is useful for building links to other resources within the application. <p>Delegates to the UrlPathHelper
* for decoding.
* This is useful for building links to other resources within the application.
* <p>Delegates to the UrlPathHelper for decoding.
* @see javax.servlet.http.HttpServletRequest#getContextPath
* @see #getUrlPathHelper
*/
@@ -422,22 +428,22 @@ public class RequestContext {
* context path and the servlet path of the original request. This is useful
* for building links to other resources within the application where a
* servlet mapping of the style {@code "/main/*"} is used.
* <p>Delegates to the UrlPathHelper for decoding the context path.
* @see javax.servlet.http.HttpServletRequest#getContextPath
* @see javax.servlet.http.HttpServletRequest#getServletPath()
* @see #getUrlPathHelper
* <p>Delegates to the UrlPathHelper to determine the context and servlet path.
*/
public String getPathToServlet() {
return this.urlPathHelper.getOriginatingContextPath(this.request)
+ this.urlPathHelper.getOriginatingServletPath(this.request);
String path = this.urlPathHelper.getOriginatingContextPath(this.request);
if (StringUtils.hasText(this.urlPathHelper.getPathWithinServletMapping(this.request))) {
path += this.urlPathHelper.getOriginatingServletPath(this.request);
}
return path;
}
/**
* Return the request URI of the original request, that is, the invoked URL without parameters. This is particularly
* useful as HTML form action target, possibly in combination with the original query string. <p><b>Note this
* implementation will correctly resolve to the URI of any originating root request in the presence of a forwarded
* request. However, this can only work when the Servlet 2.4 'forward' request attributes are present. <p>Delegates
* to the UrlPathHelper for decoding.
* request. However, this can only work when the Servlet 2.4 'forward' request attributes are present.
* <p>Delegates to the UrlPathHelper for decoding.
* @see #getQueryString
* @see org.springframework.web.util.UrlPathHelper#getOriginatingRequestUri
* @see #getUrlPathHelper
@@ -573,8 +579,9 @@ public class RequestContext {
}
/**
* Retrieve the theme message for the given code. <p>Note that theme messages are never HTML-escaped, as they
* typically denote theme-specific resource paths and not client-visible messages.
* Retrieve the theme message for the given code.
* <p>Note that theme messages are never HTML-escaped, as they typically denote
* theme-specific resource paths and not client-visible messages.
* @param code code of the message
* @param defaultMessage String to return if the lookup fails
* @return the message
@@ -584,8 +591,9 @@ public class RequestContext {
}
/**
* Retrieve the theme message for the given code. <p>Note that theme messages are never HTML-escaped, as they
* typically denote theme-specific resource paths and not client-visible messages.
* Retrieve the theme message for the given code.
* <p>Note that theme messages are never HTML-escaped, as they typically denote
* theme-specific resource paths and not client-visible messages.
* @param code code of the message
* @param args arguments for the message, or {@code null} if none
* @param defaultMessage String to return if the lookup fails
@@ -596,8 +604,9 @@ public class RequestContext {
}
/**
* Retrieve the theme message for the given code. <p>Note that theme messages are never HTML-escaped, as they
* typically denote theme-specific resource paths and not client-visible messages.
* Retrieve the theme message for the given code.
* <p>Note that theme messages are never HTML-escaped, as they typically denote
* theme-specific resource paths and not client-visible messages.
* @param code code of the message
* @param args arguments for the message as a List, or {@code null} if none
* @param defaultMessage String to return if the lookup fails
@@ -609,8 +618,9 @@ public class RequestContext {
}
/**
* Retrieve the theme message for the given code. <p>Note that theme messages are never HTML-escaped, as they
* typically denote theme-specific resource paths and not client-visible messages.
* Retrieve the theme message for the given code.
* <p>Note that theme messages are never HTML-escaped, as they typically denote
* theme-specific resource paths and not client-visible messages.
* @param code code of the message
* @return the message
* @throws org.springframework.context.NoSuchMessageException if not found
@@ -620,8 +630,9 @@ public class RequestContext {
}
/**
* Retrieve the theme message for the given code. <p>Note that theme messages are never HTML-escaped, as they
* typically denote theme-specific resource paths and not client-visible messages.
* Retrieve the theme message for the given code.
* <p>Note that theme messages are never HTML-escaped, as they typically denote
* theme-specific resource paths and not client-visible messages.
* @param code code of the message
* @param args arguments for the message, or {@code null} if none
* @return the message
@@ -632,8 +643,9 @@ public class RequestContext {
}
/**
* Retrieve the theme message for the given code. <p>Note that theme messages are never HTML-escaped, as they
* typically denote theme-specific resource paths and not client-visible messages.
* Retrieve the theme message for the given code.
* <p>Note that theme messages are never HTML-escaped, as they typically denote
* theme-specific resource paths and not client-visible messages.
* @param code code of the message
* @param args arguments for the message as a List, or {@code null} if none
* @return the message
@@ -644,8 +656,9 @@ public class RequestContext {
}
/**
* Retrieve the given MessageSourceResolvable in the current theme. <p>Note that theme messages are never
* HTML-escaped, as they typically denote theme-specific resource paths and not client-visible messages.
* Retrieve the given MessageSourceResolvable in the current theme.
* <p>Note that theme messages are never HTML-escaped, as they typically denote
* theme-specific resource paths and not client-visible messages.
* @param resolvable the MessageSourceResolvable
* @return the message
* @throws org.springframework.context.NoSuchMessageException if not found

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2013 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.
@@ -95,9 +95,12 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder {
String scheme = request.getScheme();
int port = request.getServerPort();
String header = request.getHeader("X-Forwarded-Host");
String host = StringUtils.hasText(header) ? header: request.getServerName();
ServletUriComponentsBuilder builder = new ServletUriComponentsBuilder();
builder.scheme(scheme);
builder.host(request.getServerName());
builder.host(host);
if ((scheme.equals("http") && port != 80) || (scheme.equals("https") && port != 443)) {
builder.port(port);
}
@@ -138,7 +141,10 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder {
return fromRequest(getCurrentRequest());
}
private static HttpServletRequest getCurrentRequest() {
/**
* Obtain the request through {@link RequestContextHolder}.
*/
protected static HttpServletRequest getCurrentRequest() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
Assert.state(requestAttributes != null, "Could not find current request via RequestContextHolder");
Assert.isInstanceOf(ServletRequestAttributes.class, requestAttributes);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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,7 +26,9 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.HttpSessionRequiredException;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.support.WebApplicationObjectSupport;
import org.springframework.web.servlet.mvc.LastModified;
/**
* Convenient superclass for any kind of web content generator,
@@ -79,6 +81,8 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
private int cacheSeconds = -1;
private boolean alwaysMustRevalidate = false;
/**
* Create a new WebContentGenerator which supports
@@ -194,6 +198,25 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
return this.useCacheControlNoStore;
}
/**
* An option to add 'must-revalidate' to every Cache-Control header. This
* may be useful with annotated controller methods, which can
* programmatically do a lastModified calculation as described in
* {@link WebRequest#checkNotModified(long)}. Default is "false",
* effectively relying on whether the handler implements
* {@link LastModified} or not.
*/
public void setAlwaysMustRevalidate(boolean mustRevalidate) {
this.alwaysMustRevalidate = mustRevalidate;
}
/**
* Return whether 'must-revaliate' is added to every Cache-Control header.
*/
public boolean isAlwaysMustRevalidate() {
return alwaysMustRevalidate;
}
/**
* Cache content for the given number of seconds. Default is -1,
* indicating no generation of cache-related headers.
@@ -313,7 +336,7 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
if (this.useCacheControlHeader) {
// HTTP 1.1 header
String headerValue = "max-age=" + seconds;
if (mustRevalidate) {
if (mustRevalidate || this.alwaysMustRevalidate) {
headerValue += ", must-revalidate";
}
response.setHeader(HEADER_CACHE_CONTROL, headerValue);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -50,6 +50,7 @@ import org.springframework.web.util.HtmlUtils;
* @author Rob Harrop
* @author Juergen Hoeller
* @author Scott Andrews
* @author Rossen Stoyanchev
* @since 2.0
* @see org.springframework.web.servlet.mvc.SimpleFormController
*/
@@ -411,6 +412,10 @@ public class FormTag extends AbstractHtmlElementTag {
protected String resolveAction() throws JspException {
String action = getAction();
if (StringUtils.hasText(action)) {
String pathToServlet = getRequestContext().getPathToServlet();
if (action.startsWith("/") && !action.startsWith(getRequestContext().getContextPath())) {
action = pathToServlet + action;
}
action = getDisplayString(evaluate(ACTION_ATTRIBUTE, action));
return processAction(action);
}
@@ -469,8 +474,8 @@ public class FormTag extends AbstractHtmlElementTag {
if (hiddenFields != null) {
for (String name : hiddenFields.keySet()) {
this.tagWriter.appendValue("<input type=\"hidden\" ");
this.tagWriter.appendValue("name=\"" + name + "\" value=\"" + hiddenFields.get(name) + "\">");
this.tagWriter.appendValue("</input>\n");
this.tagWriter.appendValue("name=\"" + name + "\" value=\"" + hiddenFields.get(name) + "\" ");
this.tagWriter.appendValue("/>\n");
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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,6 +19,9 @@ package org.springframework.web.servlet.view;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.context.support.WebApplicationObjectSupport;
import org.springframework.web.servlet.View;
@@ -42,19 +45,38 @@ public abstract class AbstractCachingViewResolver extends WebApplicationObjectSu
/** Default maximum number of entries for the view cache: 1024 */
public static final int DEFAULT_CACHE_LIMIT = 1024;
/** Dummy marker object for unresolved views in the cache Maps */
private static final View UNRESOLVED_VIEW = new View() {
public String getContentType() {
return null;
}
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {
}
};
/** The maximum number of entries in the cache */
private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;
/** Whether we should refrain from resolving views again if unresolved once */
private boolean cacheUnresolved = true;
/** Map from view key to View instance */
/** Fast access cache for Views, returning already cached instances without a global lock */
private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<Object, View>(DEFAULT_CACHE_LIMIT);
/** Map from view key to View instance, synchronized for View creation */
@SuppressWarnings("serial")
private final Map<Object, View> viewCache =
private final Map<Object, View> viewCreationCache =
new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) {
return size() > getCacheLimit();
if (size() > getCacheLimit()) {
viewAccessCache.remove(eldest.getKey());
return true;
}
else {
return false;
}
}
};
@@ -122,20 +144,27 @@ public abstract class AbstractCachingViewResolver extends WebApplicationObjectSu
}
else {
Object cacheKey = getCacheKey(viewName, locale);
synchronized (this.viewCache) {
View view = this.viewCache.get(cacheKey);
if (view == null && (!this.cacheUnresolved || !this.viewCache.containsKey(cacheKey))) {
// Ask the subclass to create the View object.
view = createView(viewName, locale);
if (view != null || this.cacheUnresolved) {
this.viewCache.put(cacheKey, view);
if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// Ask the subclass to create the View object.
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
}
}
}
}
return view;
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
@@ -166,17 +195,16 @@ public abstract class AbstractCachingViewResolver extends WebApplicationObjectSu
else {
Object cacheKey = getCacheKey(viewName, locale);
Object cachedView;
synchronized (this.viewCache) {
cachedView = this.viewCache.remove(cacheKey);
synchronized (this.viewCreationCache) {
this.viewAccessCache.remove(cacheKey);
cachedView = this.viewCreationCache.remove(cacheKey);
}
if (cachedView == null) {
if (logger.isDebugEnabled()) {
// Some debug output might be useful...
if (logger.isDebugEnabled()) {
if (cachedView == null) {
logger.debug("No cached instance for view '" + cacheKey + "' was found");
}
}
else {
if (logger.isDebugEnabled()) {
else {
logger.debug("Cache for view " + cacheKey + " has been cleared");
}
}
@@ -189,8 +217,9 @@ public abstract class AbstractCachingViewResolver extends WebApplicationObjectSu
*/
public void clearCache() {
logger.debug("Clearing entire view cache");
synchronized (this.viewCache) {
this.viewCache.clear();
synchronized (this.viewCreationCache) {
this.viewAccessCache.clear();
this.viewCreationCache.clear();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -25,7 +25,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.activation.FileTypeMap;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
@@ -33,6 +32,7 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.OrderComparator;
@@ -95,7 +95,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
private ContentNegotiationManager contentNegotiationManager;
private ContentNegotiationManagerFactoryBean cnManagerFactoryBean = new ContentNegotiationManagerFactoryBean();
private final ContentNegotiationManagerFactoryBean cnManagerFactoryBean = new ContentNegotiationManagerFactoryBean();
private boolean useNotAcceptableStatusCode = false;
@@ -104,10 +104,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
private List<ViewResolver> viewResolvers;
public ContentNegotiatingViewResolver() {
super();
}
public void setOrder(int order) {
this.order = order;
}
@@ -118,7 +114,9 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
/**
* Set the {@link ContentNegotiationManager} to use to determine requested media types.
* If not set, the default constructor is used.
* <p>If not set, ContentNegotiationManager's default constructor will be used,
* applying a {@link org.springframework.web.accept.HeaderContentNegotiationStrategy}.
* @see ContentNegotiationManager#ContentNegotiationManager()
*/
public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
this.contentNegotiationManager = contentNegotiationManager;
@@ -130,18 +128,16 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
* <p>For instance, when this flag is {@code true} (the default), a request for {@code /hotels.pdf}
* will result in an {@code AbstractPdfView} being resolved, while the {@code Accept} header can be the
* browser-defined {@code text/html,application/xhtml+xml}.
*
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
*/
@Deprecated
public void setFavorPathExtension(boolean favorPathExtension) {
this.cnManagerFactoryBean.setFavorParameter(favorPathExtension);
this.cnManagerFactoryBean.setFavorPathExtension(favorPathExtension);
}
/**
* Indicate whether to use the Java Activation Framework to map from file extensions to media types.
* <p>Default is {@code true}, i.e. the Java Activation Framework is used (if available).
*
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
*/
@Deprecated
@@ -155,7 +151,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
* <p>For instance, when this flag is {@code true}, a request for {@code /hotels?format=pdf} will result
* in an {@code AbstractPdfView} being resolved, while the {@code Accept} header can be the browser-defined
* {@code text/html,application/xhtml+xml}.
*
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
*/
@Deprecated
@@ -166,7 +161,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
/**
* Set the parameter name that can be used to determine the requested media type if the {@link
* #setFavorParameter} property is {@code true}. The default parameter name is {@code format}.
*
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
*/
@Deprecated
@@ -179,7 +173,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
* <p>If set to {@code true}, this view resolver will only refer to the file extension and/or
* parameter, as indicated by the {@link #setFavorPathExtension favorPathExtension} and
* {@link #setFavorParameter favorParameter} properties.
*
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
*/
@Deprecated
@@ -191,7 +184,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
* Set the mapping from file extensions to media types.
* <p>When this mapping is not set or when an extension is not present, this view resolver
* will fall back to using a {@link FileTypeMap} when the Java Action Framework is available.
*
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
*/
@Deprecated
@@ -207,7 +199,6 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
* Set the default content type.
* <p>This content type will be used when file extension, parameter, nor {@code Accept}
* header define a content-type, either through being disabled or empty.
*
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
*/
@Deprecated
@@ -275,7 +266,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
this.cnManagerFactoryBean.setServletContext(servletContext);
}
public void afterPropertiesSet() throws Exception {
public void afterPropertiesSet() {
if (this.contentNegotiationManager == null) {
this.cnManagerFactoryBean.afterPropertiesSet();
this.contentNegotiationManager = this.cnManagerFactoryBean.getObject();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -160,7 +160,7 @@ public class ResourceBundleViewResolver extends AbstractCachingViewResolver
* <p>View definitions that define their own parent or carry their own
* class can still override this. Strictly speaking, the rule that a
* default parent setting does not apply to a bean definition that
* carries a class is there for backwards compatiblity reasons.
* carries a class is there for backwards compatibility reasons.
* It still matches the typical use case.
*/
public void setDefaultParentView(String defaultParentView) {

View File

@@ -309,8 +309,8 @@
<@bind path />
<#assign id="${status.expression?replace('[','')?replace(']','')}">
<#assign isSelected = status.value?? && status.value?string=="true">
<input type="hidden" name="_${id}" value="on"/>
<input type="checkbox" id="${id}" name="${id}"<#if isSelected> checked="checked"</#if> ${attributes}/>
<input type="hidden" name="_${status.expression}" value="on"/>
<input type="checkbox" id="${id}" name="${status.expression}"<#if isSelected> checked="checked"</#if> ${attributes}/>
</#macro>
<#--

View File

@@ -43,4 +43,4 @@ public class JasperReportsCsvView extends AbstractJasperReportsSingleFormatView
return true;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.view.json;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;
import java.util.HashMap;
@@ -27,7 +28,6 @@ import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
@@ -48,13 +48,15 @@ import org.springframework.web.servlet.view.AbstractView;
* @author Jeremy Grelle
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 3.1.2
* @see org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
*/
public class MappingJackson2JsonView extends AbstractView {
/**
* Default content type. Overridable as bean property.
* Default content type: "application/json".
* Overridable through {@link #setContentType}.
*/
public static final String DEFAULT_CONTENT_TYPE = "application/json";
@@ -75,8 +77,9 @@ public class MappingJackson2JsonView extends AbstractView {
private boolean updateContentLength = false;
/**
* Construct a new {@code JacksonJsonView}, setting the content type to {@code application/json}.
* Construct a new {@code MappingJackson2JsonView}, setting the content type to {@code application/json}.
*/
public MappingJackson2JsonView() {
setContentType(DEFAULT_CONTENT_TYPE);
@@ -85,13 +88,11 @@ public class MappingJackson2JsonView extends AbstractView {
/**
* Sets the {@code ObjectMapper} for this view.
* If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} is used.
* <p>Setting a custom-configured {@code ObjectMapper} is one way to take further control
* of the JSON serialization process. For example, an extended {@code SerializerFactory}
* can be configured that provides custom serializers for specific types. The other option
* for refining the serialization process is to use Jackson's provided annotations on the
* types to be serialized, in which case a custom-configured ObjectMapper is unnecessary.
* Set the {@code ObjectMapper} for this view.
* If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} will be used.
* <p>Setting a custom-configured {@code ObjectMapper} is one way to take further control of
* the JSON serialization process. The other option is to use Jackson's provided annotations
* on the types to be serialized, in which case a custom-configured ObjectMapper is unnecessary.
*/
public void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "'objectMapper' must not be null");
@@ -99,14 +100,15 @@ public class MappingJackson2JsonView extends AbstractView {
configurePrettyPrint();
}
private void configurePrettyPrint() {
if (this.prettyPrint != null) {
this.objectMapper.configure(SerializationFeature.INDENT_OUTPUT, this.prettyPrint);
}
/**
* Return the {@code ObjectMapper} for this view.
*/
public final ObjectMapper getObjectMapper() {
return this.objectMapper;
}
/**
* Set the {@code JsonEncoding} for this converter.
* Set the {@code JsonEncoding} for this view.
* By default, {@linkplain JsonEncoding#UTF8 UTF-8} is used.
*/
public void setEncoding(JsonEncoding encoding) {
@@ -114,9 +116,16 @@ public class MappingJackson2JsonView extends AbstractView {
this.encoding = encoding;
}
/**
* Return the {@code JsonEncoding} for this view.
*/
public final JsonEncoding getEncoding() {
return this.encoding;
}
/**
* Indicates whether the JSON output by this view should be prefixed with <tt>"{} && "</tt>.
* Default is false.
* Default is {@code false}.
* <p>Prefixing the JSON string in this manner is used to help prevent JSON Hijacking.
* The prefix renders the string syntactically invalid as a script so that it cannot be hijacked.
* This prefix does not affect the evaluation of JSON, but if JSON validation is performed
@@ -127,12 +136,11 @@ public class MappingJackson2JsonView extends AbstractView {
}
/**
* Whether to use the {@link DefaultPrettyPrinter} when writing JSON.
* Whether to use the default pretty printer when writing JSON.
* This is a shortcut for setting up an {@code ObjectMapper} as follows:
* <pre>
* ObjectMapper mapper = new ObjectMapper();
* mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
* converter.setObjectMapper(mapper);
* </pre>
* <p>The default value is {@code false}.
*/
@@ -141,6 +149,12 @@ public class MappingJackson2JsonView extends AbstractView {
configurePrettyPrint();
}
private void configurePrettyPrint() {
if (this.prettyPrint != null) {
this.objectMapper.configure(SerializationFeature.INDENT_OUTPUT, this.prettyPrint);
}
}
/**
* Set the attribute in the model that should be rendered by this view.
* When set, all other model attributes will be ignored.
@@ -160,7 +174,7 @@ public class MappingJackson2JsonView extends AbstractView {
/**
* Return the attributes in the model that should be rendered by this view.
*/
public Set<String> getModelKeys() {
public final Set<String> getModelKeys() {
return this.modelKeys;
}
@@ -179,7 +193,7 @@ public class MappingJackson2JsonView extends AbstractView {
* @deprecated use {@link #getModelKeys()} instead
*/
@Deprecated
public Set<String> getRenderedAttributes() {
public final Set<String> getRenderedAttributes() {
return this.modelKeys;
}
@@ -212,6 +226,7 @@ public class MappingJackson2JsonView extends AbstractView {
this.updateContentLength = updateContentLength;
}
@Override
protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
setResponseContentType(request, response);
@@ -227,34 +242,21 @@ public class MappingJackson2JsonView extends AbstractView {
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
OutputStream stream = this.updateContentLength ? createTemporaryOutputStream() : response.getOutputStream();
OutputStream stream = (this.updateContentLength ? createTemporaryOutputStream() : response.getOutputStream());
Object value = filterModel(model);
JsonGenerator generator = this.objectMapper.getJsonFactory().createJsonGenerator(stream, this.encoding);
// A workaround for JsonGenerators not applying serialization features
// https://github.com/FasterXML/jackson-databind/issues/12
if (this.objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
generator.useDefaultPrettyPrinter();
}
if (this.prefixJson) {
generator.writeRaw("{} && ");
}
this.objectMapper.writeValue(generator, value);
writeContent(stream, value, this.prefixJson);
if (this.updateContentLength) {
writeToResponse(response, (ByteArrayOutputStream) stream);
}
}
/**
* Filters out undesired attributes from the given model.
* Filter out undesired attributes from the given model.
* The return value can be either another {@link Map} or a single value object.
* <p>The default implementation removes {@link BindingResult} instances and entries
* not included in the {@link #setRenderedAttributes renderedAttributes} property.
* @param model the model, as passed on to {@link #renderMergedOutputModel}
* @return the object to be rendered
* @return the value to be rendered
*/
protected Object filterModel(Map<String, Object> model) {
Map<String, Object> result = new HashMap<String, Object>(model.size());
@@ -267,4 +269,27 @@ public class MappingJackson2JsonView extends AbstractView {
return (this.extractValueFromSingleKeyModel && result.size() == 1 ? result.values().iterator().next() : result);
}
/**
* Write the actual JSON content to the stream.
* @param stream the output stream to use
* @param value the value to be rendered, as returned from {@link #filterModel}
* @param prefixJson whether the JSON output by this view should be prefixed
* with <tt>"{} && "</tt> (as indicated through {@link #setPrefixJson})
* @throws IOException if writing failed
*/
protected void writeContent(OutputStream stream, Object value, boolean prefixJson) throws IOException {
JsonGenerator generator = this.objectMapper.getJsonFactory().createJsonGenerator(stream, this.encoding);
// A workaround for JsonGenerators not applying serialization features
// https://github.com/FasterXML/jackson-databind/issues/12
if (this.objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
generator.useDefaultPrettyPrinter();
}
if (prefixJson) {
generator.writeRaw("{} && ");
}
this.objectMapper.writeValue(generator, value);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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,12 +17,12 @@
package org.springframework.web.servlet.view.json;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -30,15 +30,13 @@ import org.codehaus.jackson.JsonEncoding;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.SerializerFactory;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.AbstractView;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
/**
* Spring MVC {@link View} that renders JSON content by serializing the model for the current request
* using <a href="http://jackson.codehaus.org/">Jackson's</a> {@link ObjectMapper}.
@@ -50,13 +48,15 @@ import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
* @author Jeremy Grelle
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 3.0
* @see org.springframework.http.converter.json.MappingJacksonHttpMessageConverter
*/
public class MappingJacksonJsonView extends AbstractView {
/**
* Default content type. Overridable as bean property.
* Default content type: "application/json".
* Overridable through {@link #setContentType}.
*/
public static final String DEFAULT_CONTENT_TYPE = "application/json";
@@ -77,8 +77,9 @@ public class MappingJacksonJsonView extends AbstractView {
private boolean updateContentLength = false;
/**
* Construct a new {@code JacksonJsonView}, setting the content type to {@code application/json}.
* Construct a new {@code MappingJacksonJsonView}, setting the content type to {@code application/json}.
*/
public MappingJacksonJsonView() {
setContentType(DEFAULT_CONTENT_TYPE);
@@ -87,13 +88,11 @@ public class MappingJacksonJsonView extends AbstractView {
/**
* Sets the {@code ObjectMapper} for this view.
* If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} is used.
* <p>Setting a custom-configured {@code ObjectMapper} is one way to take further control
* of the JSON serialization process. For example, an extended {@link SerializerFactory}
* can be configured that provides custom serializers for specific types. The other option
* for refining the serialization process is to use Jackson's provided annotations on the
* types to be serialized, in which case a custom-configured ObjectMapper is unnecessary.
* Set the {@code ObjectMapper} for this view.
* If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} will be used.
* <p>Setting a custom-configured {@code ObjectMapper} is one way to take further control of
* the JSON serialization process. The other option is to use Jackson's provided annotations
* on the types to be serialized, in which case a custom-configured ObjectMapper is unnecessary.
*/
public void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "'objectMapper' must not be null");
@@ -101,14 +100,15 @@ public class MappingJacksonJsonView extends AbstractView {
configurePrettyPrint();
}
private void configurePrettyPrint() {
if (this.prettyPrint != null) {
this.objectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, this.prettyPrint);
}
/**
* Return the {@code ObjectMapper} for this view.
*/
public final ObjectMapper getObjectMapper() {
return this.objectMapper;
}
/**
* Set the {@code JsonEncoding} for this converter.
* Set the {@code JsonEncoding} for this view.
* By default, {@linkplain JsonEncoding#UTF8 UTF-8} is used.
*/
public void setEncoding(JsonEncoding encoding) {
@@ -116,9 +116,16 @@ public class MappingJacksonJsonView extends AbstractView {
this.encoding = encoding;
}
/**
* Return the {@code JsonEncoding} for this view.
*/
public final JsonEncoding getEncoding() {
return this.encoding;
}
/**
* Indicates whether the JSON output by this view should be prefixed with <tt>"{} && "</tt>.
* Default is false.
* Default is {@code false}.
* <p>Prefixing the JSON string in this manner is used to help prevent JSON Hijacking.
* The prefix renders the string syntactically invalid as a script so that it cannot be hijacked.
* This prefix does not affect the evaluation of JSON, but if JSON validation is performed
@@ -129,12 +136,11 @@ public class MappingJacksonJsonView extends AbstractView {
}
/**
* Whether to use the {@link DefaultPrettyPrinter} when writing JSON.
* Whether to use the default pretty printer when writing JSON.
* This is a shortcut for setting up an {@code ObjectMapper} as follows:
* <pre>
* ObjectMapper mapper = new ObjectMapper();
* mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
* converter.setObjectMapper(mapper);
* </pre>
* <p>The default value is {@code false}.
*/
@@ -143,6 +149,12 @@ public class MappingJacksonJsonView extends AbstractView {
configurePrettyPrint();
}
private void configurePrettyPrint() {
if (this.prettyPrint != null) {
this.objectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, this.prettyPrint);
}
}
/**
* Set the attribute in the model that should be rendered by this view.
* When set, all other model attributes will be ignored.
@@ -162,7 +174,7 @@ public class MappingJacksonJsonView extends AbstractView {
/**
* Return the attributes in the model that should be rendered by this view.
*/
public Set<String> getModelKeys() {
public final Set<String> getModelKeys() {
return this.modelKeys;
}
@@ -181,7 +193,7 @@ public class MappingJacksonJsonView extends AbstractView {
* @deprecated use {@link #getModelKeys()} instead
*/
@Deprecated
public Set<String> getRenderedAttributes() {
public final Set<String> getRenderedAttributes() {
return this.modelKeys;
}
@@ -230,34 +242,21 @@ public class MappingJacksonJsonView extends AbstractView {
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
OutputStream stream = this.updateContentLength ? createTemporaryOutputStream() : response.getOutputStream();
OutputStream stream = (this.updateContentLength ? createTemporaryOutputStream() : response.getOutputStream());
Object value = filterModel(model);
JsonGenerator generator = this.objectMapper.getJsonFactory().createJsonGenerator(stream, this.encoding);
// A workaround for JsonGenerators not applying serialization features
// https://github.com/FasterXML/jackson-databind/issues/12
if (this.objectMapper.getSerializationConfig().isEnabled(SerializationConfig.Feature.INDENT_OUTPUT)) {
generator.useDefaultPrettyPrinter();
}
if (this.prefixJson) {
generator.writeRaw("{} && ");
}
this.objectMapper.writeValue(generator, value);
writeContent(stream, value, this.prefixJson);
if (this.updateContentLength) {
writeToResponse(response, (ByteArrayOutputStream) stream);
}
}
/**
* Filters out undesired attributes from the given model.
* Filter out undesired attributes from the given model.
* The return value can be either another {@link Map} or a single value object.
* <p>The default implementation removes {@link BindingResult} instances and entries
* not included in the {@link #setRenderedAttributes renderedAttributes} property.
* @param model the model, as passed on to {@link #renderMergedOutputModel}
* @return the object to be rendered
* @return the value to be rendered
*/
protected Object filterModel(Map<String, Object> model) {
Map<String, Object> result = new HashMap<String, Object>(model.size());
@@ -270,4 +269,27 @@ public class MappingJacksonJsonView extends AbstractView {
return (this.extractValueFromSingleKeyModel && result.size() == 1 ? result.values().iterator().next() : result);
}
/**
* Write the actual JSON content to the stream.
* @param stream the output stream to use
* @param value the value to be rendered, as returned from {@link #filterModel}
* @param prefixJson whether the JSON output by this view should be prefixed
* with <tt>"{} && "</tt> (as indicated through {@link #setPrefixJson})
* @throws IOException if writing failed
*/
protected void writeContent(OutputStream stream, Object value, boolean prefixJson) throws IOException {
JsonGenerator generator = this.objectMapper.getJsonFactory().createJsonGenerator(stream, this.encoding);
// A workaround for JsonGenerators not applying serialization features
// https://github.com/FasterXML/jackson-databind/issues/12
if (this.objectMapper.getSerializationConfig().isEnabled(SerializationConfig.Feature.INDENT_OUTPUT)) {
generator.useDefaultPrettyPrinter();
}
if (prefixJson) {
generator.writeRaw("{} && ");
}
this.objectMapper.writeValue(generator, value);
}
}