Add support for Jackson serialization views
Spring MVC now supports Jackon's serialization views for rendering different subsets of the same POJO from different controller methods (e.g. detailed page vs summary view). Issue: SPR-7156
This commit is contained in:
committed by
Rossen Stoyanchev
parent
673a497923
commit
be0b69cbf1
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2002-2014 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;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An HttpMessageConverter that supports converting the value returned from a
|
||||
* method by incorporating {@link org.springframework.core.MethodParameter}
|
||||
* information into the conversion. Such a converter can for example take into
|
||||
* account information from method-level annotations.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.1
|
||||
*/
|
||||
public interface MethodParameterHttpMessageConverter<T> extends HttpMessageConverter<T> {
|
||||
|
||||
/**
|
||||
* This method mirrors {@link HttpMessageConverter#canRead(Class, MediaType)}
|
||||
* with an additional {@code MethodParameter}.
|
||||
*/
|
||||
boolean canRead(Class<?> clazz, MediaType mediaType, MethodParameter parameter);
|
||||
|
||||
/**
|
||||
* This method mirrors {@link HttpMessageConverter#canWrite(Class, MediaType)}
|
||||
* with an additional {@code MethodParameter}.
|
||||
*/
|
||||
boolean canWrite(Class<?> clazz, MediaType mediaType, MethodParameter parameter);
|
||||
|
||||
/**
|
||||
* This method mirrors {@link HttpMessageConverter#read(Class, HttpInputMessage)}
|
||||
* with an additional {@code MethodParameter}.
|
||||
*/
|
||||
T read(Class<? extends T> clazz, HttpInputMessage inputMessage, MethodParameter parameter)
|
||||
throws IOException, HttpMessageNotReadableException;
|
||||
|
||||
/**
|
||||
* This method mirrors {@link HttpMessageConverter#write(Object, MediaType, HttpOutputMessage)}
|
||||
* with an additional {@code MethodParameter}.
|
||||
*/
|
||||
void write(T t, MediaType contentType, HttpOutputMessage outputMessage, MethodParameter parameter)
|
||||
throws IOException, HttpMessageNotWritableException;
|
||||
|
||||
}
|
||||
@@ -244,6 +244,13 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
|
||||
this.features.put(MapperFeature.AUTO_DETECT_SETTERS, autoDetectGettersSetters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for {@link MapperFeature#DEFAULT_VIEW_INCLUSION} option.
|
||||
*/
|
||||
public void setDefaultViewInclusion(boolean defaultViewInclusion) {
|
||||
this.features.put(MapperFeature.DEFAULT_VIEW_INCLUSION, defaultViewInclusion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for {@link SerializationFeature#FAIL_ON_EMPTY_BEANS} option.
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.lang.reflect.Type;
|
||||
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;
|
||||
@@ -29,6 +30,7 @@ import com.fasterxml.jackson.databind.JavaType;
|
||||
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;
|
||||
@@ -36,6 +38,7 @@ 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;
|
||||
|
||||
@@ -57,7 +60,7 @@ import org.springframework.util.ClassUtils;
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConverter<Object>
|
||||
implements GenericHttpMessageConverter<Object> {
|
||||
implements GenericHttpMessageConverter<Object>, MethodParameterHttpMessageConverter<Object> {
|
||||
|
||||
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
|
||||
|
||||
@@ -147,6 +150,10 @@ public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConv
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead(Class<?> clazz, MediaType mediaType, MethodParameter parameter) {
|
||||
return canRead(clazz, mediaType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead(Class<?> clazz, MediaType mediaType) {
|
||||
@@ -198,6 +205,11 @@ public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConv
|
||||
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
|
||||
@@ -212,6 +224,11 @@ public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConv
|
||||
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 {
|
||||
@@ -250,13 +267,35 @@ public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConv
|
||||
if (this.jsonPrefix != null) {
|
||||
jsonGenerator.writeRaw(this.jsonPrefix);
|
||||
}
|
||||
this.objectMapper.writeValue(jsonGenerator, object);
|
||||
if (object instanceof MappingJacksonValueHolder) {
|
||||
MappingJacksonValueHolder valueHolder = (MappingJacksonValueHolder) object;
|
||||
object = valueHolder.getValue();
|
||||
Class<?> serializationView = valueHolder.getSerializationView();
|
||||
this.objectMapper.writerWithView(serializationView).writeValue(jsonGenerator, object);
|
||||
}
|
||||
else {
|
||||
this.objectMapper.writeValue(jsonGenerator, object);
|
||||
}
|
||||
}
|
||||
catch (JsonProcessingException ex) {
|
||||
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@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)},
|
||||
@@ -298,4 +337,20 @@ public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConv
|
||||
return JsonEncoding.UTF8;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MediaType getDefaultContentType(Object object) throws IOException {
|
||||
if (object instanceof MappingJacksonValueHolder) {
|
||||
object = ((MappingJacksonValueHolder) object).getValue();
|
||||
}
|
||||
return super.getDefaultContentType(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Long getContentLength(Object object, MediaType contentType) throws IOException {
|
||||
if (object instanceof MappingJacksonValueHolder) {
|
||||
object = ((MappingJacksonValueHolder) object).getValue();
|
||||
}
|
||||
return super.getContentLength(object, contentType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2002-2014 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;
|
||||
|
||||
/**
|
||||
* Holds an Object to be serialized via Jackson together with a serialization
|
||||
* view to be applied.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.1
|
||||
*
|
||||
* @see com.fasterxml.jackson.annotation.JsonView
|
||||
*/
|
||||
public class MappingJacksonValueHolder {
|
||||
|
||||
private final Object value;
|
||||
|
||||
private final Class<?> serializationView;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
* @param value the Object to be serialized
|
||||
* @param serializationView the view to be applied
|
||||
*/
|
||||
public MappingJacksonValueHolder(Object value, Class<?> serializationView) {
|
||||
this.value = value;
|
||||
this.serializationView = serializationView;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the value to be serialized.
|
||||
*/
|
||||
public Object getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the serialization view to use.
|
||||
*/
|
||||
public Class<?> getSerializationView() {
|
||||
return this.serializationView;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user