From e9b3ee1ea635deb18c84c3102b15127181fb2dfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0koda?= Date: Fri, 29 Dec 2023 14:47:32 +0100 Subject: [PATCH 01/10] Use context cache for imported contexts #292 --- .../jsonld/context/ActiveContextBuilder.java | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/apicatalog/jsonld/context/ActiveContextBuilder.java b/src/main/java/com/apicatalog/jsonld/context/ActiveContextBuilder.java index 8e38489d..5c3b18ff 100644 --- a/src/main/java/com/apicatalog/jsonld/context/ActiveContextBuilder.java +++ b/src/main/java/com/apicatalog/jsonld/context/ActiveContextBuilder.java @@ -233,27 +233,35 @@ public ActiveContext create(final JsonValue localContext, final URI baseUrl) thr throw new JsonLdError(JsonLdErrorCode.LOADING_REMOTE_CONTEXT_FAILED); } - final DocumentLoaderOptions loaderOptions = new DocumentLoaderOptions(); - loaderOptions.setProfile(ProfileConstants.CONTEXT); - loaderOptions.setRequestProfile(Arrays.asList(loaderOptions.getProfile())); - - JsonStructure importedStructure = null; + JsonValue importedStructure; + final String contextImportUriKey = contextImportUri.toString(); + if (activeContext.runtime().getContextCache() != null + && activeContext.runtime().getContextCache().containsKey(contextImportUriKey)) { + importedStructure = activeContext.runtime().getContextCache().get(contextImportUriKey); + } else { + try { + final DocumentLoaderOptions loaderOptions = new DocumentLoaderOptions(); + loaderOptions.setProfile(ProfileConstants.CONTEXT); + loaderOptions.setRequestProfile(Arrays.asList(loaderOptions.getProfile())); - try { + final Document importedDocument = activeContext.runtime().getDocumentLoader().loadDocument(contextImportUri, loaderOptions); - final Document importedDocument = activeContext.runtime().getDocumentLoader().loadDocument(contextImportUri, loaderOptions); + if (importedDocument == null) { + throw new JsonLdError(JsonLdErrorCode.INVALID_REMOTE_CONTEXT, "Imported context[" + contextImportUri + "] is null."); + } - if (importedDocument == null) { - throw new JsonLdError(JsonLdErrorCode.INVALID_REMOTE_CONTEXT, "Imported context[" + contextImportUri + "] is null."); - } + importedStructure = importedDocument + .getJsonContent() + .orElseThrow(() -> new JsonLdError(JsonLdErrorCode.INVALID_KEYWORD_IMPORT_VALUE)); - importedStructure = importedDocument - .getJsonContent() - .orElseThrow(() -> new JsonLdError(JsonLdErrorCode.INVALID_KEYWORD_IMPORT_VALUE)); + if (activeContext.runtime().getContextCache() != null) { + activeContext.runtime().getContextCache().put(contextImportUriKey, importedStructure.asJsonObject()); + } - // 5.6.5 - } catch (JsonLdError e) { - throw new JsonLdError(JsonLdErrorCode.INVALID_KEYWORD_IMPORT_VALUE, e); + } catch (JsonLdError e) { + // 5.6.5 + throw new JsonLdError(JsonLdErrorCode.INVALID_KEYWORD_IMPORT_VALUE, e); + } } // 5.6.6 From d5418e611add02dd1009b0c1515f815a8516f841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0koda?= Date: Mon, 15 Jan 2024 09:19:25 +0100 Subject: [PATCH 02/10] Revert "Use context cache for imported contexts" This reverts commit e9b3ee1ea635deb18c84c3102b15127181fb2dfe. --- .../jsonld/context/ActiveContextBuilder.java | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/apicatalog/jsonld/context/ActiveContextBuilder.java b/src/main/java/com/apicatalog/jsonld/context/ActiveContextBuilder.java index 5c3b18ff..8e38489d 100644 --- a/src/main/java/com/apicatalog/jsonld/context/ActiveContextBuilder.java +++ b/src/main/java/com/apicatalog/jsonld/context/ActiveContextBuilder.java @@ -233,35 +233,27 @@ public ActiveContext create(final JsonValue localContext, final URI baseUrl) thr throw new JsonLdError(JsonLdErrorCode.LOADING_REMOTE_CONTEXT_FAILED); } - JsonValue importedStructure; - final String contextImportUriKey = contextImportUri.toString(); - if (activeContext.runtime().getContextCache() != null - && activeContext.runtime().getContextCache().containsKey(contextImportUriKey)) { - importedStructure = activeContext.runtime().getContextCache().get(contextImportUriKey); - } else { - try { - final DocumentLoaderOptions loaderOptions = new DocumentLoaderOptions(); - loaderOptions.setProfile(ProfileConstants.CONTEXT); - loaderOptions.setRequestProfile(Arrays.asList(loaderOptions.getProfile())); - - final Document importedDocument = activeContext.runtime().getDocumentLoader().loadDocument(contextImportUri, loaderOptions); + final DocumentLoaderOptions loaderOptions = new DocumentLoaderOptions(); + loaderOptions.setProfile(ProfileConstants.CONTEXT); + loaderOptions.setRequestProfile(Arrays.asList(loaderOptions.getProfile())); - if (importedDocument == null) { - throw new JsonLdError(JsonLdErrorCode.INVALID_REMOTE_CONTEXT, "Imported context[" + contextImportUri + "] is null."); - } + JsonStructure importedStructure = null; - importedStructure = importedDocument - .getJsonContent() - .orElseThrow(() -> new JsonLdError(JsonLdErrorCode.INVALID_KEYWORD_IMPORT_VALUE)); + try { - if (activeContext.runtime().getContextCache() != null) { - activeContext.runtime().getContextCache().put(contextImportUriKey, importedStructure.asJsonObject()); - } + final Document importedDocument = activeContext.runtime().getDocumentLoader().loadDocument(contextImportUri, loaderOptions); - } catch (JsonLdError e) { - // 5.6.5 - throw new JsonLdError(JsonLdErrorCode.INVALID_KEYWORD_IMPORT_VALUE, e); + if (importedDocument == null) { + throw new JsonLdError(JsonLdErrorCode.INVALID_REMOTE_CONTEXT, "Imported context[" + contextImportUri + "] is null."); } + + importedStructure = importedDocument + .getJsonContent() + .orElseThrow(() -> new JsonLdError(JsonLdErrorCode.INVALID_KEYWORD_IMPORT_VALUE)); + + // 5.6.5 + } catch (JsonLdError e) { + throw new JsonLdError(JsonLdErrorCode.INVALID_KEYWORD_IMPORT_VALUE, e); } // 5.6.6 From 67b048970d24929a4455c8dcfa8f45765a4d359a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0koda?= Date: Mon, 15 Jan 2024 12:37:56 +0100 Subject: [PATCH 03/10] Add LRUDocumentCache --- README.md | 13 +++ .../jsonld/loader/LRUDocumentCache.java | 53 +++++++++ .../jsonld/loader/LRUDocumentCacheTest.java | 102 ++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java create mode 100644 src/test/java/com/apicatalog/jsonld/loader/LRUDocumentCacheTest.java diff --git a/README.md b/README.md index 7553c32e..c9c6cb5a 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,19 @@ static DocumentLoader LOADER = HttpLoader.defaultInstance().timeount(Duration.of JsonLd.expand(...).loader(LOADER).get(); ``` +#### Caching +Configure LRU-based cache for loading documents. +The argument determines size of the LRU-cache. +```javascript +JsonLd.toRdf("https://example/document.jsonld").loader(new LRUDocumentCache(12)).get(); +``` + +You can share instance of `LRUDocumentCache` among multiple calls to reuse cached documents. +```javascript +DocumentLoader cachedLoader = new LRUDocumentCache(12); +JsonLd.toRdf("https://example/document.jsonld").loader(cachedLoader).get(); +JsonLd.toRdf("https://example/another-document.jsonld").loader(cachedLoader).get(); +``` ## Contributing diff --git a/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java b/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java new file mode 100644 index 00000000..d36cd5f1 --- /dev/null +++ b/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java @@ -0,0 +1,53 @@ +package com.apicatalog.jsonld.loader; + +import com.apicatalog.jsonld.JsonLdError; +import com.apicatalog.jsonld.context.cache.LruCache; +import com.apicatalog.jsonld.document.Document; + +import java.net.URI; + +public class LRUDocumentCache implements DocumentLoader { + + private final DocumentLoader documentLoader; + + private final LruCache cache; + + private int counter = 0; + + public LRUDocumentCache(int cacheSize) { + this.documentLoader = SchemeRouter.defaultInstance(); + this.cache = new LruCache<>(cacheSize); + } + + public LRUDocumentCache(DocumentLoader documentLoader, int cacheSize) { + this.documentLoader = documentLoader; + this.cache = new LruCache<>(cacheSize); + } + + @Override + public Document loadDocument(URI url, DocumentLoaderOptions options) throws JsonLdError { + String key = computeCacheKey(url, options); + Document result = cache.get(key); + if (result == null) { + result = documentLoader.loadDocument(url, options); + cache.put(key, result); + } + return result; + } + + protected String computeCacheKey(URI url, DocumentLoaderOptions options) { + // We can not use options.hashCode() as it does not return same + // value for objects with same internal values. + String optionsHash = options.getProfile() + ":" + + options.isExtractAllScripts() + ":"; + var profiles = options.getRequestProfile(); + if (profiles == null) { + optionsHash += "null"; + } else { + optionsHash += String.join(",", options.getRequestProfile()); + } + // + return url.toString() + ";" + optionsHash; + } + +} diff --git a/src/test/java/com/apicatalog/jsonld/loader/LRUDocumentCacheTest.java b/src/test/java/com/apicatalog/jsonld/loader/LRUDocumentCacheTest.java new file mode 100644 index 00000000..b2376925 --- /dev/null +++ b/src/test/java/com/apicatalog/jsonld/loader/LRUDocumentCacheTest.java @@ -0,0 +1,102 @@ +package com.apicatalog.jsonld.loader; + +import com.apicatalog.jsonld.JsonLdError; +import com.apicatalog.jsonld.document.Document; +import com.apicatalog.jsonld.document.JsonDocument; +import jakarta.json.JsonValue; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +public class LRUDocumentCacheTest { + + static class Request { + final URI url; + + final DocumentLoaderOptions options; + + public Request(URI url, DocumentLoaderOptions options) { + this.url = url; + this.options = options; + } + + } + + static class DummyLoader implements DocumentLoader { + + public List requests = new ArrayList<>(); + + @Override + public Document loadDocument(URI url, DocumentLoaderOptions options) { + requests.add(new Request(url, options)); + // Return empty document. + return JsonDocument.of(JsonValue.EMPTY_JSON_ARRAY); + } + + } + + @Test + void testLoadDocument() throws JsonLdError { + DummyLoader dummyLoader = new DummyLoader(); + LRUDocumentCache cachedLoader = new LRUDocumentCache(dummyLoader, 2); + + DocumentLoaderOptions options = new DocumentLoaderOptions(); + cachedLoader.loadDocument(URI.create("http://localhost/1"), options); + cachedLoader.loadDocument(URI.create("http://localhost/1"), options); + cachedLoader.loadDocument(URI.create("http://localhost/1"), options); + + // There should be only one call as all other should be cached. + Assertions.assertEquals(1, dummyLoader.requests.size()); + // Make sure valid arguments were passed. + Request request = dummyLoader.requests.get(0); + Assertions.assertEquals("http://localhost/1",request.url.toString()); + Assertions.assertSame(options,request.options); + } + + @Test + void testCacheSize() throws JsonLdError { + DummyLoader dummyLoader = new DummyLoader(); + LRUDocumentCache cachedLoader = new LRUDocumentCache(dummyLoader, 2); + + DocumentLoaderOptions options = new DocumentLoaderOptions(); + cachedLoader.loadDocument(URI.create("http://localhost/1"), options); + cachedLoader.loadDocument(URI.create("http://localhost/1"), options); + + cachedLoader.loadDocument(URI.create("http://localhost/2"), options); + cachedLoader.loadDocument(URI.create("http://localhost/2"), options); + + // There should be only one call as all other should be cached. + Assertions.assertEquals(2, dummyLoader.requests.size()); + + // Request of new resource. + cachedLoader.loadDocument(URI.create("http://localhost/3"), options); + Assertions.assertEquals(3, dummyLoader.requests.size()); + + // Using LRU the first resources should not be in cache anymore. + cachedLoader.loadDocument(URI.create("http://localhost/1"), options); + Assertions.assertEquals(4, dummyLoader.requests.size()); + } + + @Test + void testLoadDocumentsWithDifferentOptions() throws JsonLdError { + DummyLoader dummyLoader = new DummyLoader(); + LRUDocumentCache cachedLoader = new LRUDocumentCache(dummyLoader, 2); + + // Using options with same inside should lead to cache hit. + DocumentLoaderOptions options = new DocumentLoaderOptions(); + cachedLoader.loadDocument(URI.create("http://localhost/1"), options); + DocumentLoaderOptions sameOptions = new DocumentLoaderOptions(); + cachedLoader.loadDocument(URI.create("http://localhost/1"), sameOptions); + Assertions.assertEquals(1, dummyLoader.requests.size()); + + // Use of different options should cause cache miss. + DocumentLoaderOptions differentOptions = new DocumentLoaderOptions(); + differentOptions.setProfile("profile"); + cachedLoader.loadDocument(URI.create("http://localhost/1"), differentOptions); + Assertions.assertEquals(2, dummyLoader.requests.size()); + } + +} From 7f0ab48d945be204727f454d2e80be42b9e97ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0koda?= Date: Mon, 15 Jan 2024 12:47:41 +0100 Subject: [PATCH 04/10] Remove use of var --- .../java/com/apicatalog/jsonld/loader/LRUDocumentCache.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java b/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java index d36cd5f1..f5291010 100644 --- a/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java +++ b/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java @@ -5,6 +5,7 @@ import com.apicatalog.jsonld.document.Document; import java.net.URI; +import java.util.Collection; public class LRUDocumentCache implements DocumentLoader { @@ -40,7 +41,7 @@ protected String computeCacheKey(URI url, DocumentLoaderOptions options) { // value for objects with same internal values. String optionsHash = options.getProfile() + ":" + options.isExtractAllScripts() + ":"; - var profiles = options.getRequestProfile(); + Collection profiles = options.getRequestProfile(); if (profiles == null) { optionsHash += "null"; } else { From 0312ab78d67f115db75ade0bc2b0c26f3a1ce9a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0koda?= Date: Mon, 15 Jan 2024 12:54:27 +0100 Subject: [PATCH 05/10] Remove unused field --- .../java/com/apicatalog/jsonld/loader/LRUDocumentCache.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java b/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java index f5291010..ec44721c 100644 --- a/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java +++ b/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java @@ -13,8 +13,6 @@ public class LRUDocumentCache implements DocumentLoader { private final LruCache cache; - private int counter = 0; - public LRUDocumentCache(int cacheSize) { this.documentLoader = SchemeRouter.defaultInstance(); this.cache = new LruCache<>(cacheSize); From 0ffe652dbc6656ec87588c3d7a3de3a6cc23ae47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0koda?= Date: Mon, 15 Jan 2024 13:15:39 +0100 Subject: [PATCH 06/10] Update test --- .../jsonld/loader/LRUDocumentCacheTest.java | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/test/java/com/apicatalog/jsonld/loader/LRUDocumentCacheTest.java b/src/test/java/com/apicatalog/jsonld/loader/LRUDocumentCacheTest.java index b2376925..9f92ce5c 100644 --- a/src/test/java/com/apicatalog/jsonld/loader/LRUDocumentCacheTest.java +++ b/src/test/java/com/apicatalog/jsonld/loader/LRUDocumentCacheTest.java @@ -25,9 +25,9 @@ public Request(URI url, DocumentLoaderOptions options) { } - static class DummyLoader implements DocumentLoader { + static class RecordRequestLoader implements DocumentLoader { - public List requests = new ArrayList<>(); + final List requests = new ArrayList<>(); @Override public Document loadDocument(URI url, DocumentLoaderOptions options) { @@ -40,8 +40,8 @@ public Document loadDocument(URI url, DocumentLoaderOptions options) { @Test void testLoadDocument() throws JsonLdError { - DummyLoader dummyLoader = new DummyLoader(); - LRUDocumentCache cachedLoader = new LRUDocumentCache(dummyLoader, 2); + RecordRequestLoader loader = new RecordRequestLoader(); + LRUDocumentCache cachedLoader = new LRUDocumentCache(loader, 2); DocumentLoaderOptions options = new DocumentLoaderOptions(); cachedLoader.loadDocument(URI.create("http://localhost/1"), options); @@ -49,17 +49,18 @@ void testLoadDocument() throws JsonLdError { cachedLoader.loadDocument(URI.create("http://localhost/1"), options); // There should be only one call as all other should be cached. - Assertions.assertEquals(1, dummyLoader.requests.size()); + Assertions.assertEquals(1, loader.requests.size()); + // Make sure valid arguments were passed. - Request request = dummyLoader.requests.get(0); + Request request = loader.requests.get(0); Assertions.assertEquals("http://localhost/1",request.url.toString()); Assertions.assertSame(options,request.options); } @Test void testCacheSize() throws JsonLdError { - DummyLoader dummyLoader = new DummyLoader(); - LRUDocumentCache cachedLoader = new LRUDocumentCache(dummyLoader, 2); + RecordRequestLoader loader = new RecordRequestLoader(); + LRUDocumentCache cachedLoader = new LRUDocumentCache(loader, 2); DocumentLoaderOptions options = new DocumentLoaderOptions(); cachedLoader.loadDocument(URI.create("http://localhost/1"), options); @@ -69,34 +70,34 @@ void testCacheSize() throws JsonLdError { cachedLoader.loadDocument(URI.create("http://localhost/2"), options); // There should be only one call as all other should be cached. - Assertions.assertEquals(2, dummyLoader.requests.size()); + Assertions.assertEquals(2, loader.requests.size()); // Request of new resource. cachedLoader.loadDocument(URI.create("http://localhost/3"), options); - Assertions.assertEquals(3, dummyLoader.requests.size()); + Assertions.assertEquals(3, loader.requests.size()); // Using LRU the first resources should not be in cache anymore. cachedLoader.loadDocument(URI.create("http://localhost/1"), options); - Assertions.assertEquals(4, dummyLoader.requests.size()); + Assertions.assertEquals(4, loader.requests.size()); } @Test void testLoadDocumentsWithDifferentOptions() throws JsonLdError { - DummyLoader dummyLoader = new DummyLoader(); - LRUDocumentCache cachedLoader = new LRUDocumentCache(dummyLoader, 2); + RecordRequestLoader loader = new RecordRequestLoader(); + LRUDocumentCache cachedLoader = new LRUDocumentCache(loader, 2); // Using options with same inside should lead to cache hit. DocumentLoaderOptions options = new DocumentLoaderOptions(); cachedLoader.loadDocument(URI.create("http://localhost/1"), options); DocumentLoaderOptions sameOptions = new DocumentLoaderOptions(); cachedLoader.loadDocument(URI.create("http://localhost/1"), sameOptions); - Assertions.assertEquals(1, dummyLoader.requests.size()); + Assertions.assertEquals(1, loader.requests.size()); // Use of different options should cause cache miss. DocumentLoaderOptions differentOptions = new DocumentLoaderOptions(); differentOptions.setProfile("profile"); cachedLoader.loadDocument(URI.create("http://localhost/1"), differentOptions); - Assertions.assertEquals(2, dummyLoader.requests.size()); + Assertions.assertEquals(2, loader.requests.size()); } } From 2779574f19877521244ec3ee0fe9313499829d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0koda?= Date: Wed, 17 Jan 2024 17:14:29 +0100 Subject: [PATCH 07/10] Remove LRUDocumentCache constrcutor --- README.md | 4 ++-- .../java/com/apicatalog/jsonld/loader/LRUDocumentCache.java | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c9c6cb5a..ca93ed41 100644 --- a/README.md +++ b/README.md @@ -174,11 +174,11 @@ static DocumentLoader LOADER = HttpLoader.defaultInstance().timeount(Duration.of JsonLd.expand(...).loader(LOADER).get(); ``` -#### Caching +#### Document caching Configure LRU-based cache for loading documents. The argument determines size of the LRU-cache. ```javascript -JsonLd.toRdf("https://example/document.jsonld").loader(new LRUDocumentCache(12)).get(); +JsonLd.toRdf("https://example/document.jsonld").loader(new LRUDocumentCache(loader, 12)).get(); ``` You can share instance of `LRUDocumentCache` among multiple calls to reuse cached documents. diff --git a/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java b/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java index ec44721c..7ce62979 100644 --- a/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java +++ b/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java @@ -13,11 +13,6 @@ public class LRUDocumentCache implements DocumentLoader { private final LruCache cache; - public LRUDocumentCache(int cacheSize) { - this.documentLoader = SchemeRouter.defaultInstance(); - this.cache = new LruCache<>(cacheSize); - } - public LRUDocumentCache(DocumentLoader documentLoader, int cacheSize) { this.documentLoader = documentLoader; this.cache = new LruCache<>(cacheSize); From 5022bbd815481a014b3aeeae6a82547e7b8cba34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0koda?= Date: Mon, 22 Jan 2024 09:43:13 +0100 Subject: [PATCH 08/10] Update cache to use equals and hashCode --- .../jsonld/loader/DocumentLoaderOptions.java | 48 +++++++++++++++++ .../jsonld/loader/LRUDocumentCache.java | 51 ++++++++++++------ .../jsonld/loader/LRUDocumentCacheTest.java | 54 +++++++++++++++++++ 3 files changed, 137 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/apicatalog/jsonld/loader/DocumentLoaderOptions.java b/src/main/java/com/apicatalog/jsonld/loader/DocumentLoaderOptions.java index 220ced08..3f66f012 100644 --- a/src/main/java/com/apicatalog/jsonld/loader/DocumentLoaderOptions.java +++ b/src/main/java/com/apicatalog/jsonld/loader/DocumentLoaderOptions.java @@ -15,7 +15,10 @@ */ package com.apicatalog.jsonld.loader; +import java.util.Arrays; import java.util.Collection; +import java.util.Iterator; +import java.util.Objects; /** * The {@link DocumentLoaderOptions} is used to pass various options to the @@ -64,4 +67,49 @@ public void setRequestProfile(Collection requestProfile) { this.requestProfile = requestProfile; } + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + DocumentLoaderOptions options = (DocumentLoaderOptions) other; + if (extractAllScripts != options.extractAllScripts || + !Objects.equals(profile, options.profile)) { + return false; + } + // We need to deal with the collection of profiles. + // We assume that the order does matter. + if (requestProfile == null && options.requestProfile == null) { + // They are bot null. + return true; + } + if (requestProfile == null || options.requestProfile == null) { + // Only one is null. + return false; + } + if (requestProfile.size() != options.requestProfile.size()) { + // Different size. + return false; + } + // We need to be sure the content is the same. + Iterator thisIterator = requestProfile.iterator(); + Iterator otherIterator = options.requestProfile.iterator(); + while (thisIterator.hasNext() && otherIterator.hasNext()) { + if (!Objects.equals(thisIterator.next(), otherIterator.next())) { + // One value is not the same. + return false; + } + } + // We have not found a difference thus they are the same. + return true; + } + + @Override + public int hashCode() { + return Objects.hash(extractAllScripts, profile, requestProfile); + } + } diff --git a/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java b/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java index 7ce62979..1cc46269 100644 --- a/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java +++ b/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java @@ -5,13 +5,43 @@ import com.apicatalog.jsonld.document.Document; import java.net.URI; -import java.util.Collection; +import java.util.Objects; public class LRUDocumentCache implements DocumentLoader { + protected static class CacheKey { + + private final URI url; + + private final DocumentLoaderOptions options; + + public CacheKey(URI url, DocumentLoaderOptions options) { + this.url = url; + this.options = options; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + CacheKey cacheKey = (CacheKey) other; + return Objects.equals(url, cacheKey.url) && + Objects.equals(options, cacheKey.options); + } + + @Override + public int hashCode() { + return Objects.hash(url, options); + } + } + private final DocumentLoader documentLoader; - private final LruCache cache; + private final LruCache cache; public LRUDocumentCache(DocumentLoader documentLoader, int cacheSize) { this.documentLoader = documentLoader; @@ -20,7 +50,7 @@ public LRUDocumentCache(DocumentLoader documentLoader, int cacheSize) { @Override public Document loadDocument(URI url, DocumentLoaderOptions options) throws JsonLdError { - String key = computeCacheKey(url, options); + Object key = createCacheKey(url, options); Document result = cache.get(key); if (result == null) { result = documentLoader.loadDocument(url, options); @@ -29,19 +59,8 @@ public Document loadDocument(URI url, DocumentLoaderOptions options) throws Json return result; } - protected String computeCacheKey(URI url, DocumentLoaderOptions options) { - // We can not use options.hashCode() as it does not return same - // value for objects with same internal values. - String optionsHash = options.getProfile() + ":" + - options.isExtractAllScripts() + ":"; - Collection profiles = options.getRequestProfile(); - if (profiles == null) { - optionsHash += "null"; - } else { - optionsHash += String.join(",", options.getRequestProfile()); - } - // - return url.toString() + ";" + optionsHash; + protected Object createCacheKey(URI url, DocumentLoaderOptions options){ + return new CacheKey(url, options); } } diff --git a/src/test/java/com/apicatalog/jsonld/loader/LRUDocumentCacheTest.java b/src/test/java/com/apicatalog/jsonld/loader/LRUDocumentCacheTest.java index 9f92ce5c..5c6a4297 100644 --- a/src/test/java/com/apicatalog/jsonld/loader/LRUDocumentCacheTest.java +++ b/src/test/java/com/apicatalog/jsonld/loader/LRUDocumentCacheTest.java @@ -9,11 +9,16 @@ import java.net.URI; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; import java.util.List; +import java.util.Set; public class LRUDocumentCacheTest { static class Request { + final URI url; final DocumentLoaderOptions options; @@ -100,4 +105,53 @@ void testLoadDocumentsWithDifferentOptions() throws JsonLdError { Assertions.assertEquals(2, loader.requests.size()); } + @Test + void testCachingEqualOptions() throws JsonLdError { + RecordRequestLoader loader = new RecordRequestLoader(); + LRUDocumentCache cachedLoader = new LRUDocumentCache(loader, 2); + DocumentLoaderOptions options = null; + + options = new DocumentLoaderOptions(); + options.setProfile("profile"); + options.setExtractAllScripts(true); + options.setRequestProfile(List.of("first", "second")); + cachedLoader.loadDocument(URI.create("http://localhost/1"), options); + + options = new DocumentLoaderOptions(); + options.setProfile("profile"); + options.setExtractAllScripts(true); + options.setRequestProfile(List.of("first", "second")); + cachedLoader.loadDocument(URI.create("http://localhost/1"), options); + + options = new DocumentLoaderOptions(); + options.setProfile("profile"); + options.setExtractAllScripts(true); + options.setRequestProfile(Arrays.asList("first", "second")); + cachedLoader.loadDocument(URI.create("http://localhost/1"), options); + + Assertions.assertEquals(1, loader.requests.size()); + } + + @Test + void testCachingProfilesOrderMatter() throws JsonLdError { + RecordRequestLoader loader = new RecordRequestLoader(); + LRUDocumentCache cachedLoader = new LRUDocumentCache(loader, 2); + DocumentLoaderOptions options = null; + + options = new DocumentLoaderOptions(); + options.setProfile("profile"); + options.setExtractAllScripts(true); + options.setRequestProfile(List.of("first", "second")); + cachedLoader.loadDocument(URI.create("http://localhost/1"), options); + + options = new DocumentLoaderOptions(); + options.setProfile("profile"); + options.setExtractAllScripts(true); + options.setRequestProfile(List.of("second", "first")); + cachedLoader.loadDocument(URI.create("http://localhost/1"), options); + + Assertions.assertEquals(2, loader.requests.size()); + } + + } From 0756e9f568a71f1d3cb18141e9cd0ac18bb11f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0koda?= Date: Mon, 22 Jan 2024 09:49:42 +0100 Subject: [PATCH 09/10] Remove use of new syntax --- .../jsonld/loader/LRUDocumentCacheTest.java | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/apicatalog/jsonld/loader/LRUDocumentCacheTest.java b/src/test/java/com/apicatalog/jsonld/loader/LRUDocumentCacheTest.java index 5c6a4297..d2d73c16 100644 --- a/src/test/java/com/apicatalog/jsonld/loader/LRUDocumentCacheTest.java +++ b/src/test/java/com/apicatalog/jsonld/loader/LRUDocumentCacheTest.java @@ -114,19 +114,28 @@ void testCachingEqualOptions() throws JsonLdError { options = new DocumentLoaderOptions(); options.setProfile("profile"); options.setExtractAllScripts(true); - options.setRequestProfile(List.of("first", "second")); + List firstList = new ArrayList<>(); + firstList.add("first"); + firstList.add("second"); + options.setRequestProfile(firstList); cachedLoader.loadDocument(URI.create("http://localhost/1"), options); options = new DocumentLoaderOptions(); options.setProfile("profile"); options.setExtractAllScripts(true); - options.setRequestProfile(List.of("first", "second")); + List secondList = new ArrayList<>(); + secondList.add("first"); + secondList.add("second"); + options.setRequestProfile(secondList); cachedLoader.loadDocument(URI.create("http://localhost/1"), options); options = new DocumentLoaderOptions(); options.setProfile("profile"); options.setExtractAllScripts(true); - options.setRequestProfile(Arrays.asList("first", "second")); + List thirdList = new LinkedList<>(); + thirdList.add("first"); + thirdList.add("second"); + options.setRequestProfile(thirdList); cachedLoader.loadDocument(URI.create("http://localhost/1"), options); Assertions.assertEquals(1, loader.requests.size()); @@ -141,13 +150,19 @@ void testCachingProfilesOrderMatter() throws JsonLdError { options = new DocumentLoaderOptions(); options.setProfile("profile"); options.setExtractAllScripts(true); - options.setRequestProfile(List.of("first", "second")); + List firstList = new ArrayList<>(); + firstList.add("first"); + firstList.add("second"); + options.setRequestProfile(firstList); cachedLoader.loadDocument(URI.create("http://localhost/1"), options); options = new DocumentLoaderOptions(); options.setProfile("profile"); options.setExtractAllScripts(true); - options.setRequestProfile(List.of("second", "first")); + List secondList = new ArrayList<>(); + secondList.add("second"); + secondList.add("first"); + options.setRequestProfile(secondList); cachedLoader.loadDocument(URI.create("http://localhost/1"), options); Assertions.assertEquals(2, loader.requests.size()); From f69f925d1d633288e466706025d6e10396728849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0koda?= Date: Mon, 22 Jan 2024 09:54:19 +0100 Subject: [PATCH 10/10] Resolve code-style issues --- .../apicatalog/jsonld/loader/DocumentLoaderOptions.java | 1 - .../com/apicatalog/jsonld/loader/LRUDocumentCache.java | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/apicatalog/jsonld/loader/DocumentLoaderOptions.java b/src/main/java/com/apicatalog/jsonld/loader/DocumentLoaderOptions.java index 3f66f012..db05eb57 100644 --- a/src/main/java/com/apicatalog/jsonld/loader/DocumentLoaderOptions.java +++ b/src/main/java/com/apicatalog/jsonld/loader/DocumentLoaderOptions.java @@ -15,7 +15,6 @@ */ package com.apicatalog.jsonld.loader; -import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.Objects; diff --git a/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java b/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java index 1cc46269..5d2579db 100644 --- a/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java +++ b/src/main/java/com/apicatalog/jsonld/loader/LRUDocumentCache.java @@ -9,6 +9,10 @@ public class LRUDocumentCache implements DocumentLoader { + private final DocumentLoader documentLoader; + + private final LruCache cache; + protected static class CacheKey { private final URI url; @@ -39,10 +43,6 @@ public int hashCode() { } } - private final DocumentLoader documentLoader; - - private final LruCache cache; - public LRUDocumentCache(DocumentLoader documentLoader, int cacheSize) { this.documentLoader = documentLoader; this.cache = new LruCache<>(cacheSize);