Skip to content

Commit

Permalink
Refactor @JSONVIEW support w/ ResponseBodyInterceptor
Browse files Browse the repository at this point in the history
The newly added support for ResponseBodyInterceptor is a good fit for
the (also recently added) support for the Jackson @JSONVIEW annotation.

This change refactors the original implementation of @JSONVIEW support
for @responsebody and ResponseEntity controller methods this time
implemented as an ResponseBodyInterceptor.

Issue: SPR-7156
  • Loading branch information
rstoyanchev committed May 19, 2014
1 parent 96b18c8 commit 51fc3b4
Show file tree
Hide file tree
Showing 17 changed files with 321 additions and 134 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import java.nio.charset.Charset;
import java.util.concurrent.atomic.AtomicReference;

import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
Expand All @@ -30,15 +29,13 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

import org.springframework.core.MethodParameter;
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.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.MethodParameterHttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

Expand All @@ -60,7 +57,7 @@
* @since 3.1.2
*/
public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConverter<Object>
implements GenericHttpMessageConverter<Object>, MethodParameterHttpMessageConverter<Object> {
implements GenericHttpMessageConverter<Object> {

public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

Expand Down Expand Up @@ -150,11 +147,6 @@ private void configurePrettyPrint() {
}
}

@Override
public boolean canRead(Class<?> clazz, MediaType mediaType, MethodParameter parameter) {
return canRead(clazz, mediaType);
}

@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return canRead(clazz, null, mediaType);
Expand Down Expand Up @@ -205,11 +197,6 @@ public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return false;
}

@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType, MethodParameter parameter) {
return canWrite(clazz, mediaType);
}

@Override
protected boolean supports(Class<?> clazz) {
// should not be called, since we override canRead/Write instead
Expand All @@ -224,11 +211,6 @@ protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
return readJavaType(javaType, inputMessage);
}

@Override
public Object read(Class<?> clazz, HttpInputMessage inputMessage, MethodParameter parameter) throws IOException, HttpMessageNotReadableException {
return super.read(clazz, inputMessage);
}

@Override
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
Expand Down Expand Up @@ -267,8 +249,8 @@ protected void writeInternal(Object object, HttpOutputMessage outputMessage)
if (this.jsonPrefix != null) {
jsonGenerator.writeRaw(this.jsonPrefix);
}
if (object instanceof MappingJacksonValueHolder) {
MappingJacksonValueHolder valueHolder = (MappingJacksonValueHolder) object;
if (object instanceof MappingJacksonValue) {
MappingJacksonValue valueHolder = (MappingJacksonValue) object;
object = valueHolder.getValue();
Class<?> serializationView = valueHolder.getSerializationView();
this.objectMapper.writerWithView(serializationView).writeValue(jsonGenerator, object);
Expand All @@ -282,20 +264,6 @@ protected void writeInternal(Object object, HttpOutputMessage outputMessage)
}
}

@Override
public void write(Object object, MediaType contentType, HttpOutputMessage outputMessage, MethodParameter parameter)
throws IOException, HttpMessageNotWritableException {

JsonView annot = parameter.getMethodAnnotation(JsonView.class);
if (annot != null && annot.value().length != 0) {
MappingJacksonValueHolder serializationValue = new MappingJacksonValueHolder(object, annot.value()[0]);
super.write(serializationValue, contentType, outputMessage);
}
else {
super.write(object, contentType, outputMessage);
}
}

