1 /* 2 * Copyright 2002-2012 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.springframework.web.servlet.view.json; 18 19 import java.util.Collections; 20 import java.util.HashMap; 21 import java.util.Map; 22 import java.util.Set; 23 import javax.servlet.http.HttpServletRequest; 24 import javax.servlet.http.HttpServletResponse; 25 26 import com.fasterxml.jackson.core.JsonEncoding; 27 import com.fasterxml.jackson.core.JsonGenerator; 28 import com.fasterxml.jackson.databind.ObjectMapper; 29 30 import org.springframework.util.Assert; 31 import org.springframework.util.CollectionUtils; 32 import org.springframework.validation.BindingResult; 33 import org.springframework.web.servlet.View; 34 import org.springframework.web.servlet.view.AbstractView; 35 36 /** 37 * Spring MVC {@link View} that renders JSON content by serializing the model for the current request 38 * using <a href="http://jackson.codehaus.org/">Jackson 2's</a> {@link ObjectMapper}. 39 * 40 * <p>By default, the entire contents of the model map (with the exception of framework-specific classes) 41 * will be encoded as JSON. If the model contains only one key, you can have it extracted encoded as JSON 42 * alone via {@link #setExtractValueFromSingleKeyModel}. 43 * 44 * @author Jeremy Grelle 45 * @author Arjen Poutsma 46 * @author Rossen Stoyanchev 47 * @since 3.1.2 48 * @see org.springframework.http.converter.json.MappingJackson2HttpMessageConverter 49 */ 50 public class MappingJackson2JsonView extends AbstractView { 51 52 /** 53 * Default content type. Overridable as bean property. 54 */ 55 public static final String DEFAULT_CONTENT_TYPE = "application/json"; 56 57 58 private ObjectMapper objectMapper = new ObjectMapper(); 59 60 private JsonEncoding encoding = JsonEncoding.UTF8; 61 62 private boolean prefixJson = false; 63 64 private Set<String> modelKeys; 65 66 private boolean extractValueFromSingleKeyModel = false; 67 68 private boolean disableCaching = true; 69 70 71 /** 72 * Construct a new {@code JacksonJsonView}, setting the content type to {@code application/json}. 73 */ MappingJackson2JsonView()74 public MappingJackson2JsonView() { 75 setContentType(DEFAULT_CONTENT_TYPE); 76 setExposePathVariables(false); 77 } 78 79 80 /** 81 * Sets the {@code ObjectMapper} for this view. 82 * If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} is used. 83 * <p>Setting a custom-configured {@code ObjectMapper} is one way to take further control 84 * of the JSON serialization process. For example, an extended {@code SerializerFactory} 85 * can be configured that provides custom serializers for specific types. The other option 86 * for refining the serialization process is to use Jackson's provided annotations on the 87 * types to be serialized, in which case a custom-configured ObjectMapper is unnecessary. 88 */ setObjectMapper(ObjectMapper objectMapper)89 public void setObjectMapper(ObjectMapper objectMapper) { 90 Assert.notNull(objectMapper, "'objectMapper' must not be null"); 91 this.objectMapper = objectMapper; 92 } 93 94 /** 95 * Set the {@code JsonEncoding} for this converter. 96 * By default, {@linkplain JsonEncoding#UTF8 UTF-8} is used. 97 */ setEncoding(JsonEncoding encoding)98 public void setEncoding(JsonEncoding encoding) { 99 Assert.notNull(encoding, "'encoding' must not be null"); 100 this.encoding = encoding; 101 } 102 103 /** 104 * Indicates whether the JSON output by this view should be prefixed with <tt>"{} && "</tt>. 105 * Default is false. 106 * <p>Prefixing the JSON string in this manner is used to help prevent JSON Hijacking. 107 * The prefix renders the string syntactically invalid as a script so that it cannot be hijacked. 108 * This prefix does not affect the evaluation of JSON, but if JSON validation is performed 109 * on the string, the prefix would need to be ignored. 110 */ setPrefixJson(boolean prefixJson)111 public void setPrefixJson(boolean prefixJson) { 112 this.prefixJson = prefixJson; 113 } 114 115 /** 116 * Set the attribute in the model that should be rendered by this view. 117 * When set, all other model attributes will be ignored. 118 */ setModelKey(String modelKey)119 public void setModelKey(String modelKey) { 120 this.modelKeys = Collections.singleton(modelKey); 121 } 122 123 /** 124 * Set the attributes in the model that should be rendered by this view. 125 * When set, all other model attributes will be ignored. 126 */ setModelKeys(Set<String> modelKeys)127 public void setModelKeys(Set<String> modelKeys) { 128 this.modelKeys = modelKeys; 129 } 130 131 /** 132 * Return the attributes in the model that should be rendered by this view. 133 */ getModelKeys()134 public Set<String> getModelKeys() { 135 return this.modelKeys; 136 } 137 138 /** 139 * Set the attributes in the model that should be rendered by this view. 140 * When set, all other model attributes will be ignored. 141 * @deprecated use {@link #setModelKeys(Set)} instead 142 */ 143 @Deprecated setRenderedAttributes(Set<String> renderedAttributes)144 public void setRenderedAttributes(Set<String> renderedAttributes) { 145 this.modelKeys = renderedAttributes; 146 } 147 148 /** 149 * Return the attributes in the model that should be rendered by this view. 150 * @deprecated use {@link #getModelKeys()} instead 151 */ 152 @Deprecated getRenderedAttributes()153 public Set<String> getRenderedAttributes() { 154 return this.modelKeys; 155 } 156 157 /** 158 * Set whether to serialize models containing a single attribute as a map or whether to 159 * extract the single value from the model and serialize it directly. 160 * <p>The effect of setting this flag is similar to using {@code MappingJacksonHttpMessageConverter} 161 * with an {@code @ResponseBody} request-handling method. 162 * <p>Default is {@code false}. 163 */ setExtractValueFromSingleKeyModel(boolean extractValueFromSingleKeyModel)164 public void setExtractValueFromSingleKeyModel(boolean extractValueFromSingleKeyModel) { 165 this.extractValueFromSingleKeyModel = extractValueFromSingleKeyModel; 166 } 167 168 /** 169 * Disables caching of the generated JSON. 170 * <p>Default is {@code true}, which will prevent the client from caching the generated JSON. 171 */ setDisableCaching(boolean disableCaching)172 public void setDisableCaching(boolean disableCaching) { 173 this.disableCaching = disableCaching; 174 } 175 176 177 @Override prepareResponse(HttpServletRequest request, HttpServletResponse response)178 protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) { 179 response.setContentType(getContentType()); 180 response.setCharacterEncoding(this.encoding.getJavaName()); 181 if (this.disableCaching) { 182 response.addHeader("Pragma", "no-cache"); 183 response.addHeader("Cache-Control", "no-cache, no-store, max-age=0"); 184 response.addDateHeader("Expires", 1L); 185 } 186 } 187 188 @Override renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response)189 protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, 190 HttpServletResponse response) throws Exception { 191 192 Object value = filterModel(model); 193 JsonGenerator generator = 194 this.objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(), this.encoding); 195 if (this.prefixJson) { 196 generator.writeRaw("{} && "); 197 } 198 this.objectMapper.writeValue(generator, value); 199 } 200 201 /** 202 * Filters out undesired attributes from the given model. 203 * The return value can be either another {@link Map} or a single value object. 204 * <p>The default implementation removes {@link BindingResult} instances and entries 205 * not included in the {@link #setRenderedAttributes renderedAttributes} property. 206 * @param model the model, as passed on to {@link #renderMergedOutputModel} 207 * @return the object to be rendered 208 */ filterModel(Map<String, Object> model)209 protected Object filterModel(Map<String, Object> model) { 210 Map<String, Object> result = new HashMap<String, Object>(model.size()); 211 Set<String> renderedAttributes = (!CollectionUtils.isEmpty(this.modelKeys) ? this.modelKeys : model.keySet()); 212 for (Map.Entry<String, Object> entry : model.entrySet()) { 213 if (!(entry.getValue() instanceof BindingResult) && renderedAttributes.contains(entry.getKey())) { 214 result.put(entry.getKey(), entry.getValue()); 215 } 216 } 217 return (this.extractValueFromSingleKeyModel && result.size() == 1 ? result.values().iterator().next() : result); 218 } 219 220 } 221