Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Set up JDK, for pushing into github package registry
uses: actions/setup-java@v5
with:
java-version: '25'
java-version: '21'
distribution: 'temurin'
cache: maven

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ jobs:
steps:
- uses: actions/checkout@v6

- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
java-version: '17'
java-version: '21'
distribution: 'temurin'
cache: maven

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package de.wwu.scdh.annotation.selection.cli;

import com.apicatalog.jsonld.JsonLd;
import com.apicatalog.jsonld.JsonLdEmbed;
import com.apicatalog.jsonld.JsonLdOptions;
import com.apicatalog.jsonld.api.FramingApi;
import com.apicatalog.jsonld.document.Document;
import com.apicatalog.jsonld.document.JsonDocument;
import com.apicatalog.jsonld.document.RdfDocument;
import com.apicatalog.jsonld.lang.Keywords;
import com.apicatalog.rdf.RdfDataset;
import de.wwu.scdh.annotation.selection.Resource;
import de.wwu.scdh.annotation.selection.utils.StaticDocumentLoader;
import de.wwu.scdh.annotation.selection.wadm.NormalizeAnnotation;
import jakarta.json.Json;
import jakarta.json.JsonArray;
Expand All @@ -30,9 +29,11 @@
import org.apache.jena.riot.RDFLanguages;
import org.apache.jena.riot.RDFWriterRegistry;
import org.apache.jena.riot.WriterDatasetRIOT;
import org.apache.jena.riot.system.JenaTitanium;
import org.apache.jena.riot.system.PrefixMap;
import org.apache.jena.riot.system.PrefixMapZero;
import org.apache.jena.riot.system.jsonld.JenaToTitanium;
import org.apache.jena.sparql.core.DatasetGraph;
import org.apache.jena.sparql.core.DatasetGraphFactory;
import org.apache.jena.sparql.core.DatasetImpl;
import org.apache.jena.sparql.util.Context;
import picocli.CommandLine;
Expand Down Expand Up @@ -123,17 +124,19 @@ public Integer call() throws Exception {
// do the framing and serialization with Titanium

try {
RdfDataset rdfds = JenaTitanium.convert(ds.asDatasetGraph());
Document rdfdoc = RdfDocument.of(rdfds);
// The Titanium API for framing does not allow
// RdfDocuments. Thus, we make a JsonDocument
// representing the graph like for plain output
JsonArray array = JsonLd.fromRdf(rdfdoc).get();
JsonObject jsonStructure =
Json.createObjectBuilder().add(Keywords.GRAPH, array).build();
JsonDocument jsonDocument = JsonDocument.of(jsonStructure);
// use titanium for framing
JsonLdOptions options = new JsonLdOptions();
// options.setBase(null);
options.setDocumentLoader(new StaticDocumentLoader());
options.setOmitGraph(true);
options.setEmbed(JsonLdEmbed.ALWAYS);
// add more options here!
DatasetGraph dsg = DatasetGraphFactory.create(model.getGraph());
JsonArray ja = JenaToTitanium.convert(dsg, options);
JsonDocument jDoc = JsonDocument.of(ja);
// do the framing
FramingApi api = JsonLd.frame(jsonDocument, JsonDocument.of(framingUri.openStream()));
FramingApi api = JsonLd.frame(jDoc, JsonDocument.of(framingUri.openStream()));
api.loader(options.getDocumentLoader()); // important to set loader!
final JsonObject output = api.get();

// JsonOutput.print(System.out, true);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package de.wwu.scdh.annotation.selection.utils;

import com.apicatalog.jsonld.JsonLdError;
import com.apicatalog.jsonld.JsonLdOptions;
import com.apicatalog.jsonld.document.Document;
import com.apicatalog.jsonld.loader.DocumentLoader;
import com.apicatalog.jsonld.loader.DocumentLoaderOptions;
import com.apicatalog.jsonld.loader.FileLoader;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.json.JsonReader;
import jakarta.json.JsonStructure;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The {@link StaticDocumentLoader} is a {@link DocumentLoader} that returns local assets instead of remote one.
* It is configured with a resource mapping that maps URIs to local assets. Its purpose is to speed up
* JSON-LD processing with version pinned contexts, as for Web Annotations.<P/>
*
* The loader delegated to a fallback loader when the requested URI is not in the resource mapping.<P/>
*
* The structure of the resource mapping JSON file:
*
* <pre>
* {
* "https://www.w3.org/ns/anno.jsonld": {
* "path": "anno.jsonld"
* },
* "http://www.w3.org/ns/anno.jsonld": {
* "path": "anno.jsonld"
* }
* }
* </pre>
*/
public class StaticDocumentLoader implements DocumentLoader {

private static final Logger LOG = LoggerFactory.getLogger(StaticDocumentLoader.class);

/**
* A default context mapping resource.
*/
public static final URL CONTEXT_MAPPING = StaticDocumentLoader.class.getResource("/context/context-map.json");

private final DocumentLoader fallbackLoader;

private final Map<URI, URI> contextMapping;

private final boolean delegateOnError;

/**
* Creates a new {@link StaticDocumentLoader} from a resource mapping given by {@link File}.
* @param contextMap - a JSON {@link File>} with the resource mapping
* @param fallbackLoader - a {@link DocumentLoader} used to handle request for non-mapped resources
* @param delegateOnError - whether to delegate the request to the fallback, when loading of a local asset failed.
*/
public StaticDocumentLoader(final File contextMap, final DocumentLoader fallbackLoader, boolean delegateOnError) {
this.fallbackLoader = fallbackLoader;
this.delegateOnError = delegateOnError;
contextMapping = setup(contextMap);
}

/**
* Creates a new {@link StaticDocumentLoader} from the {@link StaticDocumentLoader#CONTEXT_MAPPING}.
*/
public StaticDocumentLoader() {
fallbackLoader = (new JsonLdOptions()).getDocumentLoader(); // default loader
delegateOnError = true;
File defaultContextMapping = new File(CONTEXT_MAPPING.getPath());
contextMapping = setup(defaultContextMapping);
}

/**
* {@inheritDoc}
*/
@Override
public Document loadDocument(URI url, DocumentLoaderOptions options) throws JsonLdError {
if (contextMapping.containsKey(url)) {
URI file = contextMapping.get(url);
FileLoader fileLoader = new FileLoader();
try {
return fileLoader.loadDocument(file, options);
} catch (JsonLdError e) {
LOG.error("failed to load static asset for {}: {}", url, e.getMessage());
if (delegateOnError) {
return fallbackLoader.loadDocument(url, options);
} else {
throw new JsonLdError(e.getCode(), e.getMessage());
}
}
} else {
// delegate to fallback loader
return fallbackLoader.loadDocument(url, options);
}
}

private Map<URI, URI> setup(File contextMap) {
Path path = contextMap.toPath().getParent();
Map<URI, URI> assets = new HashMap<>();

try {
JsonReader jsonReader = Json.createReader(new FileReader(contextMap));
JsonStructure jsonStructure = jsonReader.read();
JsonObject root = jsonStructure.asJsonObject();
for (String url : root.keySet()) {
try {
URI remote = new URI(url);
String relative = root.get(url).asJsonObject().getString("path");
File resolved = path.resolve(relative).toFile();
if (resolved.isFile()) {
assets.put(remote, resolved.toURI());
} else {
LOG.error(
"context map entry {} configures file {}, which resolves to {}. File not present",
url,
relative,
resolved);
}
} catch (URISyntaxException e) {
LOG.error("context map entry {} is not a valid URI. Continuing without this entry", url);
} catch (Exception e) {
LOG.error("invalid context map entry {}. Continuing without this entry", url);
}
}
} catch (FileNotFoundException e) {
LOG.error("file not found: {}\nContinuing without context map", contextMap);
}
return Map.copyOf(assets); // makes map unmodifiable
}

/**
* Tells whether this instance has a local version for a URI.
* @param uri - the remote URI
* @return - <code>true</code> if a local version is available.
*/
public boolean hasLocal(URI uri) {
return contextMapping.containsKey(uri);
}
}
Loading