Skip to content

Commit f97e756

Browse files
author
Manish Baxi
committed
Replaced Jackson 2 JSON Serializer with GSON to check whether GSON can
be used as a drop-in replacement to Jackson 2.
1 parent 147853a commit f97e756

File tree

7 files changed

+246
-11
lines changed

7 files changed

+246
-11
lines changed

api/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@
5454
<groupId>com.fasterxml.jackson.core</groupId>
5555
<artifactId>jackson-databind</artifactId>
5656
</dependency>
57+
<dependency>
58+
<groupId>com.google.code.gson</groupId>
59+
<artifactId>gson</artifactId>
60+
</dependency>
5761
<dependency>
5862
<groupId>commons-logging</groupId>
5963
<artifactId>commons-logging</artifactId>
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/*
2+
* Copyright 2002-2013 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.http.converter.json;
18+
19+
import java.io.IOException;
20+
import java.io.InputStreamReader;
21+
import java.io.OutputStreamWriter;
22+
import java.io.Reader;
23+
import java.lang.reflect.Type;
24+
import java.nio.charset.Charset;
25+
26+
import org.springframework.http.HttpHeaders;
27+
import org.springframework.http.HttpInputMessage;
28+
import org.springframework.http.HttpOutputMessage;
29+
import org.springframework.http.MediaType;
30+
import org.springframework.http.converter.AbstractHttpMessageConverter;
31+
import org.springframework.http.converter.HttpMessageNotReadableException;
32+
import org.springframework.http.converter.HttpMessageNotWritableException;
33+
import org.springframework.util.Assert;
34+
35+
import com.google.gson.Gson;
36+
import com.google.gson.GsonBuilder;
37+
import com.google.gson.JsonIOException;
38+
import com.google.gson.JsonParseException;
39+
import com.google.gson.JsonSyntaxException;
40+
41+
/**
42+
* @author Roy Clarkson
43+
* @since 1.0
44+
*/
45+
public class GsonHttpMessageConverter extends AbstractHttpMessageConverter<Object>
46+
{
47+
48+
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
49+
50+
private Gson gson;
51+
52+
private Type type = null;
53+
54+
private boolean prefixJson = false;
55+
56+
/**
57+
* Construct a new {@code GsonHttpMessageConverter} with a default
58+
* {@link Gson#Gson() Gson}.
59+
*/
60+
public GsonHttpMessageConverter()
61+
{
62+
this(new Gson());
63+
}
64+
65+
/**
66+
* Construct a new {@code GsonHttpMessageConverter}.
67+
*
68+
* @param serializeNulls true to generate json for null values
69+
*/
70+
public GsonHttpMessageConverter(boolean serializeNulls)
71+
{
72+
this(serializeNulls ? new GsonBuilder().serializeNulls().create() : new Gson());
73+
}
74+
75+
/**
76+
* Construct a new {@code GsonHttpMessageConverter}.
77+
*
78+
* @param gson a customized {@link Gson#Gson() Gson}
79+
*/
80+
public GsonHttpMessageConverter(Gson gson)
81+
{
82+
super(new MediaType("application", "json", DEFAULT_CHARSET));
83+
setGson(gson);
84+
}
85+
86+
/**
87+
* Sets the {@code Gson} for this view. If not set, a default
88+
* {@link Gson#Gson() Gson} is used.
89+
* <p>
90+
* Setting a custom-configured {@code Gson} is one way to take further control
91+
* of the JSON serialization process.
92+
*
93+
* @throws IllegalArgumentException if gson is null
94+
*/
95+
public void setGson(Gson gson)
96+
{
97+
Assert.notNull(gson, "'gson' must not be null");
98+
this.gson = gson;
99+
}
100+
101+
public void setType(Type type)
102+
{
103+
this.type = type;
104+
}
105+
106+
public Type getType()
107+
{
108+
return type;
109+
}
110+
111+
/**
112+
* Indicates whether the JSON output by this view should be prefixed with
113+
* "{} &&". Default is false.
114+
* <p>
115+
* Prefixing the JSON string in this manner is used to help prevent JSON
116+
* Hijacking. The prefix renders the string syntactically invalid as a script
117+
* so that it cannot be hijacked. This prefix does not affect the evaluation
118+
* of JSON, but if JSON validation is performed on the string, the prefix
119+
* would need to be ignored.
120+
*/
121+
public void setPrefixJson(boolean prefixJson)
122+
{
123+
this.prefixJson = prefixJson;
124+
}
125+
126+
@Override
127+
public boolean canRead(Class<?> clazz, MediaType mediaType)
128+
{
129+
return canRead(mediaType);
130+
}
131+
132+
@Override
133+
public boolean canWrite(Class<?> clazz, MediaType mediaType)
134+
{
135+
return canWrite(mediaType);
136+
}
137+
138+
@Override
139+
protected boolean supports(Class<?> clazz)
140+
{
141+
// should not be called, since we override canRead/Write instead
142+
throw new UnsupportedOperationException();
143+
}
144+
145+
@Override
146+
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
147+
throws IOException, HttpMessageNotReadableException
148+
{
149+
150+
Reader json = new InputStreamReader(inputMessage.getBody(), getCharset(inputMessage.getHeaders()));
151+
152+
try
153+
{
154+
Type typeOfT = getType();
155+
if (typeOfT != null)
156+
{
157+
return this.gson.fromJson(json, typeOfT);
158+
}
159+
else
160+
{
161+
return this.gson.fromJson(json, clazz);
162+
}
163+
}
164+
catch (JsonSyntaxException ex)
165+
{
166+
throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
167+
}
168+
catch (JsonIOException ex)
169+
{
170+
throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
171+
}
172+
catch (JsonParseException ex)
173+
{
174+
throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
175+
}
176+
}
177+
178+
@Override
179+
protected void writeInternal(Object o, HttpOutputMessage outputMessage)
180+
throws IOException, HttpMessageNotWritableException
181+
{
182+
183+
OutputStreamWriter writer = new OutputStreamWriter(outputMessage.getBody(), getCharset(outputMessage.getHeaders()));
184+
185+
try
186+
{
187+
if (this.prefixJson)
188+
{
189+
writer.append("{} && ");
190+
}
191+
Type typeOfSrc = getType();
192+
if (typeOfSrc != null)
193+
{
194+
this.gson.toJson(o, typeOfSrc, writer);
195+
}
196+
else
197+
{
198+
this.gson.toJson(o, writer);
199+
}
200+
writer.close();
201+
}
202+
catch (JsonIOException ex)
203+
{
204+
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
205+
}
206+
}
207+
208+
// helpers
209+
210+
private Charset getCharset(HttpHeaders headers)
211+
{
212+
if (headers != null && headers.getContentType() != null
213+
&& headers.getContentType().getCharSet() != null)
214+
{
215+
return headers.getContentType().getCharSet();
216+
}
217+
return DEFAULT_CHARSET;
218+
}
219+
220+
}

