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.

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
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
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
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
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
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
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

0 comments on commit 51fc3b4

Please sign in to comment.