[742] Mandate usage of JSON-B instance provided by user's ContextResolver<Jsonb>: TCK Tests
Amending new Jakarta REST TCK by test for ContextResolver<Jsonb>.
Signed-off-by: Markus KARG <markus@headcrashing.eu>
diff --git a/jaxrs-tck/pom.xml b/jaxrs-tck/pom.xml
index 760511a..0182f32 100644
--- a/jaxrs-tck/pom.xml
+++ b/jaxrs-tck/pom.xml
@@ -162,6 +162,18 @@
</dependency>
<dependency>
+ <groupId>jakarta.json.bind</groupId>
+ <artifactId>jakarta.json.bind-api</artifactId>
+ <version>2.0.0</version>
+ </dependency>
+
+ <dependency>
+ <groupId>jakarta.json</groupId>
+ <artifactId>jakarta.json-api</artifactId>
+ <version>2.0.1</version>
+ </dependency>
+
+ <dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.jupiter.version}</version>
diff --git a/jaxrs-tck/src/main/java/jakarta/ws/rs/tck/contextprovider/JsonbContextProviderIT.java b/jaxrs-tck/src/main/java/jakarta/ws/rs/tck/contextprovider/JsonbContextProviderIT.java
new file mode 100644
index 0000000..aa2f195
--- /dev/null
+++ b/jaxrs-tck/src/main/java/jakarta/ws/rs/tck/contextprovider/JsonbContextProviderIT.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2021 Markus Karg. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package jakarta.ws.rs.tck.contextprovider;
+
+import static java.util.concurrent.TimeUnit.HOURS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import static jakarta.ws.rs.RuntimeType.CLIENT;
+import static jakarta.ws.rs.RuntimeType.SERVER;
+import static jakarta.ws.rs.SeBootstrap.Configuration.FREE_PORT;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
+
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.RuntimeType;
+import jakarta.ws.rs.SeBootstrap;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.UriBuilder;
+import jakarta.ws.rs.ext.ContextResolver;
+import jakarta.json.bind.Jsonb;
+import jakarta.json.bind.JsonbBuilder;
+import jakarta.json.bind.JsonbConfig;
+import jakarta.json.bind.serializer.JsonbDeserializer;
+import jakarta.json.bind.serializer.JsonbSerializer;
+import jakarta.json.bind.serializer.DeserializationContext;
+import jakarta.json.bind.serializer.SerializationContext;
+import jakarta.json.stream.JsonParser;
+import jakarta.json.stream.JsonGenerator;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+
+/**
+ * Compliance Test for Jsonb Context Provider of Jakarta REST API
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 3.1
+ */
+@Timeout(value = 1, unit = HOURS)
+public final class JsonbContextProviderIT {
+
+ /**
+ * Verifies that an implementation will use the {@link Jsonb} instance
+ * offered by an application-provided context resolver.
+ *
+ * @throws ExecutionException
+ * if the instance didn't boot correctly
+ * @throws InterruptedException
+ * if the test took much longer than usually expected
+ */
+ @Test
+ public final void shouldUseApplicationProvidedJsonbInstance() throws InterruptedException, ExecutionException {
+ // given
+ final Application application = new EchoApplication();
+ final UriBuilder baseUri = UriBuilder.newInstance().scheme("http").host("localhost").port(FREE_PORT);
+ final SeBootstrap.Configuration requestedConfiguration = SeBootstrap.Configuration.builder().build();
+ final CompletionStage<SeBootstrap.Instance> completionStage = SeBootstrap.start(application, requestedConfiguration);
+ final SeBootstrap.Instance instance = completionStage.toCompletableFuture().get();
+ final SeBootstrap.Configuration actualConfiguration = instance.configuration();
+ final int actualPort = actualConfiguration.port();
+ final UriBuilder effectiveUri = baseUri.port(actualPort).path("echo");
+
+ try (final Client client = ClientBuilder.newBuilder().register(new CustomJsonbProvider(CLIENT)).build()) {
+ // when
+ final String origin = String.format("Origin(%d)", mockInt());
+ final POJO requestPojo = new POJO();
+ requestPojo.setSeenBy(origin);
+ final POJO responsePojo = client.target(effectiveUri)
+ .request(APPLICATION_JSON_TYPE)
+ .buildPost(Entity.entity(requestPojo, APPLICATION_JSON_TYPE))
+ .invoke(POJO.class);
+
+ // then
+ final String expectedWaypoints = String.join(",", origin,
+ "CustomSerializer(CLIENT)",
+ "CustomDeserializer(SERVER)",
+ "EchoResource",
+ "CustomSerializer(SERVER)",
+ "CustomDeserializer(CLIENT)");
+ assertThat(responsePojo.getSeenBy(), is(expectedWaypoints));
+ }
+
+ instance.stop().toCompletableFuture().get();
+ }
+
+ public static final class CustomJsonbProvider implements ContextResolver<Jsonb> {
+ private final RuntimeType runtimeType;
+
+ private CustomJsonbProvider(final RuntimeType runtimeType) {
+ this.runtimeType = runtimeType;
+ }
+
+ public final Jsonb getContext(final Class<?> type) {
+ if (!POJO.class.isAssignableFrom(type))
+ return null;
+
+ return JsonbBuilder.create(new JsonbConfig().withSerializers(new CustomSerializer()).withDeserializers(new CustomDeserializer()));
+ }
+
+ private final class CustomSerializer implements JsonbSerializer<POJO> {
+ @Override
+ public final void serialize(final POJO pojo, final JsonGenerator generator, final SerializationContext ctx) {
+ generator.writeStartObject();
+ generator.write("seenBy", String.format("%s,CustomSerializer(%s)", pojo.getSeenBy(), CustomJsonbProvider.this.runtimeType));
+ generator.writeEnd();
+ }
+ }
+
+ private final class CustomDeserializer implements JsonbDeserializer<POJO> {
+ @Override
+ public final POJO deserialize(final JsonParser parser, final DeserializationContext ctx, final Type rtType) {
+ final POJO pojo = new POJO();
+ pojo.setSeenBy(String.format("%s,CustomDeserializer(%s)", parser.getObject().getString("seenBy"), CustomJsonbProvider.this.runtimeType));
+ return pojo;
+ }
+ }
+ }
+
+ public static final class POJO {
+ private String seenBy;
+
+ public final String getSeenBy() {
+ return this.seenBy;
+ }
+
+ public final void setSeenBy(final String seenBy) {
+ this.seenBy = seenBy;
+ }
+ }
+
+ private static final class EchoApplication extends Application {
+ @Override
+ public final Set<Class<?>> getClasses() {
+ return Collections.singleton(EchoResource.class);
+ }
+
+ @Override
+ public final Set<Object> getSingletons() {
+ return Collections.singleton(new CustomJsonbProvider(SERVER));
+ }
+
+ @Path("echo")
+ public static class EchoResource {
+ @POST
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ public POJO echo(final POJO pojo) {
+ pojo.setSeenBy(String.join(",", pojo.getSeenBy(), "EchoResource"));
+ return pojo;
+ }
+ }
+ }
+
+ private static final int mockInt() {
+ return (int) Math.round(Integer.MAX_VALUE * Math.random());
+ }
+}