Skip to content

Commit 4b37262

Browse files
committed
JCL-402: HTTP Error handling in Jena body handler
This deprecates the current methods in `JenaBodyHandlers`, and replaces them with similar method throwing an appropriate exception with error details on HTTP error instead of returning an empty dataset. The new method now have `Jena` in their name for this not to be a breaking change: `ofModel` becomes `ofJenaModel`, etc.
1 parent aa53b0e commit 4b37262

File tree

4 files changed

+234
-42
lines changed

4 files changed

+234
-42
lines changed

jena/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@
6767
<version>${slf4j.version}</version>
6868
<scope>test</scope>
6969
</dependency>
70+
<dependency>
71+
<groupId>com.inrupt.client</groupId>
72+
<artifactId>inrupt-client-jackson</artifactId>
73+
<version>${project.version}</version>
74+
<scope>test</scope>
75+
</dependency>
7076
<dependency>
7177
<groupId>org.wiremock</groupId>
7278
<artifactId>wiremock</artifactId>

jena/src/main/java/com/inrupt/client/jena/JenaBodyHandlers.java

Lines changed: 126 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@
2020
*/
2121
package com.inrupt.client.jena;
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;
@@ -43,14 +47,28 @@
4347
public final class JenaBodyHandlers {
4448

4549
private static final String CONTENT_TYPE = "Content-Type";
50+
private static JsonService jsonService;
51+
private static boolean isJsonServiceInitialized = false;
4652

47-
/**
48-
* Populate a Jena {@link Model} with an HTTP response body.
49-
*
50-
* @return an HTTP body handler
51-
*/
52-
public static Response.BodyHandler<Model> ofModel() {
53-
return responseInfo -> responseInfo.headers().firstValue(CONTENT_TYPE)
53+
private static JsonService getJsonService() {
54+
if (JenaBodyHandlers.isJsonServiceInitialized) {
55+
return JenaBodyHandlers.jsonService;
56+
}
57+
// It is acceptable for a JenaBodyHandlers instance to be in a classpath without any implementation for
58+
// JsonService, in which case the ProblemDetails exceptions will fallback to default and not be parsed.
59+
JsonService js;
60+
try {
61+
js = ServiceProvider.getJsonService();
62+
} catch (IllegalStateException e) {
63+
js = null;
64+
}
65+
JenaBodyHandlers.jsonService = js;
66+
JenaBodyHandlers.isJsonServiceInitialized = true;
67+
return JenaBodyHandlers.jsonService;
68+
}
69+
70+
private static Model responseToModel(final Response.ResponseInfo responseInfo) {
71+
return responseInfo.headers().firstValue(CONTENT_TYPE)
5472
.map(JenaBodyHandlers::toJenaLang).map(lang -> {
5573
try (final var input = new ByteArrayInputStream(responseInfo.body().array())) {
5674
final var model = ModelFactory.createDefaultModel();
@@ -65,12 +83,39 @@ public static Response.BodyHandler<Model> ofModel() {
6583
}
6684

6785
/**
68-
* Populate a Jena {@link Graph} with an HTTP response.
86+
* Populate a Jena {@link Model} with an HTTP response body.
6987
*
7088
* @return an HTTP body handler
89+
* @deprecated Use ofJenaModel instead for consistent HTTP error handling.
7190
*/
72-
public static Response.BodyHandler<Graph> ofGraph() {
73-
return responseInfo -> responseInfo.headers().firstValue(CONTENT_TYPE)
91+
public static Response.BodyHandler<Model> ofModel() {
92+
return JenaBodyHandlers::responseToModel;
93+
}
94+
95+
/**
96+
* Populate a Jena {@link Model} with an HTTP response body.
97+
*
98+
* @return an HTTP body handler
99+
*/
100+
public static Response.BodyHandler<Model> ofJenaModel() {
101+
return responseInfo -> {
102+
if (responseInfo.statusCode() >= 300) {
103+
throw new ClientHttpException(
104+
ProblemDetails.fromErrorResponse(
105+
responseInfo.statusCode(),
106+
responseInfo.headers(),
107+
responseInfo.body().array(),
108+
getJsonService()
109+
),
110+
"Deserializing the RDF from " + responseInfo.uri() + " failed"
111+
);
112+
}
113+
return responseToModel(responseInfo);
114+
};
115+
}
116+
117+
private static Graph responseToGraph(final Response.ResponseInfo responseInfo) {
118+
return responseInfo.headers().firstValue(CONTENT_TYPE)
74119
.map(JenaBodyHandlers::toJenaLang).map(lang -> {
75120
try (final var input = new ByteArrayInputStream(responseInfo.body().array())) {
76121
final var graph = GraphMemFactory.createDefaultGraph();
@@ -84,24 +129,83 @@ public static Response.BodyHandler<Graph> ofGraph() {
84129
.orElseGet(GraphMemFactory::createDefaultGraph);
85130
}
86131

132+
/**
133+
* Populate a Jena {@link Graph} with an HTTP response.
134+
*
135+
* @return an HTTP body handler
136+
* @deprecated Use ofJenaGraph instead for consistent HTTP error handling.
137+
*/
138+
public static Response.BodyHandler<Graph> ofGraph() {
139+
return JenaBodyHandlers::responseToGraph;
140+
}
141+
142+
/**
143+
* Populate a Jena {@link Graph} with an HTTP response.
144+
*
145+
* @return an HTTP body handler
146+
*/
147+
public static Response.BodyHandler<Graph> ofJenaGraph() {
148+
return responseInfo -> {
149+
if (responseInfo.statusCode() > 300) {
150+
throw new ClientHttpException(
151+
ProblemDetails.fromErrorResponse(
152+
responseInfo.statusCode(),
153+
responseInfo.headers(),
154+
responseInfo.body().array(),
155+
getJsonService()
156+
),
157+
"Deserializing the RDF from " + responseInfo.uri() + " failed"
158+
);
159+
}
160+
return JenaBodyHandlers.responseToGraph(responseInfo);
161+
};
162+
}
163+
164+
private static Dataset responseToDataset(final Response.ResponseInfo responseInfo) {
165+
return responseInfo.headers().firstValue(CONTENT_TYPE)
166+
.map(JenaBodyHandlers::toJenaLang).map(lang -> {
167+
try (final var input = new ByteArrayInputStream(responseInfo.body().array())) {
168+
final var dataset = DatasetFactory.create();
169+
RDFDataMgr.read(dataset, input, responseInfo.uri().toString(), lang);
170+
return dataset;
171+
} catch (final IOException ex) {
172+
throw new UncheckedIOException(
173+
"An I/O error occurred while data was read from the InputStream into a Dataset", ex);
174+
}
175+
})
176+
.orElseGet(DatasetFactory::create);
177+
}
178+
87179
/**
88180
* Populate a Jena {@link Dataset} with an HTTP response.
89181
*
90182
* @return an HTTP body handler
183+
* @deprecated Use ofJenaDataset instead for consistent HTTP error handling.
91184
*/
92185
public static Response.BodyHandler<Dataset> ofDataset() {
93-
return responseInfo -> responseInfo.headers().firstValue(CONTENT_TYPE)
94-
.map(JenaBodyHandlers::toJenaLang).map(lang -> {
95-
try (final var input = new ByteArrayInputStream(responseInfo.body().array())) {
96-
final var dataset = DatasetFactory.create();
97-
RDFDataMgr.read(dataset, input, responseInfo.uri().toString(), lang);
98-
return dataset;
99-
} catch (final IOException ex) {
100-
throw new UncheckedIOException(
101-
"An I/O error occurred while data was read from the InputStream into a Dataset", ex);
102-
}
103-
})
104-
.orElseGet(DatasetFactory::create);
186+
return JenaBodyHandlers::responseToDataset;
187+
}
188+
189+
/**
190+
* Populate a Jena {@link Dataset} with an HTTP response.
191+
*
192+
* @return an HTTP body handler
193+
*/
194+
public static Response.BodyHandler<Dataset> ofJenaDataset() {
195+
return responseInfo -> {
196+
if (responseInfo.statusCode() > 300) {
197+
throw new ClientHttpException(
198+
ProblemDetails.fromErrorResponse(
199+
responseInfo.statusCode(),
200+
responseInfo.headers(),
201+
responseInfo.body().array(),
202+
getJsonService()
203+
),
204+
"Deserializing the RDF from " + responseInfo.uri() + " failed"
205+
);
206+
}
207+
return JenaBodyHandlers.responseToDataset(responseInfo);
208+
};
105209
}
106210

107211
static Lang toJenaLang(final String mediaType) {

0 commit comments

Comments
 (0)