Skip to content

Commit

Permalink
feat: avoid creating temporary files when loading json-ld document ca…
Browse files Browse the repository at this point in the history
…che (#3290)

* feat: avoid creating temporary files when loading json-ld document cache

* fix failing test

* Trigger build

* Print DEPENDENCIES file on ci console
  • Loading branch information
ndr-brt authored Jul 13, 2023
1 parent 034258e commit ada8717
Show file tree
Hide file tree
Showing 10 changed files with 262 additions and 42 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ jobs:
echo "::error file=DEPENDENCIES,title=Rejected Dependencies found::Some dependencies are marked 'rejected', they cannot be used"
exit 1
fi
- name: print expected DEPENDENCIES file
cat DEPENDENCIES-gen
- name: Check for differences
run: |
diff DEPENDENCIES DEPENDENCIES-gen
Expand All @@ -66,4 +68,4 @@ jobs:
run: ./gradlew -Dorg.gradle.jvmargs="-Xmx1g" buildHealth

- name: Dependency analysis report
run: cat build/reports/dependency-analysis/build-health-report.txt
run: cat build/reports/dependency-analysis/build-health-report.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.eclipse.edc.connector.core.base.EdcHttpClientImpl;
import org.eclipse.edc.spi.http.EdcHttpClient;
import org.eclipse.edc.spi.monitor.Monitor;
import org.opentest4j.AssertionFailedError;

import java.io.File;
import java.io.IOException;
Expand All @@ -33,7 +34,6 @@
import java.util.concurrent.TimeUnit;

import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.mock;

public class TestUtils {
Expand All @@ -48,21 +48,29 @@ public class TestUtils {
GRADLE_WRAPPER = (System.getProperty("os.name").toLowerCase().contains("win")) ? GRADLE_WRAPPER_WINDOWS : GRADLE_WRAPPER_UNIX;
}

public static File getFileFromResourceName(String resourceName) {
URI uri = null;
public static URI getResource(String name) {
var resource = Thread.currentThread().getContextClassLoader().getResource(name);
if (resource == null) {
throw new AssertionFailedError("Cannot find resource " + name);
}
try {
uri = Thread.currentThread().getContextClassLoader().getResource(resourceName).toURI();
return resource.toURI();
} catch (URISyntaxException e) {
fail("Cannot proceed without File : " + resourceName);
throw new AssertionFailedError("Cannot find resource " + name, e);
}
}

return new File(uri);
public static File getFileFromResourceName(String resourceName) {
return new File(getResource(resourceName));
}

public static String getResourceFileContentAsString(String resourceName) {
var stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName);
Scanner s = new Scanner(Objects.requireNonNull(stream, "Not found: " + resourceName)).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
try (var stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName)) {
var scanner = new Scanner(Objects.requireNonNull(stream, "Not found: " + resourceName)).useDelimiter("\\A");
return scanner.hasNext() ? scanner.next() : "";
} catch (IOException e) {
throw new RuntimeException(e);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,10 @@
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.net.URI;
import java.net.URISyntaxException;

import static java.lang.String.format;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static org.eclipse.edc.jsonld.spi.Namespaces.DCAT_PREFIX;
import static org.eclipse.edc.jsonld.spi.Namespaces.DCAT_SCHEMA;
import static org.eclipse.edc.jsonld.spi.Namespaces.DCT_PREFIX;
Expand Down Expand Up @@ -93,26 +92,24 @@ public JsonLd createJsonLdService(ServiceExtensionContext context) {
service.registerNamespace(ODRL_PREFIX, ODRL_SCHEMA);
service.registerNamespace(DSPACE_PREFIX, DSPACE_SCHEMA);

getResourceFile("document" + File.separator + "odrl.jsonld")
.onSuccess(file -> service.registerCachedDocument("http://www.w3.org/ns/odrl.jsonld", file))
getResourceUri("document" + File.separator + "odrl.jsonld")
.onSuccess(uri -> service.registerCachedDocument("http://www.w3.org/ns/odrl.jsonld", uri))
.onFailure(failure -> monitor.warning("Failed to register cached json-ld document: " + failure.getFailureDetail()));

return service;
}

@NotNull
private Result<File> getResourceFile(String name) {
try (var stream = getClass().getClassLoader().getResourceAsStream(name)) {
if (stream == null) {
return Result.failure(format("Cannot find resource %s", name));
}
var filename = Path.of(name).getFileName().toString();
var parts = filename.split("\\.");
var tempFile = Files.createTempFile(parts[0], "." + parts[1]);
Files.copy(stream, tempFile, REPLACE_EXISTING);
return Result.success(tempFile.toFile());
} catch (Exception e) {
return Result.failure(format("Cannot read resource %s: ", name));
private Result<URI> getResourceUri(String name) {
var uri = getClass().getClassLoader().getResource(name);
if (uri == null) {
return Result.failure(format("Cannot find resource %s", name));
}

try {
return Result.success(uri.toURI());
} catch (URISyntaxException e) {
return Result.failure(format("Cannot read resource %s: %s", name, e.getMessage()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@
import com.apicatalog.jsonld.loader.HttpLoader;
import com.apicatalog.jsonld.loader.SchemeRouter;
import jakarta.json.JsonObject;
import org.eclipse.edc.jsonld.document.JarLoader;
import org.eclipse.edc.jsonld.spi.JsonLd;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.result.Result;

import java.io.File;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -97,8 +97,8 @@ public void registerNamespace(String prefix, String contextIri) {
}

@Override
public void registerCachedDocument(String contextUrl, File file) {
documentLoader.register(contextUrl, file);
public void registerCachedDocument(String contextUrl, URI uri) {
documentLoader.register(contextUrl, uri);
}

private JsonObject injectVocab(JsonObject json) {
Expand Down Expand Up @@ -126,28 +126,30 @@ private JsonObject createContextObject() {

private static class CachedDocumentLoader implements DocumentLoader {

private final Map<String, File> cache = new HashMap<>();
private final Map<String, URI> cache = new HashMap<>();
private final DocumentLoader loader;

CachedDocumentLoader(JsonLdConfiguration configuration) {
loader = new SchemeRouter()
.set("http", configuration.isHttpEnabled() ? HttpLoader.defaultInstance() : null)
.set("https", configuration.isHttpsEnabled() ? HttpLoader.defaultInstance() : null)
.set("file", new FileLoader());
.set("file", new FileLoader())
.set("jar", new JarLoader());
}

@Override
public Document loadDocument(URI url, DocumentLoaderOptions options) throws JsonLdError {
var uri = Optional.of(url.toString())
.map(cache::get)
.map(File::toURI)
.orElse(url);

return loader.loadDocument(uri, options);
}

public void register(String contextUrl, File file) {
cache.put(contextUrl, file);
public void register(String contextUrl, URI uri) {
cache.put(contextUrl, uri);
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.jsonld.document;

import com.apicatalog.jsonld.JsonLdError;
import com.apicatalog.jsonld.JsonLdErrorCode;
import com.apicatalog.jsonld.StringUtils;
import com.apicatalog.jsonld.document.Document;
import com.apicatalog.jsonld.document.JsonDocument;
import com.apicatalog.jsonld.document.RdfDocument;
import com.apicatalog.jsonld.http.media.MediaType;
import com.apicatalog.jsonld.loader.DocumentLoader;
import com.apicatalog.jsonld.loader.DocumentLoaderOptions;
import org.eclipse.edc.spi.result.Result;
import org.jetbrains.annotations.NotNull;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.NoSuchFileException;
import java.util.Optional;
import java.util.function.Function;

/**
* Enables loading documents from jar files
*/
public class JarLoader implements DocumentLoader {

@Override
public Document loadDocument(URI uri, DocumentLoaderOptions options) throws JsonLdError {
if (!"jar".equalsIgnoreCase(uri.getScheme())) {
throw new JsonLdError(JsonLdErrorCode.LOADING_DOCUMENT_FAILED, "Unsupported URL scheme [" + uri.getScheme() + "]. JarLoader accepts only jar scheme.");
}

try (var is = uri.toURL().openStream()) {
var document = createDocument(uri)
.apply(is)
.orElseThrow(f -> new JsonLdError(JsonLdErrorCode.LOADING_DOCUMENT_FAILED, f.getFailureDetail()));
document.setDocumentUrl(uri);
return document;

} catch (NoSuchFileException | FileNotFoundException e) {
throw new JsonLdError(JsonLdErrorCode.LOADING_DOCUMENT_FAILED, "File not found [" + uri + "]: " + e.getMessage());
} catch (IOException e) {
throw new JsonLdError(JsonLdErrorCode.LOADING_DOCUMENT_FAILED, e);
}
}

@NotNull
private Function<InputStream, Result<Document>> createDocument(URI uri) {
var type = detectedContentType(uri.getSchemeSpecificPart().toLowerCase())
.orElse(MediaType.JSON);

if (JsonDocument.accepts(type)) {
return jsonDocumentResolver(type);
}

if (RdfDocument.accepts(type)) {
return rdfDocumentResolver(type);
}

return s -> Result.failure("cannot read document");
}

@NotNull
private Function<InputStream, Result<Document>> jsonDocumentResolver(MediaType type) {
return stream -> {
try {
return Result.success(JsonDocument.of(type, stream));
} catch (JsonLdError e) {
return Result.failure(e.getMessage());
}
};
}

@NotNull
private Function<InputStream, Result<Document>> rdfDocumentResolver(MediaType type) {
return stream -> {
try {
return Result.success(RdfDocument.of(type, stream));
} catch (JsonLdError e) {
return Result.failure(e.getMessage());
}
};
}

private Optional<MediaType> detectedContentType(String name) {
if (name == null || StringUtils.isBlank(name)) {
return Optional.empty();
}
if (name.endsWith(".nq")) {
return Optional.of(MediaType.N_QUADS);
}
if (name.endsWith(".json")) {
return Optional.of(MediaType.JSON);
}
if (name.endsWith(".jsonld")) {
return Optional.of(MediaType.JSON_LD);
}
if (name.endsWith(".html")) {
return Optional.of(MediaType.HTML);
}

return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import org.junit.jupiter.api.Test;
import org.mockserver.integration.ClientAndServer;

import java.io.File;
import java.net.URI;

import static jakarta.json.Json.createArrayBuilder;
import static jakarta.json.Json.createObjectBuilder;
Expand All @@ -43,8 +43,7 @@ class TitaniumJsonLdTest {

private final int port = getFreePort();
private final ClientAndServer server = startClientAndServer(port);

private final Monitor monitor = mock(Monitor.class);
private final Monitor monitor = mock();

@AfterEach
void tearDown() {
Expand Down Expand Up @@ -150,7 +149,7 @@ void documentResolution_shouldNotCallHttpEndpoint_whenFileContextIsRegistered()
.add("test:key", "value")
.build();
var service = defaultService();
service.registerCachedDocument(contextUrl, getFileFromResourceName("test-context.jsonld"));
service.registerCachedDocument(contextUrl, getFileFromResourceName("test-context.jsonld").toURI());

var expanded = service.expand(jsonObject);

Expand All @@ -172,7 +171,7 @@ void documentResolution_shouldFailByDefault_whenContextIsNotRegisteredAndHttpIsN
.add("test:key", "value")
.build();
var service = defaultService();
service.registerCachedDocument("http//any.other/url", new File("any"));
service.registerCachedDocument("http//any.other/url", URI.create("any:uri"));

var expanded = service.expand(jsonObject);

Expand All @@ -188,7 +187,7 @@ void documentResolution_shouldCallHttpEndpoint_whenContextIsNotRegistered_andHtt
.add("test:key", "value")
.build();
var service = httpEnabledService();
service.registerCachedDocument("http//any.other/url", new File("any"));
service.registerCachedDocument("http//any.other/url", URI.create("any:uri"));

var expanded = service.expand(jsonObject);

Expand Down
Loading

0 comments on commit ada8717

Please sign in to comment.