diff --git a/build-spring-framework/resources/changelog.txt b/build-spring-framework/resources/changelog.txt
index 52957824ea9..38f92ea0796 100644
--- a/build-spring-framework/resources/changelog.txt
+++ b/build-spring-framework/resources/changelog.txt
@@ -16,7 +16,7 @@ Changes in version 3.1.2 (2012-06-??)
* ServletContextResource's getFile implementation falls back to getRealPath for non-existent files
* fixed StandardServletMultipartResolver compatibility with Resin (only deleting actual file parts)
* fix issue with parsing invalid Content-Type or Accept headers
-
+* add Jackson 2 HttpMessageConverter and View types
Changes in version 3.1.1 (2012-02-16)
-------------------------------------
diff --git a/org.springframework.web.servlet/.classpath b/org.springframework.web.servlet/.classpath
index ebeb77d8ec5..a735aa4ded1 100644
--- a/org.springframework.web.servlet/.classpath
+++ b/org.springframework.web.servlet/.classpath
@@ -23,6 +23,9 @@
+
+
+
diff --git a/org.springframework.web.servlet/ivy.xml b/org.springframework.web.servlet/ivy.xml
index c15efda0b22..2729836258b 100644
--- a/org.springframework.web.servlet/ivy.xml
+++ b/org.springframework.web.servlet/ivy.xml
@@ -69,6 +69,8 @@
conf="optional, velocity->compile"/>
+ 1.4.2
true
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.0.2
+ true
+ org.springframeworkspring-asm
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
index f0d09aaff75..080565ea16a 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2012 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,6 +35,7 @@ import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
@@ -65,52 +66,52 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv
import org.w3c.dom.Element;
/**
- * A {@link BeanDefinitionParser} that provides the configuration for the
+ * A {@link BeanDefinitionParser} that provides the configuration for the
* {@code } MVC namespace element.
*
*
This class registers the following {@link HandlerMapping}s:
*
- *
{@link RequestMappingHandlerMapping}
+ *
{@link RequestMappingHandlerMapping}
* ordered at 0 for mapping requests to annotated controller methods.
- *
{@link BeanNameUrlHandlerMapping}
+ *
{@link BeanNameUrlHandlerMapping}
* ordered at 2 to map URL paths to controller bean names.
*
*
- *
Note: Additional HandlerMappings may be registered
- * as a result of using the {@code } or the
+ *
Note: Additional HandlerMappings may be registered
+ * as a result of using the {@code } or the
* {@code } MVC namespace elements.
- *
+ *
*
This class registers the following {@link HandlerAdapter}s:
*
- *
{@link RequestMappingHandlerAdapter}
+ *
{@link RequestMappingHandlerAdapter}
* for processing requests with annotated controller methods.
- *
{@link HttpRequestHandlerAdapter}
+ *
{@link HttpRequestHandlerAdapter}
* for processing requests with {@link HttpRequestHandler}s.
- *
{@link SimpleControllerHandlerAdapter}
+ *
{@link SimpleControllerHandlerAdapter}
* for processing requests with interface-based {@link Controller}s.
*
- *
+ *
*
This class registers the following {@link HandlerExceptionResolver}s:
*
- *
{@link ExceptionHandlerExceptionResolver} for handling exceptions
+ *
{@link ExceptionHandlerExceptionResolver} for handling exceptions
* through @{@link ExceptionHandler} methods.
- *
{@link ResponseStatusExceptionResolver} for exceptions annotated
+ *
{@link ResponseStatusExceptionResolver} for exceptions annotated
* with @{@link ResponseStatus}.
- *
{@link DefaultHandlerExceptionResolver} for resolving known Spring
+ *
{@link DefaultHandlerExceptionResolver} for resolving known Spring
* exception types
*
- *
- *
Both the {@link RequestMappingHandlerAdapter} and the
- * {@link ExceptionHandlerExceptionResolver} are configured with default
+ *
+ *
Both the {@link RequestMappingHandlerAdapter} and the
+ * {@link ExceptionHandlerExceptionResolver} are configured with default
* instances of the following kind, unless custom instances are provided:
*
*
A {@link DefaultFormattingConversionService}
- *
A {@link LocalValidatorFactoryBean} if a JSR-303 implementation is
+ *
A {@link LocalValidatorFactoryBean} if a JSR-303 implementation is
* available on the classpath
- *
A range of {@link HttpMessageConverter}s depending on what 3rd party
+ *
A range of {@link HttpMessageConverter}s depending on what 3rd party
* libraries are available on the classpath.
*
- *
+ *
* @author Keith Donald
* @author Juergen Hoeller
* @author Arjen Poutsma
@@ -125,6 +126,10 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
private static final boolean jaxb2Present =
ClassUtils.isPresent("javax.xml.bind.Binder", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
+ private static final boolean jackson2Present =
+ ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
+ ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
+
private static final boolean jacksonPresent =
ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
@@ -158,7 +163,7 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
ManagedList> messageConverters = getMessageConverters(element, source, parserContext);
ManagedList> argumentResolvers = getArgumentResolvers(element, source, parserContext);
ManagedList> returnValueHandlers = getReturnValueHandlers(element, source, parserContext);
-
+
RootBeanDefinition methodAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
methodAdapterDef.setSource(source);
methodAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
@@ -215,7 +220,7 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
- // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
+ // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
parserContext.popAndRegisterContainingComponent();
@@ -309,7 +314,10 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
messageConverters
.add(createConverterBeanDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
}
- if (jacksonPresent) {
+ if (jackson2Present) {
+ messageConverters.add(createConverterBeanDefinition(MappingJackson2HttpMessageConverter.class, source));
+ }
+ else if (jacksonPresent) {
messageConverters.add(createConverterBeanDefinition(MappingJacksonHttpMessageConverter.class, source));
}
if (romePresent) {
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
index 03c8480bba0..55d418d8071 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
@@ -41,6 +41,7 @@ import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
@@ -74,59 +75,59 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv
/**
* This is the main class providing the configuration behind the MVC Java config.
- * It is typically imported by adding {@link EnableWebMvc @EnableWebMvc} to an
- * application {@link Configuration @Configuration} class. An alternative more
+ * It is typically imported by adding {@link EnableWebMvc @EnableWebMvc} to an
+ * application {@link Configuration @Configuration} class. An alternative more
* advanced option is to extend directly from this class and override methods as
- * necessary remembering to add {@link Configuration @Configuration} to the
+ * necessary remembering to add {@link Configuration @Configuration} to the
* subclass and {@link Bean @Bean} to overridden {@link Bean @Bean} methods.
* For more details see the Javadoc of {@link EnableWebMvc @EnableWebMvc}.
- *
+ *
*
This class registers the following {@link HandlerMapping}s:
*
- *
{@link RequestMappingHandlerMapping}
+ *
{@link RequestMappingHandlerMapping}
* ordered at 0 for mapping requests to annotated controller methods.
- *
{@link HandlerMapping}
+ *
{@link HandlerMapping}
* ordered at 1 to map URL paths directly to view names.
- *
{@link BeanNameUrlHandlerMapping}
+ *
{@link BeanNameUrlHandlerMapping}
* ordered at 2 to map URL paths to controller bean names.
- *
{@link HandlerMapping}
+ *
{@link HandlerMapping}
* ordered at {@code Integer.MAX_VALUE-1} to serve static resource requests.
- *
{@link HandlerMapping}
+ *
{@link HandlerMapping}
* ordered at {@code Integer.MAX_VALUE} to forward requests to the default servlet.
*
*
*
Registers these {@link HandlerAdapter}s:
*
- *
{@link RequestMappingHandlerAdapter}
+ *
{@link RequestMappingHandlerAdapter}
* for processing requests with annotated controller methods.
- *
{@link HttpRequestHandlerAdapter}
+ *
{@link HttpRequestHandlerAdapter}
* for processing requests with {@link HttpRequestHandler}s.
- *
{@link SimpleControllerHandlerAdapter}
+ *
{@link SimpleControllerHandlerAdapter}
* for processing requests with interface-based {@link Controller}s.
*
*
*
Registers a {@link HandlerExceptionResolverComposite} with this chain of
* exception resolvers:
*
- *
{@link ExceptionHandlerExceptionResolver} for handling exceptions
+ *
{@link ExceptionHandlerExceptionResolver} for handling exceptions
* through @{@link ExceptionHandler} methods.
- *
{@link ResponseStatusExceptionResolver} for exceptions annotated
+ *
{@link ResponseStatusExceptionResolver} for exceptions annotated
* with @{@link ResponseStatus}.
- *
{@link DefaultHandlerExceptionResolver} for resolving known Spring
+ *
{@link DefaultHandlerExceptionResolver} for resolving known Spring
* exception types
*
*
- *
Both the {@link RequestMappingHandlerAdapter} and the
- * {@link ExceptionHandlerExceptionResolver} are configured with default
+ *
Both the {@link RequestMappingHandlerAdapter} and the
+ * {@link ExceptionHandlerExceptionResolver} are configured with default
* instances of the following kind, unless custom instances are provided:
*
*
A {@link DefaultFormattingConversionService}
- *
A {@link LocalValidatorFactoryBean} if a JSR-303 implementation is
+ *
A {@link LocalValidatorFactoryBean} if a JSR-303 implementation is
* available on the classpath
- *
A range of {@link HttpMessageConverter}s depending on the 3rd party
+ *
A range of {@link HttpMessageConverter}s depending on the 3rd party
* libraries available on the classpath.
*
- *
+ *
* @see EnableWebMvc
* @see WebMvcConfigurer
* @see WebMvcConfigurerAdapter
@@ -151,9 +152,9 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
-
+
/**
- * Return a {@link RequestMappingHandlerMapping} ordered at 0 for mapping
+ * Return a {@link RequestMappingHandlerMapping} ordered at 0 for mapping
* requests to annotated controllers.
*/
@Bean
@@ -163,11 +164,11 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
handlerMapping.setInterceptors(getInterceptors());
return handlerMapping;
}
-
+
/**
- * Provide access to the shared handler interceptors used to configure
- * {@link HandlerMapping} instances with. This method cannot be overridden,
- * use {@link #addInterceptors(InterceptorRegistry)} instead.
+ * Provide access to the shared handler interceptors used to configure
+ * {@link HandlerMapping} instances with. This method cannot be overridden,
+ * use {@link #addInterceptors(InterceptorRegistry)} instead.
*/
protected final Object[] getInterceptors() {
if (interceptors == null) {
@@ -178,9 +179,9 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
return interceptors.toArray();
}
-
+
/**
- * Override this method to add Spring MVC interceptors for
+ * Override this method to add Spring MVC interceptors for
* pre- and post-processing of controller invocation.
* @see InterceptorRegistry
*/
@@ -188,15 +189,15 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
- * Return a handler mapping ordered at 1 to map URL paths directly to
- * view names. To configure view controllers, override
- * {@link #addViewControllers}.
+ * Return a handler mapping ordered at 1 to map URL paths directly to
+ * view names. To configure view controllers, override
+ * {@link #addViewControllers}.
*/
@Bean
public HandlerMapping viewControllerHandlerMapping() {
ViewControllerRegistry registry = new ViewControllerRegistry();
addViewControllers(registry);
-
+
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
handlerMapping = handlerMapping != null ? handlerMapping : new EmptyHandlerMapping();
handlerMapping.setInterceptors(getInterceptors());
@@ -209,9 +210,9 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
*/
protected void addViewControllers(ViewControllerRegistry registry) {
}
-
+
/**
- * Return a {@link BeanNameUrlHandlerMapping} ordered at 2 to map URL
+ * Return a {@link BeanNameUrlHandlerMapping} ordered at 2 to map URL
* paths to controller bean names.
*/
@Bean
@@ -223,8 +224,8 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
- * Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped
- * resource handlers. To configure resource handling, override
+ * Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped
+ * resource handlers. To configure resource handling, override
* {@link #addResourceHandlers}.
*/
@Bean
@@ -237,16 +238,16 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
- * Override this method to add resource handlers for serving static resources.
+ * Override this method to add resource handlers for serving static resources.
* @see ResourceHandlerRegistry
*/
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
}
/**
- * Return a handler mapping ordered at Integer.MAX_VALUE with a mapped
- * default servlet handler. To configure "default" Servlet handling,
- * override {@link #configureDefaultServletHandling}.
+ * Return a handler mapping ordered at Integer.MAX_VALUE with a mapped
+ * default servlet handler. To configure "default" Servlet handling,
+ * override {@link #configureDefaultServletHandling}.
*/
@Bean
public HandlerMapping defaultServletHandlerMapping() {
@@ -258,15 +259,15 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
- * Override this method to configure "default" Servlet handling.
+ * Override this method to configure "default" Servlet handling.
* @see DefaultServletHandlerConfigurer
*/
protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
/**
- * Returns a {@link RequestMappingHandlerAdapter} for processing requests
- * through annotated controller methods. Consider overriding one of these
+ * Returns a {@link RequestMappingHandlerAdapter} for processing requests
+ * through annotated controller methods. Consider overriding one of these
* other more fine-grained methods:
*
*
{@link #addArgumentResolvers} for adding custom argument resolvers.
@@ -279,13 +280,13 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
ConfigurableWebBindingInitializer webBindingInitializer = new ConfigurableWebBindingInitializer();
webBindingInitializer.setConversionService(mvcConversionService());
webBindingInitializer.setValidator(mvcValidator());
-
+
List argumentResolvers = new ArrayList();
addArgumentResolvers(argumentResolvers);
List returnValueHandlers = new ArrayList();
addReturnValueHandlers(returnValueHandlers);
-
+
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
adapter.setMessageConverters(getMessageConverters());
adapter.setWebBindingInitializer(webBindingInitializer);
@@ -295,40 +296,40 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
- * Add custom {@link HandlerMethodArgumentResolver}s to use in addition to
+ * Add custom {@link HandlerMethodArgumentResolver}s to use in addition to
* the ones registered by default.
- *
Custom argument resolvers are invoked before built-in resolvers
- * except for those that rely on the presence of annotations (e.g.
- * {@code @RequestParameter}, {@code @PathVariable}, etc.).
- * The latter can be customized by configuring the
- * {@link RequestMappingHandlerAdapter} directly.
- * @param argumentResolvers the list of custom converters;
+ *
Custom argument resolvers are invoked before built-in resolvers
+ * except for those that rely on the presence of annotations (e.g.
+ * {@code @RequestParameter}, {@code @PathVariable}, etc.).
+ * The latter can be customized by configuring the
+ * {@link RequestMappingHandlerAdapter} directly.
+ * @param argumentResolvers the list of custom converters;
* initially an empty list.
*/
protected void addArgumentResolvers(List argumentResolvers) {
}
/**
- * Add custom {@link HandlerMethodReturnValueHandler}s in addition to the
+ * Add custom {@link HandlerMethodReturnValueHandler}s in addition to the
* ones registered by default.
- *
Custom return value handlers are invoked before built-in ones except
- * for those that rely on the presence of annotations (e.g.
- * {@code @ResponseBody}, {@code @ModelAttribute}, etc.).
- * The latter can be customized by configuring the
+ *
Custom return value handlers are invoked before built-in ones except
+ * for those that rely on the presence of annotations (e.g.
+ * {@code @ResponseBody}, {@code @ModelAttribute}, etc.).
+ * The latter can be customized by configuring the
* {@link RequestMappingHandlerAdapter} directly.
- * @param returnValueHandlers the list of custom handlers;
+ * @param returnValueHandlers the list of custom handlers;
* initially an empty list.
*/
protected void addReturnValueHandlers(List returnValueHandlers) {
}
/**
- * Provides access to the shared {@link HttpMessageConverter}s used by the
- * {@link RequestMappingHandlerAdapter} and the
- * {@link ExceptionHandlerExceptionResolver}.
- * This method cannot be overridden.
+ * Provides access to the shared {@link HttpMessageConverter}s used by the
+ * {@link RequestMappingHandlerAdapter} and the
+ * {@link ExceptionHandlerExceptionResolver}.
+ * This method cannot be overridden.
* Use {@link #configureMessageConverters(List)} instead.
- * Also see {@link #addDefaultHttpMessageConverters(List)} that can be
+ * Also see {@link #addDefaultHttpMessageConverters(List)} that can be
* used to add default message converters.
*/
protected final List> getMessageConverters() {
@@ -343,21 +344,21 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
- * Override this method to add custom {@link HttpMessageConverter}s to use
- * with the {@link RequestMappingHandlerAdapter} and the
- * {@link ExceptionHandlerExceptionResolver}. Adding converters to the
+ * Override this method to add custom {@link HttpMessageConverter}s to use
+ * with the {@link RequestMappingHandlerAdapter} and the
+ * {@link ExceptionHandlerExceptionResolver}. Adding converters to the
* list turns off the default converters that would otherwise be registered
- * by default. Also see {@link #addDefaultHttpMessageConverters(List)} that
+ * by default. Also see {@link #addDefaultHttpMessageConverters(List)} that
* can be used to add default message converters.
- * @param converters a list to add message converters to;
+ * @param converters a list to add message converters to;
* initially an empty list.
*/
protected void configureMessageConverters(List> converters) {
}
/**
- * Adds a set of default HttpMessageConverter instances to the given list.
- * Subclasses can call this method from {@link #configureMessageConverters(List)}.
+ * Adds a set of default HttpMessageConverter instances to the given list.
+ * Subclasses can call this method from {@link #configureMessageConverters(List)}.
* @param messageConverters the list to add the default message converters to
*/
protected final void addDefaultHttpMessageConverters(List> messageConverters) {
@@ -374,7 +375,10 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
if (ClassUtils.isPresent("javax.xml.bind.Binder", classLoader)) {
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
- if (ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", classLoader)) {
+ if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)) {
+ messageConverters.add(new MappingJackson2HttpMessageConverter());
+ }
+ else if (ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", classLoader)) {
messageConverters.add(new MappingJacksonHttpMessageConverter());
}
if (ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", classLoader)) {
@@ -382,10 +386,10 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
messageConverters.add(new RssChannelHttpMessageConverter());
}
}
-
+
/**
- * Returns a {@link FormattingConversionService} for use with annotated
- * controller methods and the {@code spring:eval} JSP tag.
+ * Returns a {@link FormattingConversionService} for use with annotated
+ * controller methods and the {@code spring:eval} JSP tag.
* Also see {@link #addFormatters} as an alternative to overriding this method.
*/
@Bean
@@ -402,11 +406,11 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
- * Returns a global {@link Validator} instance for example for validating
+ * Returns a global {@link Validator} instance for example for validating
* {@code @ModelAttribute} and {@code @RequestBody} method arguments.
* Delegates to {@link #getValidator()} first and if that returns {@code null}
* checks the classpath for the presence of a JSR-303 implementations
- * before creating a {@code LocalValidatorFactoryBean}.If a JSR-303
+ * before creating a {@code LocalValidatorFactoryBean}.If a JSR-303
* implementation is not available, a no-op {@link Validator} is returned.
*/
@Bean
@@ -446,7 +450,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
- * Returns a {@link HttpRequestHandlerAdapter} for processing requests
+ * Returns a {@link HttpRequestHandlerAdapter} for processing requests
* with {@link HttpRequestHandler}s.
*/
@Bean
@@ -455,7 +459,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
- * Returns a {@link SimpleControllerHandlerAdapter} for processing requests
+ * Returns a {@link SimpleControllerHandlerAdapter} for processing requests
* with interface-based controllers.
*/
@Bean
@@ -465,23 +469,23 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
/**
* Returns a {@link HandlerExceptionResolverComposite} containing a list
- * of exception resolvers obtained either through
+ * of exception resolvers obtained either through
* {@link #configureHandlerExceptionResolvers(List)} or through
* {@link #addDefaultHandlerExceptionResolvers(List)}.
*
Note: This method cannot be made final due to CGLib
* constraints. Rather than overriding it, consider overriding
- * {@link #configureHandlerExceptionResolvers(List)}, which allows
+ * {@link #configureHandlerExceptionResolvers(List)}, which allows
* providing a list of resolvers.
*/
@Bean
public HandlerExceptionResolver handlerExceptionResolver() {
List exceptionResolvers = new ArrayList();
configureHandlerExceptionResolvers(exceptionResolvers);
-
+
if (exceptionResolvers.isEmpty()) {
addDefaultHandlerExceptionResolvers(exceptionResolvers);
}
-
+
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
composite.setOrder(0);
composite.setExceptionResolvers(exceptionResolvers);
@@ -489,27 +493,27 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
/**
- * Override this method to configure the list of
- * {@link HandlerExceptionResolver}s to use. Adding resolvers to the list
- * turns off the default resolvers that would otherwise be registered by
- * default. Also see {@link #addDefaultHandlerExceptionResolvers(List)}
+ * Override this method to configure the list of
+ * {@link HandlerExceptionResolver}s to use. Adding resolvers to the list
+ * turns off the default resolvers that would otherwise be registered by
+ * default. Also see {@link #addDefaultHandlerExceptionResolvers(List)}
* that can be used to add the default exception resolvers.
- * @param exceptionResolvers a list to add exception resolvers to;
+ * @param exceptionResolvers a list to add exception resolvers to;
* initially an empty list.
*/
protected void configureHandlerExceptionResolvers(List exceptionResolvers) {
}
/**
- * A method available to subclasses for adding default
+ * A method available to subclasses for adding default
* {@link HandlerExceptionResolver}s.
*
Adds the following exception resolvers:
*
- *
{@link ExceptionHandlerExceptionResolver}
+ *
{@link ExceptionHandlerExceptionResolver}
* for handling exceptions through @{@link ExceptionHandler} methods.
- *
{@link ResponseStatusExceptionResolver}
+ *
{@link ResponseStatusExceptionResolver}
* for exceptions annotated with @{@link ResponseStatus}.
- *
{@link DefaultHandlerExceptionResolver}
+ *
{@link DefaultHandlerExceptionResolver}
* for resolving known Spring exception types
*
*/
@@ -524,11 +528,11 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
}
private final static class EmptyHandlerMapping extends AbstractHandlerMapping {
-
+
@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
return null;
}
}
-
+
}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java
new file mode 100644
index 00000000000..4427b138d6a
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2002-2012 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.view.json;
+
+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;
+
+import com.fasterxml.jackson.core.JsonEncoding;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+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;
+
+/**
+ * Spring MVC {@link View} that renders JSON content by serializing the model for the current request
+ * using Jackson 2's {@link ObjectMapper}.
+ *
+ *
By default, the entire contents of the model map (with the exception of framework-specific classes)
+ * will be encoded as JSON. If the model contains only one key, you can have it extracted encoded as JSON
+ * alone via {@link #setExtractValueFromSingleKeyModel}.
+ *
+ * @author Jeremy Grelle
+ * @author Arjen Poutsma
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ * @see org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
+ */
+public class MappingJackson2JsonView extends AbstractView {
+
+ /**
+ * Default content type. Overridable as bean property.
+ */
+ public static final String DEFAULT_CONTENT_TYPE = "application/json";
+
+
+ private ObjectMapper objectMapper = new ObjectMapper();
+
+ private JsonEncoding encoding = JsonEncoding.UTF8;
+
+ private boolean prefixJson = false;
+
+ private Set modelKeys;
+
+ private boolean extractValueFromSingleKeyModel = false;
+
+ private boolean disableCaching = true;
+
+
+ /**
+ * Construct a new {@code JacksonJsonView}, setting the content type to {@code application/json}.
+ */
+ public MappingJackson2JsonView() {
+ setContentType(DEFAULT_CONTENT_TYPE);
+ setExposePathVariables(false);
+ }
+
+
+ /**
+ * Sets the {@code ObjectMapper} for this view.
+ * If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} is used.
+ *
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.
+ */
+ public void setObjectMapper(ObjectMapper objectMapper) {
+ Assert.notNull(objectMapper, "'objectMapper' must not be null");
+ this.objectMapper = objectMapper;
+ }
+
+ /**
+ * Set the {@code JsonEncoding} for this converter.
+ * By default, {@linkplain JsonEncoding#UTF8 UTF-8} is used.
+ */
+ public void setEncoding(JsonEncoding encoding) {
+ Assert.notNull(encoding, "'encoding' must not be null");
+ this.encoding = encoding;
+ }
+
+ /**
+ * Indicates whether the JSON output by this view should be prefixed with "{} && ".
+ * Default is false.
+ *
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
+ * on the string, the prefix would need to be ignored.
+ */
+ public void setPrefixJson(boolean prefixJson) {
+ this.prefixJson = prefixJson;
+ }
+
+ /**
+ * Set the attribute in the model that should be rendered by this view.
+ * When set, all other model attributes will be ignored.
+ */
+ public void setModelKey(String modelKey) {
+ this.modelKeys = Collections.singleton(modelKey);
+ }
+
+ /**
+ * Set the attributes in the model that should be rendered by this view.
+ * When set, all other model attributes will be ignored.
+ */
+ public void setModelKeys(Set modelKeys) {
+ this.modelKeys = modelKeys;
+ }
+
+ /**
+ * Return the attributes in the model that should be rendered by this view.
+ */
+ public Set getModelKeys() {
+ return this.modelKeys;
+ }
+
+ /**
+ * Set the attributes in the model that should be rendered by this view.
+ * When set, all other model attributes will be ignored.
+ * @deprecated use {@link #setModelKeys(Set)} instead
+ */
+ @Deprecated
+ public void setRenderedAttributes(Set renderedAttributes) {
+ this.modelKeys = renderedAttributes;
+ }
+
+ /**
+ * Return the attributes in the model that should be rendered by this view.
+ * @deprecated use {@link #getModelKeys()} instead
+ */
+ @Deprecated
+ public Set getRenderedAttributes() {
+ return this.modelKeys;
+ }
+
+ /**
+ * Set whether to serialize models containing a single attribute as a map or whether to
+ * extract the single value from the model and serialize it directly.
+ *
The effect of setting this flag is similar to using {@code MappingJacksonHttpMessageConverter}
+ * with an {@code @ResponseBody} request-handling method.
+ *
Default is {@code false}.
+ */
+ public void setExtractValueFromSingleKeyModel(boolean extractValueFromSingleKeyModel) {
+ this.extractValueFromSingleKeyModel = extractValueFromSingleKeyModel;
+ }
+
+ /**
+ * Disables caching of the generated JSON.
+ *
Default is {@code true}, which will prevent the client from caching the generated JSON.
+ */
+ public void setDisableCaching(boolean disableCaching) {
+ this.disableCaching = disableCaching;
+ }
+
+
+ @Override
+ protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
+ response.setContentType(getContentType());
+ response.setCharacterEncoding(this.encoding.getJavaName());
+ if (this.disableCaching) {
+ response.addHeader("Pragma", "no-cache");
+ response.addHeader("Cache-Control", "no-cache, no-store, max-age=0");
+ response.addDateHeader("Expires", 1L);
+ }
+ }
+
+ @Override
+ protected void renderMergedOutputModel(Map model, HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+
+ Object value = filterModel(model);
+ JsonGenerator generator =
+ this.objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(), this.encoding);
+ if (this.prefixJson) {
+ generator.writeRaw("{} && ");
+ }
+ this.objectMapper.writeValue(generator, value);
+ }
+
+ /**
+ * Filters out undesired attributes from the given model.
+ * The return value can be either another {@link Map} or a single value object.
+ *
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
+ */
+ protected Object filterModel(Map model) {
+ Map result = new HashMap(model.size());
+ Set renderedAttributes = (!CollectionUtils.isEmpty(this.modelKeys) ? this.modelKeys : model.keySet());
+ for (Map.Entry entry : model.entrySet()) {
+ if (!(entry.getValue() instanceof BindingResult) && renderedAttributes.contains(entry.getKey())) {
+ result.put(entry.getKey(), entry.getValue());
+ }
+ }
+ return (this.extractValueFromSingleKeyModel && result.size() == 1 ? result.values().iterator().next() : result);
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java
index 3f5fbef0e96..8dc615c4af6 100644
--- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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,7 +35,7 @@ import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.http.converter.HttpMessageConverter;
-import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockServletContext;
import org.springframework.stereotype.Controller;
@@ -73,12 +73,12 @@ public class WebMvcConfigurationSupportTests {
public void setUp() {
mvcConfiguration = new TestWebMvcConfiguration();
}
-
+
@Test
public void requestMappingHandlerMapping() throws Exception {
StaticWebApplicationContext cxt = new StaticWebApplicationContext();
cxt.registerSingleton("controller", TestController.class);
-
+
RequestMappingHandlerMapping handlerMapping = mvcConfiguration.requestMappingHandlerMapping();
assertEquals(0, handlerMapping.getOrder());
@@ -95,7 +95,7 @@ public class WebMvcConfigurationSupportTests {
assertEquals(Integer.MAX_VALUE, handlerMapping.getOrder());
assertTrue(handlerMapping.getClass().getName().endsWith("EmptyHandlerMapping"));
}
-
+
@Test
public void beanNameHandlerMapping() throws Exception {
StaticWebApplicationContext cxt = new StaticWebApplicationContext();
@@ -112,7 +112,7 @@ public class WebMvcConfigurationSupportTests {
assertEquals(2, chain.getInterceptors().length);
assertEquals(ConversionServiceExposingInterceptor.class, chain.getInterceptors()[1].getClass());
}
-
+
@Test
public void emptyResourceHandlerMapping() {
mvcConfiguration.setApplicationContext(new StaticWebApplicationContext());
@@ -121,7 +121,7 @@ public class WebMvcConfigurationSupportTests {
assertEquals(Integer.MAX_VALUE, handlerMapping.getOrder());
assertTrue(handlerMapping.getClass().getName().endsWith("EmptyHandlerMapping"));
}
-
+
@Test
public void emptyDefaultServletHandlerMapping() {
mvcConfiguration.setServletContext(new MockServletContext());
@@ -130,7 +130,7 @@ public class WebMvcConfigurationSupportTests {
assertEquals(Integer.MAX_VALUE, handlerMapping.getOrder());
assertTrue(handlerMapping.getClass().getName().endsWith("EmptyHandlerMapping"));
}
-
+
@Test
public void requestMappingHandlerAdapter() throws Exception {
RequestMappingHandlerAdapter adapter = mvcConfiguration.requestMappingHandlerAdapter();
@@ -145,29 +145,29 @@ public class WebMvcConfigurationSupportTests {
ConversionService conversionService = initializer.getConversionService();
assertNotNull(conversionService);
assertTrue(conversionService instanceof FormattingConversionService);
-
+
Validator validator = initializer.getValidator();
assertNotNull(validator);
assertTrue(validator instanceof LocalValidatorFactoryBean);
-
+
assertEquals(false, new DirectFieldAccessor(adapter).getPropertyValue("ignoreDefaultModelOnRedirect"));
}
-
+
@Test
public void handlerExceptionResolver() throws Exception {
- HandlerExceptionResolverComposite compositeResolver =
+ HandlerExceptionResolverComposite compositeResolver =
(HandlerExceptionResolverComposite) mvcConfiguration.handlerExceptionResolver();
-
+
assertEquals(0, compositeResolver.getOrder());
List expectedResolvers = new ArrayList();
mvcConfiguration.addDefaultHandlerExceptionResolvers(expectedResolvers);
assertEquals(expectedResolvers.size(), compositeResolver.getExceptionResolvers().size());
}
-
- @Test
+
+ @Test
public void webMvcConfigurerExtensionHooks() throws Exception {
-
+
StaticWebApplicationContext appCxt = new StaticWebApplicationContext();
appCxt.setServletContext(new MockServletContext(new FileSystemResourceLoader()));
appCxt.registerSingleton("controller", TestController.class);
@@ -175,33 +175,33 @@ public class WebMvcConfigurationSupportTests {
WebConfig webConfig = new WebConfig();
webConfig.setApplicationContext(appCxt);
webConfig.setServletContext(appCxt.getServletContext());
-
+
String actual = webConfig.mvcConversionService().convert(new TestBean(), String.class);
assertEquals("converted", actual);
RequestMappingHandlerAdapter adapter = webConfig.requestMappingHandlerAdapter();
assertEquals(1, adapter.getMessageConverters().size());
-
+
ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) adapter.getWebBindingInitializer();
assertNotNull(initializer);
-
+
BeanPropertyBindingResult bindingResult = new BeanPropertyBindingResult(null, "");
initializer.getValidator().validate(null, bindingResult);
assertEquals("invalid", bindingResult.getAllErrors().get(0).getCode());
@SuppressWarnings("unchecked")
- List argResolvers= (List)
+ List argResolvers= (List)
new DirectFieldAccessor(adapter).getPropertyValue("customArgumentResolvers");
assertEquals(1, argResolvers.size());
@SuppressWarnings("unchecked")
- List handlers = (List)
+ List handlers = (List)
new DirectFieldAccessor(adapter).getPropertyValue("customReturnValueHandlers");
assertEquals(1, handlers.size());
-
+
HandlerExceptionResolverComposite composite = (HandlerExceptionResolverComposite) webConfig.handlerExceptionResolver();
assertEquals(1, composite.getExceptionResolvers().size());
-
+
RequestMappingHandlerMapping rmHandlerMapping = webConfig.requestMappingHandlerMapping();
rmHandlerMapping.setApplicationContext(appCxt);
HandlerExecutionChain chain = rmHandlerMapping.getHandler(new MockHttpServletRequest("GET", "/"));
@@ -234,7 +234,7 @@ public class WebMvcConfigurationSupportTests {
@Controller
private static class TestController {
-
+
@SuppressWarnings("unused")
@RequestMapping("/")
public void handle() {
@@ -242,15 +242,15 @@ public class WebMvcConfigurationSupportTests {
}
private static class TestWebMvcConfiguration extends WebMvcConfigurationSupport {
-
+
}
-
+
/**
- * The purpose of this class is to test that an implementation of a {@link WebMvcConfigurer}
+ * The purpose of this class is to test that an implementation of a {@link WebMvcConfigurer}
* can also apply customizations by extension from {@link WebMvcConfigurationSupport}.
*/
private class WebConfig extends WebMvcConfigurationSupport implements WebMvcConfigurer {
-
+
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter() {
@@ -262,7 +262,7 @@ public class WebMvcConfigurationSupportTests {
@Override
public void configureMessageConverters(List> converters) {
- converters.add(new MappingJacksonHttpMessageConverter());
+ converters.add(new MappingJackson2HttpMessageConverter());
}
@Override
@@ -312,5 +312,5 @@ public class WebMvcConfigurationSupportTests {
configurer.enable("default");
}
}
-
+
}
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/json/MappingJackson2JsonViewTest.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/json/MappingJackson2JsonViewTest.java
new file mode 100644
index 00000000000..93774c21ca8
--- /dev/null
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/json/MappingJackson2JsonViewTest.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2002-2012 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.view.json;
+
+import static org.easymock.EasyMock.createMock;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.ContextFactory;
+import org.mozilla.javascript.ScriptableObject;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.validation.BindingResult;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig;
+import com.fasterxml.jackson.databind.ser.BasicSerializerFactory;
+import com.fasterxml.jackson.databind.ser.BeanSerializerFactory;
+import com.fasterxml.jackson.databind.ser.SerializerFactory;
+import com.fasterxml.jackson.databind.ser.Serializers;
+
+/**
+ * @author Jeremy Grelle
+ * @author Arjen Poutsma
+ * @author Rossen Stoyanchev
+ */
+public class MappingJackson2JsonViewTest {
+
+ private MappingJackson2JsonView view;
+
+ private MockHttpServletRequest request;
+
+ private MockHttpServletResponse response;
+
+ private Context jsContext;
+
+ private ScriptableObject jsScope;
+
+ @Before
+ public void setUp() {
+ request = new MockHttpServletRequest();
+ response = new MockHttpServletResponse();
+
+ jsContext = ContextFactory.getGlobal().enterContext();
+ jsScope = jsContext.initStandardObjects();
+
+ view = new MappingJackson2JsonView();
+ }
+
+ @Test
+ public void isExposePathVars() {
+ assertEquals("Must not expose path variables", false, view.isExposePathVariables());
+ }
+
+ @Test
+ public void renderSimpleMap() throws Exception {
+
+ Map model = new HashMap();
+ model.put("bindingResult", createMock("binding_result", BindingResult.class));
+ model.put("foo", "bar");
+
+ view.render(model, request, response);
+
+ assertEquals("no-cache", response.getHeader("Pragma"));
+ assertEquals("no-cache, no-store, max-age=0", response.getHeader("Cache-Control"));
+ assertNotNull(response.getHeader("Expires"));
+
+ assertEquals(MappingJacksonJsonView.DEFAULT_CONTENT_TYPE, response.getContentType());
+
+ String jsonResult = response.getContentAsString();
+ assertTrue(jsonResult.length() > 0);
+
+ validateResult();
+ }
+
+ @Test
+ public void renderCaching() throws Exception {
+ view.setDisableCaching(false);
+
+ Map model = new HashMap();
+ model.put("bindingResult", createMock("binding_result", BindingResult.class));
+ model.put("foo", "bar");
+
+ view.render(model, request, response);
+
+ assertNull(response.getHeader("Pragma"));
+ assertNull(response.getHeader("Cache-Control"));
+ assertNull(response.getHeader("Expires"));
+ }
+
+ @Test
+ public void renderSimpleMapPrefixed() throws Exception {
+ view.setPrefixJson(true);
+ renderSimpleMap();
+ }
+
+ @Test
+ public void renderSimpleBean() throws Exception {
+
+ Object bean = new TestBeanSimple();
+ Map model = new HashMap();
+ model.put("bindingResult", createMock("binding_result", BindingResult.class));
+ model.put("foo", bean);
+
+ view.render(model, request, response);
+
+ assertTrue(response.getContentAsString().length() > 0);
+
+ validateResult();
+ }
+
+ @Test
+ public void renderSimpleBeanPrefixed() throws Exception {
+
+ view.setPrefixJson(true);
+ renderSimpleBean();
+ }
+
+ @Test
+ public void renderWithCustomSerializerLocatedByAnnotation() throws Exception {
+
+ Object bean = new TestBeanSimpleAnnotated();
+ Map model = new HashMap();
+ model.put("foo", bean);
+
+ view.render(model, request, response);
+
+ assertTrue(response.getContentAsString().length() > 0);
+ assertEquals("{\"foo\":{\"testBeanSimple\":\"custom\"}}", response.getContentAsString());
+
+ validateResult();
+ }
+
+ @Test
+ public void renderWithCustomSerializerLocatedByFactory() throws Exception {
+
+ SerializerFactory factory = new DelegatingSerializerFactory(null);
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.setSerializerFactory(factory);
+ view.setObjectMapper(mapper);
+
+ Object bean = new TestBeanSimple();
+ Map model = new HashMap();
+ model.put("foo", bean);
+ model.put("bar", new TestChildBean());
+
+ view.render(model, request, response);
+
+ String result = response.getContentAsString();
+ assertTrue(result.length() > 0);
+ assertTrue(result.contains("\"foo\":{\"testBeanSimple\":\"custom\"}"));
+
+ validateResult();
+ }
+
+ @Test
+ public void renderOnlyIncludedAttributes() throws Exception {
+
+ Set attrs = new HashSet();
+ attrs.add("foo");
+ attrs.add("baz");
+ attrs.add("nil");
+
+ view.setModelKeys(attrs);
+ Map model = new HashMap();
+ model.put("foo", "foo");
+ model.put("bar", "bar");
+ model.put("baz", "baz");
+
+ view.render(model, request, response);
+
+ String result = response.getContentAsString();
+ assertTrue(result.length() > 0);
+ assertTrue(result.contains("\"foo\":\"foo\""));
+ assertTrue(result.contains("\"baz\":\"baz\""));
+
+ validateResult();
+ }
+
+ @Test
+ public void filterSingleKeyModel() throws Exception {
+ view.setExtractValueFromSingleKeyModel(true);
+
+ Map model = new HashMap();
+ TestBeanSimple bean = new TestBeanSimple();
+ model.put("foo", bean);
+
+ Object actual = view.filterModel(model);
+
+ assertSame(bean, actual);
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Test
+ public void filterTwoKeyModel() throws Exception {
+ view.setExtractValueFromSingleKeyModel(true);
+
+ Map model = new HashMap();
+ TestBeanSimple bean1 = new TestBeanSimple();
+ TestBeanSimple bean2 = new TestBeanSimple();
+ model.put("foo1", bean1);
+ model.put("foo2", bean2);
+
+ Object actual = view.filterModel(model);
+
+ assertTrue(actual instanceof Map);
+ assertSame(bean1, ((Map) actual).get("foo1"));
+ assertSame(bean2, ((Map) actual).get("foo2"));
+ }
+
+ private void validateResult() throws Exception {
+ Object jsResult =
+ jsContext.evaluateString(jsScope, "(" + response.getContentAsString() + ")", "JSON Stream", 1, null);
+ assertNotNull("Json Result did not eval as valid JavaScript", jsResult);
+ }
+
+
+ public static class TestBeanSimple {
+
+ private String value = "foo";
+
+ private boolean test = false;
+
+ private long number = 42;
+
+ private TestChildBean child = new TestChildBean();
+
+ public String getValue() {
+ return value;
+ }
+
+ public boolean getTest() {
+ return test;
+ }
+
+ public long getNumber() {
+ return number;
+ }
+
+ public Date getNow() {
+ return new Date();
+ }
+
+ public TestChildBean getChild() {
+ return child;
+ }
+ }
+
+ @JsonSerialize(using=TestBeanSimpleSerializer.class)
+ public static class TestBeanSimpleAnnotated extends TestBeanSimple {
+
+ }
+
+ public static class TestChildBean {
+
+ private String value = "bar";
+
+ private String baz = null;
+
+ private TestBeanSimple parent = null;
+
+ public String getValue() {
+ return value;
+ }
+
+ public String getBaz() {
+ return baz;
+ }
+
+ public TestBeanSimple getParent() {
+ return parent;
+ }
+
+ public void setParent(TestBeanSimple parent) {
+ this.parent = parent;
+ }
+ }
+
+ public static class TestBeanSimpleSerializer extends JsonSerializer
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.0.2
+ true
+ org.springframeworkspring-aop
diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java b/org.springframework.web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java
new file mode 100644
index 00000000000..343b02fe8f4
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2002-2012 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.http.converter.json;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.List;
+
+import org.springframework.http.HttpInputMessage;
+import org.springframework.http.HttpOutputMessage;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.AbstractHttpMessageConverter;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.http.converter.HttpMessageNotWritableException;
+import org.springframework.util.Assert;
+
+import com.fasterxml.jackson.core.JsonEncoding;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter}
+ * that can read and write JSON using Jackson 2's {@link ObjectMapper}.
+ *
+ *
This converter can be used to bind to typed beans, or untyped {@link java.util.HashMap HashMap} instances.
+ *
+ *
By default, this converter supports {@code application/json}. This can be overridden by setting the
+ * {@link #setSupportedMediaTypes(List) supportedMediaTypes} property.
+ *
+ * @author Arjen Poutsma
+ * @author Keith Donald
+ * @since 3.2
+ * @see org.springframework.web.servlet.view.json.MappingJackson2JsonView
+ */
+public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConverter {
+
+ public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+
+
+ private ObjectMapper objectMapper = new ObjectMapper();
+
+ private boolean prefixJson = false;
+
+
+ /**
+ * Construct a new {@code BindingJacksonHttpMessageConverter}.
+ */
+ public MappingJackson2HttpMessageConverter() {
+ super(new MediaType("application", "json", DEFAULT_CHARSET));
+ }
+
+ /**
+ * Set the {@code ObjectMapper} for this view. If not set, a default
+ * {@link ObjectMapper#ObjectMapper() ObjectMapper} is used.
+ *
Setting a custom-configured {@code ObjectMapper} is one way to take further control of the JSON
+ * serialization process. For example, an extended {@link org.codehaus.jackson.map.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.
+ */
+ public void setObjectMapper(ObjectMapper objectMapper) {
+ Assert.notNull(objectMapper, "ObjectMapper must not be null");
+ this.objectMapper = objectMapper;
+ }
+
+ /**
+ * Return the underlying {@code ObjectMapper} for this view.
+ */
+ public ObjectMapper getObjectMapper() {
+ return this.objectMapper;
+ }
+
+ /**
+ * Indicate whether the JSON output by this view should be prefixed with "{} &&". Default is false.
+ *
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 on the
+ * string, the prefix would need to be ignored.
+ */
+ public void setPrefixJson(boolean prefixJson) {
+ this.prefixJson = prefixJson;
+ }
+
+
+ @Override
+ public boolean canRead(Class> clazz, MediaType mediaType) {
+ JavaType javaType = getJavaType(clazz);
+ return (this.objectMapper.canDeserialize(javaType) && canRead(mediaType));
+ }
+
+ @Override
+ public boolean canWrite(Class> clazz, MediaType mediaType) {
+ return (this.objectMapper.canSerialize(clazz) && canWrite(mediaType));
+ }
+
+ @Override
+ protected boolean supports(Class> clazz) {
+ // should not be called, since we override canRead/Write instead
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected Object readInternal(Class> clazz, HttpInputMessage inputMessage)
+ throws IOException, HttpMessageNotReadableException {
+
+ JavaType javaType = getJavaType(clazz);
+ try {
+ return this.objectMapper.readValue(inputMessage.getBody(), javaType);
+ }
+ catch (JsonProcessingException ex) {
+ throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ protected void writeInternal(Object object, HttpOutputMessage outputMessage)
+ throws IOException, HttpMessageNotWritableException {
+
+ JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
+ JsonGenerator jsonGenerator =
+ this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
+ try {
+ if (this.prefixJson) {
+ jsonGenerator.writeRaw("{} && ");
+ }
+ this.objectMapper.writeValue(jsonGenerator, object);
+ }
+ catch (JsonProcessingException ex) {
+ throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
+ }
+ }
+
+
+ /**
+ * Return the Jackson {@link JavaType} for the specified class.
+ *
The default implementation returns {@link ObjectMapper#constructType(java.lang.reflect.Type)},
+ * but this can be overridden in subclasses, to allow for custom generic collection handling.
+ * For instance:
+ *