/**
* Return the Jackson {@link JavaType} for the specified type and context class.
* <p>The default implementation returns {@code typeFactory.constructType(type, contextClass)},
Expand Down Expand Up @@ -339,16 +307,16 @@ protected JsonEncoding getJsonEncoding(MediaType contentType) {

@Override
protected MediaType getDefaultContentType(Object object) throws IOException {
if (object instanceof MappingJacksonValueHolder) {
object = ((MappingJacksonValueHolder) object).getValue();
if (object instanceof MappingJacksonValue) {
object = ((MappingJacksonValue) object).getValue();
}
return super.getDefaultContentType(object);
}

@Override
protected Long getContentLength(Object object, MediaType contentType) throws IOException {
if (object instanceof MappingJacksonValueHolder) {
object = ((MappingJacksonValueHolder) object).getValue();
if (object instanceof MappingJacksonValue) {
object = ((MappingJacksonValue) object).getValue();
}
return super.getContentLength(object, contentType);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
*
* @see com.fasterxml.jackson.annotation.JsonView
*/
public class MappingJacksonValueHolder {
public class MappingJacksonValue {

private final Object value;

Expand All @@ -37,7 +37,7 @@ public class MappingJacksonValueHolder {
* @param value the Object to be serialized
* @param serializationView the view to be applied
*/
public MappingJacksonValueHolder(Object value, Class<?> serializationView) {
public MappingJacksonValue(Object value, Class<?> serializationView) {
this.value = value;
this.serializationView = serializationView;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ public void jsonView() throws Exception {
bean.setWithView1("with");
bean.setWithView2("with");
bean.setWithoutView("without");
MappingJacksonValueHolder jsv = new MappingJacksonValueHolder(bean, MyJacksonView1.class);
MappingJacksonValue jsv = new MappingJacksonValue(bean, MyJacksonView1.class);
this.converter.writeInternal(jsv, outputMessage);

String result = outputMessage.getBodyAsString(Charset.forName("UTF-8"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.json.MappingJacksonValueHolder;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

Expand Down Expand Up @@ -220,8 +220,8 @@ public void jsonPostForObjectWithJacksonView() throws URISyntaxException {
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.setContentType(new MediaType("application", "json", Charset.forName("UTF-8")));
MySampleBean bean = new MySampleBean("with", "with", "without");
MappingJacksonValueHolder jsv = new MappingJacksonValueHolder(bean, MyJacksonView1.class);
HttpEntity<MappingJacksonValueHolder> entity = new HttpEntity<MappingJacksonValueHolder>(jsv);
MappingJacksonValue jsv = new MappingJacksonValue(bean, MyJacksonView1.class);
HttpEntity<MappingJacksonValue> entity = new HttpEntity<MappingJacksonValue>(jsv);
String s = template.postForObject(baseUrl + "/jsonpost", entity, String.class, "post");
assertTrue(s.contains("\"with1\":\"with\""));
assertFalse(s.contains("\"with2\":\"with\""));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.List;
import java.util.Properties;

import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyInterceptor;
import org.w3c.dom.Element;

import org.springframework.beans.factory.FactoryBean;
Expand Down Expand Up @@ -196,6 +197,7 @@ else if (element.hasAttribute("enableMatrixVariables")) {
handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
addResponseBodyInterceptors(handlerAdapterDef);

if (element.hasAttribute("ignore-default-model-on-redirect")) {
Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));
Expand Down Expand Up @@ -247,6 +249,8 @@ else if (element.hasAttribute("ignoreDefaultModelOnRedirect")) {
exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
addResponseBodyInterceptors(exceptionHandlerExceptionResolver);

String methodExceptionResolverName =
parserContext.getReaderContext().registerWithGeneratedName(exceptionHandlerExceptionResolver);

Expand Down Expand Up @@ -280,6 +284,13 @@ else if (element.hasAttribute("ignoreDefaultModelOnRedirect")) {
return null;
}

protected void addResponseBodyInterceptors(RootBeanDefinition beanDef) {
if (jackson2Present) {
beanDef.getPropertyValues().add("responseBodyInterceptors",
new RootBeanDefinition(JsonViewResponseBodyInterceptor.class));
}
}

private RuntimeBeanReference getConversionService(Element element, Object source, ParserContext parserContext) {
RuntimeBeanReference conversionServiceRef;
if (element.hasAttribute("conversion-service")) {
Expand Down Expand Up @@ -493,6 +504,7 @@ private RootBeanDefinition createConverterDefinition(Class<?> converterClass, Ob
return beanDefinition;
}


private ManagedList<BeanDefinitionHolder> extractBeanSubElements(Element parentElement, ParserContext parserContext) {
ManagedList<BeanDefinitionHolder> list = new ManagedList<BeanDefinitionHolder>();
list.setSource(parserContext.extractSource(parentElement));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.springframework.web.servlet.config.annotation;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -74,8 +75,10 @@
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyInterceptor;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyInterceptor;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
import org.springframework.web.servlet.resource.ResourceUrlProvider;
import org.springframework.web.servlet.resource.ResourceUrlProviderExposingInterceptor;
Expand Down Expand Up @@ -417,6 +420,11 @@ public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
adapter.setCustomArgumentResolvers(argumentResolvers);
adapter.setCustomReturnValueHandlers(returnValueHandlers);

if (jackson2Present) {
ResponseBodyInterceptor interceptor = new JsonViewResponseBodyInterceptor();
adapter.setResponseBodyInterceptors(Arrays.asList(interceptor));
}

AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
configureAsyncSupport(configurer);

Expand Down Expand Up @@ -695,6 +703,10 @@ protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionRe
exceptionHandlerExceptionResolver.setApplicationContext(this.applicationContext);
exceptionHandlerExceptionResolver.setContentNegotiationManager(mvcContentNegotiationManager());
exceptionHandlerExceptionResolver.setMessageConverters(getMessageConverters());
if (jackson2Present) {
ResponseBodyInterceptor interceptor = new JsonViewResponseBodyInterceptor();
exceptionHandlerExceptionResolver.setResponseBodyInterceptors(Arrays.asList(interceptor));
}
exceptionHandlerExceptionResolver.afterPropertiesSet();

exceptionResolvers.add(exceptionHandlerExceptionResolver);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.MethodParameterHttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.CollectionUtils;
Expand Down Expand Up @@ -148,17 +147,6 @@ else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICAT
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter instanceof MethodParameterHttpMessageConverter) {
MethodParameterHttpMessageConverter<T> c = (MethodParameterHttpMessageConverter<T>) messageConverter;
if (c.canWrite(returnValueClass, selectedMediaType, returnType)) {
c.write(returnValue, selectedMediaType, outputMessage, returnType);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
messageConverter + "]");
}
return;
}
}
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
returnValue = this.interceptorChain.invoke(returnValue, selectedMediaType,
(Class<HttpMessageConverter<T>>) messageConverter.getClass(),
Expand Down
Loading

0 comments on commit 51fc3b4

Please sign in to comment.