Add Jackson 2 HttpMessageConverter and View
Jackson 2 uses completely new package names and new maven artifact ids. This change adds Jackson 2 as an optional dependency and also provides MappingJackson2HttpMessageConverter and MappingJackson2JsonView for use with the new version. The MVC namespace and the MVC Java config detect and use MappingJackson2HttpMessageConverter if Jackson 2 is present. Otherwise if Jackson 1.x is present, then MappingJacksonHttpMessageConverter is used. Issue: SPR-9302
This commit is contained in:
@@ -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<HandlerExceptionResolver> expectedResolvers = new ArrayList<HandlerExceptionResolver>();
|
||||
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<HandlerMethodArgumentResolver> argResolvers= (List<HandlerMethodArgumentResolver>)
|
||||
List<HandlerMethodArgumentResolver> argResolvers= (List<HandlerMethodArgumentResolver>)
|
||||
new DirectFieldAccessor(adapter).getPropertyValue("customArgumentResolvers");
|
||||
assertEquals(1, argResolvers.size());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<HandlerMethodReturnValueHandler> handlers = (List<HandlerMethodReturnValueHandler>)
|
||||
List<HandlerMethodReturnValueHandler> handlers = (List<HandlerMethodReturnValueHandler>)
|
||||
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<TestBean, String>() {
|
||||
@@ -262,7 +262,7 @@ public class WebMvcConfigurationSupportTests {
|
||||
|
||||
@Override
|
||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
converters.add(new MappingJacksonHttpMessageConverter());
|
||||
converters.add(new MappingJackson2HttpMessageConverter());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -312,5 +312,5 @@ public class WebMvcConfigurationSupportTests {
|
||||
configurer.enable("default");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String, Object> model = new HashMap<String, Object>();
|
||||
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<String, Object> model = new HashMap<String, Object>();
|
||||
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<String, Object> model = new HashMap<String, Object>();
|
||||
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<String, Object> model = new HashMap<String, Object>();
|
||||
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<String, Object> model = new HashMap<String, Object>();
|
||||
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<String> attrs = new HashSet<String>();
|
||||
attrs.add("foo");
|
||||
attrs.add("baz");
|
||||
attrs.add("nil");
|
||||
|
||||
view.setModelKeys(attrs);
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
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<String, Object> model = new HashMap<String, Object>();
|
||||
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<String, Object> model = new HashMap<String, Object>();
|
||||
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<Object> {
|
||||
|
||||
@Override
|
||||
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
|
||||
jgen.writeStartObject();
|
||||
jgen.writeFieldName("testBeanSimple");
|
||||
jgen.writeString("custom");
|
||||
jgen.writeEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
public static class DelegatingSerializerFactory extends BasicSerializerFactory {
|
||||
|
||||
private SerializerFactory beanSerializer = BeanSerializerFactory.instance;
|
||||
|
||||
protected DelegatingSerializerFactory(SerializerFactoryConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonSerializer<Object> createSerializer(SerializerProvider prov, JavaType type, BeanProperty property) throws JsonMappingException {
|
||||
if (type.getRawClass() == TestBeanSimple.class) {
|
||||
return new TestBeanSimpleSerializer();
|
||||
}
|
||||
else {
|
||||
return beanSerializer.createSerializer(prov, type, property);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SerializerFactory withConfig(SerializerFactoryConfig config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterable<Serializers> customSerializers() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import org.codehaus.jackson.map.SerializerFactory;
|
||||
import org.codehaus.jackson.map.SerializerProvider;
|
||||
import org.codehaus.jackson.map.annotate.JsonSerialize;
|
||||
import org.codehaus.jackson.map.ser.BeanSerializerFactory;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mozilla.javascript.Context;
|
||||
@@ -213,10 +214,10 @@ public class MappingJacksonJsonViewTest {
|
||||
model.put("foo", bean);
|
||||
|
||||
Object actual = view.filterModel(model);
|
||||
|
||||
|
||||
assertSame(bean, actual);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Test
|
||||
public void filterTwoKeyModel() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user