This commit is contained in:
Rossen Stoyanchev
2014-05-21 18:11:49 -04:00
parent 811330f5c8
commit bc3ca2dea1
5 changed files with 60 additions and 59 deletions

View File

@@ -51,13 +51,12 @@ final class GsonBase64ByteArrayJsonTypeAdapter implements JsonSerializer<byte[]>
@Override @Override
public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) { public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(new String(this.base64.encode(src), DEFAULT_CHARSET)); String encoded = new String(this.base64.encode(src), DEFAULT_CHARSET);
return new JsonPrimitive(encoded);
} }
@Override @Override
public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) public byte[] deserialize(JsonElement json, Type type, JsonDeserializationContext cxt) throws JsonParseException {
throws JsonParseException {
return this.base64.decode(json.getAsString().getBytes(DEFAULT_CHARSET)); return this.base64.decode(json.getAsString().getBytes(DEFAULT_CHARSET));
} }

View File

@@ -25,13 +25,12 @@ import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
/** /**
* A {@link FactoryBean} for creating a Google Gson 2.x {@link Gson} * A {@link FactoryBean} for creating a Google Gson 2.x {@link Gson} instance.
* *
* @author Roy Clarkson * @author Roy Clarkson
* @since 4.1 * @since 4.1
@@ -43,6 +42,7 @@ public class GsonFactoryBean implements FactoryBean<Gson>, BeanClassLoaderAware,
private final Log logger = LogFactory.getLog(getClass()); private final Log logger = LogFactory.getLog(getClass());
private Gson gson; private Gson gson;
private GsonBuilder gsonBuilder; private GsonBuilder gsonBuilder;
@@ -61,15 +61,15 @@ public class GsonFactoryBean implements FactoryBean<Gson>, BeanClassLoaderAware,
/** /**
* Set the GsonBuilder instance to use. If not set, the GsonBuilder will be created * Set the GsonBuilder instance to use. If not set, the GsonBuilder will be
* using its default constructor. * created using its default constructor.
*/ */
public void setGsonBuilder(GsonBuilder gsonBuilder) { public void setGsonBuilder(GsonBuilder gsonBuilder) {
this.gsonBuilder = gsonBuilder; this.gsonBuilder = gsonBuilder;
} }
/** /**
* Return the GsonBuilder instance being used. * Return the configured GsonBuilder instance to use, if any.
* @return the GsonBuilder instance * @return the GsonBuilder instance
*/ */
public GsonBuilder getGsonBuilder() { public GsonBuilder getGsonBuilder() {
@@ -77,8 +77,8 @@ public class GsonFactoryBean implements FactoryBean<Gson>, BeanClassLoaderAware,
} }
/** /**
* Whether to use the {@link GsonBuilder#setPrettyPrinting()} when writing JSON. This * Whether to use the {@link GsonBuilder#setPrettyPrinting()} when writing
* is a shortcut for setting up a {@code Gson} as follows: * JSON. This is a shortcut for setting up a {@code Gson} as follows:
* *
* <pre class="code"> * <pre class="code">
* new GsonBuilder().setPrettyPrinting().create(); * new GsonBuilder().setPrettyPrinting().create();
@@ -89,8 +89,9 @@ public class GsonFactoryBean implements FactoryBean<Gson>, BeanClassLoaderAware,
} }
/** /**
* Whether to use the {@link GsonBuilder#serializeNulls()} option when writing JSON. * Whether to use the {@link GsonBuilder#serializeNulls()} option when
* This is a shortcut for setting up a {@code Gson} as follows: * writing JSON. This is a shortcut for setting up a {@code Gson} as
* follows:
* *
* <pre class="code"> * <pre class="code">
* new GsonBuilder().serializeNulls().create(); * new GsonBuilder().serializeNulls().create();
@@ -101,9 +102,9 @@ public class GsonFactoryBean implements FactoryBean<Gson>, BeanClassLoaderAware,
} }
/** /**
* Whether to use the {@link GsonBuilder#disableHtmlEscaping()} when writing JSON. Set * Whether to use the {@link GsonBuilder#disableHtmlEscaping()} when writing
* to {@code true} to disable HTML escaping in JSON. This is a shortcut for setting up * JSON. Set to {@code true} to disable HTML escaping in JSON. This is a
* a {@code Gson} as follows: * shortcut for setting up a {@code Gson} as follows:
* *
* <pre class="code"> * <pre class="code">
* new GsonBuilder().disableHtmlEscaping().create(); * new GsonBuilder().disableHtmlEscaping().create();
@@ -146,7 +147,8 @@ public class GsonFactoryBean implements FactoryBean<Gson>, BeanClassLoaderAware,
* writing JSON. * writing JSON.
* *
* <p>When set to {@code true} a custom {@link com.google.gson.TypeAdapter} * <p>When set to {@code true} a custom {@link com.google.gson.TypeAdapter}
* is registered via {@link GsonBuilder#registerTypeHierarchyAdapter(Class, Object)} * is registered via
* {@link GsonBuilder#registerTypeHierarchyAdapter(Class, Object)}
* that serializes a {@code byte[]} property to and from a Base64 encoded * that serializes a {@code byte[]} property to and from a Base64 encoded
* string instead of a JSON array. * string instead of a JSON array.
* *
@@ -167,7 +169,7 @@ public class GsonFactoryBean implements FactoryBean<Gson>, BeanClassLoaderAware,
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
if (gsonBuilder == null) { if (this.gsonBuilder == null) {
this.gsonBuilder = new GsonBuilder(); this.gsonBuilder = new GsonBuilder();
} }
if (this.prettyPrint != null && this.prettyPrint) { if (this.prettyPrint != null && this.prettyPrint) {
@@ -188,14 +190,14 @@ public class GsonFactoryBean implements FactoryBean<Gson>, BeanClassLoaderAware,
} }
} }
else if (logger.isDebugEnabled()) { else if (logger.isDebugEnabled()) {
logger.debug("org.apache.commons.codec.binary.Base64 is not available on the class path. Gson Base64 encoding is disabled."); logger.debug("org.apache.commons.codec.binary.Base64 is not " +
"available on the class path. Gson Base64 encoding is disabled.");
} }
this.gson = this.gsonBuilder.create(); this.gson = this.gsonBuilder.create();
} }
/** /**
* Return the singleton Gson. * Return the created Gson instance.
*/ */
@Override @Override
public Gson getObject() throws Exception { public Gson getObject() throws Exception {

View File

@@ -76,18 +76,19 @@ public class GsonHttpMessageConverter extends AbstractHttpMessageConverter<Objec
} }
/** /**
* Set the {@code Gson} for this view. * Set the {@code Gson} instance to use.
* If not set, a default {@link Gson#Gson() Gson} is used. * If not set, a default {@link Gson#Gson() Gson} instance is used.
*
* <p>Setting a custom-configured {@code Gson} is one way to take further * <p>Setting a custom-configured {@code Gson} is one way to take further
* control of the JSON serialization process. * control of the JSON serialization process.
*/ */
public void setGson(Gson gson) { public void setGson(Gson gson) {
Assert.notNull(gson, "Gson must not be null"); Assert.notNull(gson, "'gson' is required");
this.gson = gson; this.gson = gson;
} }
/** /**
* Return the underlying {@code GsonBuilder} for this converter. * Return the configured {@code Gson} instance for this converter.
*/ */
public Gson getGson() { public Gson getGson() {
return this.gson; return this.gson;
@@ -106,10 +107,11 @@ public class GsonHttpMessageConverter extends AbstractHttpMessageConverter<Objec
* Indicate whether the JSON output by this view should be prefixed with "{} &&". * Indicate whether the JSON output by this view should be prefixed with "{} &&".
* Default is {@code false}. * Default is {@code false}.
* *
* <p>Prefixing the JSON string in this manner is used to help prevent JSON Hijacking. * <p>Prefixing the JSON string in this manner is used to help prevent JSON
* The prefix renders the string syntactically invalid as a script so that it cannot * Hijacking. The prefix renders the string syntactically invalid as a script
* be hijacked. This prefix does not affect the evaluation of JSON, but if JSON * so that it cannot be hijacked. This prefix does not affect the evaluation
* validation is performed on the string, the prefix would need to be ignored. * of JSON, but if JSON validation is performed on the string, the prefix
* would need to be ignored.
* *
* @see #setJsonPrefix * @see #setJsonPrefix
*/ */
@@ -157,9 +159,10 @@ public class GsonHttpMessageConverter extends AbstractHttpMessageConverter<Objec
/** /**
* Return the Gson {@link TypeToken} for the specified type. * Return the Gson {@link TypeToken} for the specified type.
* <p>The default implementation returns {@code TypeToken.get(type)}, but this can be *
* overridden in subclasses to allow for custom generic collection handling. * <p>The default implementation returns {@code TypeToken.get(type)}, but
* For instance: * this can be overridden in subclasses to allow for custom generic
* collection handling. For instance:
* <pre class="code"> * <pre class="code">
* protected TypeToken<?> getTypeToken(Type type) { * protected TypeToken<?> getTypeToken(Type type) {
* if (type instanceof Class && List.class.isAssignableFrom((Class<?>) type)) { * if (type instanceof Class && List.class.isAssignableFrom((Class<?>) type)) {
@@ -200,12 +203,11 @@ public class GsonHttpMessageConverter extends AbstractHttpMessageConverter<Objec
Charset charset = getCharset(outputMessage.getHeaders()); Charset charset = getCharset(outputMessage.getHeaders());
OutputStreamWriter writer = new OutputStreamWriter(outputMessage.getBody(), charset); OutputStreamWriter writer = new OutputStreamWriter(outputMessage.getBody(), charset);
try { try {
if (this.jsonPrefix != null) { if (this.jsonPrefix != null) {
writer.append(this.jsonPrefix); writer.append(this.jsonPrefix);
} }
gson.toJson(o, writer); this.gson.toJson(o, writer);
writer.close(); writer.close();
} }
catch(JsonIOException ex) { catch(JsonIOException ex) {

View File

@@ -28,14 +28,12 @@ import com.google.gson.Gson;
import static org.junit.Assert.*; import static org.junit.Assert.*;
/** /**
* {@link GsonFactoryBean} tests * {@link GsonFactoryBean} tests.
* *
* @author Roy Clarkson * @author Roy Clarkson
*/ */
public class GsonFactoryBeanTests { public class GsonFactoryBeanTests {
private static final String NEWLINE_SYSTEM_PROPERTY = System.getProperty("line.separator");
private static final String DATE_FORMAT = "yyyy-MM-dd"; private static final String DATE_FORMAT = "yyyy-MM-dd";
private GsonFactoryBean factory; private GsonFactoryBean factory;
@@ -54,7 +52,8 @@ public class GsonFactoryBeanTests {
StringBean bean = new StringBean(); StringBean bean = new StringBean();
bean.setName("Jason"); bean.setName("Jason");
String result = gson.toJson(bean); String result = gson.toJson(bean);
assertEquals("{" + NEWLINE_SYSTEM_PROPERTY + " \"name\": \"Jason\"" + NEWLINE_SYSTEM_PROPERTY + "}", result); String lineSeparator = System.getProperty("line.separator");
assertEquals("{" + lineSeparator + " \"name\": \"Jason\"" + lineSeparator + "}", result);
} }
@Test @Test
@@ -233,7 +232,6 @@ public class GsonFactoryBeanTests {
public void setBytes(byte[] bytes) { public void setBytes(byte[] bytes) {
this.bytes = bytes; this.bytes = bytes;
} }
} }
} }

