From 0b1b1da3adc83a339d12c160a7fe9cdb4c068e16 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Mon, 15 Apr 2024 00:18:31 +0200 Subject: [PATCH 01/11] JCL-402: RDF4J body handlers http error handling --- .../client/rdf4j/RDF4JBodyHandlers.java | 108 +++++++++++++++--- .../client/rdf4j/RDF4JBodyHandlersTest.java | 70 ++++++++++-- 2 files changed, 151 insertions(+), 27 deletions(-) diff --git a/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java b/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java index 3273b22e477..d5635cf9edd 100644 --- a/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java +++ b/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java @@ -20,7 +20,11 @@ */ package com.inrupt.client.rdf4j; +import com.inrupt.client.ClientHttpException; +import com.inrupt.client.ProblemDetails; import com.inrupt.client.Response; +import com.inrupt.client.spi.JsonService; +import com.inrupt.client.spi.ServiceProvider; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -41,45 +45,119 @@ */ public final class RDF4JBodyHandlers { + private static JsonService jsonService; + private static boolean isJsonServiceInitialized = false; + + private static JsonService getJsonService() { + if (RDF4JBodyHandlers.isJsonServiceInitialized) { + return RDF4JBodyHandlers.jsonService; + } + // It is acceptable for a JenaBodyHandlers instance to be in a classpath without any implementation for + // JsonService, in which case the ProblemDetails exceptions will fallback to default and not be parsed. + JsonService js; + try { + js = ServiceProvider.getJsonService(); + } catch (IllegalStateException e) { + js = null; + } + RDF4JBodyHandlers.jsonService = js; + RDF4JBodyHandlers.isJsonServiceInitialized = true; + return RDF4JBodyHandlers.jsonService; + } + + private static Model responseToModel(final Response.ResponseInfo responseInfo) { + return responseInfo.headers().firstValue("Content-Type") + .map(RDF4JBodyHandlers::toRDF4JFormat).map(format -> { + try (final InputStream stream = new ByteArrayInputStream(responseInfo.body().array())) { + return Rio.parse(stream, responseInfo.uri().toString(), format); + } catch (final IOException ex) { + throw new UncheckedIOException( + "An I/O error occurred while data was read from the InputStream", ex); + } + }) + .orElseGet(() -> new DynamicModelFactory().createEmptyModel()); + } + /** * Populate a RDF4J {@link Model} with an HTTP response. * * @return an HTTP body handler + * @deprecated */ public static Response.BodyHandler ofModel() { - return responseInfo -> responseInfo.headers().firstValue("Content-Type") - .map(RDF4JBodyHandlers::toRDF4JFormat).map(format -> { - try (final InputStream stream = new ByteArrayInputStream(responseInfo.body().array())) { - return Rio.parse(stream, responseInfo.uri().toString(), format); - } catch (final IOException ex) { - throw new UncheckedIOException( - "An I/O error occurred while data was read from the InputStream", ex); - } - }) - .orElseGet(() -> new DynamicModelFactory().createEmptyModel()); + return RDF4JBodyHandlers::responseToModel; } /** - * Populate a RDF4J {@link Repository} with an HTTP response. + * Populate a RDF4J {@link Model} with an HTTP response. * * @return an HTTP body handler */ - public static Response.BodyHandler ofRepository() { - return responseInfo -> responseInfo.headers().firstValue("Content-Type") + public static Response.BodyHandler ofRDF4JModel() { + return responseInfo -> { + if (responseInfo.statusCode() >= 300) { + throw new ClientHttpException( + ProblemDetails.fromErrorResponse( + responseInfo.statusCode(), + responseInfo.headers(), + responseInfo.body().array(), + getJsonService() + ), + "Deserializing the RDF from " + responseInfo.uri() + " failed" + ); + } + return responseToModel(responseInfo); + }; + } + + private static Repository responseToRepository(final Response.ResponseInfo responseInfo) { + return responseInfo.headers().firstValue("Content-Type") .map(RDF4JBodyHandlers::toRDF4JFormat).map(format -> { final Repository repository = new SailRepository(new MemoryStore()); try (final InputStream stream = new ByteArrayInputStream(responseInfo.body().array()); - final RepositoryConnection conn = repository.getConnection()) { + final RepositoryConnection conn = repository.getConnection()) { conn.add(stream, responseInfo.uri().toString(), format); } catch (final IOException ex) { throw new UncheckedIOException( - "An I/O error occurred while data was read from the InputStream", ex); + "An I/O error occurred while data was read from the InputStream", ex); } return repository; }) .orElseGet(() -> new SailRepository(new MemoryStore())); } + /** + * Populate a RDF4J {@link Repository} with an HTTP response. + * + * @return an HTTP body handler + * @deprecated + */ + public static Response.BodyHandler ofRepository() { + return RDF4JBodyHandlers::responseToRepository; + } + + /** + * Populate a RDF4J {@link Repository} with an HTTP response. + * + * @return an HTTP body handler + */ + public static Response.BodyHandler ofRDF4JRepository() { + return responseInfo -> { + if (responseInfo.statusCode() >= 300) { + throw new ClientHttpException( + ProblemDetails.fromErrorResponse( + responseInfo.statusCode(), + responseInfo.headers(), + responseInfo.body().array(), + getJsonService() + ), + "Deserializing the RDF from " + responseInfo.uri() + " failed" + ); + } + return responseToRepository(responseInfo); + }; + } + static RDFFormat toRDF4JFormat(final String mediaType) { return Rio.getParserFormatForMIMEType(mediaType).orElse(RDFFormat.TURTLE); } diff --git a/rdf4j/src/test/java/com/inrupt/client/rdf4j/RDF4JBodyHandlersTest.java b/rdf4j/src/test/java/com/inrupt/client/rdf4j/RDF4JBodyHandlersTest.java index 668d42ba8ac..037892bb2b6 100644 --- a/rdf4j/src/test/java/com/inrupt/client/rdf4j/RDF4JBodyHandlersTest.java +++ b/rdf4j/src/test/java/com/inrupt/client/rdf4j/RDF4JBodyHandlersTest.java @@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.*; +import com.inrupt.client.ClientHttpException; import com.inrupt.client.Request; import com.inrupt.client.Response; import com.inrupt.client.spi.HttpService; @@ -32,6 +33,7 @@ import java.net.URI; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; @@ -61,14 +63,14 @@ static void teardown() { } @Test - void testOfModelHandlerAsync() throws IOException, + void testOfRDF4JModelHandlerAsync() throws IOException, InterruptedException, ExecutionException, TimeoutException { final Request request = Request.newBuilder() .uri(URI.create(config.get("rdf_uri") + "/oneTriple")) .GET() .build(); - final Response res = client.send(request, RDF4JBodyHandlers.ofModel()).toCompletableFuture().join(); + final Response res = client.send(request, RDF4JBodyHandlers.ofRDF4JModel()).toCompletableFuture().join(); final int statusCode = res.statusCode(); final Model responseBody = res.body(); @@ -84,14 +86,14 @@ void testOfModelHandlerAsync() throws IOException, } @Test - void testOfModelHandler() throws IOException, + void testOfRDF4JModelHandler() throws IOException, InterruptedException, ExecutionException, TimeoutException { final Request request = Request.newBuilder() .uri(URI.create(config.get("rdf_uri") + "/oneTriple")) .GET() .build(); - final Response response = client.send(request, RDF4JBodyHandlers.ofModel()) + final Response response = client.send(request, RDF4JBodyHandlers.ofRDF4JModel()) .toCompletableFuture().join(); assertEquals(200, response.statusCode()); @@ -106,13 +108,13 @@ void testOfModelHandler() throws IOException, } @Test - void testOfModelHandlerWithURL() throws IOException, InterruptedException { + void testOfRDF4JModelHandlerWithURL() throws IOException, InterruptedException { final Request request = Request.newBuilder() .uri(URI.create(config.get("rdf_uri") + "/example")) .GET() .build(); - final Response response = client.send(request, RDF4JBodyHandlers.ofModel()) + final Response response = client.send(request, RDF4JBodyHandlers.ofRDF4JModel()) .toCompletableFuture().join(); assertEquals(200, response.statusCode()); @@ -128,14 +130,36 @@ void testOfModelHandlerWithURL() throws IOException, InterruptedException { } @Test - void testOfRepositoryHandlerAsync() throws IOException, + void testOfRDF4JModelHandlerError() throws IOException, + InterruptedException, ExecutionException, TimeoutException { + final Request request = Request.newBuilder() + .uri(URI.create(config.get("rdf_uri") + "/error")) + .GET() + .build(); + + final CompletionException completionException = assertThrows( + CompletionException.class, + () -> client.send(request, RDF4JBodyHandlers.ofRDF4JModel()).toCompletableFuture().join() + ); + + final ClientHttpException httpException = (ClientHttpException) completionException.getCause(); + + assertEquals(429, httpException.getProblemDetails().getStatus()); + assertEquals("Too Many Requests", httpException.getProblemDetails().getTitle()); + assertEquals("Some details", httpException.getProblemDetails().getDetails()); + assertEquals("https://example.org/type", httpException.getProblemDetails().getType().toString()); + assertEquals("https://example.org/instance", httpException.getProblemDetails().getInstance().toString()); + } + + @Test + void testOfRDF4JRepositoryHandlerAsync() throws IOException, InterruptedException, ExecutionException, TimeoutException { final Request request = Request.newBuilder() .uri(URI.create(config.get("rdf_uri") + "/oneTriple")) .GET() .build(); - final Response res = client.send(request, RDF4JBodyHandlers.ofRepository()) + final Response res = client.send(request, RDF4JBodyHandlers.ofRDF4JRepository()) .toCompletableFuture().join(); final int statusCode = res.statusCode(); @@ -155,14 +179,14 @@ void testOfRepositoryHandlerAsync() throws IOException, } @Test - void testOfRepositoryHandler() throws IOException, + void testOfRDF4JRepositoryHandler() throws IOException, InterruptedException, ExecutionException, TimeoutException { final Request request = Request.newBuilder() .uri(URI.create(config.get("rdf_uri") + "/oneTriple")) .GET() .build(); - final Response response = client.send(request, RDF4JBodyHandlers.ofRepository()) + final Response response = client.send(request, RDF4JBodyHandlers.ofRDF4JRepository()) .toCompletableFuture().join(); assertEquals(200, response.statusCode()); @@ -180,13 +204,13 @@ void testOfRepositoryHandler() throws IOException, } @Test - void testOfRepositoryHandlerWithURL() throws IOException, InterruptedException { + void testOfRDF4JRepositoryHandlerWithURL() throws IOException, InterruptedException { final Request request = Request.newBuilder() .uri(URI.create(config.get("rdf_uri") + "/example")) .GET() .build(); - final Response response = client.send(request, RDF4JBodyHandlers.ofRepository()) + final Response response = client.send(request, RDF4JBodyHandlers.ofRDF4JRepository()) .toCompletableFuture().join(); assertEquals(200, response.statusCode()); @@ -202,4 +226,26 @@ void testOfRepositoryHandlerWithURL() throws IOException, InterruptedException { ); } } + + @Test + void testOfRDF4JRepositoryHandlerError() throws IOException, + InterruptedException, ExecutionException, TimeoutException { + final Request request = Request.newBuilder() + .uri(URI.create(config.get("rdf_uri") + "/error")) + .GET() + .build(); + + final CompletionException completionException = assertThrows( + CompletionException.class, + () -> client.send(request, RDF4JBodyHandlers.ofRDF4JRepository()).toCompletableFuture().join() + ); + + final ClientHttpException httpException = (ClientHttpException) completionException.getCause(); + + assertEquals(429, httpException.getProblemDetails().getStatus()); + assertEquals("Too Many Requests", httpException.getProblemDetails().getTitle()); + assertEquals("Some details", httpException.getProblemDetails().getDetails()); + assertEquals("https://example.org/type", httpException.getProblemDetails().getType().toString()); + assertEquals("https://example.org/instance", httpException.getProblemDetails().getInstance().toString()); + } } From 7d27d85a50bdafd070e6fa833e317ab2d8cabe31 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Wed, 17 Apr 2024 10:09:42 +0200 Subject: [PATCH 02/11] Use throwing body handler --- .../client/rdf4j/RDF4JBodyHandlers.java | 56 ++++--------------- 1 file changed, 10 insertions(+), 46 deletions(-) diff --git a/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java b/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java index d5635cf9edd..c01d8b84e2d 100644 --- a/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java +++ b/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java @@ -45,24 +45,8 @@ */ public final class RDF4JBodyHandlers { - private static JsonService jsonService; - private static boolean isJsonServiceInitialized = false; - - private static JsonService getJsonService() { - if (RDF4JBodyHandlers.isJsonServiceInitialized) { - return RDF4JBodyHandlers.jsonService; - } - // It is acceptable for a JenaBodyHandlers instance to be in a classpath without any implementation for - // JsonService, in which case the ProblemDetails exceptions will fallback to default and not be parsed. - JsonService js; - try { - js = ServiceProvider.getJsonService(); - } catch (IllegalStateException e) { - js = null; - } - RDF4JBodyHandlers.jsonService = js; - RDF4JBodyHandlers.isJsonServiceInitialized = true; - return RDF4JBodyHandlers.jsonService; + private static Boolean isSuccess(final Response.ResponseInfo responseInfo) { + return responseInfo.statusCode() < 300; } private static Model responseToModel(final Response.ResponseInfo responseInfo) { @@ -94,20 +78,10 @@ public static Response.BodyHandler ofModel() { * @return an HTTP body handler */ public static Response.BodyHandler ofRDF4JModel() { - return responseInfo -> { - if (responseInfo.statusCode() >= 300) { - throw new ClientHttpException( - ProblemDetails.fromErrorResponse( - responseInfo.statusCode(), - responseInfo.headers(), - responseInfo.body().array(), - getJsonService() - ), - "Deserializing the RDF from " + responseInfo.uri() + " failed" - ); - } - return responseToModel(responseInfo); - }; + return Response.BodyHandlers.throwOnError( + RDF4JBodyHandlers::responseToModel, + RDF4JBodyHandlers::isSuccess + ); } private static Repository responseToRepository(final Response.ResponseInfo responseInfo) { @@ -142,20 +116,10 @@ public static Response.BodyHandler ofRepository() { * @return an HTTP body handler */ public static Response.BodyHandler ofRDF4JRepository() { - return responseInfo -> { - if (responseInfo.statusCode() >= 300) { - throw new ClientHttpException( - ProblemDetails.fromErrorResponse( - responseInfo.statusCode(), - responseInfo.headers(), - responseInfo.body().array(), - getJsonService() - ), - "Deserializing the RDF from " + responseInfo.uri() + " failed" - ); - } - return responseToRepository(responseInfo); - }; + return Response.BodyHandlers.throwOnError( + RDF4JBodyHandlers::responseToRepository, + RDF4JBodyHandlers::isSuccess + ); } static RDFFormat toRDF4JFormat(final String mediaType) { From 47509841f869b723998d204d3cd1e1216332e95c Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Wed, 17 Apr 2024 10:10:39 +0200 Subject: [PATCH 03/11] fixup! Use throwing body handler --- .../main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java b/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java index c01d8b84e2d..5ae6aa0fcb9 100644 --- a/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java +++ b/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java @@ -20,11 +20,7 @@ */ package com.inrupt.client.rdf4j; -import com.inrupt.client.ClientHttpException; -import com.inrupt.client.ProblemDetails; import com.inrupt.client.Response; -import com.inrupt.client.spi.JsonService; -import com.inrupt.client.spi.ServiceProvider; import java.io.ByteArrayInputStream; import java.io.IOException; From be500bf5b181fc28c7a753113cf6a9e2afaa5af3 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Thu, 18 Apr 2024 14:46:40 +0200 Subject: [PATCH 04/11] Use shared isSuccess --- .../java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java b/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java index 5ae6aa0fcb9..d77c9d50140 100644 --- a/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java +++ b/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java @@ -41,10 +41,6 @@ */ public final class RDF4JBodyHandlers { - private static Boolean isSuccess(final Response.ResponseInfo responseInfo) { - return responseInfo.statusCode() < 300; - } - private static Model responseToModel(final Response.ResponseInfo responseInfo) { return responseInfo.headers().firstValue("Content-Type") .map(RDF4JBodyHandlers::toRDF4JFormat).map(format -> { @@ -76,7 +72,7 @@ public static Response.BodyHandler ofModel() { public static Response.BodyHandler ofRDF4JModel() { return Response.BodyHandlers.throwOnError( RDF4JBodyHandlers::responseToModel, - RDF4JBodyHandlers::isSuccess + (r) -> Response.isSuccess(r.statusCode()) ); } @@ -114,7 +110,7 @@ public static Response.BodyHandler ofRepository() { public static Response.BodyHandler ofRDF4JRepository() { return Response.BodyHandlers.throwOnError( RDF4JBodyHandlers::responseToRepository, - RDF4JBodyHandlers::isSuccess + (r) -> Response.isSuccess(r.statusCode()) ); } From 9f648494e05958f6fb72309fe1739942686ffa8c Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 23 Apr 2024 09:52:02 +0200 Subject: [PATCH 05/11] Drop usage of throwOnError body handler --- .../client/rdf4j/RDF4JBodyHandlers.java | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java b/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java index d77c9d50140..2a48a79452e 100644 --- a/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java +++ b/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java @@ -20,12 +20,14 @@ */ package com.inrupt.client.rdf4j; +import com.inrupt.client.ClientHttpException; import com.inrupt.client.Response; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import org.eclipse.rdf4j.model.Model; import org.eclipse.rdf4j.model.impl.DynamicModelFactory; @@ -41,6 +43,18 @@ */ public final class RDF4JBodyHandlers { + private static void throwOnError(final Response.ResponseInfo responseInfo) { + if (!Response.isSuccess(responseInfo.statusCode())) { + throw new ClientHttpException( + "Could not map to a Jena entity.", + responseInfo.uri(), + responseInfo.statusCode(), + responseInfo.headers(), + new String(responseInfo.body().array(), StandardCharsets.UTF_8) + ); + } + } + private static Model responseToModel(final Response.ResponseInfo responseInfo) { return responseInfo.headers().firstValue("Content-Type") .map(RDF4JBodyHandlers::toRDF4JFormat).map(format -> { @@ -70,10 +84,10 @@ public static Response.BodyHandler ofModel() { * @return an HTTP body handler */ public static Response.BodyHandler ofRDF4JModel() { - return Response.BodyHandlers.throwOnError( - RDF4JBodyHandlers::responseToModel, - (r) -> Response.isSuccess(r.statusCode()) - ); + return responseInfo -> { + RDF4JBodyHandlers.throwOnError(responseInfo); + return RDF4JBodyHandlers.responseToModel(responseInfo); + }; } private static Repository responseToRepository(final Response.ResponseInfo responseInfo) { @@ -108,10 +122,10 @@ public static Response.BodyHandler ofRepository() { * @return an HTTP body handler */ public static Response.BodyHandler ofRDF4JRepository() { - return Response.BodyHandlers.throwOnError( - RDF4JBodyHandlers::responseToRepository, - (r) -> Response.isSuccess(r.statusCode()) - ); + return responseInfo -> { + RDF4JBodyHandlers.throwOnError(responseInfo); + return RDF4JBodyHandlers.responseToRepository(responseInfo); + }; } static RDFFormat toRDF4JFormat(final String mediaType) { From d0a00034dd903b993ee08b2247942ca6c81d43d0 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Tue, 23 Apr 2024 10:37:54 +0200 Subject: [PATCH 06/11] Enhance deprecation Javadoc entry --- .../main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java b/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java index 2a48a79452e..079807950c6 100644 --- a/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java +++ b/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java @@ -72,7 +72,7 @@ private static Model responseToModel(final Response.ResponseInfo responseInfo) { * Populate a RDF4J {@link Model} with an HTTP response. * * @return an HTTP body handler - * @deprecated + * @deprecated Use {@link RDF4JBodyHandlers#ofRDF4JModel} instead for consistent HTTP error handling. */ public static Response.BodyHandler ofModel() { return RDF4JBodyHandlers::responseToModel; @@ -110,7 +110,7 @@ private static Repository responseToRepository(final Response.ResponseInfo respo * Populate a RDF4J {@link Repository} with an HTTP response. * * @return an HTTP body handler - * @deprecated + * @deprecated Use {@link RDF4JBodyHandlers#ofRDF4JRepository} instead for consistent HTTP error handling. */ public static Response.BodyHandler ofRepository() { return RDF4JBodyHandlers::responseToRepository; From 930db97786a0a40401cf0de3fbc8645e8178e187 Mon Sep 17 00:00:00 2001 From: Zwifi Date: Thu, 25 Apr 2024 12:11:51 +0200 Subject: [PATCH 07/11] Update rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java --- .../main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java b/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java index 079807950c6..35186b7548d 100644 --- a/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java +++ b/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java @@ -46,7 +46,7 @@ public final class RDF4JBodyHandlers { private static void throwOnError(final Response.ResponseInfo responseInfo) { if (!Response.isSuccess(responseInfo.statusCode())) { throw new ClientHttpException( - "Could not map to a Jena entity.", + "Could not map to an RDF4J entity.", responseInfo.uri(), responseInfo.statusCode(), responseInfo.headers(), From da6872d97c24f2c71ae9b4d0ae1e398935df46da Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Thu, 25 Apr 2024 14:29:41 +0200 Subject: [PATCH 08/11] Unit test deprecated functions --- .../client/rdf4j/RDF4JBodyHandlersTest.java | 181 +++++++++++++++++- 1 file changed, 174 insertions(+), 7 deletions(-) diff --git a/rdf4j/src/test/java/com/inrupt/client/rdf4j/RDF4JBodyHandlersTest.java b/rdf4j/src/test/java/com/inrupt/client/rdf4j/RDF4JBodyHandlersTest.java index 037892bb2b6..e1d485dc8ef 100644 --- a/rdf4j/src/test/java/com/inrupt/client/rdf4j/RDF4JBodyHandlersTest.java +++ b/rdf4j/src/test/java/com/inrupt/client/rdf4j/RDF4JBodyHandlersTest.java @@ -85,6 +85,33 @@ void testOfRDF4JModelHandlerAsync() throws IOException, ); } + /** + * @deprecated covers the deprecated RDF4JBodyHandlers::ofModel function. To be removed when removing the function + * from the API. + */ + @Test + void testOfModelHandlerAsync() throws IOException, + InterruptedException, ExecutionException, TimeoutException { + final Request request = Request.newBuilder() + .uri(URI.create(config.get("rdf_uri") + "/oneTriple")) + .GET() + .build(); + + final Response res = client.send(request, RDF4JBodyHandlers.ofModel()).toCompletableFuture().join(); + + final int statusCode = res.statusCode(); + final Model responseBody = res.body(); + + assertEquals(200, statusCode); + assertEquals(1, responseBody.size()); + assertTrue(responseBody.contains( + (Resource)SimpleValueFactory.getInstance().createIRI("http://example.test/s"), + null, + null, + (Resource)null) + ); + } + @Test void testOfRDF4JModelHandler() throws IOException, InterruptedException, ExecutionException, TimeoutException { @@ -107,6 +134,32 @@ void testOfRDF4JModelHandler() throws IOException, ); } + /** + * @deprecated covers the deprecated RDF4JBodyHandlers::ofModel function. To be removed when removing the function + * from the API. + */ + @Test + void testOfModelHandler() throws IOException, + InterruptedException, ExecutionException, TimeoutException { + final Request request = Request.newBuilder() + .uri(URI.create(config.get("rdf_uri") + "/oneTriple")) + .GET() + .build(); + + final Response response = client.send(request, RDF4JBodyHandlers.ofModel()) + .toCompletableFuture().join(); + + assertEquals(200, response.statusCode()); + final Model responseBody = response.body(); + assertEquals(1, responseBody.size()); + assertTrue(responseBody.contains( + (Resource)SimpleValueFactory.getInstance().createIRI("http://example.test/s"), + null, + null, + (Resource)null) + ); + } + @Test void testOfRDF4JModelHandlerWithURL() throws IOException, InterruptedException { final Request request = Request.newBuilder() @@ -129,6 +182,32 @@ void testOfRDF4JModelHandlerWithURL() throws IOException, InterruptedException { ); } + /** + * @deprecated covers the deprecated RDF4JBodyHandlers::ofModel function. To be removed when removing the function + * from the API. + */ + @Test + void testOfModelHandlerWithURL() throws IOException, InterruptedException { + final Request request = Request.newBuilder() + .uri(URI.create(config.get("rdf_uri") + "/example")) + .GET() + .build(); + + final Response response = client.send(request, RDF4JBodyHandlers.ofModel()) + .toCompletableFuture().join(); + + assertEquals(200, response.statusCode()); + final Model responseBody = response.body(); + assertEquals(7, responseBody.size()); + assertTrue(responseBody.contains( + null, + SimpleValueFactory.getInstance().createIRI("http://www.w3.org/ns/pim/space#preferencesFile"), + null, + (Resource)null + ) + ); + } + @Test void testOfRDF4JModelHandlerError() throws IOException, InterruptedException, ExecutionException, TimeoutException { @@ -160,7 +239,7 @@ void testOfRDF4JRepositoryHandlerAsync() throws IOException, .build(); final Response res = client.send(request, RDF4JBodyHandlers.ofRDF4JRepository()) - .toCompletableFuture().join(); + .toCompletableFuture().join(); final int statusCode = res.statusCode(); assertEquals(200, statusCode); @@ -168,12 +247,43 @@ void testOfRDF4JRepositoryHandlerAsync() throws IOException, final Repository responseBody = res.body(); try (final RepositoryConnection conn = responseBody.getConnection()) { assertTrue(conn.hasStatement( - (Resource)SimpleValueFactory.getInstance().createIRI("http://example.test/s"), - null, - null, - false, - (Resource)null - ) + (Resource)SimpleValueFactory.getInstance().createIRI("http://example.test/s"), + null, + null, + false, + (Resource)null + ) + ); + } + } + + /** + * @deprecated covers the deprecated RDF4JBodyHandlers::ofRepository function. + * To be removed when removing the function from the API. + */ + @Test + void testOfRepositoryHandlerAsync() throws IOException, + InterruptedException, ExecutionException, TimeoutException { + final Request request = Request.newBuilder() + .uri(URI.create(config.get("rdf_uri") + "/oneTriple")) + .GET() + .build(); + + final Response res = client.send(request, RDF4JBodyHandlers.ofRepository()) + .toCompletableFuture().join(); + + final int statusCode = res.statusCode(); + assertEquals(200, statusCode); + + final Repository responseBody = res.body(); + try (final RepositoryConnection conn = responseBody.getConnection()) { + assertTrue(conn.hasStatement( + (Resource)SimpleValueFactory.getInstance().createIRI("http://example.test/s"), + null, + null, + false, + (Resource)null + ) ); } } @@ -203,6 +313,35 @@ void testOfRDF4JRepositoryHandler() throws IOException, } } + /** + * @deprecated covers the deprecated RDF4JBodyHandlers::ofRepository function. + * To be removed when removing the function from the API. + */ + @Test + void testOfRepositoryHandler() throws IOException, + InterruptedException, ExecutionException, TimeoutException { + final Request request = Request.newBuilder() + .uri(URI.create(config.get("rdf_uri") + "/oneTriple")) + .GET() + .build(); + + final Response response = client.send(request, RDF4JBodyHandlers.ofRepository()) + .toCompletableFuture().join(); + assertEquals(200, response.statusCode()); + + final Repository responseBody = response.body(); + try (final RepositoryConnection conn = responseBody.getConnection()) { + assertTrue(conn.hasStatement( + (Resource)SimpleValueFactory.getInstance().createIRI("http://example.test/s"), + null, + null, + false, + (Resource)null + ) + ); + } + } + @Test void testOfRDF4JRepositoryHandlerWithURL() throws IOException, InterruptedException { final Request request = Request.newBuilder() @@ -227,6 +366,34 @@ void testOfRDF4JRepositoryHandlerWithURL() throws IOException, InterruptedExcept } } + /** + * @deprecated covers the deprecated RDF4JBodyHandlers::ofRepository function. + * To be removed when removing the function from the API. + */ + @Test + void testOfRepositoryHandlerWithURL() throws IOException, InterruptedException { + final Request request = Request.newBuilder() + .uri(URI.create(config.get("rdf_uri") + "/example")) + .GET() + .build(); + + final Response response = client.send(request, RDF4JBodyHandlers.ofRepository()) + .toCompletableFuture().join(); + assertEquals(200, response.statusCode()); + + final Repository responseBody = response.body(); + try (final RepositoryConnection conn = responseBody.getConnection()) { + assertTrue(conn.hasStatement( + null, + SimpleValueFactory.getInstance().createIRI("http://www.w3.org/ns/pim/space#preferencesFile"), + null, + false, + (Resource)null + ) + ); + } + } + @Test void testOfRDF4JRepositoryHandlerError() throws IOException, InterruptedException, ExecutionException, TimeoutException { From ce0d912b5deecfced3120494cbb5521e02e07aab Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Thu, 25 Apr 2024 14:41:41 +0200 Subject: [PATCH 09/11] Unit test deprecated functions in jena module --- .../client/jena/JenaBodyHandlersTest.java | 212 +++++++++++++++++- 1 file changed, 207 insertions(+), 5 deletions(-) diff --git a/jena/src/test/java/com/inrupt/client/jena/JenaBodyHandlersTest.java b/jena/src/test/java/com/inrupt/client/jena/JenaBodyHandlersTest.java index 712541142b2..ee87d0e0e92 100644 --- a/jena/src/test/java/com/inrupt/client/jena/JenaBodyHandlersTest.java +++ b/jena/src/test/java/com/inrupt/client/jena/JenaBodyHandlersTest.java @@ -79,6 +79,31 @@ void testOfJenaModelHandler() throws IOException, ); } + /** + * @deprecated covers the deprecated JenaBodyHandlers::ofModel function. To be removed when removing the function + * from the API. + */ + @Test + void testOfModelHandler() throws IOException, + InterruptedException { + final Request request = Request.newBuilder() + .uri(URI.create(config.get("rdf_uri") + "/oneTriple")) + .GET() + .build(); + + final var response = client.send(request, JenaBodyHandlers.ofModel()) + .toCompletableFuture().join(); + + assertEquals(200, response.statusCode()); + final var responseBody = response.body(); + assertEquals(1, responseBody.size()); + assertTrue(responseBody.contains( + null, + null, + ResourceFactory.createResource("http://example.test/o")) + ); + } + @Test void testOfJenaModelHandlerAsync() throws IOException, InterruptedException, ExecutionException { @@ -102,6 +127,33 @@ void testOfJenaModelHandlerAsync() throws IOException, ); } + /** + * @deprecated covers the deprecated JenaBodyHandlers::ofModel function. To be removed when removing the function + * from the API. + */ + @Test + void testOfModelHandlerAsync() throws IOException, + InterruptedException, ExecutionException { + final Request request = Request.newBuilder() + .uri(URI.create(config.get("rdf_uri") + "/oneTriple")) + .header("Accept", "text/turtle") + .GET() + .build(); + + final var asyncResponse = client.send(request, JenaBodyHandlers.ofModel()); + + final int statusCode = asyncResponse.thenApply(Response::statusCode).toCompletableFuture().join(); + assertEquals(200, statusCode); + + final var responseBody = asyncResponse.thenApply(Response::body).toCompletableFuture().join(); + assertEquals(1, responseBody.size()); + assertTrue(responseBody.contains( + null, + null, + ResourceFactory.createResource("http://example.test/o")) + ); + } + @Test void testOfJenaModelHandlerWithURL() throws IOException, InterruptedException { final Request request = Request.newBuilder() @@ -121,6 +173,29 @@ void testOfJenaModelHandlerWithURL() throws IOException, InterruptedException { ); } + /** + * @deprecated covers the deprecated JenaBodyHandlers::ofModel function. To be removed when removing the function + * from the API. + */ + @Test + void testOfModelHandlerWithURL() throws IOException, InterruptedException { + final Request request = Request.newBuilder() + .uri(URI.create(config.get("rdf_uri") + "/example")) + .GET() + .build(); + + final var response = client.send(request, JenaBodyHandlers.ofModel()) + .toCompletableFuture().join(); + + assertEquals(200, response.statusCode()); + final var responseBody = response.body(); + assertEquals(7, responseBody.size()); + assertTrue(responseBody.contains( + null, + ResourceFactory.createProperty("http://www.w3.org/ns/pim/space#preferencesFile")) + ); + } + @Test void testOfJenaModelHandlerError() throws IOException, InterruptedException { @@ -152,16 +227,42 @@ void testOfJenaDatasetHandler() throws IOException, .build(); final var response = client.send(request, JenaBodyHandlers.ofJenaDataset()) - .toCompletableFuture().join(); + .toCompletableFuture().join(); assertEquals(200, response.statusCode()); final var responseBody = response.body(); assertEquals(1, responseBody.asDatasetGraph().stream().count()); assertTrue(responseBody.asDatasetGraph().contains( - null, - NodeFactory.createURI("http://example.test/s"), - null, - null) + null, + NodeFactory.createURI("http://example.test/s"), + null, + null) + ); + } + + /** + * @deprecated covers the deprecated JenaBodyHandlers::ofDataset function. To be removed when removing the function + * from the API. + */ + @Test + void testOfDatasetHandler() throws IOException, + InterruptedException { + final Request request = Request.newBuilder() + .uri(URI.create(config.get("rdf_uri") + "/oneTriple")) + .GET() + .build(); + + final var response = client.send(request, JenaBodyHandlers.ofDataset()) + .toCompletableFuture().join(); + + assertEquals(200, response.statusCode()); + final var responseBody = response.body(); + assertEquals(1, responseBody.asDatasetGraph().stream().count()); + assertTrue(responseBody.asDatasetGraph().contains( + null, + NodeFactory.createURI("http://example.test/s"), + null, + null) ); } @@ -186,6 +287,31 @@ void testOfJenaDatasetHandlerWithURL() throws IOException, InterruptedException ); } + /** + * @deprecated covers the deprecated JenaBodyHandlers::ofDataset function. To be removed when removing the function + * from the API. + */ + @Test + void testOfDatasetHandlerWithURL() throws IOException, InterruptedException { + final Request request = Request.newBuilder() + .uri(URI.create(config.get("rdf_uri") + "/example")) + .GET() + .build(); + + final var response = client.send(request, JenaBodyHandlers.ofDataset()) + .toCompletableFuture().join(); + + assertEquals(200, response.statusCode()); + final var responseBody = response.body(); + assertEquals(7, responseBody.asDatasetGraph().stream().count()); + assertTrue(responseBody.asDatasetGraph().contains( + null, + null, + NodeFactory.createURI("http://www.w3.org/ns/pim/space#preferencesFile"), + null) + ); + } + @Test void testOfJenaDatasetHandlerError() throws IOException, InterruptedException { @@ -231,6 +357,33 @@ void testOfJenaGraphHandlerAsync() throws IOException, ); } + /** + * @deprecated covers the deprecated JenaBodyHandlers::ofGraph function. To be removed when removing the function + * from the API. + */ + @Test + void testOfGraphHandlerAsync() throws IOException, + InterruptedException, ExecutionException { + final Request request = Request.newBuilder() + .uri(URI.create(config.get("rdf_uri") + "/oneTriple")) + .header("Accept", "text/turtle") + .GET() + .build(); + + final var asyncResponse = client.send(request, JenaBodyHandlers.ofGraph()); + + final int statusCode = asyncResponse.thenApply(Response::statusCode).toCompletableFuture().join(); + assertEquals(200, statusCode); + + final var responseBody = asyncResponse.thenApply(Response::body).toCompletableFuture().join(); + assertEquals(1, responseBody.size()); + assertTrue(responseBody.contains( + NodeFactory.createURI("http://example.test/s"), + null, + null) + ); + } + @Test void testOfJenaGraphHandler() throws IOException, InterruptedException { @@ -252,6 +405,31 @@ void testOfJenaGraphHandler() throws IOException, ); } + /** + * @deprecated covers the deprecated JenaBodyHandlers::ofGraph function. To be removed when removing the function + * from the API. + */ + @Test + void testOfGraphHandler() throws IOException, + InterruptedException { + final Request request = Request.newBuilder() + .uri(URI.create(config.get("rdf_uri") + "/oneTriple")) + .GET() + .build(); + + final var response = client.send(request, JenaBodyHandlers.ofGraph()) + .toCompletableFuture().join(); + + assertEquals(200, response.statusCode()); + final var responseBody = response.body(); + assertEquals(1, responseBody.size()); + assertTrue(responseBody.contains( + NodeFactory.createURI("http://example.test/s"), + null, + null) + ); + } + @Test void testOfJenaGraphHandlerWithURL() throws IOException, InterruptedException { final Request request = Request.newBuilder() @@ -272,6 +450,30 @@ void testOfJenaGraphHandlerWithURL() throws IOException, InterruptedException { ); } + /** + * @deprecated covers the deprecated JenaBodyHandlers::ofGraph function. To be removed when removing the function + * from the API. + */ + @Test + void testOfGraphHandlerWithURL() throws IOException, InterruptedException { + final Request request = Request.newBuilder() + .uri(URI.create(config.get("rdf_uri") + "/example")) + .GET() + .build(); + + final var response = client.send(request, JenaBodyHandlers.ofGraph()) + .toCompletableFuture().join(); + + assertEquals(200, response.statusCode()); + final var responseBody = response.body(); + assertEquals(7, responseBody.size()); + assertTrue(responseBody.contains( + null, + NodeFactory.createURI("http://www.w3.org/ns/pim/space#preferencesFile"), + null) + ); + } + @Test void testOfJenaGraphHandlerError() throws IOException, InterruptedException { From bcca12a4a433b286fad995522c27f8d883214798 Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Fri, 26 Apr 2024 11:44:14 +0200 Subject: [PATCH 10/11] Lint --- .../inrupt/client/rdf4j/RDF4JBodyHandlersTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rdf4j/src/test/java/com/inrupt/client/rdf4j/RDF4JBodyHandlersTest.java b/rdf4j/src/test/java/com/inrupt/client/rdf4j/RDF4JBodyHandlersTest.java index e1d485dc8ef..600688acfcf 100644 --- a/rdf4j/src/test/java/com/inrupt/client/rdf4j/RDF4JBodyHandlersTest.java +++ b/rdf4j/src/test/java/com/inrupt/client/rdf4j/RDF4JBodyHandlersTest.java @@ -384,12 +384,12 @@ void testOfRepositoryHandlerWithURL() throws IOException, InterruptedException { final Repository responseBody = response.body(); try (final RepositoryConnection conn = responseBody.getConnection()) { assertTrue(conn.hasStatement( - null, - SimpleValueFactory.getInstance().createIRI("http://www.w3.org/ns/pim/space#preferencesFile"), - null, - false, - (Resource)null - ) + null, + SimpleValueFactory.getInstance().createIRI("http://www.w3.org/ns/pim/space#preferencesFile"), + null, + false, + (Resource)null + ) ); } } From 475adc0481ce65c9a8e34c9f3de1e878ac61e4ce Mon Sep 17 00:00:00 2001 From: Nicolas Ayral Seydoux Date: Fri, 26 Apr 2024 15:16:40 +0200 Subject: [PATCH 11/11] Improve RDF body mappers error message --- jena/src/main/java/com/inrupt/client/jena/JenaBodyHandlers.java | 2 +- .../main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jena/src/main/java/com/inrupt/client/jena/JenaBodyHandlers.java b/jena/src/main/java/com/inrupt/client/jena/JenaBodyHandlers.java index 697e0f82f2b..7232cc00c7c 100644 --- a/jena/src/main/java/com/inrupt/client/jena/JenaBodyHandlers.java +++ b/jena/src/main/java/com/inrupt/client/jena/JenaBodyHandlers.java @@ -49,7 +49,7 @@ public final class JenaBodyHandlers { private static void throwOnError(final Response.ResponseInfo responseInfo) { if (!Response.isSuccess(responseInfo.statusCode())) { throw new ClientHttpException( - "Could not map to a Jena entity.", + "An HTTP error was encountered mapping to a Jena entity.", responseInfo.uri(), responseInfo.statusCode(), responseInfo.headers(), diff --git a/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java b/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java index 35186b7548d..bba3ac04680 100644 --- a/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java +++ b/rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java @@ -46,7 +46,7 @@ public final class RDF4JBodyHandlers { private static void throwOnError(final Response.ResponseInfo responseInfo) { if (!Response.isSuccess(responseInfo.statusCode())) { throw new ClientHttpException( - "Could not map to an RDF4J entity.", + "An HTTP error was encountered mapping to an RDF4J entity.", responseInfo.uri(), responseInfo.statusCode(), responseInfo.headers(),