Skip to content

Commit

Permalink
[jwtk#719] JSON-B extension.
Browse files Browse the repository at this point in the history
  • Loading branch information
bmarwell committed Apr 15, 2022
1 parent 20b0437 commit 06bbcd4
Show file tree
Hide file tree
Showing 10 changed files with 401 additions and 1 deletion.
1 change: 1 addition & 0 deletions extensions/jsonb/bnd.bnd
@@ -0,0 +1 @@
Fragment-Host: io.jsonwebtoken.jjwt-api
47 changes: 47 additions & 0 deletions extensions/jsonb/pom.xml
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-root</artifactId>
<version>0.11.3-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

<artifactId>jjwt-jsonb</artifactId>
<name>JJWT :: Extensions :: JSON-B</name>
<packaging>jar</packaging>

<properties>
<jjwt.root>${basedir}/../..</jjwt.root>
<!-- JSON-B uses static methods in interfaces. -->
<jdk.version>8</jdk.version>
</properties>

<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.json.bind</groupId>
<artifactId>jakarta.json.bind-api</artifactId>
<scope>provided</scope>
</dependency>

<!-- Test dependency -->
<dependency>
<groupId>org.apache.johnzon</groupId>
<artifactId>johnzon-jsonb</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,66 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.jsonb.io;

import io.jsonwebtoken.io.DeserializationException;
import io.jsonwebtoken.io.Deserializer;

import javax.json.bind.Jsonb;
import javax.json.bind.JsonbException;
import java.nio.charset.StandardCharsets;

import static java.util.Objects.requireNonNull;

/**
* @since 0.10.0
*/
public class JsonbDeserializer<T> implements Deserializer<T> {

private final Class<T> returnType;
private final Jsonb jsonb;

@SuppressWarnings("unused") //used via reflection by RuntimeClasspathDeserializerLocator
public JsonbDeserializer() {
this(JsonbSerializer.DEFAULT_JSONB);
}

@SuppressWarnings({"unchecked", "WeakerAccess", "unused"}) // for end-users providing a custom ObjectMapper
public JsonbDeserializer(Jsonb jsonb) {
this(jsonb, (Class<T>) Object.class);
}

private JsonbDeserializer(Jsonb jsonb, Class<T> returnType) {
requireNonNull(jsonb, "ObjectMapper cannot be null.");
requireNonNull(returnType, "Return type cannot be null.");
this.jsonb = jsonb;
this.returnType = returnType;
}

@Override
public T deserialize(byte[] bytes) throws DeserializationException {
try {
return readValue(bytes);
} catch (JsonbException jsonbException) {
String msg = "Unable to deserialize bytes into a " + returnType.getName() + " instance: " + jsonbException.getMessage();
throw new DeserializationException(msg, jsonbException);
}
}

protected T readValue(byte[] bytes) {
return jsonb.fromJson(new String(bytes, StandardCharsets.UTF_8), returnType);
}

}
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.jsonb.io;

import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.io.SerializationException;
import io.jsonwebtoken.io.Serializer;
import io.jsonwebtoken.lang.Assert;

import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbException;
import java.nio.charset.StandardCharsets;

import static java.util.Objects.requireNonNull;

/**
* @since 0.10.0
*/
public class JsonbSerializer<T> implements Serializer<T> {

static final Jsonb DEFAULT_JSONB = JsonbBuilder.create();

private final Jsonb jsonb;

@SuppressWarnings("unused") //used via reflection by RuntimeClasspathDeserializerLocator
public JsonbSerializer() {
this( DEFAULT_JSONB );
}

@SuppressWarnings("WeakerAccess") //intended for end-users to use when providing a custom ObjectMapper
public JsonbSerializer( Jsonb jsonb) {
requireNonNull(jsonb, "Jsonb cannot be null.");
this.jsonb = jsonb;
}

@Override
public byte[] serialize(T t) throws SerializationException {
Assert.notNull(t, "Object to serialize cannot be null.");
try {
return writeValueAsBytes(t);
} catch (JsonbException jsonbException) {
String msg = "Unable to serialize object: " + jsonbException.getMessage();
throw new SerializationException(msg, jsonbException);
}
}

@SuppressWarnings("WeakerAccess") //for testing
protected byte[] writeValueAsBytes(T t) {
final Object o;

if (t instanceof byte[]) {
o = Encoders.BASE64.encode((byte[]) t);
} else if (t instanceof char[]) {
o = new String((char[]) t);
} else {
o = t;
}

return this.jsonb.toJson(o).getBytes(StandardCharsets.UTF_8);
}
}
@@ -0,0 +1 @@
io.jsonwebtoken.jsonb.io.JsonbDeserializer
@@ -0,0 +1 @@
io.jsonwebtoken.jsonb.io.JsonbSerializer
@@ -0,0 +1,76 @@
package io.jsonwebtoken.jsonb.io

import io.jsonwebtoken.io.DeserializationException
import io.jsonwebtoken.io.Deserializer
import io.jsonwebtoken.lang.Strings
import org.junit.Test

import javax.json.bind.JsonbBuilder

import static org.easymock.EasyMock.*
import static org.hamcrest.CoreMatchers.instanceOf
import static org.hamcrest.MatcherAssert.assertThat
import static org.junit.Assert.*

class JsonbDeserializerTest {

@Test
void loadService() {
def deserializer = ServiceLoader.load(Deserializer).iterator().next()
assertThat(deserializer, instanceOf(JsonbDeserializer))
}


@Test
void testDefaultConstructor() {
def deserializer = new JsonbDeserializer()
assertNotNull deserializer.jsonb
}

@Test
void testObjectMapperConstructor() {
def customJsonb = JsonbBuilder.create()
def deserializer = new JsonbDeserializer(customJsonb)
assertSame customJsonb, deserializer.jsonb
}

@Test(expected = NullPointerException)
void testObjectMapperConstructorWithNullArgument() {
new JsonbDeserializer<>(null)
}

@Test
void testDeserialize() {
byte[] serialized = '{"hello":"世界"}'.getBytes(Strings.UTF_8)
def expected = [hello: '世界']
def result = new JsonbDeserializer().deserialize(serialized)
assertEquals expected, result
}

@Test
void testDeserializeFailsWithJsonProcessingException() {

def ex = createMock javax.json.bind.JsonbException

expect(ex.getMessage()).andReturn('foo')

def deserializer = new JsonbDeserializer() {
@Override
protected Object readValue(byte[] bytes) throws javax.json.bind.JsonbException {
throw ex
}
}

replay ex

try {
deserializer.deserialize('{"hello":"世界"}'.getBytes(Strings.UTF_8))
fail()
} catch (DeserializationException se) {
assertEquals 'Unable to deserialize bytes into a java.lang.Object instance: foo', se.getMessage()
assertSame ex, se.getCause()
}

verify ex
}
}
@@ -0,0 +1,111 @@
package io.jsonwebtoken.jsonb.io

import io.jsonwebtoken.io.SerializationException
import io.jsonwebtoken.io.Serializer
import io.jsonwebtoken.lang.Strings
import org.junit.Test

import javax.json.bind.JsonbBuilder
import javax.json.bind.JsonbException

import static org.easymock.EasyMock.*
import static org.hamcrest.CoreMatchers.instanceOf
import static org.hamcrest.MatcherAssert.assertThat
import static org.junit.Assert.*

class JsonbSerializerTest {

@Test
void loadService() {
def serializer = ServiceLoader.load(Serializer).iterator().next()
assertThat(serializer, instanceOf(JsonbSerializer))
}

@Test
void testDefaultConstructor() {
def serializer = new JsonbSerializer()
assertNotNull serializer.jsonb
}

@Test
void testObjectMapperConstructor() {
def customJsonb = JsonbBuilder.create()
def serializer = new JsonbSerializer<>(customJsonb)
assertSame customJsonb, serializer.jsonb
}

@Test(expected = NullPointerException)
void testObjectMapperConstructorWithNullArgument() {
new JsonbSerializer<>(null)
}

@Test
void testByte() {
byte[] expected = "120".getBytes(Strings.UTF_8) //ascii("x") = 120
byte[] bytes = "x".getBytes(Strings.UTF_8)
byte[] result = new JsonbSerializer().serialize(bytes[0]) //single byte
assertTrue Arrays.equals(expected, result)
}

@Test
void testByteArray() { //expect Base64 string by default:
byte[] bytes = "hi".getBytes(Strings.UTF_8)
String expected = '"aGk="' as String //base64(hi) --> aGk=
byte[] result = new JsonbSerializer().serialize(bytes)
assertEquals expected, new String(result, Strings.UTF_8)
}

@Test
void testEmptyByteArray() { //expect Base64 string by default:
byte[] bytes = new byte[0]
byte[] result = new JsonbSerializer().serialize(bytes)
assertEquals '""', new String(result, Strings.UTF_8)
}

@Test
void testChar() { //expect Base64 string by default:
byte[] result = new JsonbSerializer().serialize('h' as char)
assertEquals "\"h\"", new String(result, Strings.UTF_8)
}

@Test
void testCharArray() { //expect Base64 string by default:
byte[] result = new JsonbSerializer().serialize("hi".toCharArray())
assertEquals "\"hi\"", new String(result, Strings.UTF_8)
}

@Test
void testSerialize() {
byte[] expected = '{"hello":"世界"}'.getBytes(Strings.UTF_8)
byte[] result = new JsonbSerializer().serialize([hello: '世界'])
assertTrue Arrays.equals(expected, result)
}


@Test
void testSerializeFailsWithJsonProcessingException() {

def ex = createMock(JsonbException)

expect(ex.getMessage()).andReturn('foo')

def serializer = new JsonbSerializer() {
@Override
protected byte[] writeValueAsBytes(Object o) throws JsonbException {
throw ex
}
}

replay ex

try {
serializer.serialize([hello: 'world'])
fail()
} catch (SerializationException se) {
assertEquals 'Unable to serialize object: foo', se.getMessage()
assertSame ex, se.getCause()
}

verify ex
}
}
3 changes: 2 additions & 1 deletion extensions/pom.xml
Expand Up @@ -37,5 +37,6 @@
<module>jackson</module>
<module>orgjson</module>
<module>gson</module>
<module>jsonb</module>
</modules>
</project>
</project>

0 comments on commit 06bbcd4

Please sign in to comment.