Support Jackson based XML serialization/deserialization
This commit adds support for XML serialization/deserialization based on the jackson-dataformat-xml extension. When using @EnableWebMvc or <mvc:annotation-driven/>, Jackson will be used by default instead of JAXB2 if jackson-dataformat-xml classes are found in the classpath. This commit introduces MappingJackson2XmlHttpMessageConverter and MappingJackson2XmlView classes, and common parts between JSON and XML processing have been moved to AbstractJackson2HttpMessageConverter and AbstractJackson2View classes. MappingJackson2XmlView supports serialization of a single object. If the model contains multiple entries, MappingJackson2XmlView.setModelKey() should be used to specify the entry to serialize. Pretty print works in XML, but tests are not included since a Woodstox dependency is needed, and it is better to continue testing spring-web and spring-webmvc against JAXB2. Issue: SPR-11785
This commit is contained in:
@@ -47,6 +47,7 @@ import org.springframework.http.converter.json.GsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -159,6 +160,9 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
|
||||
|
||||
private static final boolean jackson2XmlPresent =
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
|
||||
|
||||
private static final boolean gsonPresent =
|
||||
ClassUtils.isPresent("com.google.gson.Gson", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
|
||||
|
||||
@@ -401,7 +405,7 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
props.put("atom", MediaType.APPLICATION_ATOM_XML_VALUE);
|
||||
props.put("rss", "application/rss+xml");
|
||||
}
|
||||
if (jaxb2Present) {
|
||||
if (jaxb2Present || jackson2XmlPresent) {
|
||||
props.put("xml", MediaType.APPLICATION_XML_VALUE);
|
||||
}
|
||||
if (jackson2Present || gsonPresent) {
|
||||
@@ -528,7 +532,10 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));
|
||||
messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));
|
||||
}
|
||||
if (jaxb2Present) {
|
||||
if(jackson2XmlPresent) {
|
||||
messageConverters.add(createConverterDefinition(MappingJackson2XmlHttpMessageConverter.class, source));
|
||||
}
|
||||
else if (jaxb2Present) {
|
||||
messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
|
||||
}
|
||||
if (jackson2Present) {
|
||||
|
||||
@@ -48,6 +48,7 @@ import org.springframework.http.converter.json.GsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -171,6 +172,9 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", WebMvcConfigurationSupport.class.getClassLoader()) &&
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", WebMvcConfigurationSupport.class.getClassLoader());
|
||||
|
||||
private static final boolean jackson2XmlPresent =
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", WebMvcConfigurationSupport.class.getClassLoader());
|
||||
|
||||
private static final boolean gsonPresent =
|
||||
ClassUtils.isPresent("com.google.gson.Gson", WebMvcConfigurationSupport.class.getClassLoader());
|
||||
|
||||
@@ -302,7 +306,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
map.put("atom", MediaType.APPLICATION_ATOM_XML);
|
||||
map.put("rss", MediaType.valueOf("application/rss+xml"));
|
||||
}
|
||||
if (jaxb2Present) {
|
||||
if (jaxb2Present || jackson2XmlPresent) {
|
||||
map.put("xml", MediaType.APPLICATION_XML);
|
||||
}
|
||||
if (jackson2Present || gsonPresent) {
|
||||
@@ -653,7 +657,10 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
messageConverters.add(new AtomFeedHttpMessageConverter());
|
||||
messageConverters.add(new RssChannelHttpMessageConverter());
|
||||
}
|
||||
if (jaxb2Present) {
|
||||
if(jackson2XmlPresent) {
|
||||
messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
|
||||
}
|
||||
else if (jaxb2Present) {
|
||||
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
|
||||
}
|
||||
if (jackson2Present) {
|
||||
|
||||
@@ -29,8 +29,8 @@ import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* A convenient base class for a {@code ResponseBodyAdvice} to instruct the
|
||||
* {@link org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
|
||||
* MappingJackson2HttpMessageConverter} to serialize with JSONP formatting.
|
||||
* {@link org.springframework.http.converter.json.MappingJackson2HttpMessageConverter}
|
||||
* to serialize with JSONP formatting.
|
||||
*
|
||||
* <p>Sub-classes must specify the query parameter name(s) to check for the name
|
||||
* of the JSONP callback function.
|
||||
|
||||
@@ -27,8 +27,7 @@ import org.springframework.http.server.ServerHttpResponse;
|
||||
/**
|
||||
* A convenient base class for {@code ResponseBodyAdvice} implementations
|
||||
* that customize the response before JSON serialization with
|
||||
* {@link org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
|
||||
* MappingJackson2HttpMessageConverter}.
|
||||
* {@link MappingJackson2HttpMessageConverter}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.1
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* 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.web.servlet.view.json;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import com.fasterxml.jackson.core.JsonEncoding;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
import org.springframework.http.converter.json.MappingJacksonValue;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.servlet.view.AbstractView;
|
||||
|
||||
/**
|
||||
* Abstract base class for Jackson based and content type independent
|
||||
* {@link AbstractView} implementations.
|
||||
*
|
||||
* <p>Compatible with Jackson 2.1 and higher.
|
||||
*
|
||||
* @author Jeremy Grelle
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @author Sebastien Deleuze
|
||||
* @since 4.1
|
||||
*/
|
||||
public abstract class AbstractJackson2View extends AbstractView {
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
private JsonEncoding encoding = JsonEncoding.UTF8;
|
||||
|
||||
private Boolean prettyPrint;
|
||||
|
||||
private boolean disableCaching = true;
|
||||
|
||||
protected boolean updateContentLength = false;
|
||||
|
||||
|
||||
protected AbstractJackson2View(ObjectMapper objectMapper, String contentType) {
|
||||
this.objectMapper = objectMapper;
|
||||
setContentType(contentType);
|
||||
setExposePathVariables(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@code ObjectMapper} for this view.
|
||||
* If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} will be used.
|
||||
* <p>Setting a custom-configured {@code ObjectMapper} is one way to take further control of
|
||||
* the JSON serialization process. The other option 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;
|
||||
configurePrettyPrint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@code ObjectMapper} for this view.
|
||||
*/
|
||||
public final ObjectMapper getObjectMapper() {
|
||||
return this.objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@code JsonEncoding} for this view.
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@code JsonEncoding} for this view.
|
||||
*/
|
||||
public final JsonEncoding getEncoding() {
|
||||
return this.encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use the default pretty printer when writing JSON.
|
||||
* This is a shortcut for setting up an {@code ObjectMapper} as follows:
|
||||
* <pre class="code">
|
||||
* ObjectMapper mapper = new ObjectMapper();
|
||||
* mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
|
||||
* </pre>
|
||||
* <p>The default value is {@code false}.
|
||||
*/
|
||||
public void setPrettyPrint(boolean prettyPrint) {
|
||||
this.prettyPrint = prettyPrint;
|
||||
configurePrettyPrint();
|
||||
}
|
||||
|
||||
private void configurePrettyPrint() {
|
||||
if (this.prettyPrint != null) {
|
||||
this.objectMapper.configure(SerializationFeature.INDENT_OUTPUT, this.prettyPrint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the attribute in the model that should be rendered by this view.
|
||||
* When set, all other model attributes will be ignored.
|
||||
*/
|
||||
public abstract void setModelKey(String modelKey);
|
||||
|
||||
/**
|
||||
* Disables caching of the generated JSON.
|
||||
* <p>Default is {@code true}, which will prevent the client from caching the generated JSON.
|
||||
*/
|
||||
public void setDisableCaching(boolean disableCaching) {
|
||||
this.disableCaching = disableCaching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to update the 'Content-Length' header of the response. When set to
|
||||
* {@code true}, the response is buffered in order to determine the content
|
||||
* length and set the 'Content-Length' header of the response.
|
||||
* <p>The default setting is {@code false}.
|
||||
*/
|
||||
public void setUpdateContentLength(boolean updateContentLength) {
|
||||
this.updateContentLength = updateContentLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
|
||||
setResponseContentType(request, response);
|
||||
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<String, Object> model, HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
|
||||
OutputStream stream = (this.updateContentLength ? createTemporaryOutputStream() : response.getOutputStream());
|
||||
Object value = filterAndWrapModel(model, request);
|
||||
|
||||
writeContent(stream, value);
|
||||
if (this.updateContentLength) {
|
||||
writeToResponse(response, (ByteArrayOutputStream) stream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter and optionally wrap the model in {@link MappingJacksonValue} container.
|
||||
* @param model the model, as passed on to {@link #renderMergedOutputModel}
|
||||
* @param request current HTTP request
|
||||
* @return the wrapped or unwrapped value to be rendered
|
||||
*/
|
||||
protected Object filterAndWrapModel(Map<String, Object> model, HttpServletRequest request) {
|
||||
Object value = filterModel(model);
|
||||
Class<?> serializationView = (Class<?>) model.get(JsonView.class.getName());
|
||||
if (serializationView != null) {
|
||||
MappingJacksonValue container = new MappingJacksonValue(value);
|
||||
container.setSerializationView(serializationView);
|
||||
value = container;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out undesired attributes from the given model.
|
||||
* The return value can be either another {@link Map} or a single value object.
|
||||
* @param model the model, as passed on to {@link #renderMergedOutputModel}
|
||||
* @return the value to be rendered
|
||||
*/
|
||||
protected abstract Object filterModel(Map<String, Object> model);
|
||||
|
||||
/**
|
||||
* Write the actual JSON content to the stream.
|
||||
* @param stream the output stream to use
|
||||
* @param object the value to be rendered, as returned from {@link #filterModel}
|
||||
* @throws IOException if writing failed
|
||||
*/
|
||||
protected void writeContent(OutputStream stream, Object object)
|
||||
throws IOException {
|
||||
|
||||
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(stream, this.encoding);
|
||||
|
||||
writePrefix(generator, object);
|
||||
Class<?> serializationView = null;
|
||||
Object value = object;
|
||||
|
||||
if (value instanceof MappingJacksonValue) {
|
||||
MappingJacksonValue container = (MappingJacksonValue) value;
|
||||
value = container.getValue();
|
||||
serializationView = container.getSerializationView();
|
||||
}
|
||||
if (serializationView != null) {
|
||||
this.objectMapper.writerWithView(serializationView).writeValue(generator, value);
|
||||
}
|
||||
else {
|
||||
this.objectMapper.writeValue(generator, value);
|
||||
}
|
||||
writeSuffix(generator, object);
|
||||
generator.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a prefix before the main content.
|
||||
* @param generator the generator to use for writing content.
|
||||
* @param object the object to write to the output message.
|
||||
*/
|
||||
protected void writePrefix(JsonGenerator generator, Object object) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a suffix after the main content.
|
||||
* @param generator the generator to use for writing content.
|
||||
* @param object the object to write to the output message.
|
||||
*/
|
||||
protected void writeSuffix(JsonGenerator generator, Object object) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,9 +16,7 @@
|
||||
|
||||
package org.springframework.web.servlet.view.json;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -29,18 +27,14 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import com.fasterxml.jackson.core.JsonEncoding;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
import org.springframework.http.converter.json.MappingJacksonValue;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
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
|
||||
@@ -59,7 +53,7 @@ import org.springframework.web.servlet.view.AbstractView;
|
||||
* @author Sebastien Deleuze
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public class MappingJackson2JsonView extends AbstractView {
|
||||
public class MappingJackson2JsonView extends AbstractJackson2View {
|
||||
|
||||
/**
|
||||
* Default content type: "application/json".
|
||||
@@ -72,23 +66,12 @@ public class MappingJackson2JsonView extends AbstractView {
|
||||
*/
|
||||
public static final String DEFAULT_JSONP_CONTENT_TYPE = "application/javascript";
|
||||
|
||||
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private JsonEncoding encoding = JsonEncoding.UTF8;
|
||||
|
||||
private String jsonPrefix;
|
||||
|
||||
private Boolean prettyPrint;
|
||||
|
||||
private Set<String> modelKeys;
|
||||
|
||||
private boolean extractValueFromSingleKeyModel = false;
|
||||
|
||||
private boolean disableCaching = true;
|
||||
|
||||
private boolean updateContentLength = false;
|
||||
|
||||
private Set<String> jsonpParameterNames = new LinkedHashSet<String>(Arrays.asList("jsonp", "callback"));
|
||||
|
||||
|
||||
@@ -96,45 +79,7 @@ public class MappingJackson2JsonView extends AbstractView {
|
||||
* Construct a new {@code MappingJackson2JsonView}, setting the content type to {@code application/json}.
|
||||
*/
|
||||
public MappingJackson2JsonView() {
|
||||
setContentType(DEFAULT_CONTENT_TYPE);
|
||||
setExposePathVariables(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the {@code ObjectMapper} for this view.
|
||||
* If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} will be used.
|
||||
* <p>Setting a custom-configured {@code ObjectMapper} is one way to take further control of
|
||||
* the JSON serialization process. The other option 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;
|
||||
configurePrettyPrint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@code ObjectMapper} for this view.
|
||||
*/
|
||||
public final ObjectMapper getObjectMapper() {
|
||||
return this.objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@code JsonEncoding} for this view.
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@code JsonEncoding} for this view.
|
||||
*/
|
||||
public final JsonEncoding getEncoding() {
|
||||
return this.encoding;
|
||||
super(new ObjectMapper(), DEFAULT_CONTENT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,29 +105,9 @@ public class MappingJackson2JsonView extends AbstractView {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use the default pretty printer when writing JSON.
|
||||
* This is a shortcut for setting up an {@code ObjectMapper} as follows:
|
||||
* <pre class="code">
|
||||
* ObjectMapper mapper = new ObjectMapper();
|
||||
* mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
|
||||
* </pre>
|
||||
* <p>The default value is {@code false}.
|
||||
*/
|
||||
public void setPrettyPrint(boolean prettyPrint) {
|
||||
this.prettyPrint = prettyPrint;
|
||||
configurePrettyPrint();
|
||||
}
|
||||
|
||||
private void configurePrettyPrint() {
|
||||
if (this.prettyPrint != null) {
|
||||
this.objectMapper.configure(SerializationFeature.INDENT_OUTPUT, this.prettyPrint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the attribute in the model that should be rendered by this view.
|
||||
* When set, all other model attributes will be ignored.
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setModelKey(String modelKey) {
|
||||
this.modelKeys = Collections.singleton(modelKey);
|
||||
}
|
||||
@@ -232,24 +157,6 @@ public class MappingJackson2JsonView extends AbstractView {
|
||||
this.extractValueFromSingleKeyModel = extractValueFromSingleKeyModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables caching of the generated JSON.
|
||||
* <p>Default is {@code true}, which will prevent the client from caching the generated JSON.
|
||||
*/
|
||||
public void setDisableCaching(boolean disableCaching) {
|
||||
this.disableCaching = disableCaching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to update the 'Content-Length' header of the response. When set to
|
||||
* {@code true}, the response is buffered in order to determine the content
|
||||
* length and set the 'Content-Length' header of the response.
|
||||
* <p>The default setting is {@code false}.
|
||||
*/
|
||||
public void setUpdateContentLength(boolean updateContentLength) {
|
||||
this.updateContentLength = updateContentLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set JSONP request parameter names. Each time a request has one of those
|
||||
* parameters, the resulting JSON will be wrapped into a function named as
|
||||
@@ -262,40 +169,6 @@ public class MappingJackson2JsonView extends AbstractView {
|
||||
this.jsonpParameterNames = jsonpParameterNames;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
|
||||
setResponseContentType(request, response);
|
||||
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<String, Object> model, HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
|
||||
OutputStream stream = (this.updateContentLength ? createTemporaryOutputStream() : response.getOutputStream());
|
||||
|
||||
Class<?> serializationView = (Class<?>) model.get(JsonView.class.getName());
|
||||
String jsonpParameterValue = getJsonpParameterValue(request);
|
||||
Object value = filterModel(model);
|
||||
if (serializationView != null || jsonpParameterValue != null) {
|
||||
MappingJacksonValue container = new MappingJacksonValue(value);
|
||||
container.setSerializationView(serializationView);
|
||||
container.setJsonpFunction(jsonpParameterValue);
|
||||
value = container;
|
||||
}
|
||||
|
||||
writeContent(stream, value, this.jsonPrefix);
|
||||
if (this.updateContentLength) {
|
||||
writeToResponse(response, (ByteArrayOutputStream) stream);
|
||||
}
|
||||
}
|
||||
|
||||
private String getJsonpParameterValue(HttpServletRequest request) {
|
||||
if (this.jsonpParameterNames != null) {
|
||||
for (String name : this.jsonpParameterNames) {
|
||||
@@ -316,6 +189,7 @@ public class MappingJackson2JsonView extends AbstractView {
|
||||
* @param model the model, as passed on to {@link #renderMergedOutputModel}
|
||||
* @return the value to be rendered
|
||||
*/
|
||||
@Override
|
||||
protected Object filterModel(Map<String, Object> model) {
|
||||
Map<String, Object> result = new HashMap<String, Object>(model.size());
|
||||
Set<String> modelKeys = (!CollectionUtils.isEmpty(this.modelKeys) ? this.modelKeys : model.keySet());
|
||||
@@ -328,41 +202,44 @@ public class MappingJackson2JsonView extends AbstractView {
|
||||
return (this.extractValueFromSingleKeyModel && result.size() == 1 ? result.values().iterator().next() : result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the actual JSON content to the stream.
|
||||
* @param stream the output stream to use
|
||||
* @param value the value to be rendered, as returned from {@link #filterModel}
|
||||
* @param jsonPrefix the prefix for this view's JSON output
|
||||
* (as indicated through {@link #setJsonPrefix}/{@link #setPrefixJson})
|
||||
* @throws IOException if writing failed
|
||||
*/
|
||||
protected void writeContent(OutputStream stream, Object value, String jsonPrefix)
|
||||
throws IOException {
|
||||
|
||||
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(stream, this.encoding);
|
||||
if (jsonPrefix != null) {
|
||||
generator.writeRaw(jsonPrefix);
|
||||
@Override
|
||||
protected Object filterAndWrapModel(Map<String, Object> model, HttpServletRequest request) {
|
||||
Object value = super.filterAndWrapModel(model, request);
|
||||
String jsonpParameterValue = getJsonpParameterValue(request);
|
||||
if (jsonpParameterValue != null) {
|
||||
if(value instanceof MappingJacksonValue) {
|
||||
((MappingJacksonValue) value).setJsonpFunction(jsonpParameterValue);
|
||||
} else {
|
||||
MappingJacksonValue container = new MappingJacksonValue(value);
|
||||
container.setJsonpFunction(jsonpParameterValue);
|
||||
value = container;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writePrefix(JsonGenerator generator, Object object) throws IOException {
|
||||
if (this.jsonPrefix != null) {
|
||||
generator.writeRaw(this.jsonPrefix);
|
||||
}
|
||||
Class<?> serializationView = null;
|
||||
String jsonpFunction = null;
|
||||
if (value instanceof MappingJacksonValue) {
|
||||
MappingJacksonValue container = (MappingJacksonValue) value;
|
||||
value = container.getValue();
|
||||
serializationView = container.getSerializationView();
|
||||
jsonpFunction = container.getJsonpFunction();
|
||||
if (object instanceof MappingJacksonValue) {
|
||||
jsonpFunction = ((MappingJacksonValue)object).getJsonpFunction();
|
||||
}
|
||||
if (jsonpFunction != null) {
|
||||
generator.writeRaw(jsonpFunction + "(" );
|
||||
}
|
||||
if (serializationView != null) {
|
||||
this.objectMapper.writerWithView(serializationView).writeValue(generator, value);
|
||||
}
|
||||
else {
|
||||
this.objectMapper.writeValue(generator, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeSuffix(JsonGenerator generator, Object object) throws IOException {
|
||||
String jsonpFunction = null;
|
||||
if (object instanceof MappingJacksonValue) {
|
||||
jsonpFunction = ((MappingJacksonValue)object).getJsonpFunction();
|
||||
}
|
||||
if (jsonpFunction != null) {
|
||||
generator.writeRaw(");");
|
||||
generator.flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.web.servlet.view.xml;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.servlet.View;
|
||||
import org.springframework.web.servlet.view.json.AbstractJackson2View;
|
||||
|
||||
/**
|
||||
* Spring MVC {@link View} that renders XML content by serializing the model for the current request
|
||||
* using <a href="http://jackson.codehaus.org/">Jackson 2's</a> {@link XmlMapper}.
|
||||
*
|
||||
* <p>The Object to be serialized is supplied as a parameter in the model. The first serializable
|
||||
* entry is used. Users can either specify a specific entry in the model via the
|
||||
* {@link #setModelKey(String) sourceKey} property.
|
||||
*
|
||||
* <p>Compatible with Jackson 2.1 and higher.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 4.1
|
||||
*/
|
||||
public class MappingJackson2XmlView extends AbstractJackson2View {
|
||||
|
||||
public static final String DEFAULT_CONTENT_TYPE = "application/xml";
|
||||
|
||||
|
||||
private String modelKey;
|
||||
|
||||
|
||||
public MappingJackson2XmlView() {
|
||||
super(new XmlMapper(), DEFAULT_CONTENT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setModelKey(String modelKey) {
|
||||
this.modelKey = modelKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out undesired attributes from the given model.
|
||||
* The return value can be either another {@link Map} or a single value object.
|
||||
* @param model the model, as passed on to {@link #renderMergedOutputModel}
|
||||
* @return the value to be rendered
|
||||
*/
|
||||
@Override
|
||||
protected Object filterModel(Map<String, Object> model) {
|
||||
Object value = null;
|
||||
if (this.modelKey != null) {
|
||||
value = model.get(this.modelKey);
|
||||
if (value == null) {
|
||||
throw new IllegalStateException(
|
||||
"Model contains no object with key [" + this.modelKey + "]");
|
||||
}
|
||||
} else {
|
||||
for (Map.Entry<String, Object> entry : model.entrySet()) {
|
||||
if (!(entry.getValue() instanceof BindingResult) &&
|
||||
!entry.getKey().equals(JsonView.class.getName())) {
|
||||
if(value != null) {
|
||||
throw new IllegalStateException("Model contains more than one object to render, only one is supported");
|
||||
}
|
||||
value = entry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user