Skip to content

Commit

Permalink
fix: #473: support Json-LD 1.1 framing + create LdiRdfWriter once per…
Browse files Browse the repository at this point in the history
… pipeline. (#541)

Co-authored-by: Yalz <Yalz@users.noreply.github.com>
  • Loading branch information
Yalz and Yalz committed Mar 13, 2024
1 parent 5b10a0a commit d49c2f1
Show file tree
Hide file tree
Showing 12 changed files with 139 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package be.vlaanderen.informatievlaanderen.ldes.ldi.exceptions;

public class JsonLDFrameException extends RuntimeException {
public JsonLDFrameException(Exception e) {
super(e);
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
package be.vlaanderen.informatievlaanderen.ldes.ldi.rdf.formatter;

import org.apache.jena.atlas.json.JSON;
import org.apache.jena.atlas.json.JsonObject;
import be.vlaanderen.informatievlaanderen.ldes.ldi.exceptions.JsonLDFrameException;
import com.apicatalog.jsonld.JsonLd;
import com.apicatalog.jsonld.JsonLdError;
import com.apicatalog.jsonld.document.JsonDocument;
import jakarta.json.JsonObject;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.riot.*;
import org.apache.jena.riot.writer.JsonLD10Writer;
import org.apache.jena.sparql.util.Context;
import org.apache.jena.riot.Lang;
import org.apache.jena.riot.RDFFormat;
import org.apache.jena.riot.RDFWriter;
import org.apache.jena.riot.RDFWriterBuilder;

import java.io.OutputStream;

import com.github.jsonldjava.core.JsonLdOptions;
import java.io.StringReader;

import static be.vlaanderen.informatievlaanderen.ldes.ldi.rdf.formatter.PrefixAdder.addPrefixesToModel;

public class JsonLdFrameWriter implements LdiRdfWriter {
private final RDFWriterBuilder rdfWriter;
private final JsonDocument frame;

public JsonLdFrameWriter(JsonDocument frame) {
this.frame = frame;
this.rdfWriter = RDFWriter.create().format(RDFFormat.JSONLD);
}

public JsonLdFrameWriter(LdiRdfWriterProperties properties) {
String frame = properties.getJsonLdFrame();
this.rdfWriter = RDFWriter.create().context(getFramedContext(frame)).format(RDFFormat.JSONLD10_FRAME_PRETTY);
public static JsonLdFrameWriter fromProperties(LdiRdfWriterProperties properties) throws JsonLdError {
return new JsonLdFrameWriter(JsonDocument.of(new StringReader(properties.getJsonLdFrame())));
}

@Override
Expand All @@ -28,24 +36,19 @@ public String getContentType() {

@Override
public String write(Model model) {
return rdfWriter.source(addPrefixesToModel(model)).asString();
JsonObject jsonObject;
try {
JsonDocument input = JsonDocument.of(new StringReader(rdfWriter.source(addPrefixesToModel(model)).asString()));
jsonObject = JsonLd.frame(input, frame).get();
} catch (JsonLdError e) {
throw new JsonLDFrameException(e);
}

return jsonObject.toString();
}

@Override
public void writeToOutputStream(Model model, OutputStream outputStream) {
rdfWriter.source(addPrefixesToModel(model)).output(outputStream);
}

protected static Context getFramedContext(String context) {
JsonLDWriteContext jenaCtx = new JsonLDWriteContext();

JsonObject frame = JSON.parse(context);
jenaCtx.set(JsonLD10Writer.JSONLD_FRAME, frame.toString());

JsonLdOptions jsonLdOptions = new JsonLdOptions();
jsonLdOptions.setOmitGraph(true);
jenaCtx.setOptions(jsonLdOptions);

return jenaCtx;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class JsonLdPrettyWriter implements LdiRdfWriter {
private final RDFWriterBuilder rdfWriter;

public JsonLdPrettyWriter() {
this.rdfWriter = RDFWriter.create().format(RDFFormat.JSONLD10_PRETTY);
this.rdfWriter = RDFWriter.create().format(RDFFormat.JSONLD_PRETTY);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package be.vlaanderen.informatievlaanderen.ldes.ldi.rdf.formatter;

import be.vlaanderen.informatievlaanderen.ldes.ldi.exceptions.JsonLDFrameException;
import com.apicatalog.jsonld.JsonLdError;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.riot.Lang;

Expand All @@ -17,9 +19,13 @@ public interface LdiRdfWriter {

static LdiRdfWriter getRdfWriter(LdiRdfWriterProperties properties) {
if (Lang.JSONLD.equals(properties.getLang())) {
return isBlank(properties.getJsonLdFrame())
? new JsonLdPrettyWriter()
: new JsonLdFrameWriter(properties);
try {
return isBlank(properties.getJsonLdFrame())
? new JsonLdPrettyWriter()
: JsonLdFrameWriter.fromProperties(properties);
} catch (JsonLdError e) {
throw new JsonLDFrameException(e);
}
} else {
return new GenericRdfWriter(properties);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
import org.apache.jena.atlas.json.JSON;
import org.apache.jena.atlas.json.JsonObject;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.riot.JsonLDWriteContext;
import org.apache.jena.riot.Lang;
import org.apache.jena.riot.RDFParser;
import org.apache.jena.riot.writer.JsonLD10Writer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.ParameterizedTest;
Expand All @@ -24,18 +22,33 @@
import java.util.Objects;
import java.util.stream.Stream;

import com.github.jsonldjava.core.JsonLdOptions;

import static be.vlaanderen.informatievlaanderen.ldes.ldi.rdf.formatter.LdiRdfWriterProperties.FRAME;
import static org.assertj.core.api.Assertions.assertThat;

class LdiRdfWriterTest {

@Test
void formatModel_jsonLD() throws IOException, URISyntaxException {
void formatModel_jsonLD10() throws IOException, URISyntaxException {
Model model = RDFParser.source("rdf/formatter/product.jsonld").lang(Lang.JSONLD).toModel();
String frame = getFileContentString("rdf/formatter/product.frame.jsonld");
Model expected = RDFParser.source("rdf/formatter/expected/product.jsonld").lang(Lang.JSONLD).toModel();
Model expected = RDFParser.source("rdf/formatter/expected/product.json").lang(Lang.JSONLD).toModel();
LdiRdfWriterProperties writerProperties = new LdiRdfWriterProperties(Map.of(FRAME, frame));

String output = LdiRdfWriter.getRdfWriter(writerProperties.withLang(Lang.JSONLD)).write(model);
JsonObject outputJson = JSON.parse(output);
Model outputModel = RDFParser.fromString(output).lang(Lang.JSONLD).toModel();

assertThat(outputJson.hasKey("@graph")).isFalse();
assertThat(outputModel).matches(expected::isIsomorphicWith);
}

@Test
void formatModel_jsonLD11() {
Model model = RDFParser.source("rdf/formatter/logisticslocation.json").lang(Lang.JSONLD).toModel();
String frame = """
{ "@context": "https://geojson.org/geojson-ld/geojson-context.jsonld", "@type": "https://vocabulary.uncefact.org/LogisticsLocation" }
""";
Model expected = RDFParser.source("rdf/formatter/expected/logisticslocation.json").lang(Lang.JSONLD).toModel();
LdiRdfWriterProperties writerProperties = new LdiRdfWriterProperties(Map.of(FRAME, frame));

String output = LdiRdfWriter.getRdfWriter(writerProperties.withLang(Lang.JSONLD)).write(model);
Expand All @@ -46,6 +59,7 @@ void formatModel_jsonLD() throws IOException, URISyntaxException {
assertThat(outputModel).matches(expected::isIsomorphicWith);
}


@Test
void formatModel_jsonLD_withoutFrame() {
Model model = RDFParser.source("rdf/formatter/product.jsonld").lang(Lang.JSONLD).toModel();
Expand Down Expand Up @@ -79,25 +93,6 @@ void formatModel_nquads() {
assertThat(outputModel).matches(expectedModel::isIsomorphicWith);
}

@Test
void getFramedContext() {
String frame = """
{
"@context": "http://schema.org/",
"@type": "Person"
}
""";

JsonLDWriteContext context = (JsonLDWriteContext) JsonLdFrameWriter.getFramedContext(frame);
JsonObject frameObject = JSON.parse((String) context.get(JsonLD10Writer.JSONLD_FRAME));

assertThat(frameObject)
.matches(json -> json.hasKey("@type"))
.matches(json -> json.hasKey("@context"));
assertThat(((JsonLdOptions) context.get(JsonLD10Writer.JSONLD_OPTIONS)).getOmitGraph())
.isTrue();
}

@ParameterizedTest
@ArgumentsSource(LangProvider.class)
void test_WritingWithByteArrayStream(Lang outputLang) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"@context": "https://geojson.org/geojson-ld/geojson-context.jsonld",
"id": "https://vocabulary.uncefact.org/identifier/node/1/1",
"type": "https://vocabulary.uncefact.org/LogisticsLocation",
"http://purl.org/dc/terms/isVersionOf": "unece:identifier/node/1",
"http://www.w3.org/ns/prov#generatedAtTime": {
"type": "http://www.w3.org/2001/XMLSchema#dateTime",
"@value": "2020-05-20T10:00:00Z"
},
"geometry": {
"type": "Point",
"coordinates": [
4.9041,
52.3676
]
},
"https://vocabulary.uncefact.org/name": {
"type": "http://www.w3.org/2001/XMLSchema#string",
"@value": "k1700"
},
"https://vocabulary.uncefact.org/servicingSpecifiedParty": {
"id": "https://vocabulary.uncefact.org/identifier/servicingSpecifiedParty/1",
"type": "https://vocabulary.uncefact.org/LocationParty",
"https://vocabulary.uncefact.org/name": {
"type": "http://www.w3.org/2001/XMLSchema#string",
"@value": "DP-World"
}
},
"https://vocabulary.uncefact.org/statusCode": {
"type": "http://www.w3.org/2001/XMLSchema#string",
"@value": "0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"@context": [
"https://vocabulary.uncefact.org/unece-context.jsonld",
"https://geojson.org/geojson-ld/geojson-context.jsonld",
{
"isVersionOf": "http://purl.org/dc/terms/isVersionOf",
"generatedAtTime": {
"@id": "http://www.w3.org/ns/prov#generatedAtTime",
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
}
}
],
"type": "LogisticsLocation",
"isVersionOf": "unece:identifier/node/1",
"generatedAtTime": "2020-05-20T10:00:00Z",
"id": "unece:identifier/node/1/1",
"name": "k1700",
"statusCode": "0",
"geometry": {
"type": "Point",
"coordinates": [
4.9041,
52.3676
]
},
"servicingSpecifiedParty": {
"type": "LocationParty",
"id": "unece:identifier/servicingSpecifiedParty/1",
"name": "DP-World"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package be.vlaanderen.informatievlaanderen.ldes.ldio;

import be.vlaanderen.informatievlaanderen.ldes.ldi.rdf.formatter.LdiRdfWriter;
import be.vlaanderen.informatievlaanderen.ldes.ldi.rdf.formatter.LdiRdfWriterProperties;
import be.vlaanderen.informatievlaanderen.ldes.ldi.types.LdiOutput;
import org.apache.jena.rdf.model.Model;
Expand All @@ -12,15 +13,14 @@
public class LdiConsoleOut implements LdiOutput {
public static final String NAME = "Ldio:ConsoleOut";
private final Logger log = LoggerFactory.getLogger(LdiConsoleOut.class);

private final LdiRdfWriterProperties properties;
private final LdiRdfWriter ldiRdfWriter;

public LdiConsoleOut(LdiRdfWriterProperties properties) {
this.properties = properties;
ldiRdfWriter = getRdfWriter(properties);
}

@Override
public void accept(Model model) {
log.info(getRdfWriter(properties).write(model));
log.info(ldiRdfWriter.write(model));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,21 @@ public class LdioHttpOut implements LdiOutput {
private final RequestExecutor requestExecutor;
private final String targetURL;
private final LdiRdfWriterProperties rdfWriterProperties;
private final LdiRdfWriter ldiRdfWriter;

public LdioHttpOut(RequestExecutor requestExecutor, String targetURL,
LdiRdfWriterProperties rdfWriterProperties) {
this.requestExecutor = requestExecutor;
this.targetURL = targetURL;
this.rdfWriterProperties = rdfWriterProperties;
this.ldiRdfWriter = LdiRdfWriter.getRdfWriter(rdfWriterProperties);
}

@Override
public void accept(Model linkedDataModel) {
if (!linkedDataModel.isEmpty()) {
final ByteArrayOutputStream output = new ByteArrayOutputStream();
LdiRdfWriter.getRdfWriter(rdfWriterProperties).writeToOutputStream(linkedDataModel, output);
ldiRdfWriter.writeToOutputStream(linkedDataModel, output);
final String contentType = rdfWriterProperties.getLang().getHeaderString();
final RequestHeader requestHeader = new RequestHeader(HttpHeaders.CONTENT_TYPE, contentType);
final PostRequest request = new PostRequest(targetURL, new RequestHeaders(List.of(requestHeader)), output.toByteArray());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,24 @@
import org.apache.kafka.common.header.internals.RecordHeaders;
import org.springframework.kafka.core.KafkaTemplate;

import static be.vlaanderen.informatievlaanderen.ldes.ldi.rdf.formatter.LdiRdfWriter.getRdfWriter;
import static be.vlaanderen.informatievlaanderen.ldes.ldio.config.KafkaOutConfigKeys.CONTENT_TYPE;

public class LdioKafkaOut implements LdiOutput {
public static final String NAME = "Ldio:KafkaOut";
private final KafkaTemplate<String, String> kafkaTemplate;
private final Lang lang;
private final String topic;
private final LdiRdfWriterProperties writerProperties;
private final KafkaKeyExtractor keyExtractor;
private final LdiRdfWriter ldiRdfWriter;

public LdioKafkaOut(KafkaTemplate<String, String> kafkaTemplate, String topic,
LdiRdfWriterProperties writerProperties, KafkaKeyExtractor keyExtractor) {
this.kafkaTemplate = kafkaTemplate;
this.topic = topic;
this.writerProperties = writerProperties;
this.lang = writerProperties.getLang();
this.keyExtractor = keyExtractor;
this.ldiRdfWriter = getRdfWriter(writerProperties);
}

@Override
Expand All @@ -35,7 +36,7 @@ public void accept(Model model) {
}

private ProducerRecord<String, String> createProducerRecord(Lang lang, String topic, Model model) {
final String value = LdiRdfWriter.getRdfWriter(writerProperties).write(model);
final String value = ldiRdfWriter.write(model);
final String key = keyExtractor.getKey(model);
final var headers = new RecordHeaders().add(CONTENT_TYPE, lang.getHeaderString().getBytes());
return new ProducerRecord<>(topic, null, key, value, headers);
Expand Down

0 comments on commit d49c2f1

Please sign in to comment.