View File

@@ -49,29 +49,30 @@ public class GsonHttpMessageConverterTests {
@Test @Test
public void canRead() { public void canRead() {
assertTrue(converter.canRead(MyBean.class, new MediaType("application", "json"))); assertTrue(this.converter.canRead(MyBean.class, new MediaType("application", "json")));
assertTrue(converter.canRead(Map.class, new MediaType("application", "json"))); assertTrue(this.converter.canRead(Map.class, new MediaType("application", "json")));
} }
@Test @Test
public void canWrite() { public void canWrite() {
assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "json"))); assertTrue(this.converter.canWrite(MyBean.class, new MediaType("application", "json")));
assertTrue(converter.canWrite(Map.class, new MediaType("application", "json"))); assertTrue(this.converter.canWrite(Map.class, new MediaType("application", "json")));
} }
@Test @Test
public void canReadAndWriteMicroformats() { public void canReadAndWriteMicroformats() {
assertTrue(converter.canRead(MyBean.class, new MediaType("application", "vnd.test-micro-type+json"))); assertTrue(this.converter.canRead(MyBean.class, new MediaType("application", "vnd.test-micro-type+json")));
assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "vnd.test-micro-type+json"))); assertTrue(this.converter.canWrite(MyBean.class, new MediaType("application", "vnd.test-micro-type+json")));
} }
@Test @Test
public void readTyped() throws IOException { public void readTyped() throws IOException {
String body = String body = "{\"bytes\":[1,2],\"array\":[\"Foo\",\"Bar\"]," +
"{\"bytes\":[1,2],\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}"; "\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
inputMessage.getHeaders().setContentType(new MediaType("application", "json")); inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
MyBean result = (MyBean) converter.read(MyBean.class, inputMessage); MyBean result = (MyBean) this.converter.read(MyBean.class, inputMessage);
assertEquals("Foo", result.getString()); assertEquals("Foo", result.getString());
assertEquals(42, result.getNumber()); assertEquals(42, result.getNumber());
assertEquals(42F, result.getFraction(), 0F); assertEquals(42F, result.getFraction(), 0F);
@@ -83,11 +84,11 @@ public class GsonHttpMessageConverterTests {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void readUntyped() throws IOException { public void readUntyped() throws IOException {
String body = String body = "{\"bytes\":[1,2],\"array\":[\"Foo\",\"Bar\"]," +
"{\"bytes\":[1,2],\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}"; "\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
inputMessage.getHeaders().setContentType(new MediaType("application", "json")); inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
HashMap<String, Object> result = (HashMap<String, Object>) converter.read(HashMap.class, inputMessage); HashMap<String, Object> result = (HashMap<String, Object>) this.converter.read(HashMap.class, inputMessage);
assertEquals("Foo", result.get("string")); assertEquals("Foo", result.get("string"));
Number n = (Number) result.get("number"); Number n = (Number) result.get("number");
assertEquals(42, n.longValue()); assertEquals(42, n.longValue());
@@ -116,7 +117,7 @@ public class GsonHttpMessageConverterTests {
body.setArray(new String[]{"Foo", "Bar"}); body.setArray(new String[]{"Foo", "Bar"});
body.setBool(true); body.setBool(true);
body.setBytes(new byte[]{0x1, 0x2}); body.setBytes(new byte[]{0x1, 0x2});
converter.write(body, null, outputMessage); this.converter.write(body, null, outputMessage);
Charset utf8 = Charset.forName("UTF-8"); Charset utf8 = Charset.forName("UTF-8");
String result = outputMessage.getBodyAsString(utf8); String result = outputMessage.getBodyAsString(utf8);
assertTrue(result.contains("\"string\":\"Foo\"")); assertTrue(result.contains("\"string\":\"Foo\""));
@@ -135,7 +136,7 @@ public class GsonHttpMessageConverterTests {
MediaType contentType = new MediaType("application", "json", utf16); MediaType contentType = new MediaType("application", "json", utf16);
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
String body = "H\u00e9llo W\u00f6rld"; String body = "H\u00e9llo W\u00f6rld";
converter.write(body, contentType, outputMessage); this.converter.write(body, contentType, outputMessage);
assertEquals("Invalid result", "\"" + body + "\"", outputMessage.getBodyAsString(utf16)); assertEquals("Invalid result", "\"" + body + "\"", outputMessage.getBodyAsString(utf16));
assertEquals("Invalid content-type", contentType, outputMessage.getHeaders().getContentType()); assertEquals("Invalid content-type", contentType, outputMessage.getHeaders().getContentType());
} }
@@ -145,14 +146,13 @@ public class GsonHttpMessageConverterTests {
String body = "FooBar"; String body = "FooBar";
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
inputMessage.getHeaders().setContentType(new MediaType("application", "json")); inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
converter.read(MyBean.class, inputMessage); this.converter.read(MyBean.class, inputMessage);
} }
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void readGenerics() throws IOException { public void readGenerics() throws IOException {
GsonHttpMessageConverter converter = new GsonHttpMessageConverter() { GsonHttpMessageConverter converter = new GsonHttpMessageConverter() {
@Override @Override
protected TypeToken<?> getTypeToken(Type type) { protected TypeToken<?> getTypeToken(Type type) {
if (type instanceof Class && List.class.isAssignableFrom((Class<?>) type)) { if (type instanceof Class && List.class.isAssignableFrom((Class<?>) type)) {
@@ -164,7 +164,8 @@ public class GsonHttpMessageConverterTests {
} }
} }
}; };
String body = "[{\"bytes\":[1,2],\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}]"; String body = "[{\"bytes\":[1,2],\"array\":[\"Foo\",\"Bar\"]," +
"\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}]";
MockHttpInputMessage inputMessage = new MockHttpInputMessage( MockHttpInputMessage inputMessage = new MockHttpInputMessage(
body.getBytes(UTF8)); body.getBytes(UTF8));
inputMessage.getHeaders().setContentType(new MediaType("application", "json")); inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
@@ -186,7 +187,8 @@ public class GsonHttpMessageConverterTests {
ParameterizedTypeReference<List<MyBean>> beansList = new ParameterizedTypeReference<List<MyBean>>() { ParameterizedTypeReference<List<MyBean>> beansList = new ParameterizedTypeReference<List<MyBean>>() {
}; };
String body = "[{\"bytes\":[1,2],\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}]"; String body = "[{\"bytes\":[1,2],\"array\":[\"Foo\",\"Bar\"]," +
"\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}]";
MockHttpInputMessage inputMessage = new MockHttpInputMessage( MockHttpInputMessage inputMessage = new MockHttpInputMessage(
body.getBytes(UTF8)); body.getBytes(UTF8));
inputMessage.getHeaders().setContentType(new MediaType("application", "json")); inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
@@ -208,7 +210,6 @@ public class GsonHttpMessageConverterTests {
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
this.converter.setPrefixJson(true); this.converter.setPrefixJson(true);
this.converter.writeInternal("foo", outputMessage); this.converter.writeInternal("foo", outputMessage);
assertEquals("{} && \"foo\"", outputMessage.getBodyAsString(UTF8)); assertEquals("{} && \"foo\"", outputMessage.getBodyAsString(UTF8));
} }
@@ -217,7 +218,6 @@ public class GsonHttpMessageConverterTests {
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
this.converter.setJsonPrefix(")]}',"); this.converter.setJsonPrefix(")]}',");
this.converter.writeInternal("foo", outputMessage); this.converter.writeInternal("foo", outputMessage);
assertEquals(")]}',\"foo\"", outputMessage.getBodyAsString(UTF8)); assertEquals(")]}',\"foo\"", outputMessage.getBodyAsString(UTF8));
} }