From d8e640d70817a0fa85fd980145a92c0fad929779 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sun, 11 Dec 2011 22:07:12 +0000 Subject: [PATCH] polishing --- .../MarshallingMessageConverter.java | 20 +-- .../oxm/xstream/XStreamMarshaller.java | 30 ++--- .../view/json/MappingJacksonJsonView.java | 119 +++++++++--------- .../org/springframework/http/MediaType.java | 1 - .../MappingJacksonHttpMessageConverter.java | 72 ++++++----- 5 files changed, 122 insertions(+), 120 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 9c508c793e..d6b0dbe13c 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 d6521ff5dd..7eb474af4c 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 9e1c9364c1..cdc59c24ef 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. @@ -20,7 +20,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -28,6 +27,7 @@ import org.codehaus.jackson.JsonEncoding; import org.codehaus.jackson.JsonGenerator; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.SerializerFactory; + import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.validation.BindingResult; @@ -35,19 +35,18 @@ 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. If the model contains only one key, you can have it extracted encoded as JSON alone via - * {@link #setExtractValueFromSingleKeyModel(boolean)}. Or you can select specific model attributes to be encoded - * as JSON via ... TODO + *

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 * @author Rossen Stoyanchev - * @see org.springframework.http.converter.json.MappingJacksonHttpMessageConverter * @since 3.0 + * @see org.springframework.http.converter.json.MappingJacksonHttpMessageConverter */ public class MappingJacksonJsonView extends AbstractView { @@ -56,6 +55,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; @@ -68,6 +68,7 @@ public class MappingJacksonJsonView extends AbstractView { private boolean disableCaching = true; + /** * Construct a new {@code JacksonJsonView}, setting the content type to {@code application/json}. */ @@ -76,14 +77,15 @@ public class MappingJacksonJsonView extends AbstractView { setExposePathVariables(false); } + /** - * 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"); @@ -91,7 +93,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"); @@ -99,18 +102,19 @@ public class MappingJacksonJsonView extends AbstractView { } /** - * 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. + * 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; } - + /** - * Sets the attribute in the model that should be rendered by this view. + * Set the attribute in the model that should be rendered by this view. * When set, all other model attributes will be ignored. */ public void setModelKey(String modelKey) { @@ -118,7 +122,7 @@ public class MappingJacksonJsonView extends AbstractView { } /** - * Sets 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 void setModelKeys(Set modelKeys) { @@ -126,56 +130,56 @@ public class MappingJacksonJsonView extends AbstractView { } /** - * Returns the attributes in the model that should be rendered by this view. + * Return the attributes in the model that should be rendered by this view. */ public Set getModelKeys() { - return modelKeys; + return this.modelKeys; } /** - * Sets 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. * @deprecated use {@link #setModelKeys(Set)} instead */ + @Deprecated public void setRenderedAttributes(Set renderedAttributes) { this.modelKeys = renderedAttributes; } /** - * Returns the attributes in the model that should be rendered by this view. + * Return the attributes in the model that should be rendered by this view. * @deprecated use {@link #getModelKeys()} instead */ + @Deprecated public Set getRenderedAttributes() { - return modelKeys; + return this.modelKeys; + } + + /** + * Set whether to serialize models containing a single attribute as a map or whether to + * extract the single value from the model and serialize it directly. + *

The effect of setting this flag is similar to using {@code MappingJacksonHttpMessageConverter} + * with an {@code @ResponseBody} request-handling method. + *

Default is {@code false}. + */ + public void setExtractValueFromSingleKeyModel(boolean extractValueFromSingleKeyModel) { + this.extractValueFromSingleKeyModel = extractValueFromSingleKeyModel; } /** * 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; } - /** - * Set whether to serialize models containing a single attribute as a map or whether to - * extract the single value from the model and serialize it directly. - * The effect of setting this flag is similar to using - * {@code MappingJacksonHttpMessageConverter} with an {@code @ResponseBody}. - * request-handling method. - * - *

Default is {@code false}. - */ - public void setExtractValueFromSingleKeyModel(boolean extractValueFromSingleKeyModel) { - this.extractValueFromSingleKeyModel = extractValueFromSingleKeyModel; - } @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); @@ -183,38 +187,35 @@ 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.modelKeys) ? this.modelKeys : model.keySet(); + Set renderedAttributes = (!CollectionUtils.isEmpty(this.modelKeys) ? this.modelKeys : model.keySet()); for (Map.Entry entry : model.entrySet()) { if (!(entry.getValue() instanceof BindingResult) && renderedAttributes.contains(entry.getKey())) { result.put(entry.getKey(), entry.getValue()); } } - return (this.extractValueFromSingleKeyModel && result.size() == 1) ? result.values().iterator().next() : result; + return (this.extractValueFromSingleKeyModel && result.size() == 1 ? result.values().iterator().next() : result); } } diff --git a/org.springframework.web/src/main/java/org/springframework/http/MediaType.java b/org.springframework.web/src/main/java/org/springframework/http/MediaType.java index 04f522b66b..b2f8a800b0 100644 --- a/org.springframework.web/src/main/java/org/springframework/http/MediaType.java +++ b/org.springframework.web/src/main/java/org/springframework/http/MediaType.java @@ -474,7 +474,6 @@ public class MediaType implements Comparable { int otherPlusIdx = other.subtype.indexOf('+'); if (thisPlusIdx != -1 && otherPlusIdx != -1) { String thisSubtypeNoSuffix = this.subtype.substring(0, thisPlusIdx); - String thisSubtypeSuffix = this.subtype.substring(thisPlusIdx + 1); String otherSubtypeSuffix = other.subtype.substring(otherPlusIdx + 1); if (thisSubtypeSuffix.equals(otherSubtypeSuffix) && WILDCARD_TYPE.equals(thisSubtypeNoSuffix)) { 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 48a3cfd932..2eb6e62171 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()) { -- GitLab