Skip to content

Commit c9889a9

Browse files
krauzerjamesagnew
andauthored
Upgrade uses of Jena to 5.3.0 (hapifhir#6704)
* Upgrade uses of Jena to 5.3.0 -fixes CVE-2024-7254 in dependencies prior to 5.2.0 -hapi-fhir-base: include httpclient dependency which was removed from jena-arq -hapi-fhir-base RDFParser: init JenaSystem on initialization -hapi-fhir-base RDFParser: specify signature for null values on methods that are overloaded -hapi-fhir-base RDFUtil: Jena Riot RDFDataMgr no longer accepts reading and writing into generic reader and writer. Handle reading for StringReader and InputStreamReader. Handle writing by building an output stream from the writer. -hapi-fhir-structures-r4: Remove shexjava dependency - unmaintained -hapi-fhir-structures-r4: Use jena-shex for shex validation -hapi-fhir-structures-r4 RDFParserTest: use jena libraries - compatibile with jena-shex -hapi-fhir-structures-r4 RDFParserTest: simplify FixedShapeMapEntry by attaching nodes vs recreating them (handles blank nodes) -hapi-fhir-structures-r4 RDFParserR4Test: jena 5.0.0 defaults to using PREFIX, not @Prefix. Read testing strings and use isomorphic comparison to check for equality. Added extra assertion to test isomorphism check. * Credit for hapifhir#6704 * Add missing error codes to exceptions --------- Co-authored-by: James Agnew <[email protected]>
1 parent a8a6600 commit c9889a9

File tree

8 files changed

+147
-92
lines changed

8 files changed

+147
-92
lines changed

hapi-fhir-base/pom.xml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,18 @@
6262
<optional>true</optional>
6363
</dependency>
6464

65-
<!-- <dependency> <groupId>xerces</groupId> <artifactId>xercesImpl</artifactId>
65+
<!-- <dependency> <groupId>xerces</groupId> <artifactId>xercesImpl</artifactId>
6666
<version>2.11.0</version> <optional>true</optional> </dependency> -->
6767

6868
<!-- General -->
6969
<dependency>
7070
<groupId>org.apache.commons</groupId>
7171
<artifactId>commons-lang3</artifactId>
7272
</dependency>
73-
73+
<dependency>
74+
<groupId>org.apache.httpcomponents</groupId>
75+
<artifactId>httpclient</artifactId>
76+
</dependency>
7477
<!-- JENA Dependencies - Used for Turtle encoding -->
7578
<dependency>
7679
<groupId>org.apache.jena</groupId>

hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ public class RDFParser extends BaseParser {
9696
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(RDFParser.class);
9797

9898
public static final String NODE_ROLE = "nodeRole";
99+
100+
static {
101+
org.apache.jena.sys.JenaSystem.init(); // Jena must be initialized before RDF.type.getURI() is run;
102+
}
103+
99104
private static final List<String> ignoredPredicates =
100105
Arrays.asList(RDF.type.getURI(), FHIR_NS + FHIR_INDEX, FHIR_NS + NODE_ROLE);
101106
public static final String TREE_ROOT = "treeRoot";
@@ -158,7 +163,11 @@ protected void doEncodeResourceToWriter(
158163

159164
encodeResourceToRDFStreamWriter(resource, rdfModel, false, resourceId, encodeContext, true, null);
160165

161-
RDFUtil.writeRDFModel(writer, rdfModel, lang);
166+
try {
167+
RDFUtil.writeRDFModel(writer, rdfModel, lang);
168+
} catch (Exception e) {
169+
throw new DataFormatException(Msg.code(2618) + "Error writing RDF model to writer", e);
170+
}
162171
}
163172

164173
/**
@@ -172,7 +181,13 @@ protected void doEncodeResourceToWriter(
172181
@Override
173182
protected <T extends IBaseResource> T doParseResource(final Class<T> resourceType, final Reader reader)
174183
throws DataFormatException {
175-
Model model = RDFUtil.readRDFToModel(reader, this.lang);
184+
185+
Model model = null;
186+
try {
187+
model = RDFUtil.readRDFToModel(reader, this.lang);
188+
} catch (Exception e) {
189+
throw new DataFormatException(Msg.code(2619) + "Error reading RDF model from reader", e);
190+
}
176191
return parseResource(resourceType, model);
177192
}
178193

@@ -213,7 +228,7 @@ private Resource encodeResourceToRDFStreamWriter(
213228

214229
if (parentResource == null) {
215230
if (!resource.getIdElement().toUnqualified().hasIdPart()) {
216-
parentResource = rdfModel.getResource(null);
231+
parentResource = rdfModel.getResource((String) null);
217232
} else {
218233

219234
String resourceUri = IRIs.resolve(

hapi-fhir-base/src/main/java/ca/uhn/fhir/util/rdf/RDFUtil.java

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,18 @@
1919
*/
2020
package ca.uhn.fhir.util.rdf;
2121

22+
import org.apache.commons.io.IOUtils;
23+
import org.apache.commons.io.output.WriterOutputStream;
2224
import org.apache.jena.rdf.model.Model;
2325
import org.apache.jena.rdf.model.ModelFactory;
2426
import org.apache.jena.riot.Lang;
2527
import org.apache.jena.riot.RDFDataMgr;
2628

29+
import java.io.IOException;
30+
import java.io.InputStreamReader;
31+
import java.io.OutputStream;
2732
import java.io.Reader;
33+
import java.io.StringReader;
2834
import java.io.Writer;
2935

3036
public class RDFUtil {
@@ -34,17 +40,28 @@ public static Model initializeRDFModel() {
3440
return ModelFactory.createDefaultModel();
3541
}
3642

37-
public static Model readRDFToModel(final Reader reader, final Lang lang) {
43+
public static Model readRDFToModel(final Reader reader, final Lang lang) throws IOException {
44+
// Jena has removed methods that use a generic Reader.
45+
// reads only from InputStream, StringReader, or String
46+
// Reader must be explicitly cast to StringReader
3847
Model rdfModel = initializeRDFModel();
39-
RDFDataMgr.read(rdfModel, reader, null, lang);
48+
if (reader instanceof StringReader) {
49+
RDFDataMgr.read(rdfModel, (StringReader) reader, null, lang);
50+
} else if (reader instanceof InputStreamReader) {
51+
String content = IOUtils.toString(reader);
52+
RDFDataMgr.read(rdfModel, new StringReader(content), null, lang);
53+
}
4054
return rdfModel;
4155
}
4256

43-
public static void writeRDFModel(Writer writer, Model rdfModel, Lang lang) {
44-
// This writes to the provided Writer.
45-
// Jena has deprecated methods that use a generic Writer
46-
// writer could be explicitly casted to StringWriter in order to hit a
47-
// non-deprecated overload
48-
RDFDataMgr.write(writer, rdfModel, lang);
57+
public static void writeRDFModel(Writer writer, Model rdfModel, Lang lang) throws IOException {
58+
// Jena has removed methods that use a generic Writer.
59+
// Writer must be explicitly cast to StringWriter or OutputStream
60+
// in order to hit a write method.
61+
OutputStream outputStream = WriterOutputStream.builder()
62+
.setWriter(writer)
63+
.setCharset("UTF-8")
64+
.get();
65+
RDFDataMgr.write(outputStream, rdfModel, lang);
4966
}
5067
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
type: change
3+
issue: 6704
4+
title: "The JENA library used to provide RDF/Turtle encoding and parsing services has been
5+
upgraded from 4.9.0 to 5.3.0. Thanks to Dylan Krause for the contribution!"

hapi-fhir-structures-r4/pom.xml

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@
1313
<artifactId>hapi-fhir-structures-r4</artifactId>
1414
<packaging>bundle</packaging>
1515

16-
<properties>
17-
<shexjava_version>1.3beta</shexjava_version>
18-
</properties>
19-
2016
<name>HAPI FHIR Structures - FHIR R4</name>
2117

2218
<dependencies>
@@ -25,7 +21,7 @@
2521
<artifactId>okhttp</artifactId>
2622
<optional>true</optional>
2723
</dependency>
28-
24+
2925
<dependency>
3026
<groupId>ca.uhn.hapi.fhir</groupId>
3127
<artifactId>hapi-fhir-base</artifactId>
@@ -59,7 +55,7 @@
5955
</exclusions>
6056
</dependency>
6157

62-
<!--
58+
<!--
6359
Optional dependencies from RI codebase
6460
-->
6561
<dependency>
@@ -85,7 +81,7 @@
8581

8682

8783
<!--
88-
Test dependencies on other optional parts of HAPI
84+
Test dependencies on other optional parts of HAPI
8985
-->
9086
<dependency>
9187
<groupId>ca.uhn.hapi.fhir</groupId>
@@ -124,21 +120,8 @@
124120
</exclusions>
125121
</dependency>
126122
<dependency>
127-
<groupId>fr.inria.lille.shexjava</groupId>
128-
<artifactId>shexjava-core</artifactId>
129-
<version>${shexjava_version}</version>
130-
<scope>test</scope>
131-
<exclusions>
132-
<!-- If this is included it confused the RDFParser -->
133-
<exclusion>
134-
<groupId>org.apache.commons</groupId>
135-
<artifactId>commons-rdf-jena</artifactId>
136-
</exclusion>
137-
<exclusion>
138-
<artifactId>xml-apis</artifactId>
139-
<groupId>xml-apis</groupId>
140-
</exclusion>
141-
</exclusions>
123+
<groupId>org.apache.jena</groupId>
124+
<artifactId>jena-shex</artifactId>
142125
</dependency>
143126
<dependency>
144127
<groupId>com.helger.commons</groupId>

hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/RDFParserR4Test.java

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,26 @@
22

33
import ca.uhn.fhir.context.FhirContext;
44
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
5+
import org.apache.jena.rdf.model.Model;
6+
import org.apache.jena.rdf.model.ModelFactory;
7+
import org.apache.jena.riot.Lang;
8+
import org.apache.jena.riot.RDFDataMgr;
59
import org.hl7.fhir.r4.model.DecimalType;
610
import org.hl7.fhir.r4.model.HumanName;
711
import org.hl7.fhir.r4.model.Patient;
812
import org.hl7.fhir.r4.model.StringType;
913
import org.junit.jupiter.api.Test;
1014

15+
import java.io.StringReader;
16+
17+
import static org.junit.jupiter.api.Assertions.assertFalse;
18+
import static org.junit.jupiter.api.Assertions.assertTrue;
1119
import static org.junit.jupiter.api.Assertions.assertEquals;
1220
import static org.junit.jupiter.api.Assertions.assertThrows;
1321

1422
public class RDFParserR4Test {
1523
private static final FhirContext ourCtx = FhirContext.forR4Cached();
1624

17-
18-
1925
@Test
2026
public void testEncodeToString_PrimitiveDataType() {
2127
DecimalType object = new DecimalType("123.456000");
@@ -43,8 +49,32 @@ public void testEncodeToString_Resource() {
4349
fhir:nodeRole fhir:treeRoot .
4450
""";
4551

52+
Model expectedModel = ModelFactory.createDefaultModel();
53+
RDFDataMgr.read(expectedModel, new StringReader(expected), null, Lang.TURTLE);
54+
4655
String actual = ourCtx.newRDFParser().encodeToString(p);
47-
assertEquals(expected, actual);
56+
Model actualModel = ModelFactory.createDefaultModel();
57+
RDFDataMgr.read(actualModel, new StringReader(actual), null, Lang.TURTLE);
58+
59+
assertTrue(expectedModel.isIsomorphicWith(actualModel));
60+
61+
String unexpected = """
62+
@prefix fhir: <http://hl7.org/fhir/> .
63+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
64+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
65+
@prefix sct: <http://snomed.info/id#> .
66+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
67+
68+
<http://hl7.org/fhir/Patient/123>
69+
rdf:type fhir:Patient;
70+
fhir:Patient.active [ fhir:value true ];
71+
fhir:Resource.id [ fhir:value "124" ];
72+
fhir:nodeRole fhir:treeRoot .
73+
""";
74+
75+
Model unexpectedModel = ModelFactory.createDefaultModel();
76+
RDFDataMgr.read(unexpectedModel, new StringReader(unexpected), null, Lang.TURTLE);
77+
assertFalse(actualModel.isIsomorphicWith(unexpectedModel));
4878
}
4979

5080
@Test

0 commit comments

Comments
 (0)