Skip to content

Commit 2b65ce0

Browse files
acoburntimea-solid
andauthored
JCL-279: Cache abstraction with Guava and Caffeine implementations (#410)
* JCL-279: Cache abstraction with Guava and Caffeine implementations * OpenId integration * UMA integration * Examples integration * Archetype integration * Access Grant integration * Adjust cache creation definition --------- Co-authored-by: Timea <4144203+timea-solid@users.noreply.github.com>
1 parent 9f65aa1 commit 2b65ce0

File tree

30 files changed

+931
-57
lines changed

30 files changed

+931
-57
lines changed

access-grant/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@
5555
<version>${project.version}</version>
5656
<scope>test</scope>
5757
</dependency>
58+
<dependency>
59+
<groupId>com.inrupt.client</groupId>
60+
<artifactId>inrupt-client-caffeine</artifactId>
61+
<version>${project.version}</version>
62+
<scope>test</scope>
63+
</dependency>
5864
<dependency>
5965
<groupId>com.inrupt.client</groupId>
6066
<artifactId>inrupt-client-core</artifactId>

access-grant/src/main/java/com/inrupt/client/accessgrant/AccessGrantClient.java

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import static java.nio.charset.StandardCharsets.UTF_8;
2424

2525
import com.inrupt.client.Client;
26+
import com.inrupt.client.ClientCache;
2627
import com.inrupt.client.ClientProvider;
2728
import com.inrupt.client.Request;
2829
import com.inrupt.client.Response;
@@ -36,6 +37,7 @@
3637
import java.io.InputStream;
3738
import java.io.UncheckedIOException;
3839
import java.net.URI;
40+
import java.time.Duration;
3941
import java.time.Instant;
4042
import java.util.ArrayList;
4143
import java.util.Arrays;
@@ -92,6 +94,7 @@ public class AccessGrantClient {
9294
private static final Set<String> ACCESS_GRANT_TYPES = getAccessGrantTypes();
9395

9496
private final Client client;
97+
private final ClientCache<URI, Metadata> metadataCache;
9598
private final JsonService jsonService;
9699
private final AccessGrantConfiguration config;
97100

@@ -107,23 +110,38 @@ public AccessGrantClient(final URI issuer) {
107110
/**
108111
* Create an access grant client.
109112
*
110-
* @param issuer the issuer
111113
* @param client the client
114+
* @param issuer the issuer
112115
*/
113116
public AccessGrantClient(final Client client, final URI issuer) {
114-
this(client, new AccessGrantConfiguration(issuer));
117+
this(client, ServiceProvider.getCacheBuilder().build(100, Duration.ofMinutes(60)),
118+
new AccessGrantConfiguration(issuer));
119+
}
120+
121+
/**
122+
* Create an access grant client.
123+
*
124+
* @param client the client
125+
* @param issuer the issuer
126+
* @param metadataCache the metadata cache
127+
*/
128+
public AccessGrantClient(final Client client, final URI issuer, final ClientCache<URI, Metadata> metadataCache) {
129+
this(client, metadataCache, new AccessGrantConfiguration(issuer));
115130
}
116131

117132
/**
118133
* Create an access grant client.
119134
*
120135
* @param client the client
136+
* @param metadataCache the metadata cache
121137
* @param config the access grant configuration
122138
*/
123139
// This ctor may be made public at a later point
124-
private AccessGrantClient(final Client client, final AccessGrantConfiguration config) {
125-
this.client = Objects.requireNonNull(client);
126-
this.config = Objects.requireNonNull(config);
140+
private AccessGrantClient(final Client client, final ClientCache<URI, Metadata> metadataCache,
141+
final AccessGrantConfiguration config) {
142+
this.client = Objects.requireNonNull(client, "client may not be null!");
143+
this.config = Objects.requireNonNull(config, "config may not be null!");
144+
this.metadataCache = Objects.requireNonNull(metadataCache, "metadataCache may not be null!");
127145
this.jsonService = ServiceProvider.getJsonService();
128146
}
129147

@@ -135,7 +153,7 @@ private AccessGrantClient(final Client client, final AccessGrantConfiguration co
135153
*/
136154
public AccessGrantClient session(final Session session) {
137155
Objects.requireNonNull(session, "Session may not be null!");
138-
return new AccessGrantClient(client.session(session), config);
156+
return new AccessGrantClient(client.session(session), metadataCache, config);
139157
}
140158

141159
/**
@@ -346,6 +364,11 @@ List<AccessGrant> processQueryResponse(final InputStream input, final Set<String
346364

347365
CompletionStage<Metadata> v1Metadata() {
348366
final URI uri = URIBuilder.newBuilder(config.getIssuer()).path(".well-known/vc-configuration").build();
367+
final Metadata cached = metadataCache.get(uri);
368+
if (cached != null) {
369+
return CompletableFuture.completedFuture(cached);
370+
}
371+
349372
final Request req = Request.newBuilder(uri).header("Accept", APPLICATION_JSON).build();
350373
return client.send(req, Response.BodyHandlers.ofInputStream())
351374
.thenApply(res -> {
@@ -369,6 +392,7 @@ CompletionStage<Metadata> v1Metadata() {
369392
m.issueEndpoint = asUri(metadata.get("issuerService"));
370393
m.verifyEndpoint = asUri(metadata.get("verifierService"));
371394
m.statusEndpoint = asUri(metadata.get("statusService"));
395+
metadataCache.put(uri, m);
372396
return m;
373397
});
374398
}

access-grant/src/test/java/com/inrupt/client/accessgrant/AccessGrantClientTest.java

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,13 @@ class AccessGrantClientTest {
6565
private static final URI ACCESS_REQUEST = URI.create("http://www.w3.org/ns/solid/vc#SolidAccessRequest");
6666

6767
private static final MockAccessGrantServer mockServer = new MockAccessGrantServer();
68+
private static AccessGrantClient agClient;
6869
private static URI baseUri;
6970

7071
@BeforeAll
7172
static void setup() {
7273
baseUri = URI.create(mockServer.start());
74+
agClient = new AccessGrantClient(baseUri);
7375
}
7476

7577
@AfterAll
@@ -93,8 +95,7 @@ void testFetch1() {
9395
claims.put("azp", AZP);
9496
final String token = generateIdToken(claims);
9597
final URI uri = URIBuilder.newBuilder(baseUri).path("access-grant-1").build();
96-
final AccessGrantClient client = new AccessGrantClient(baseUri)
97-
.session(OpenIdSession.ofIdToken(token));
98+
final AccessGrantClient client = agClient.session(OpenIdSession.ofIdToken(token));
9899
final AccessGrant grant = client.fetch(uri).toCompletableFuture().join();
99100

100101
assertEquals(uri, grant.getIdentifier());
@@ -116,8 +117,7 @@ void testFetch2() {
116117
claims.put("azp", AZP);
117118
final String token = generateIdToken(claims);
118119
final URI uri = URIBuilder.newBuilder(baseUri).path("access-grant-2").build();
119-
final AccessGrantClient client = new AccessGrantClient(baseUri)
120-
.session(OpenIdSession.ofIdToken(token));
120+
final AccessGrantClient client = agClient.session(OpenIdSession.ofIdToken(token));
121121
final AccessGrant grant = client.fetch(uri).toCompletableFuture().join();
122122

123123
assertEquals(uri, grant.getIdentifier());
@@ -143,8 +143,7 @@ void testFetch6() {
143143
claims.put("azp", AZP);
144144
final String token = generateIdToken(claims);
145145
final URI uri = URIBuilder.newBuilder(baseUri).path("access-grant-6").build();
146-
final AccessGrantClient client = new AccessGrantClient(baseUri)
147-
.session(OpenIdSession.ofIdToken(token));
146+
final AccessGrantClient client = agClient.session(OpenIdSession.ofIdToken(token));
148147
final AccessGrant grant = client.fetch(uri).toCompletableFuture().join();
149148

150149
assertEquals(uri, grant.getIdentifier());
@@ -168,35 +167,32 @@ void testNotAccessGrant() {
168167
claims.put("azp", AZP);
169168
final String token = generateIdToken(claims);
170169
final URI uri = URIBuilder.newBuilder(baseUri).path("vc-3").build();
171-
final AccessGrantClient client = new AccessGrantClient(baseUri)
172-
.session(OpenIdSession.ofIdToken(token));
170+
final AccessGrantClient client = agClient.session(OpenIdSession.ofIdToken(token));
173171
final CompletionException err = assertThrows(CompletionException.class,
174172
client.fetch(uri).toCompletableFuture()::join);
175173
}
176174

177175
@Test
178176
void testFetchInvalid() {
179177
final URI uri = URIBuilder.newBuilder(baseUri).path(".well-known/vc-configuration").build();
180-
final AccessGrantClient client = new AccessGrantClient(baseUri);
181178
final CompletionException err = assertThrows(CompletionException.class,
182-
client.fetch(uri).toCompletableFuture()::join);
179+
agClient.fetch(uri).toCompletableFuture()::join);
183180

184181
assertTrue(err.getCause() instanceof AccessGrantException);
185182
}
186183

187184
@Test
188185
void testFetchNotFound() {
189186
final URI uri = URIBuilder.newBuilder(baseUri).path("not-found").build();
190-
final AccessGrantClient client = new AccessGrantClient(uri);
191187
final CompletionException err1 = assertThrows(CompletionException.class,
192-
client.fetch(uri).toCompletableFuture()::join);
188+
agClient.fetch(uri).toCompletableFuture()::join);
193189

194190
assertTrue(err1.getCause() instanceof AccessGrantException);
195191

196192
final URI agent = URI.create("https://id.test/agent");
197193

198194
final CompletionException err2 = assertThrows(CompletionException.class,
199-
client.issue(ACCESS_GRANT, agent, Collections.emptySet(), Collections.emptySet(),
195+
agClient.issue(ACCESS_GRANT, agent, Collections.emptySet(), Collections.emptySet(),
200196
Collections.emptySet(), Instant.now()).toCompletableFuture()::join);
201197
assertTrue(err2.getCause() instanceof AccessGrantException);
202198
}
@@ -209,8 +205,7 @@ void testIssueGrant() {
209205
claims.put("iss", ISS);
210206
claims.put("azp", AZP);
211207
final String token = generateIdToken(claims);
212-
final AccessGrantClient client = new AccessGrantClient(baseUri)
213-
.session(OpenIdSession.ofIdToken(token));
208+
final AccessGrantClient client = agClient.session(OpenIdSession.ofIdToken(token));
214209

215210
final URI agent = URI.create("https://id.test/agent");
216211
final Instant expiration = Instant.parse("2022-08-27T12:00:00Z");
@@ -238,8 +233,7 @@ void testIssueRequest() {
238233
claims.put("iss", ISS);
239234
claims.put("azp", AZP);
240235
final String token = generateIdToken(claims);
241-
final AccessGrantClient client = new AccessGrantClient(baseUri)
242-
.session(OpenIdSession.ofIdToken(token));
236+
final AccessGrantClient client = agClient.session(OpenIdSession.ofIdToken(token));
243237

244238
final URI agent = URI.create("https://id.test/agent");
245239
final Instant expiration = Instant.parse("2022-08-27T12:00:00Z");
@@ -261,16 +255,14 @@ void testIssueRequest() {
261255

262256
@Test
263257
void testIssueNoAuth() {
264-
final AccessGrantClient client = new AccessGrantClient(baseUri);
265-
266258
final URI agent = URI.create("https://id.test/agent");
267259
final Instant expiration = Instant.parse("2022-08-27T12:00:00Z");
268260
final Set<String> modes = new HashSet<>(Arrays.asList("Read", "Append"));
269261
final Set<String> purposes = Collections.singleton("https://purpose.test/Purpose1");
270262

271263
final Set<URI> resources = Collections.singleton(URI.create("https://storage.test/data/"));
272264
final CompletionException err = assertThrows(CompletionException.class, () ->
273-
client.issue(ACCESS_GRANT, agent, resources, modes, purposes, expiration)
265+
agClient.issue(ACCESS_GRANT, agent, resources, modes, purposes, expiration)
274266
.toCompletableFuture().join());
275267
assertTrue(err.getCause() instanceof AccessGrantException);
276268
}
@@ -283,8 +275,7 @@ void testIssueOther() {
283275
claims.put("iss", ISS);
284276
claims.put("azp", AZP);
285277
final String token = generateIdToken(claims);
286-
final AccessGrantClient client = new AccessGrantClient(baseUri)
287-
.session(OpenIdSession.ofIdToken(token));
278+
final AccessGrantClient client = agClient.session(OpenIdSession.ofIdToken(token));
288279

289280
final URI agent = URI.create("https://id.test/agent");
290281
final Instant expiration = Instant.parse("2022-08-27T12:00:00Z");
@@ -306,8 +297,7 @@ void testQueryGrant() {
306297
claims.put("iss", ISS);
307298
claims.put("azp", AZP);
308299
final String token = generateIdToken(claims);
309-
final AccessGrantClient client = new AccessGrantClient(baseUri)
310-
.session(OpenIdSession.ofIdToken(token));
300+
final AccessGrantClient client = agClient.session(OpenIdSession.ofIdToken(token));
311301

312302
final List<AccessGrant> grants = client.query(URI.create("SolidAccessGrant"), null,
313303
URI.create("https://storage.example/e973cc3d-5c28-4a10-98c5-e40079289358/a/b/c"), "Read")
@@ -323,8 +313,7 @@ void testQueryRequest() {
323313
claims.put("iss", ISS);
324314
claims.put("azp", AZP);
325315
final String token = generateIdToken(claims);
326-
final AccessGrantClient client = new AccessGrantClient(baseUri)
327-
.session(OpenIdSession.ofIdToken(token));
316+
final AccessGrantClient client = agClient.session(OpenIdSession.ofIdToken(token));
328317

329318
final List<AccessGrant> grants = client.query(URI.create("SolidAccessRequest"), null,
330319
URI.create("https://storage.example/f1759e6d-4dda-4401-be61-d90d070a5474/a/b/c"), "Read")
@@ -334,10 +323,8 @@ void testQueryRequest() {
334323

335324
@Test
336325
void testQueryInvalidAuth() {
337-
final AccessGrantClient client = new AccessGrantClient(baseUri);
338-
339326
final CompletionException err = assertThrows(CompletionException.class,
340-
client.query(URI.create("SolidAccessGrant"), null, null, null).toCompletableFuture()::join);
327+
agClient.query(URI.create("SolidAccessGrant"), null, null, null).toCompletableFuture()::join);
341328

342329
assertTrue(err.getCause() instanceof AccessGrantException);
343330
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2023 Inrupt Inc.
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal in
6+
* the Software without restriction, including without limitation the rights to use,
7+
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
8+
* Software, and to permit persons to whom the Software is furnished to do so,
9+
* subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
16+
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
17+
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
19+
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
*/
21+
package com.inrupt.client;
22+
23+
/**
24+
* A generic caching abstraction for use in the Inrupt Client Libraries.
25+
*
26+
* @param <T> the key type
27+
* @param <U> the value type
28+
*/
29+
public interface ClientCache<T, U> {
30+
31+
/**
32+
* Retrieve a cached value.
33+
*
34+
* @param key the key
35+
* @return the cached value, may be {@code null} if not present
36+
*/
37+
U get(T key);
38+
39+
/**
40+
* Set a cached value.
41+
*
42+
* @param key the key, not {@code null}
43+
* @param value the value, not {@code null}
44+
*/
45+
void put(T key, U value);
46+
47+
/**
48+
* Invalidate a single cached value.
49+
*
50+
* @param key the key, not {@code null}
51+
*/
52+
void invalidate(T key);
53+
54+
/**
55+
* Invalidate all values in the cache.
56+
*/
57+
void invalidateAll();
58+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2023 Inrupt Inc.
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal in
6+
* the Software without restriction, including without limitation the rights to use,
7+
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
8+
* Software, and to permit persons to whom the Software is furnished to do so,
9+
* subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
16+
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
17+
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
19+
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
*/
21+
package com.inrupt.client.spi;
22+
23+
import com.inrupt.client.ClientCache;
24+
25+
import java.time.Duration;
26+
27+
/**
28+
* A cache builder abstraction for use with different cache implementations.
29+
*/
30+
public interface CacheBuilderService {
31+
32+
/**
33+
* Build a cache.
34+
*
35+
* @param maximumSize the maximum cache size
36+
* @param expiration the duration after which items should expire from the cache
37+
* @param <T> the key type
38+
* @param <U> the value type
39+
* @return the cache
40+
*/
41+
<T, U> ClientCache<T, U> build(int maximumSize, Duration expiration);
42+
43+
}

0 commit comments

Comments
 (0)