Skip to content

Commit 1ef8bee

Browse files
committed
JCL-402: RDF4J body handlers http error handling
1 parent 4b37262 commit 1ef8bee

File tree

3 files changed

+157
-27
lines changed

3 files changed

+157
-27
lines changed

rdf4j/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,12 @@
150150
<version>${junit.version}</version>
151151
<scope>test</scope>
152152
</dependency>
153+
<dependency>
154+
<groupId>com.inrupt.client</groupId>
155+
<artifactId>inrupt-client-jackson</artifactId>
156+
<version>${project.version}</version>
157+
<scope>test</scope>
158+
</dependency>
153159
</dependencies>
154160

155161
<build>

rdf4j/src/main/java/com/inrupt/client/rdf4j/RDF4JBodyHandlers.java

Lines changed: 93 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@
2020
*/
2121
package com.inrupt.client.rdf4j;
2222

23+
import com.inrupt.client.ClientHttpException;
24+
import com.inrupt.client.ProblemDetails;
2325
import com.inrupt.client.Response;
26+
import com.inrupt.client.spi.JsonService;
27+
import com.inrupt.client.spi.ServiceProvider;
2428

2529
import java.io.ByteArrayInputStream;
2630
import java.io.IOException;
@@ -41,45 +45,119 @@
4145
*/
4246
public final class RDF4JBodyHandlers {
4347

48+
private static JsonService jsonService;
49+
private static boolean isJsonServiceInitialized = false;
50+
51+
private static JsonService getJsonService() {
52+
if (RDF4JBodyHandlers.isJsonServiceInitialized) {
53+
return RDF4JBodyHandlers.jsonService;
54+
}
55+
// It is acceptable for a JenaBodyHandlers instance to be in a classpath without any implementation for
56+
// JsonService, in which case the ProblemDetails exceptions will fallback to default and not be parsed.
57+
JsonService js;
58+
try {
59+
js = ServiceProvider.getJsonService();
60+
} catch (IllegalStateException e) {
61+
js = null;
62+
}
63+
RDF4JBodyHandlers.jsonService = js;
64+
RDF4JBodyHandlers.isJsonServiceInitialized = true;
65+
return RDF4JBodyHandlers.jsonService;
66+
}
67+
68+
private static Model responseToModel(final Response.ResponseInfo responseInfo) {
69+
return responseInfo.headers().firstValue("Content-Type")
70+
.map(RDF4JBodyHandlers::toRDF4JFormat).map(format -> {
71+
try (final InputStream stream = new ByteArrayInputStream(responseInfo.body().array())) {
72+
return Rio.parse(stream, responseInfo.uri().toString(), format);
73+
} catch (final IOException ex) {
74+
throw new UncheckedIOException(
75+
"An I/O error occurred while data was read from the InputStream", ex);
76+
}
77+
})
78+
.orElseGet(() -> new DynamicModelFactory().createEmptyModel());
79+
}
80+
4481
/**
4582
* Populate a RDF4J {@link Model} with an HTTP response.
4683
*
4784
* @return an HTTP body handler
85+
* @deprecated
4886
*/
4987
public static Response.BodyHandler<Model> ofModel() {
50-
return responseInfo -> responseInfo.headers().firstValue("Content-Type")
51-
.map(RDF4JBodyHandlers::toRDF4JFormat).map(format -> {
52-
try (final InputStream stream = new ByteArrayInputStream(responseInfo.body().array())) {
53-
return Rio.parse(stream, responseInfo.uri().toString(), format);
54-
} catch (final IOException ex) {
55-
throw new UncheckedIOException(
56-
"An I/O error occurred while data was read from the InputStream", ex);
57-
}
58-
})
59-
.orElseGet(() -> new DynamicModelFactory().createEmptyModel());
88+
return RDF4JBodyHandlers::responseToModel;
6089
}
6190

6291
/**
63-
* Populate a RDF4J {@link Repository} with an HTTP response.
92+
* Populate a RDF4J {@link Model} with an HTTP response.
6493
*
6594
* @return an HTTP body handler
6695
*/
67-
public static Response.BodyHandler<Repository> ofRepository() {
68-
return responseInfo -> responseInfo.headers().firstValue("Content-Type")
96+
public static Response.BodyHandler<Model> ofRDF4JModel() {
97+
return responseInfo -> {
98+
if (responseInfo.statusCode() >= 300) {
99+
throw new ClientHttpException(
100+
ProblemDetails.fromErrorResponse(
101+
responseInfo.statusCode(),
102+
responseInfo.headers(),
103+
responseInfo.body().array(),
104+
getJsonService()
105+
),
106+
"Deserializing the RDF from " + responseInfo.uri() + " failed"
107+
);
108+
}
109+
return responseToModel(responseInfo);
110+
};
111+
}
112+
113+
private static Repository responseToRepository(final Response.ResponseInfo responseInfo) {
114+
return responseInfo.headers().firstValue("Content-Type")
69115
.map(RDF4JBodyHandlers::toRDF4JFormat).map(format -> {
70116
final Repository repository = new SailRepository(new MemoryStore());
71117
try (final InputStream stream = new ByteArrayInputStream(responseInfo.body().array());
72-
final RepositoryConnection conn = repository.getConnection()) {
118+
final RepositoryConnection conn = repository.getConnection()) {
73119
conn.add(stream, responseInfo.uri().toString(), format);
74120
} catch (final IOException ex) {
75121
throw new UncheckedIOException(
76-
"An I/O error occurred while data was read from the InputStream", ex);
122+
"An I/O error occurred while data was read from the InputStream", ex);
77123
}
78124
return repository;
79125
})
80126
.orElseGet(() -> new SailRepository(new MemoryStore()));
81127
}
82128

129+
/**
130+
* Populate a RDF4J {@link Repository} with an HTTP response.
131+
*
132+
* @return an HTTP body handler
133+
* @deprecated
134+
*/
135+
public static Response.BodyHandler<Repository> ofRepository() {
136+
return RDF4JBodyHandlers::responseToRepository;
137+
}
138+
139+
/**
140+
* Populate a RDF4J {@link Repository} with an HTTP response.
141+
*
142+
* @return an HTTP body handler
143+
*/
144+
public static Response.BodyHandler<Repository> ofRDF4JRepository() {
145+
return responseInfo -> {
146+
if (responseInfo.statusCode() >= 300) {
147+
throw new ClientHttpException(
148+
ProblemDetails.fromErrorResponse(
149+
responseInfo.statusCode(),
150+
responseInfo.headers(),
151+
responseInfo.body().array(),
152+
getJsonService()
153+
),
154+
"Deserializing the RDF from " + responseInfo.uri() + " failed"
155+
);
156+
}
157+
return responseToRepository(responseInfo);
158+
};
159+
}
160+
83161
static RDFFormat toRDF4JFormat(final String mediaType) {
84162
return Rio.getParserFormatForMIMEType(mediaType).orElse(RDFFormat.TURTLE);
85163
}

rdf4j/src/test/java/com/inrupt/client/rdf4j/RDF4JBodyHandlersTest.java

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import static org.junit.jupiter.api.Assertions.*;
2424

25+
import com.inrupt.client.ClientHttpException;
2526
import com.inrupt.client.Request;
2627
import com.inrupt.client.Response;
2728
import com.inrupt.client.spi.HttpService;
@@ -32,6 +33,7 @@
3233
import java.net.URI;
3334
import java.util.HashMap;
3435
import java.util.Map;
36+
import java.util.concurrent.CompletionException;
3537
import java.util.concurrent.ExecutionException;
3638
import java.util.concurrent.TimeoutException;
3739

@@ -61,14 +63,14 @@ static void teardown() {
6163
}
6264

6365
@Test
64-
void testOfModelHandlerAsync() throws IOException,
66+
void testOfRDF4JModelHandlerAsync() throws IOException,
6567
InterruptedException, ExecutionException, TimeoutException {
6668
final Request request = Request.newBuilder()
6769
.uri(URI.create(config.get("rdf_uri") + "/oneTriple"))
6870
.GET()
6971
.build();
7072

71-
final Response<Model> res = client.send(request, RDF4JBodyHandlers.ofModel()).toCompletableFuture().join();
73+
final Response<Model> res = client.send(request, RDF4JBodyHandlers.ofRDF4JModel()).toCompletableFuture().join();
7274

7375
final int statusCode = res.statusCode();
7476
final Model responseBody = res.body();
@@ -84,14 +86,14 @@ void testOfModelHandlerAsync() throws IOException,
8486
}
8587

8688
@Test
87-
void testOfModelHandler() throws IOException,
89+
void testOfRDF4JModelHandler() throws IOException,
8890
InterruptedException, ExecutionException, TimeoutException {
8991
final Request request = Request.newBuilder()
9092
.uri(URI.create(config.get("rdf_uri") + "/oneTriple"))
9193
.GET()
9294
.build();
9395

94-
final Response<Model> response = client.send(request, RDF4JBodyHandlers.ofModel())
96+
final Response<Model> response = client.send(request, RDF4JBodyHandlers.ofRDF4JModel())
9597
.toCompletableFuture().join();
9698

9799
assertEquals(200, response.statusCode());
@@ -106,13 +108,13 @@ void testOfModelHandler() throws IOException,
106108
}
107109

108110
@Test
109-
void testOfModelHandlerWithURL() throws IOException, InterruptedException {
111+
void testOfRDF4JModelHandlerWithURL() throws IOException, InterruptedException {
110112
final Request request = Request.newBuilder()
111113
.uri(URI.create(config.get("rdf_uri") + "/example"))
112114
.GET()
113115
.build();
114116

115-
final Response<Model> response = client.send(request, RDF4JBodyHandlers.ofModel())
117+
final Response<Model> response = client.send(request, RDF4JBodyHandlers.ofRDF4JModel())
116118
.toCompletableFuture().join();
117119

118120
assertEquals(200, response.statusCode());
@@ -128,14 +130,36 @@ void testOfModelHandlerWithURL() throws IOException, InterruptedException {
128130
}
129131

130132
@Test
131-
void testOfRepositoryHandlerAsync() throws IOException,
133+
void testOfRDF4JModelHandlerError() throws IOException,
134+
InterruptedException, ExecutionException, TimeoutException {
135+
final Request request = Request.newBuilder()
136+
.uri(URI.create(config.get("rdf_uri") + "/error"))
137+
.GET()
138+
.build();
139+
140+
final CompletionException completionException = assertThrows(
141+
CompletionException.class,
142+
() -> client.send(request, RDF4JBodyHandlers.ofRDF4JModel()).toCompletableFuture().join()
143+
);
144+
145+
final ClientHttpException httpException = (ClientHttpException) completionException.getCause();
146+
147+
assertEquals(429, httpException.getProblemDetails().getStatus());
148+
assertEquals("Too Many Requests", httpException.getProblemDetails().getTitle());
149+
assertEquals("Some details", httpException.getProblemDetails().getDetails());
150+
assertEquals("https://example.org/type", httpException.getProblemDetails().getType().toString());
151+
assertEquals("https://example.org/instance", httpException.getProblemDetails().getInstance().toString());
152+
}
153+
154+
@Test
155+
void testOfRDF4JRepositoryHandlerAsync() throws IOException,
132156
InterruptedException, ExecutionException, TimeoutException {
133157
final Request request = Request.newBuilder()
134158
.uri(URI.create(config.get("rdf_uri") + "/oneTriple"))
135159
.GET()
136160
.build();
137161

138-
final Response<Repository> res = client.send(request, RDF4JBodyHandlers.ofRepository())
162+
final Response<Repository> res = client.send(request, RDF4JBodyHandlers.ofRDF4JRepository())
139163
.toCompletableFuture().join();
140164

141165
final int statusCode = res.statusCode();
@@ -155,14 +179,14 @@ void testOfRepositoryHandlerAsync() throws IOException,
155179
}
156180

157181
@Test
158-
void testOfRepositoryHandler() throws IOException,
182+
void testOfRDF4JRepositoryHandler() throws IOException,
159183
InterruptedException, ExecutionException, TimeoutException {
160184
final Request request = Request.newBuilder()
161185
.uri(URI.create(config.get("rdf_uri") + "/oneTriple"))
162186
.GET()
163187
.build();
164188

165-
final Response<Repository> response = client.send(request, RDF4JBodyHandlers.ofRepository())
189+
final Response<Repository> response = client.send(request, RDF4JBodyHandlers.ofRDF4JRepository())
166190
.toCompletableFuture().join();
167191
assertEquals(200, response.statusCode());
168192

@@ -180,13 +204,13 @@ void testOfRepositoryHandler() throws IOException,
180204
}
181205

182206
@Test
183-
void testOfRepositoryHandlerWithURL() throws IOException, InterruptedException {
207+
void testOfRDF4JRepositoryHandlerWithURL() throws IOException, InterruptedException {
184208
final Request request = Request.newBuilder()
185209
.uri(URI.create(config.get("rdf_uri") + "/example"))
186210
.GET()
187211
.build();
188212

189-
final Response<Repository> response = client.send(request, RDF4JBodyHandlers.ofRepository())
213+
final Response<Repository> response = client.send(request, RDF4JBodyHandlers.ofRDF4JRepository())
190214
.toCompletableFuture().join();
191215
assertEquals(200, response.statusCode());
192216

@@ -202,4 +226,26 @@ void testOfRepositoryHandlerWithURL() throws IOException, InterruptedException {
202226
);
203227
}
204228
}
229+
230+
@Test
231+
void testOfRDF4JRepositoryHandlerError() throws IOException,
232+
InterruptedException, ExecutionException, TimeoutException {
233+
final Request request = Request.newBuilder()
234+
.uri(URI.create(config.get("rdf_uri") + "/error"))
235+
.GET()
236+
.build();
237+
238+
final CompletionException completionException = assertThrows(
239+
CompletionException.class,
240+
() -> client.send(request, RDF4JBodyHandlers.ofRDF4JRepository()).toCompletableFuture().join()
241+
);
242+
243+
final ClientHttpException httpException = (ClientHttpException) completionException.getCause();
244+
245+
assertEquals(429, httpException.getProblemDetails().getStatus());
246+
assertEquals("Too Many Requests", httpException.getProblemDetails().getTitle());
247+
assertEquals("Some details", httpException.getProblemDetails().getDetails());
248+
assertEquals("https://example.org/type", httpException.getProblemDetails().getType().toString());
249+
assertEquals("https://example.org/instance", httpException.getProblemDetails().getInstance().toString());
250+
}
205251
}

0 commit comments

Comments
 (0)