From 4c01c3ab4235b3c01d03f306d4fd5941c9434e23 Mon Sep 17 00:00:00 2001 From: Krishna Kondaka Date: Tue, 19 Aug 2025 05:59:51 +0000 Subject: [PATCH 1/4] CaffeineCache wrapper package Signed-off-by: Krishna Kondaka --- data-prepper-plugins/common/build.gradle | 3 +- .../common/utils/CaffeineCache.java | 131 ++++++++++ .../common/utils/CaffeineCacheTest.java | 224 ++++++++++++++++++ 3 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 data-prepper-plugins/common/src/main/java/org/opensearch/dataprepper/common/utils/CaffeineCache.java create mode 100644 data-prepper-plugins/common/src/test/java/org/opensearch/dataprepper/common/utils/CaffeineCacheTest.java diff --git a/data-prepper-plugins/common/build.gradle b/data-prepper-plugins/common/build.gradle index 0f2489c0a8..22e43d49e0 100644 --- a/data-prepper-plugins/common/build.gradle +++ b/data-prepper-plugins/common/build.gradle @@ -10,6 +10,7 @@ dependencies { api project(':data-prepper-api') implementation 'com.fasterxml.jackson.core:jackson-databind' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml' + implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8' implementation libs.commons.io implementation 'software.amazon.awssdk:s3' implementation 'software.amazon.awssdk:acm' @@ -37,4 +38,4 @@ jacocoTestCoverageVerification { } } } -} \ No newline at end of file +} diff --git a/data-prepper-plugins/common/src/main/java/org/opensearch/dataprepper/common/utils/CaffeineCache.java b/data-prepper-plugins/common/src/main/java/org/opensearch/dataprepper/common/utils/CaffeineCache.java new file mode 100644 index 0000000000..738fe3503a --- /dev/null +++ b/data-prepper-plugins/common/src/main/java/org/opensearch/dataprepper/common/utils/CaffeineCache.java @@ -0,0 +1,131 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.dataprepper.common.utils; +import com.google.common.annotations.VisibleForTesting; + +import org.opensearch.dataprepper.model.event.Event; + +import com.github.benmanes.caffeine.cache.RemovalCause; +import com.github.benmanes.caffeine.cache.RemovalListener; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.stats.CacheStats; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.Weigher; + +import java.lang.instrument.Instrumentation; +import java.util.concurrent.TimeUnit; +import java.util.Arrays; + +public class CaffeineCache { + + public static final int OVERHEAD_PER_CACHE_ENTRY = 32; + + class ByteArrayValueWeigher implements Weigher { + + @Override + public int weigh(K key, V value) { + return value.toString().length() + key.toString().length(); + } + } + + private final Cache cache; + private final long maxBytes; + private final int maxEntries; + private int curEntries; + private long curBytes; + private boolean strictLRU; + + public CaffeineCache(int maxEntries, long maxBytes, int ttlSeconds, boolean strictLRU) { + this.maxBytes = maxBytes; + this.strictLRU = strictLRU; + this.maxEntries = maxEntries; + Caffeine caffeine; + if (strictLRU) { + caffeine = (Caffeine) Caffeine.newBuilder() + .maximumSize(maxEntries); + } else { + caffeine = (Caffeine) Caffeine.newBuilder() + .maximumWeight(maxBytes) + .weigher(new ByteArrayValueWeigher()); + } + this.cache = caffeine + .expireAfterAccess(ttlSeconds, TimeUnit.SECONDS) + .removalListener((K k, V v, RemovalCause cause) -> { + int valueLength = (v instanceof String) ? ((String)v).length() : + ((Event)v).toJsonString().length(); + curBytes -= getEntrySize(k, v); + curEntries--; + }) + .recordStats() + .build(); + } + + long getEntrySize(K key, V value) { + long size = OVERHEAD_PER_CACHE_ENTRY; + if (key instanceof String) { + size += (long)((String)key).length(); + } else { + size += 8L; + } + size += (value instanceof String) ? ((String)value).length() : + ((Event)value).toJsonString().length(); + return size; + } + + public void put(K key, V value) { + if (!(value instanceof Event) && !(value instanceof String)) { + throw new RuntimeException("Currently only Event/String type values are supported"); + } + if (!(key instanceof String) && !(key instanceof Integer) && !(key instanceof Long)) { + throw new RuntimeException("Currently only String/Integer/Long type keys are supported"); + } + cache.put(key, value); + curEntries++; + curBytes += getEntrySize(key, value); + // run cleanUp to create space by removing any expired entries + if (curEntries > maxEntries/2 || curBytes > maxBytes/2) { + cache.cleanUp(); + } + } + + public V get(K key) { + return cache.getIfPresent(key); + } + + public boolean containsKey(K key) { + return cache.getIfPresent(key) != null; + } + + public void remove(K key) { + V value = cache.getIfPresent(key); + if (value == null) { + return; + } + cache.invalidate(key); + curEntries--; + curBytes += getEntrySize(key, value); + } + + public CacheStats getStats() { + return cache.stats(); + } + + public void clear() { + cache.invalidateAll(); + cache.cleanUp(); + } + + @VisibleForTesting + int getNumEntries() { + return curEntries; + } + + @VisibleForTesting + long getSize() { + return curBytes; + } +} + diff --git a/data-prepper-plugins/common/src/test/java/org/opensearch/dataprepper/common/utils/CaffeineCacheTest.java b/data-prepper-plugins/common/src/test/java/org/opensearch/dataprepper/common/utils/CaffeineCacheTest.java new file mode 100644 index 0000000000..fa5b7adede --- /dev/null +++ b/data-prepper-plugins/common/src/test/java/org/opensearch/dataprepper/common/utils/CaffeineCacheTest.java @@ -0,0 +1,224 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.dataprepper.common.utils; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.opensearch.dataprepper.model.event.Event; +import org.opensearch.dataprepper.model.event.JacksonEvent; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import com.github.benmanes.caffeine.cache.stats.CacheStats; + +import java.util.Map; + +public class CaffeineCacheTest { + static private final String TEST_EVENT_TYPE="event"; + private CaffeineCache cache; + + CaffeineCache createObjectUnderTest(int maxEntries, long maxBytes, int ttlSeconds, boolean strictLRU) { + return new CaffeineCache(maxEntries, maxBytes, ttlSeconds, strictLRU); + } + + @Test + public void test_CaffeineCacheTestWithEvents() { + cache = createObjectUnderTest(100, 100, 10, true); + String key1 = "key1"; + Map data = Map.of("k1", "value1", "k2", "value2"); + Event testEvent1 = JacksonEvent.builder() + .withData(data) + .withEventType(TEST_EVENT_TYPE) + .build(); + String key2 = "key2"; + data = Map.of("k11", "value11", "k22", "value22"); + Event testEvent2 = JacksonEvent.builder() + .withData(data) + .withEventType(TEST_EVENT_TYPE) + .build(); + cache.put(key1, testEvent1); + assertThat(cache.get(key1), equalTo(testEvent1)); + assertThat(cache.getNumEntries(), equalTo(1)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key1, testEvent1))); + CacheStats stats = cache.getStats(); + assertThat(stats.hitCount(), equalTo(1L)); + cache.put(key2, testEvent2); + assertThat(cache.get(key2), equalTo(testEvent2)); + assertThat(cache.getNumEntries(), equalTo(2)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key1, testEvent1) + cache.getEntrySize(key2, testEvent2))); + stats = cache.getStats(); + assertThat(stats.hitCount(), equalTo(2L)); + } + + @Test + public void test_CaffeineCacheBasic_LRUMode() { + cache = createObjectUnderTest(100, 100, 10, true); + String value1 = "value1"; + String key1 = "key1"; + cache.put(key1, value1); + assertThat(cache.get(key1), equalTo(value1)); + assertThat(cache.getNumEntries(), equalTo(1)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key1, value1))); + String key2 = "key2"; + String value2 = "value2"; + cache.put(key2, value2); + assertThat(cache.get(key2), equalTo(value2)); + assertThat(cache.getNumEntries(), equalTo(2)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key1, value1) + cache.getEntrySize(key2, value2))); + cache.clear(); + assertFalse(cache.containsKey(key1)); + assertFalse(cache.containsKey(key2)); + assertThat(cache.getNumEntries(), equalTo(0)); + assertThat(cache.getSize(), equalTo(0L)); + } + + @Test + public void test_CaffeineCacheBasic_TinyLFUMode() { + cache = createObjectUnderTest(100, 100, 10, false); + String value1 = "value1"; + String key1 = "key1"; + cache.put(key1, value1); + assertThat(cache.get(key1), equalTo(value1)); + assertThat(cache.getNumEntries(), equalTo(1)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key1, value1))); + String key2 = "key2"; + String value2 = "value2"; + cache.put(key2, value2); + assertThat(cache.get(key2), equalTo(value2)); + assertThat(cache.getNumEntries(), equalTo(2)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key1, value1) + cache.getEntrySize(key2, value2))); + cache.clear(); + assertFalse(cache.containsKey(key1)); + assertFalse(cache.containsKey(key2)); + assertThat(cache.getNumEntries(), equalTo(0)); + assertThat(cache.getSize(), equalTo(0L)); + } + + @Test + public void test_CaffeineCache_eviction_when_capacity_exceeds() { + cache = createObjectUnderTest(2, 100, 10, true); + String key1 = "key1"; + String value1 = "value1"; + cache.put(key1, value1); + assertThat(cache.getNumEntries(), equalTo(1)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key1, value1))); + String key2 = "key2"; + String value2 = "value2"; + cache.put(key2, value2); + assertThat(cache.getNumEntries(), equalTo(2)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key1, value1) + cache.getEntrySize(key2, value2))); + assertThat(cache.get(key1), equalTo(value1)); + assertThat(cache.get(key2), equalTo(value2)); + assertTrue(cache.containsKey(key2)); + String key3 = "key3"; + String value3 = "value33"; + cache.put(key3, value3); + assertTrue(cache.containsKey(key3)); + assertTrue(cache.containsKey(key2)); + assertFalse(cache.containsKey(key1)); + assertThat(cache.getNumEntries(), equalTo(2)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key3, value3) + cache.getEntrySize(key2, value2))); + } + + @Test + public void test_CaffeineCache_eviction_when_capacity_exceeds_TinyLFUMode() { + cache = createObjectUnderTest(2, 20, 10, false); + String value1 = "value1"; + String key1 = "key1"; + cache.put(key1, value1); + assertThat(cache.getNumEntries(), equalTo(1)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key1, value1))); + String value2 = "value2"; + String key2 = "key2"; + cache.put(key2, value2); + assertThat(cache.getNumEntries(), equalTo(2)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key1, value1) + cache.getEntrySize(key2, value2))); + assertThat(cache.get(key1), equalTo(value1)); + assertThat(cache.get(key2), equalTo(value2)); + String key3 = "key3"; + String value3 = "value33"; + cache.put(key3, value3); + assertTrue(cache.containsKey(key2)); + assertTrue(cache.containsKey(key1)); + assertFalse(cache.containsKey(key3)); + assertThat(cache.getNumEntries(), equalTo(2)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key1, value1) + cache.getEntrySize(key2, value2))); + } + + @Test + public void test_CaffeineCache_put_after_expiry() { + cache = createObjectUnderTest(10, 100, 2, true); + String value1 = "value1"; + String key1 = "key1"; + cache.put(key1, value1); + assertThat(cache.getNumEntries(), equalTo(1)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key1, value1))); + String value2 = "value2"; + String key2 = "key2"; + cache.put(key2, value2); + assertThat(cache.getNumEntries(), equalTo(2)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key1, value1) + cache.getEntrySize(key2, value2))); + assertThat(cache.get(key1), equalTo(value1)); + assertThat(cache.get(key2), equalTo(value2)); + try { + Thread.sleep(3); + } catch (Exception e){} + cache.clear(); + String key3="key3"; + String value3 = "value33"; + cache.put(key3, value3); + assertTrue(cache.containsKey(key3)); + assertFalse(cache.containsKey(key2)); + assertFalse(cache.containsKey(key1)); + assertThat(cache.getNumEntries(), equalTo(1)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key3, value3))); + } + + @Test + public void test_CaffeineCache_put_after_expiry_TinyLFUMode() { + cache = createObjectUnderTest(10, 100, 2, false); + String value1 = "value1"; + String key1="key1"; + cache.put("key1", value1); + assertThat(cache.getNumEntries(), equalTo(1)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key1, value1))); + String key2="key2"; + String value2 = "value2"; + cache.put("key2", value2); + assertThat(cache.getNumEntries(), equalTo(2)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key1, value1) + cache.getEntrySize(key2, value2))); + assertThat(cache.get(key1), equalTo(value1)); + assertThat(cache.get(key2), equalTo(value2)); + try { + Thread.sleep(3); + } catch (Exception e){} + cache.clear(); + String value3 = "value33"; + String key3="key3"; + cache.put(key3, value3); + assertTrue(cache.containsKey(key3)); + assertFalse(cache.containsKey(key2)); + assertFalse(cache.containsKey(key1)); + assertThat(cache.getNumEntries(), equalTo(1)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key3, value3))); + } + + @Test + public void testCacheRemove() { + cache = createObjectUnderTest(10, 100, 2, false); + String value1 = "value1"; + String key1="key1"; + cache.put("key1", value1); + assertThat(cache.getNumEntries(), equalTo(1)); + assertThat(cache.getSize(), equalTo(cache.getEntrySize(key1, value1))); + assertTrue(cache.containsKey(key1)); + cache.remove(key1); + assertFalse(cache.containsKey(key1)); + } + +} From b28f6e0eaa09375d7038eb029aaf13c5e66cc557 Mon Sep 17 00:00:00 2001 From: Krishna Kondaka Date: Wed, 20 Aug 2025 06:34:21 +0000 Subject: [PATCH 2/4] Addressed review comments Signed-off-by: Krishna Kondaka --- .../org/opensearch/dataprepper/common/utils/CaffeineCache.java | 2 -- .../opensearch/dataprepper/common/utils/CaffeineCacheTest.java | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data-prepper-plugins/common/src/main/java/org/opensearch/dataprepper/common/utils/CaffeineCache.java b/data-prepper-plugins/common/src/main/java/org/opensearch/dataprepper/common/utils/CaffeineCache.java index 738fe3503a..222b7078d9 100644 --- a/data-prepper-plugins/common/src/main/java/org/opensearch/dataprepper/common/utils/CaffeineCache.java +++ b/data-prepper-plugins/common/src/main/java/org/opensearch/dataprepper/common/utils/CaffeineCache.java @@ -105,8 +105,6 @@ public void remove(K key) { return; } cache.invalidate(key); - curEntries--; - curBytes += getEntrySize(key, value); } public CacheStats getStats() { diff --git a/data-prepper-plugins/common/src/test/java/org/opensearch/dataprepper/common/utils/CaffeineCacheTest.java b/data-prepper-plugins/common/src/test/java/org/opensearch/dataprepper/common/utils/CaffeineCacheTest.java index fa5b7adede..d19bf368a5 100644 --- a/data-prepper-plugins/common/src/test/java/org/opensearch/dataprepper/common/utils/CaffeineCacheTest.java +++ b/data-prepper-plugins/common/src/test/java/org/opensearch/dataprepper/common/utils/CaffeineCacheTest.java @@ -219,6 +219,8 @@ public void testCacheRemove() { assertTrue(cache.containsKey(key1)); cache.remove(key1); assertFalse(cache.containsKey(key1)); + assertThat(cache.getSize(), equalTo(0L)); + assertThat(cache.getNumEntries(), equalTo(0)); } } From 7587ef7c69873c51d4986316f827c571314a8797 Mon Sep 17 00:00:00 2001 From: Krishna Kondaka Date: Wed, 20 Aug 2025 23:40:43 +0000 Subject: [PATCH 3/4] Modified to use atomic variables because multiple threads may be using them Signed-off-by: Krishna Kondaka --- .../common/utils/CaffeineCache.java | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/data-prepper-plugins/common/src/main/java/org/opensearch/dataprepper/common/utils/CaffeineCache.java b/data-prepper-plugins/common/src/main/java/org/opensearch/dataprepper/common/utils/CaffeineCache.java index 222b7078d9..7a03e575f8 100644 --- a/data-prepper-plugins/common/src/main/java/org/opensearch/dataprepper/common/utils/CaffeineCache.java +++ b/data-prepper-plugins/common/src/main/java/org/opensearch/dataprepper/common/utils/CaffeineCache.java @@ -18,6 +18,8 @@ import java.lang.instrument.Instrumentation; import java.util.concurrent.TimeUnit; import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; public class CaffeineCache { @@ -34,16 +36,16 @@ public int weigh(K key, V value) { private final Cache cache; private final long maxBytes; private final int maxEntries; - private int curEntries; - private long curBytes; - private boolean strictLRU; + private AtomicInteger curEntries; + private AtomicLong curBytes; - public CaffeineCache(int maxEntries, long maxBytes, int ttlSeconds, boolean strictLRU) { + public CaffeineCache(int maxEntries, long maxBytes, int ttlSeconds, boolean lruMode) { this.maxBytes = maxBytes; - this.strictLRU = strictLRU; this.maxEntries = maxEntries; + this.curEntries = new AtomicInteger(0); + this.curBytes = new AtomicLong(0); Caffeine caffeine; - if (strictLRU) { + if (lruMode) { caffeine = (Caffeine) Caffeine.newBuilder() .maximumSize(maxEntries); } else { @@ -56,8 +58,8 @@ public CaffeineCache(int maxEntries, long maxBytes, int ttlSeconds, boolean stri .removalListener((K k, V v, RemovalCause cause) -> { int valueLength = (v instanceof String) ? ((String)v).length() : ((Event)v).toJsonString().length(); - curBytes -= getEntrySize(k, v); - curEntries--; + curBytes.addAndGet(-getEntrySize(k, v)); + curEntries.decrementAndGet(); }) .recordStats() .build(); @@ -83,10 +85,10 @@ public void put(K key, V value) { throw new RuntimeException("Currently only String/Integer/Long type keys are supported"); } cache.put(key, value); - curEntries++; - curBytes += getEntrySize(key, value); + curEntries.incrementAndGet(); + curBytes.addAndGet(getEntrySize(key, value)); // run cleanUp to create space by removing any expired entries - if (curEntries > maxEntries/2 || curBytes > maxBytes/2) { + if (curEntries.get() > maxEntries/2 || curBytes.get() > maxBytes/2) { cache.cleanUp(); } } @@ -105,6 +107,7 @@ public void remove(K key) { return; } cache.invalidate(key); + cache.cleanUp(); } public CacheStats getStats() { @@ -118,12 +121,12 @@ public void clear() { @VisibleForTesting int getNumEntries() { - return curEntries; + return curEntries.get(); } @VisibleForTesting long getSize() { - return curBytes; + return curBytes.get(); } } From 92e9efe39fb84881e8e01e50b8ce257d7b7b4a4d Mon Sep 17 00:00:00 2001 From: Krishna Kondaka Date: Thu, 21 Aug 2025 00:34:57 +0000 Subject: [PATCH 4/4] Added support for list as key and added tests Signed-off-by: Krishna Kondaka --- .../common/utils/CaffeineCache.java | 3 +- .../common/utils/CaffeineCacheTest.java | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/data-prepper-plugins/common/src/main/java/org/opensearch/dataprepper/common/utils/CaffeineCache.java b/data-prepper-plugins/common/src/main/java/org/opensearch/dataprepper/common/utils/CaffeineCache.java index 7a03e575f8..6fca80aa9d 100644 --- a/data-prepper-plugins/common/src/main/java/org/opensearch/dataprepper/common/utils/CaffeineCache.java +++ b/data-prepper-plugins/common/src/main/java/org/opensearch/dataprepper/common/utils/CaffeineCache.java @@ -18,6 +18,7 @@ import java.lang.instrument.Instrumentation; import java.util.concurrent.TimeUnit; import java.util.Arrays; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -81,7 +82,7 @@ public void put(K key, V value) { if (!(value instanceof Event) && !(value instanceof String)) { throw new RuntimeException("Currently only Event/String type values are supported"); } - if (!(key instanceof String) && !(key instanceof Integer) && !(key instanceof Long)) { + if (!(key instanceof String) && !(key instanceof Integer) && !(key instanceof Long) && !(key instanceof List)) { throw new RuntimeException("Currently only String/Integer/Long type keys are supported"); } cache.put(key, value); diff --git a/data-prepper-plugins/common/src/test/java/org/opensearch/dataprepper/common/utils/CaffeineCacheTest.java b/data-prepper-plugins/common/src/test/java/org/opensearch/dataprepper/common/utils/CaffeineCacheTest.java index d19bf368a5..a29ae0c85c 100644 --- a/data-prepper-plugins/common/src/test/java/org/opensearch/dataprepper/common/utils/CaffeineCacheTest.java +++ b/data-prepper-plugins/common/src/test/java/org/opensearch/dataprepper/common/utils/CaffeineCacheTest.java @@ -11,12 +11,15 @@ import org.opensearch.dataprepper.model.event.Event; import org.opensearch.dataprepper.model.event.JacksonEvent; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.hamcrest.Matchers.equalTo; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertFalse; import com.github.benmanes.caffeine.cache.stats.CacheStats; import java.util.Map; +import java.util.List; +import java.util.UUID; public class CaffeineCacheTest { static private final String TEST_EVENT_TYPE="event"; @@ -223,4 +226,41 @@ public void testCacheRemove() { assertThat(cache.getNumEntries(), equalTo(0)); } + @Test + public void testListOfStringsKeys() { + CaffeineCache, Object> dcache = new CaffeineCache, Object>(10, 100, 2, true); + final String testString1 = UUID.randomUUID().toString(); + List list1 = List.of("key1", "key2"); + List list2 = List.of("key1", "key2"); + List list3 = List.of("key1", "key3"); + dcache.put(list1, testString1); + assertTrue(dcache.containsKey(list2)); + assertThat(dcache.get(list2), equalTo(testString1)); + assertFalse(dcache.containsKey(list3)); + } + + @Test + public void testListOfObjectsKeys() { + CaffeineCache, Object> dcache = new CaffeineCache, Object>(10, 100, 2, true); + final String testString1 = UUID.randomUUID().toString(); + List olist1 = List.of("key1", 2222); + List olist2 = List.of("key1", 2222); + List olist3 = List.of("key1", 3333); + dcache.put(olist1, testString1); + assertTrue(dcache.containsKey(olist2)); + assertThat(dcache.get(olist2), equalTo(testString1)); + assertFalse(dcache.containsKey(olist3)); + } + + @Test + public void testInvalidKeys() { + CaffeineCache dcache = new CaffeineCache(10, 100, 2, true); + assertThrows(RuntimeException.class, () -> dcache.put(3.33d, "failing test")); + } + + @Test + public void testInvalidValues() { + CaffeineCache dcache = new CaffeineCache(10, 100, 2, true); + assertThrows(RuntimeException.class, () -> dcache.put("testKey", 1234)); + } }