From 1bd84db56ef79de65cbfc15d3d15de737e683d55 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sun, 11 Dec 2011 22:06:59 +0000 Subject: [PATCH] polishing (alignment with 3.1 GA) --- .../MarshallingMessageConverter.java | 20 +++-- .../oxm/xstream/XStreamMarshaller.java | 30 ++++--- .../view/json/MappingJacksonJsonView.java | 87 ++++++++++--------- .../MappingJacksonHttpMessageConverter.java | 72 ++++++++------- 4 files changed, 107 insertions(+), 102 deletions(-) diff --git a/org.springframework.jms/src/main/java/org/springframework/jms/support/converter/MarshallingMessageConverter.java b/org.springframework.jms/src/main/java/org/springframework/jms/support/converter/MarshallingMessageConverter.java index 9c508c793ee..d6b0dbe13ca 100644 --- a/org.springframework.jms/src/main/java/org/springframework/jms/support/converter/MarshallingMessageConverter.java +++ b/org.springframework.jms/src/main/java/org/springframework/jms/support/converter/MarshallingMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -57,10 +57,11 @@ public class MarshallingMessageConverter implements MessageConverter, Initializi private MessageType targetType = MessageType.BYTES; + /** - * Construct a new MarshallingMessageConverter with no {@link Marshaller} or {@link Unmarshaller} set. - * The marshaller must be set after construction by invoking {@link #setMarshaller(Marshaller)} and - * {@link #setUnmarshaller(Unmarshaller)} . + * Construct a new MarshallingMessageConverter with no {@link Marshaller} + * or {@link Unmarshaller} set. The marshaller must be set after construction by invoking + * {@link #setMarshaller(Marshaller)} and {@link #setUnmarshaller(Unmarshaller)} . */ public MarshallingMessageConverter() { } @@ -80,7 +81,7 @@ public class MarshallingMessageConverter implements MessageConverter, Initializi if (!(marshaller instanceof Unmarshaller)) { throw new IllegalArgumentException( "Marshaller [" + marshaller + "] does not implement the Unmarshaller " + - "interface. Please set an Unmarshaller explicitely by using the " + + "interface. Please set an Unmarshaller explicitly by using the " + "MarshallingMessageConverter(Marshaller, Unmarshaller) constructor."); } else { @@ -127,6 +128,7 @@ public class MarshallingMessageConverter implements MessageConverter, Initializi * @see MessageType#TEXT */ public void setTargetType(MessageType targetType) { + Assert.notNull(targetType, "MessageType must not be null"); this.targetType = targetType; } @@ -251,8 +253,8 @@ public class MarshallingMessageConverter implements MessageConverter, Initializi protected Message marshalToMessage(Object object, Session session, Marshaller marshaller, MessageType targetType) throws JMSException, IOException, XmlMappingException { - throw new IllegalArgumentException( - "Unsupported message type [" + targetType + "]. Cannot marshal to the specified message type."); + throw new IllegalArgumentException("Unsupported message type [" + targetType + + "]. MarshallingMessageConverter by default only supports TextMessages and BytesMessages."); } @@ -308,8 +310,8 @@ public class MarshallingMessageConverter implements MessageConverter, Initializi protected Object unmarshalFromMessage(Message message, Unmarshaller unmarshaller) throws JMSException, IOException, XmlMappingException { - throw new IllegalArgumentException( - "MarshallingMessageConverter only supports TextMessages and BytesMessages"); + throw new IllegalArgumentException("Unsupported message type [" + message.getClass() + + "]. MarshallingMessageConverter by default only supports TextMessages and BytesMessages."); } } diff --git a/org.springframework.oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java b/org.springframework.oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java index d6521ff5dd3..7eb474af4ce 100644 --- a/org.springframework.oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java +++ b/org.springframework.oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -110,11 +110,12 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin private ClassLoader classLoader; + /** * Returns the XStream instance used by this marshaller. */ public XStream getXStream() { - return xstream; + return this.xstream; } /** @@ -150,7 +151,6 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin /** * Sets an alias/type map, consisting of string aliases mapped to classes. Keys are aliases; values are either * {@code Class} instances, or String class names. - * * @see XStream#alias(String, Class) */ public void setAliases(Map aliases) throws ClassNotFoundException { @@ -165,7 +165,6 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin * Sets the aliases by type map, consisting of string aliases mapped to classes. Any class that is assignable to * this type will be aliased to the same name. Keys are aliases; values are either * {@code Class} instances, or String class names. - * * @see XStream#aliasType(String, Class) */ public void setAliasesByType(Map aliases) throws ClassNotFoundException { @@ -325,9 +324,9 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin } /** - * Set the auto-detection mode of XStream. - *

Note that auto-detection implies that the XStream is configured while it is processing the - * XML steams, and thus introduces a potential concurrency problem. + * Set the autodetection mode of XStream. + *

Note that auto-detection implies that the XStream is configured while + * it is processing the XML streams, and thus introduces a potential concurrency problem. * @see XStream#autodetectAnnotations(boolean) */ public void setAutodetectAnnotations(boolean autodetectAnnotations) { @@ -358,22 +357,24 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin this.supportedClasses = supportedClasses; } - public final void afterPropertiesSet() throws Exception { - customizeXStream(getXStream()); - } - public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } + + public final void afterPropertiesSet() throws Exception { + customizeXStream(getXStream()); + } + /** * Template to allow for customizing of the given {@link XStream}. - *

Default implementation is empty. + *

The default implementation is empty. * @param xstream the {@code XStream} instance */ protected void customizeXStream(XStream xstream) { } + public boolean supports(Class clazz) { if (ObjectUtils.isEmpty(this.supportedClasses)) { return true; @@ -438,8 +439,8 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin @Override protected void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException { - if (streamDriver != null) { - marshal(graph, streamDriver.createWriter(writer)); + if (this.streamDriver != null) { + marshal(graph, this.streamDriver.createWriter(writer)); } else { marshal(graph, new CompactWriter(writer)); @@ -467,6 +468,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin } } + // Unmarshalling @Override diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/json/MappingJacksonJsonView.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/json/MappingJacksonJsonView.java index c06ba11478e..8245810ea72 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/json/MappingJacksonJsonView.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/json/MappingJacksonJsonView.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -34,17 +34,17 @@ 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 using Jackson's {@link ObjectMapper}. + * Spring MVC {@link View} that renders JSON content by serializing the model for the current request + * using Jackson's {@link ObjectMapper}. * - *

By default, the entire contents of the model map (with the exception of framework-specific classes) will be - * encoded as JSON. For cases where the contents of the map need to be filtered, users may specify a specific set of - * model attributes to encode via the {@link #setRenderedAttributes(Set) renderedAttributes} property. + *

By default, the entire contents of the model map (with the exception of framework-specific classes) + * will be encoded as JSON. If the model contains only one key, you can have it extracted encoded as JSON + * alone via {@link #setExtractValueFromSingleKeyModel}. * * @author Jeremy Grelle * @author Arjen Poutsma - * @see org.springframework.http.converter.json.MappingJacksonHttpMessageConverter * @since 3.0 + * @see org.springframework.http.converter.json.MappingJacksonHttpMessageConverter */ public class MappingJacksonJsonView extends AbstractView { @@ -53,6 +53,7 @@ public class MappingJacksonJsonView extends AbstractView { */ public static final String DEFAULT_CONTENT_TYPE = "application/json"; + private ObjectMapper objectMapper = new ObjectMapper(); private JsonEncoding encoding = JsonEncoding.UTF8; @@ -63,6 +64,7 @@ public class MappingJacksonJsonView extends AbstractView { private boolean disableCaching = true; + /** * Construct a new {@code JacksonJsonView}, setting the content type to {@code application/json}. */ @@ -70,14 +72,15 @@ public class MappingJacksonJsonView extends AbstractView { setContentType(DEFAULT_CONTENT_TYPE); } + /** - * Sets the {@code ObjectMapper} for this view. If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} - * is used. - * - *

Setting a custom-configured {@code ObjectMapper} is one way to take further control of the JSON serialization - * process. For example, an extended {@link SerializerFactory} can be configured that provides custom serializers for - * specific types. The other option for refining the serialization process is to use Jackson's provided annotations on - * the types to be serialized, in which case a custom-configured ObjectMapper is unnecessary. + * Sets the {@code ObjectMapper} for this view. + * If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} is used. + *

Setting a custom-configured {@code ObjectMapper} is one way to take further control + * of the JSON serialization process. For example, an extended {@link SerializerFactory} + * can be configured that provides custom serializers for specific types. The other option + * for refining the serialization process 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"); @@ -85,7 +88,8 @@ public class MappingJacksonJsonView extends AbstractView { } /** - * Sets the {@code JsonEncoding} for this converter. By default, {@linkplain JsonEncoding#UTF8 UTF-8} is used. + * Set the {@code JsonEncoding} for this converter. + * By default, {@linkplain JsonEncoding#UTF8 UTF-8} is used. */ public void setEncoding(JsonEncoding encoding) { Assert.notNull(encoding, "'encoding' must not be null"); @@ -93,45 +97,46 @@ public class MappingJacksonJsonView extends AbstractView { } /** - * Indicates whether the JSON output by this view should be prefixed with "{@code {} &&}". Default is false. - * - *

Prefixing the JSON string in this manner is used to help prevent JSON Hijacking. The prefix renders the string - * syntactically invalid as a script so that it cannot be hijacked. This prefix does not affect the evaluation of JSON, - * but if JSON validation is performed on the string, the prefix would need to be ignored. + * Indicates whether the JSON output by this view should be prefixed with "{} && ". + * Default is false. + *

Prefixing the JSON string in this manner is used to help prevent JSON Hijacking. + * The prefix renders the string syntactically invalid as a script so that it cannot be hijacked. + * This prefix does not affect the evaluation of JSON, but if JSON validation is performed + * on the string, the prefix would need to be ignored. */ public void setPrefixJson(boolean prefixJson) { this.prefixJson = prefixJson; } /** - * Returns the attributes in the model that should be rendered by this view. + * Set the attributes in the model that should be rendered by this view. + * When set, all other model attributes will be ignored. */ - public Set getRenderedAttributes() { - return renderedAttributes; + public void setRenderedAttributes(Set renderedAttributes) { + this.renderedAttributes = renderedAttributes; } /** - * Sets the attributes in the model that should be rendered by this view. When set, all other model attributes will be - * ignored. + * Return the attributes in the model that should be rendered by this view. */ - public void setRenderedAttributes(Set renderedAttributes) { - this.renderedAttributes = renderedAttributes; + public Set getRenderedAttributes() { + return this.renderedAttributes; } /** * Disables caching of the generated JSON. - * *

Default is {@code true}, which will prevent the client from caching the generated JSON. */ public void setDisableCaching(boolean disableCaching) { this.disableCaching = disableCaching; } + @Override protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) { response.setContentType(getContentType()); - response.setCharacterEncoding(encoding.getJavaName()); - if (disableCaching) { + 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); @@ -139,32 +144,30 @@ public class MappingJacksonJsonView extends AbstractView { } @Override - protected void renderMergedOutputModel(Map model, - HttpServletRequest request, + protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { + Object value = filterModel(model); JsonGenerator generator = - objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(), encoding); - if (prefixJson) { + this.objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(), this.encoding); + if (this.prefixJson) { generator.writeRaw("{} && "); } - objectMapper.writeValue(generator, value); + this.objectMapper.writeValue(generator, value); } /** - * Filters out undesired attributes from the given model. The return value can be either another {@link Map}, or a - * single value object. - * - *

Default implementation removes {@link BindingResult} instances and entries not included in the {@link - * #setRenderedAttributes(Set) renderedAttributes} property. - * + * Filters out undesired attributes from the given model. + * The return value can be either another {@link Map} or a single value object. + *

The default implementation removes {@link BindingResult} instances and entries + * not included in the {@link #setRenderedAttributes renderedAttributes} property. * @param model the model, as passed on to {@link #renderMergedOutputModel} * @return the object to be rendered */ protected Object filterModel(Map model) { Map result = new HashMap(model.size()); Set renderedAttributes = - !CollectionUtils.isEmpty(this.renderedAttributes) ? this.renderedAttributes : model.keySet(); + (!CollectionUtils.isEmpty(this.renderedAttributes) ? this.renderedAttributes : model.keySet()); for (Map.Entry entry : model.entrySet()) { if (!(entry.getValue() instanceof BindingResult) && renderedAttributes.contains(entry.getKey())) { result.put(entry.getKey(), entry.getValue()); diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java b/org.springframework.web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java index 48a3cfd9326..2eb6e62171b 100644 --- a/org.springframework.web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java +++ b/org.springframework.web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java @@ -16,16 +16,13 @@ package org.springframework.http.converter.json; -import java.io.EOFException; import java.io.IOException; import java.nio.charset.Charset; import java.util.List; import org.codehaus.jackson.JsonEncoding; -import org.codehaus.jackson.JsonGenerationException; import org.codehaus.jackson.JsonGenerator; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.map.JsonMappingException; +import org.codehaus.jackson.JsonProcessingException; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.type.TypeFactory; import org.codehaus.jackson.type.JavaType; @@ -55,6 +52,7 @@ public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConve public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + private ObjectMapper objectMapper = new ObjectMapper(); private boolean prefixJson = false; @@ -106,27 +104,6 @@ public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConve return (this.objectMapper.canDeserialize(javaType) && canRead(mediaType)); } - /** - * Returns the Jackson {@link JavaType} for the specific class. - *

The default implementation returns {@link TypeFactory#type(java.lang.reflect.Type)}, - * but this can be overridden in subclasses, to allow for custom generic collection handling. - * For instance: - *

-	 * protected JavaType getJavaType(Class<?> clazz) {
-	 *   if (List.class.isAssignableFrom(clazz)) {
-	 *     return TypeFactory.collectionType(ArrayList.class, MyBean.class);
-	 *   } else {
-	 *     return super.getJavaType(clazz);
-	 *   }
-	 * }
-	 * 
- * @param clazz the class to return the java type for - * @return the java type - */ - protected JavaType getJavaType(Class clazz) { - return TypeFactory.type(clazz); - } - @Override public boolean canWrite(Class clazz, MediaType mediaType) { return (this.objectMapper.canSerialize(clazz) && canWrite(mediaType)); @@ -146,36 +123,57 @@ public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConve try { return this.objectMapper.readValue(inputMessage.getBody(), javaType); } - catch (JsonParseException ex) { - throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex); - } - catch (JsonMappingException ex) { - throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex); - } - catch (EOFException ex) { + catch (JsonProcessingException ex) { throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex); } } @Override - protected void writeInternal(Object o, HttpOutputMessage outputMessage) + protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { - JsonEncoding encoding = getEncoding(outputMessage.getHeaders().getContentType()); + JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType()); JsonGenerator jsonGenerator = this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding); try { if (this.prefixJson) { jsonGenerator.writeRaw("{} && "); } - this.objectMapper.writeValue(jsonGenerator, o); + this.objectMapper.writeValue(jsonGenerator, object); } - catch (JsonGenerationException ex) { + catch (JsonProcessingException ex) { throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex); } } - private JsonEncoding getEncoding(MediaType contentType) { + + /** + * Return the Jackson {@link JavaType} for the specified class. + *

The default implementation returns {@link TypeFactory#type(java.lang.reflect.Type)}, + * but this can be overridden in subclasses, to allow for custom generic collection handling. + * For instance: + *

+	 * protected JavaType getJavaType(Class<?> clazz) {
+	 *   if (List.class.isAssignableFrom(clazz)) {
+	 *     return TypeFactory.collectionType(ArrayList.class, MyBean.class);
+	 *   } else {
+	 *     return super.getJavaType(clazz);
+	 *   }
+	 * }
+	 * 
+ * @param clazz the class to return the java type for + * @return the java type + */ + protected JavaType getJavaType(Class clazz) { + return TypeFactory.type(clazz); + } + + /** + * Determine the JSON encoding to use for the given content type. + * @param contentType the media type as requested by the caller + * @return the JSON encoding to use (never null) + */ + protected JsonEncoding getJsonEncoding(MediaType contentType) { if (contentType != null && contentType.getCharSet() != null) { Charset charset = contentType.getCharSet(); for (JsonEncoding encoding : JsonEncoding.values()) {