Skip to content

Commit 06bbcd4

Browse files
committed
[jwtk#719] JSON-B extension.
1 parent 20b0437 commit 06bbcd4

File tree

10 files changed

+401
-1
lines changed

10 files changed

+401
-1
lines changed

extensions/jsonb/bnd.bnd

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fragment-Host: io.jsonwebtoken.jjwt-api

extensions/jsonb/pom.xml

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>io.jsonwebtoken</groupId>
9+
<artifactId>jjwt-root</artifactId>
10+
<version>0.11.3-SNAPSHOT</version>
11+
<relativePath>../../pom.xml</relativePath>
12+
</parent>
13+
14+
<artifactId>jjwt-jsonb</artifactId>
15+
<name>JJWT :: Extensions :: JSON-B</name>
16+
<packaging>jar</packaging>
17+
18+
<properties>
19+
<jjwt.root>${basedir}/../..</jjwt.root>
20+
<!-- JSON-B uses static methods in interfaces. -->
21+
<jdk.version>8</jdk.version>
22+
</properties>
23+
24+
<dependencies>
25+
<dependency>
26+
<groupId>io.jsonwebtoken</groupId>
27+
<artifactId>jjwt-api</artifactId>
28+
</dependency>
29+
<dependency>
30+
<groupId>jakarta.json</groupId>
31+
<artifactId>jakarta.json-api</artifactId>
32+
<scope>test</scope>
33+
</dependency>
34+
<dependency>
35+
<groupId>jakarta.json.bind</groupId>
36+
<artifactId>jakarta.json.bind-api</artifactId>
37+
<scope>provided</scope>
38+
</dependency>
39+
40+
<!-- Test dependency -->
41+
<dependency>
42+
<groupId>org.apache.johnzon</groupId>
43+
<artifactId>johnzon-jsonb</artifactId>
44+
<scope>test</scope>
45+
</dependency>
46+
</dependencies>
47+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (C) 2014 jsonwebtoken.io
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+
package io.jsonwebtoken.jsonb.io;
17+
18+
import io.jsonwebtoken.io.DeserializationException;
19+
import io.jsonwebtoken.io.Deserializer;
20+
21+
import javax.json.bind.Jsonb;
22+
import javax.json.bind.JsonbException;
23+
import java.nio.charset.StandardCharsets;
24+
25+
import static java.util.Objects.requireNonNull;
26+
27+
/**
28+
* @since 0.10.0
29+
*/
30+
public class JsonbDeserializer<T> implements Deserializer<T> {
31+
32+
private final Class<T> returnType;
33+
private final Jsonb jsonb;
34+
35+
@SuppressWarnings("unused") //used via reflection by RuntimeClasspathDeserializerLocator
36+
public JsonbDeserializer() {
37+
this(JsonbSerializer.DEFAULT_JSONB);
38+
}
39+
40+
@SuppressWarnings({"unchecked", "WeakerAccess", "unused"}) // for end-users providing a custom ObjectMapper
41+
public JsonbDeserializer(Jsonb jsonb) {
42+
this(jsonb, (Class<T>) Object.class);
43+
}
44+
45+
private JsonbDeserializer(Jsonb jsonb, Class<T> returnType) {
46+
requireNonNull(jsonb, "ObjectMapper cannot be null.");
47+
requireNonNull(returnType, "Return type cannot be null.");
48+
this.jsonb = jsonb;
49+
this.returnType = returnType;
50+
}
51+
52+
@Override
53+
public T deserialize(byte[] bytes) throws DeserializationException {
54+
try {
55+
return readValue(bytes);
56+
} catch (JsonbException jsonbException) {
57+
String msg = "Unable to deserialize bytes into a " + returnType.getName() + " instance: " + jsonbException.getMessage();
58+
throw new DeserializationException(msg, jsonbException);
59+
}
60+
}
61+
62+
protected T readValue(byte[] bytes) {
63+
return jsonb.fromJson(new String(bytes, StandardCharsets.UTF_8), returnType);
64+
}
65+
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright (C) 2014 jsonwebtoken.io
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+
package io.jsonwebtoken.jsonb.io;
17+
18+
import io.jsonwebtoken.io.Encoders;
19+
import io.jsonwebtoken.io.SerializationException;
20+
import io.jsonwebtoken.io.Serializer;
21+
import io.jsonwebtoken.lang.Assert;
22+
23+
import javax.json.bind.Jsonb;
24+
import javax.json.bind.JsonbBuilder;
25+
import javax.json.bind.JsonbException;
26+
import java.nio.charset.StandardCharsets;
27+
28+
import static java.util.Objects.requireNonNull;
29+
30+
/**
31+
* @since 0.10.0
32+
*/
33+
public class JsonbSerializer<T> implements Serializer<T> {
34+
35+
static final Jsonb DEFAULT_JSONB = JsonbBuilder.create();
36+
37+
private final Jsonb jsonb;
38+
39+
@SuppressWarnings("unused") //used via reflection by RuntimeClasspathDeserializerLocator
40+
public JsonbSerializer() {
41+
this( DEFAULT_JSONB );
42+
}
43+
44+
@SuppressWarnings("WeakerAccess") //intended for end-users to use when providing a custom ObjectMapper
45+
public JsonbSerializer( Jsonb jsonb) {
46+
requireNonNull(jsonb, "Jsonb cannot be null.");
47+
this.jsonb = jsonb;
48+
}
49+
50+
@Override
51+
public byte[] serialize(T t) throws SerializationException {
52+
Assert.notNull(t, "Object to serialize cannot be null.");
53+
try {
54+
return writeValueAsBytes(t);
55+
} catch (JsonbException jsonbException) {
56+
String msg = "Unable to serialize object: " + jsonbException.getMessage();
57+
throw new SerializationException(msg, jsonbException);
58+
}
59+
}
60+
61+
@SuppressWarnings("WeakerAccess") //for testing
62+
protected byte[] writeValueAsBytes(T t) {
63+
final Object o;
64+
65+
if (t instanceof byte[]) {
66+
o = Encoders.BASE64.encode((byte[]) t);
67+
} else if (t instanceof char[]) {
68+
o = new String((char[]) t);
69+
} else {
70+
o = t;
71+
}
72+
73+
return this.jsonb.toJson(o).getBytes(StandardCharsets.UTF_8);
74+
}
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.jsonwebtoken.jsonb.io.JsonbDeserializer
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.jsonwebtoken.jsonb.io.JsonbSerializer
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package io.jsonwebtoken.jsonb.io
2+
3+
import io.jsonwebtoken.io.DeserializationException
4+
import io.jsonwebtoken.io.Deserializer
5+
import io.jsonwebtoken.lang.Strings
6+
import org.junit.Test
7+
8+
import javax.json.bind.JsonbBuilder
9+
10+
import static org.easymock.EasyMock.*
11+
import static org.hamcrest.CoreMatchers.instanceOf
12+
import static org.hamcrest.MatcherAssert.assertThat
13+
import static org.junit.Assert.*
14+
15+
class JsonbDeserializerTest {
16+
17+
@Test
18+
void loadService() {
19+
def deserializer = ServiceLoader.load(Deserializer).iterator().next()
20+
assertThat(deserializer, instanceOf(JsonbDeserializer))
21+
}
22+
23+
24+
@Test
25+
void testDefaultConstructor() {
26+
def deserializer = new JsonbDeserializer()
27+
assertNotNull deserializer.jsonb
28+
}
29+
30+
@Test
31+
void testObjectMapperConstructor() {
32+
def customJsonb = JsonbBuilder.create()
33+
def deserializer = new JsonbDeserializer(customJsonb)
34+
assertSame customJsonb, deserializer.jsonb
35+
}
36+
37+
@Test(expected = NullPointerException)
38+
void testObjectMapperConstructorWithNullArgument() {
39+
new JsonbDeserializer<>(null)
40+
}
41+
42+
@Test
43+
void testDeserialize() {
44+
byte[] serialized = '{"hello":"世界"}'.getBytes(Strings.UTF_8)
45+
def expected = [hello: '世界']
46+
def result = new JsonbDeserializer().deserialize(serialized)
47+
assertEquals expected, result
48+
}
49+
50+
@Test
51+
void testDeserializeFailsWithJsonProcessingException() {
52+
53+
def ex = createMock javax.json.bind.JsonbException
54+
55+
expect(ex.getMessage()).andReturn('foo')
56+
57+
def deserializer = new JsonbDeserializer() {
58+
@Override
59+
protected Object readValue(byte[] bytes) throws javax.json.bind.JsonbException {
60+
throw ex
61+
}
62+
}
63+
64+
replay ex
65+
66+
try {
67+
deserializer.deserialize('{"hello":"世界"}'.getBytes(Strings.UTF_8))
68+
fail()
69+
} catch (DeserializationException se) {
70+
assertEquals 'Unable to deserialize bytes into a java.lang.Object instance: foo', se.getMessage()
71+
assertSame ex, se.getCause()
72+
}
73+
74+
verify ex
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package io.jsonwebtoken.jsonb.io
2+
3+
import io.jsonwebtoken.io.SerializationException
4+
import io.jsonwebtoken.io.Serializer
5+
import io.jsonwebtoken.lang.Strings
6+
import org.junit.Test
7+
8+
import javax.json.bind.JsonbBuilder
9+
import javax.json.bind.JsonbException
10+
11+
import static org.easymock.EasyMock.*
12+
import static org.hamcrest.CoreMatchers.instanceOf
13+
import static org.hamcrest.MatcherAssert.assertThat
14+
import static org.junit.Assert.*
15+
16+
class JsonbSerializerTest {
17+
18+
@Test
19+
void loadService() {
20+
def serializer = ServiceLoader.load(Serializer).iterator().next()
21+
assertThat(serializer, instanceOf(JsonbSerializer))
22+
}
23+
24+
@Test
25+
void testDefaultConstructor() {
26+
def serializer = new JsonbSerializer()
27+
assertNotNull serializer.jsonb
28+
}
29+
30+
@Test
31+
void testObjectMapperConstructor() {
32+
def customJsonb = JsonbBuilder.create()
33+
def serializer = new JsonbSerializer<>(customJsonb)
34+
assertSame customJsonb, serializer.jsonb
35+
}
36+
37+
@Test(expected = NullPointerException)
38+
void testObjectMapperConstructorWithNullArgument() {
39+
new JsonbSerializer<>(null)
40+
}
41+
42+
@Test
43+
void testByte() {
44+
byte[] expected = "120".getBytes(Strings.UTF_8) //ascii("x") = 120
45+
byte[] bytes = "x".getBytes(Strings.UTF_8)
46+
byte[] result = new JsonbSerializer().serialize(bytes[0]) //single byte
47+
assertTrue Arrays.equals(expected, result)
48+
}
49+
50+
@Test
51+
void testByteArray() { //expect Base64 string by default:
52+
byte[] bytes = "hi".getBytes(Strings.UTF_8)
53+
String expected = '"aGk="' as String //base64(hi) --> aGk=
54+
byte[] result = new JsonbSerializer().serialize(bytes)
55+
assertEquals expected, new String(result, Strings.UTF_8)
56+
}
57+
58+
@Test
59+
void testEmptyByteArray() { //expect Base64 string by default:
60+
byte[] bytes = new byte[0]
61+
byte[] result = new JsonbSerializer().serialize(bytes)
62+
assertEquals '""', new String(result, Strings.UTF_8)
63+
}
64+
65+
@Test
66+
void testChar() { //expect Base64 string by default:
67+
byte[] result = new JsonbSerializer().serialize('h' as char)
68+
assertEquals "\"h\"", new String(result, Strings.UTF_8)
69+
}
70+
71+
@Test
72+
void testCharArray() { //expect Base64 string by default:
73+
byte[] result = new JsonbSerializer().serialize("hi".toCharArray())
74+
assertEquals "\"hi\"", new String(result, Strings.UTF_8)
75+
}
76+
77+
@Test
78+
void testSerialize() {
79+
byte[] expected = '{"hello":"世界"}'.getBytes(Strings.UTF_8)
80+
byte[] result = new JsonbSerializer().serialize([hello: '世界'])
81+
assertTrue Arrays.equals(expected, result)
82+
}
83+
84+
85+
@Test
86+
void testSerializeFailsWithJsonProcessingException() {
87+
88+
def ex = createMock(JsonbException)
89+
90+
expect(ex.getMessage()).andReturn('foo')
91+
92+
def serializer = new JsonbSerializer() {
93+
@Override
94+
protected byte[] writeValueAsBytes(Object o) throws JsonbException {
95+
throw ex
96+
}
97+
}
98+
99+
replay ex
100+
101+
try {
102+
serializer.serialize([hello: 'world'])
103+
fail()
104+
} catch (SerializationException se) {
105+
assertEquals 'Unable to serialize object: foo', se.getMessage()
106+
assertSame ex, se.getCause()
107+
}
108+
109+
verify ex
110+
}
111+
}

extensions/pom.xml

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,6 @@
3737
<module>jackson</module>
3838
<module>orgjson</module>
3939
<module>gson</module>
40+
<module>jsonb</module>
4041
</modules>
41-
</project>
42+
</project>

0 commit comments

Comments
 (0)