api/src/main/resources/springAPIContext.xml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@
2121
<context:component-scan base-package="org.example.api" />
2222
<mvc:annotation-driven>
2323
<mvc:message-converters>
24-
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
25-
<property name="prettyPrint" value="true" />
24+
<bean class="org.springframework.http.converter.json.GsonHttpMessageConverter">
2625
<property name="supportedMediaTypes" value="application/json" />
2726
</bean>
2827
</mvc:message-converters>

api/src/main/scala/org/example/api/UserAuthenticationController.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package org.example.api
22

33
import javax.servlet.http.{ HttpServletRequest, HttpServletResponse }
4-
54
import org.example.transfer.Response
6-
75
import org.springframework.beans.factory.annotation.{ Autowired, Qualifier }
86
import org.springframework.security.authentication.{ AuthenticationManager, InternalAuthenticationServiceException, UsernamePasswordAuthenticationToken }
97
import org.springframework.security.core.{ Authentication, AuthenticationException }
108
import org.springframework.security.core.context.SecurityContextHolder
119
import org.springframework.security.web.context.SecurityContextRepository
1210
import org.springframework.web.bind.annotation.{ RequestMapping, ResponseBody, RestController, RequestParam, RequestMethod }
11+
import scala.beans.BeanProperty
1312

1413
/**
1514
* User Authentication controller.
@@ -44,7 +43,8 @@ class UserAuthenticationController {
4443
authenticationResponse.addError("Unable to authenticate the user.")
4544
} else {
4645
// Add an authentication token to the response.
47-
authenticationResponse.token = authenticationResult.getDetails.asInstanceOf[String]
46+
println("%% " + authenticationResult.getDetails)
47+
authenticationResponse.token = authenticationResult.getDetails.asInstanceOf[String]
4848

4949
// Save the authentication token in the security context.
5050
SecurityContextHolder.getContext.setAuthentication(authenticationResult)
@@ -72,6 +72,7 @@ class UserAuthenticationController {
7272
/**
7373
* Represents an API authentication response.
7474
*/
75-
class UserAuthenticationResponse(val username: String) extends Response {
75+
class UserAuthenticationResponse(@transient val username: String) extends Response {
76+
@BeanProperty
7677
var token: String = _
7778
}

api/src/main/scala/org/example/api/security/APIAuthenticationToken.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ import org.springframework.security.core.context.SecurityContextHolder;
1313
* authentication and authorization information for a person.
1414
*/
1515
final class APIAuthenticationToken(user: UserAuthenticationResponse) extends Authentication with CredentialsContainer {
16-
private val authorities: Set[GrantedAuthority] = new HashSet[GrantedAuthority](1)
16+
private[this] val authorities: Set[GrantedAuthority] = new HashSet[GrantedAuthority](1)
1717
authorities.add(new SimpleGrantedAuthority(user.role.name))
18-
private val name = user.name
18+
private[this] val name = user.name
1919
private val principal = user.username
20-
private val role = user.role
21-
private val token = TokenGenerator.generateToken
20+
private[this] val role = user.role
21+
private[this] val token = TokenGenerator.generateToken
2222

2323
/**
2424
* @see org.springframework.security.core.CredentialsContainer#eraseCredentials()
@@ -38,7 +38,7 @@ final class APIAuthenticationToken(user: UserAuthenticationResponse) extends Aut
3838
/**
3939
* @see org.springframework.security.core.Authentication#getDetails()
4040
*/
41-
override def getDetails = null
41+
override def getDetails = this.token
4242

4343
/**
4444
* @see java.security.Principal#getName()

api/src/main/scala/org/example/api/security/UsernamePasswordAuthenticationProvider.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ class UsernamePasswordAuthenticationProvider extends AuthenticationProvider {
2323
override def authenticate(authentication: Authentication): Authentication = {
2424
val authenticationResponse = this.userService.authenticate(new UserAuthenticationRequest(authentication.getName, authentication.getCredentials.asInstanceOf[String]))
2525

26+
println("## " + authenticationResponse.firstName)
27+
println("## " + authenticationResponse.lastName)
28+
println("## " + authenticationResponse.role)
29+
println("## " + authenticationResponse.username)
30+
2631
if (authenticationResponse.isSuccess) {
2732
SecurityContextHolder.getContext.setAuthentication(new APIAuthenticationToken(authenticationResponse))
2833

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@
8585
<artifactId>jackson-databind</artifactId>
8686
<version>${jackson.version}</version>
8787
</dependency>
88+
<dependency>
89+
<groupId>com.google.code.gson</groupId>
90+
<artifactId>gson</artifactId>
91+
<version>${gson.version}</version>
92+
</dependency>
8893
<dependency>
8994
<groupId>commons-logging</groupId>
9095
<artifactId>commons-logging</artifactId>
@@ -285,6 +290,7 @@
285290
<cglib.version>3.0</cglib.version>
286291
<ehcache.version>2.6.6</ehcache.version>
287292
<freemarker.version>2.3.20</freemarker.version>
293+
<gson.version>2.2.4</gson.version>
288294
<hamcrest.version>1.3</hamcrest.version>
289295
<jackson.version>2.3.1</jackson.version>
290296
<junit.version>4.11</junit.version>

0 commit comments

Comments
 (0)