Skip to content

Commit

Permalink
feat: Cache interface now extends Closeable
Browse files Browse the repository at this point in the history
  • Loading branch information
nstdio committed Apr 16, 2022
1 parent 0692c14 commit 2e9076a
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 20 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file.

## [Unreleased](https://github.com/nstdio/http-client-ext/compare/v2.1.2...HEAD)

### ♻️ Improvements
- Improve cache write. ([b7f1289](https://github.com/nstdio/http-client-ext/commit/b7f128900372ee033aaef79b11ab09ed65f5c0ce))
## [v2.1.2](https://github.com/nstdio/http-client-ext/compare/v2.1.1...v2.1.2)

### 🐞 Bug Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ tasks.withType<JacocoReport> {
html.required.set(!isCI)
}

afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.map {
fileTree(it).apply { exclude("io/**/NullCache.class") }
}))
}

executionData(tasks.withType<Test>())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ dependencies {

testImplementation("org.slf4j:slf4j-simple:$slf4jVersion")

testImplementation("org.awaitility:awaitility:4.2.0")
testImplementation("org.awaitility:awaitility-kotlin:4.2.0")

testImplementation("nl.jqno.equalsverifier:equalsverifier:3.10")
testImplementation("com.github.tomakehurst:wiremock-jre8:2.33.1")
Expand Down
11 changes: 10 additions & 1 deletion src/main/java/io/github/nstdio/http/ext/Cache.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import java.io.Closeable;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse.BodySubscriber;
import java.nio.ByteBuffer;
Expand All @@ -36,7 +37,7 @@
import static java.util.Objects.requireNonNull;

@SuppressWarnings("WeakerAccess")
public interface Cache {
public interface Cache extends Closeable {
/**
* Creates a new {@code InMemoryCacheBuilder} instance.
*
Expand Down Expand Up @@ -146,7 +147,15 @@ default long bodySize() {
}
}

/**
* The strategy for creating cache instances.
*/
interface CacheBuilder {
/**
* Creates a {@code Cache} instance.
*
* @return a cache.
*/
Cache build();
}

Expand Down
20 changes: 17 additions & 3 deletions src/main/java/io/github/nstdio/http/ext/DiskCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Flow.Subscriber;
import java.util.concurrent.Flow.Subscription;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
Expand All @@ -40,7 +41,7 @@
class DiskCache extends SizeConstrainedCache {
private final MetadataSerializer metadataSerializer;
private final StreamFactory streamFactory;
private final Executor executor;
private final ExecutorService executor;
private final Path dir;

DiskCache(long maxBytes, int maxItems, MetadataSerializer metadataSerializer, StreamFactory streamFactory, Path dir) {
Expand Down Expand Up @@ -86,8 +87,21 @@ public void put(HttpRequest request, CacheEntry entry) {
writeMetadata((DiskCacheEntry) entry);
}

@Override
public void close() {
super.close();
executor.shutdown();
try {
//noinspection ResultOfMethodCallIgnored
executor.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException ignored) {
}
}

private void writeMetadata(DiskCacheEntry diskEntry) {
executor.execute(() -> metadataSerializer.write(diskEntry.metadata(), diskEntry.path().metadata()));
CacheEntryMetadata metadata = diskEntry.metadata();
Path metadataPath = diskEntry.path().metadata();
executor.execute(() -> metadataSerializer.write(metadata, metadataPath));
}

private void deleteQuietly(CacheEntry entry) {
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/io/github/nstdio/http/ext/FilteringCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package io.github.nstdio.http.ext;

import java.io.IOException;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse.ResponseInfo;
import java.util.function.Predicate;
Expand Down Expand Up @@ -81,4 +82,9 @@ public <T> Writer<T> writer(CacheEntryMetadata metadata) {

return blackhole();
}

@Override
public void close() throws IOException {
delegate.close();
}
}
6 changes: 6 additions & 0 deletions src/main/java/io/github/nstdio/http/ext/NullCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package io.github.nstdio.http.ext;

import java.io.IOException;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodySubscribers;
Expand Down Expand Up @@ -80,4 +81,9 @@ public CacheStats stats() {
public <T> Writer<T> writer(CacheEntryMetadata metadata) {
return blackhole();
}

@Override
public void close() throws IOException {
// intentional noop
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ public CacheStats stats() {
return stats;
}

@Override
public void close() {
cache.clear();
}

private void putInternal(HttpRequest k, CacheEntry e) {
size += e.bodySize();
cache.putSingle(k.uri(), e, idxFn(k));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package io.github.nstdio.http.ext;

import java.io.IOException;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.function.Consumer;
Expand Down Expand Up @@ -76,4 +77,9 @@ public Consumer<T> finisher() {
}
};
}

@Override
public synchronized void close() throws IOException {
delegate.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,19 @@ import com.github.tomakehurst.wiremock.core.WireMockConfiguration
import com.github.tomakehurst.wiremock.junit5.WireMockExtension
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo
import io.github.nstdio.http.ext.Assertions.assertThat
import org.awaitility.kotlin.await
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
import java.io.File
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse.BodyHandlers.discarding
import java.net.http.HttpResponse.BodyHandlers.ofByteArray
import java.nio.file.Files
import java.time.Clock
import java.util.concurrent.TimeUnit.SECONDS
import javax.crypto.SecretKey

internal class DiskExtendedHttpClientIntegrationTest : ExtendedHttpClientContract {
Expand Down Expand Up @@ -63,31 +66,49 @@ internal class DiskExtendedHttpClientIntegrationTest : ExtendedHttpClientContrac
@Test
fun `Should restore cache`() {
//given
stubFor(
get(urlPathMatching("/any/[0-9]+"))
.willReturn(
ok()
.withHeader("Cache-Control", "max-age=86400")
.withBody("abc")
)
)

val intRange = 0..64
val requests = intRange
.map { "${wm.baseUrl()}/any/$it".toUri() }
.map { HttpRequest.newBuilder(it).build() }
.toList()
stubNumericCached()

val requests = httpRequests(0..64).toList()
requests.map { client.send(it, ofByteArray()) }.forEach { it.body() }

//when
val newClient = ExtendedHttpClient(delegate, createCache(), Clock.systemUTC())
val responses = requests.map { newClient.send(it, ofByteArray()) }.toList()
val responses = requests.map { newClient.send(it, ofByteArray()) }

//then
responses.forEach { assertThat(it).isCached }
}

@Test
fun `Should close cache`() {
//given
stubNumericCached()

//when
cache.use {
httpRequests()
.forEach { client.send(it, discarding()).body() }
}

//then
await.atMost(1, SECONDS).until { cacheDir.listFiles()?.isEmpty() }
}

private fun stubNumericCached() {
stubFor(
get(urlPathMatching("/[0-9]+"))
.willReturn(
ok()
.withHeader("Cache-Control", "max-age=86400")
.withBody("abc")
)
)
}

private fun httpRequests(range: IntRange = 0..8) = range
.map { "${wm.baseUrl()}/$it".toUri() }
.map { HttpRequest.newBuilder(it).build() }

private fun createCache() = Cache.newDiskCacheBuilder()
.dir(cacheDir.toPath())
.encrypted()
Expand Down
35 changes: 35 additions & 0 deletions src/test/kotlin/io/github/nstdio/http/ext/FilteringCacheTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2022 Edgar Asatryan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.nstdio.http.ext

import org.junit.jupiter.api.Test
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify

internal class FilteringCacheTest {
@Test
fun `Should call delegate`() {
//given
val mock = mock(Cache::class.java)
val cache = FilteringCache(mock, { true }, { true })

//when
cache.close()

//then
verify(mock).close()
}
}

0 comments on commit 2e9076a

Please sign in to comment.