From ed1eb3097ee0d3c013be9e9376fee83679ee0507 Mon Sep 17 00:00:00 2001 From: iProdigy <8106344+iProdigy@users.noreply.github.com> Date: Sun, 7 Apr 2024 12:24:23 -0700 Subject: [PATCH] feat: add infinispan jdk17 module (#245) --- README.md | 23 ++--- .../xanthic/cache/core/CacheApiSettings.java | 1 + provider-infinispan-java17/build.gradle.kts | 22 +++++ .../infinispanjdk17/InfinispanDelegate.java | 87 +++++++++++++++++++ .../infinispanjdk17/InfinispanListener.java | 76 ++++++++++++++++ .../infinispanjdk17/InfinispanProvider.java | 50 +++++++++++ .../InfinispanJava17ProviderTest.java | 11 +++ settings.gradle.kts | 2 + 8 files changed, 261 insertions(+), 11 deletions(-) create mode 100644 provider-infinispan-java17/build.gradle.kts create mode 100644 provider-infinispan-java17/src/main/java/io/github/xanthic/cache/provider/infinispanjdk17/InfinispanDelegate.java create mode 100644 provider-infinispan-java17/src/main/java/io/github/xanthic/cache/provider/infinispanjdk17/InfinispanListener.java create mode 100644 provider-infinispan-java17/src/main/java/io/github/xanthic/cache/provider/infinispanjdk17/InfinispanProvider.java create mode 100644 provider-infinispan-java17/src/test/java/io/github/xanthic/cache/provider/infinispanjdk17/InfinispanJava17ProviderTest.java diff --git a/README.md b/README.md index ec37b093..39c11349 100644 --- a/README.md +++ b/README.md @@ -34,17 +34,18 @@ example), so it is safer to code against this API for long-term flexibility* The following backing cache implementations have bindings already provided by this library: -| Backend | Provider | Artifact | -| :-----: | :------: | :------: | -| [Caffeine](https://github.com/ben-manes/caffeine/wiki) | `CaffeineProvider` | `cache-provider-caffeine` | -| [Caffeine3](https://github.com/ben-manes/caffeine/wiki) | `Caffeine3Provider` | `cache-provider-caffeine3` | -| [Guava](https://github.com/google/guava/wiki/CachesExplained) | `GuavaProvider` | `cache-provider-guava` | -| [Cache2k](https://cache2k.org) | `Cache2kProvider` | `cache-provider-cache2k` | -| [AndroidX](https://developer.android.com/reference/androidx/collection/LruCache) | `AndroidLruProvider` | `cache-provider-androidx` | -| [ExpiringMap](https://github.com/jhalterman/expiringmap#expiringmap) | `ExpiringMapProvider` | `cache-provider-expiringmap` | -| [Ehcache v3 (heap)](https://www.ehcache.org/documentation/3.0/index.html) | `EhcacheProvider` | `cache-provider-ehcache` | -| [Infinispan (heap)](https://infinispan.org/documentation/) | `InfinispanProvider` | `cache-provider-infinispan` | -| [Infinispan v14 (heap)](https://infinispan.org/documentation/) | `InfinispanProvider` | `cache-provider-infinispan-java11` | +| Backend | Provider | Artifact | +|:--------------------------------------------------------------------------------:|:---------------------:|:----------------------------------:| +| [Caffeine](https://github.com/ben-manes/caffeine/wiki) | `CaffeineProvider` | `cache-provider-caffeine` | +| [Caffeine3](https://github.com/ben-manes/caffeine/wiki) | `Caffeine3Provider` | `cache-provider-caffeine3` | +| [Guava](https://github.com/google/guava/wiki/CachesExplained) | `GuavaProvider` | `cache-provider-guava` | +| [Cache2k](https://cache2k.org) | `Cache2kProvider` | `cache-provider-cache2k` | +| [AndroidX](https://developer.android.com/reference/androidx/collection/LruCache) | `AndroidLruProvider` | `cache-provider-androidx` | +| [ExpiringMap](https://github.com/jhalterman/expiringmap#expiringmap) | `ExpiringMapProvider` | `cache-provider-expiringmap` | +| [Ehcache v3 (heap)](https://www.ehcache.org/documentation/3.0/index.html) | `EhcacheProvider` | `cache-provider-ehcache` | +| [Infinispan (heap)](https://infinispan.org/documentation/) | `InfinispanProvider` | `cache-provider-infinispan` | +| [Infinispan v14 (heap)](https://infinispan.org/documentation/) | `InfinispanProvider` | `cache-provider-infinispan-java11` | +| [Infinispan v15 (heap)](https://infinispan.org/documentation/) | `InfinispanProvider` | `cache-provider-infinispan-java17` | Don't see your preferred implementation listed above? Fear not, it is not difficult to create your own binding, and we'd be happy to accept it in a PR! diff --git a/core/src/main/java/io/github/xanthic/cache/core/CacheApiSettings.java b/core/src/main/java/io/github/xanthic/cache/core/CacheApiSettings.java index c02d190c..94f12db0 100644 --- a/core/src/main/java/io/github/xanthic/cache/core/CacheApiSettings.java +++ b/core/src/main/java/io/github/xanthic/cache/core/CacheApiSettings.java @@ -139,6 +139,7 @@ private static void populateProviders(CacheApiSettings cacheApiSettings) { loadImpl.accept("io.github.xanthic.cache.provider.caffeine3.Caffeine3Provider"); loadImpl.accept("io.github.xanthic.cache.provider.caffeine.CaffeineProvider"); loadImpl.accept("io.github.xanthic.cache.provider.cache2k.Cache2kProvider"); + loadImpl.accept("io.github.xanthic.cache.provider.infinispanjdk17.InfinispanProvider"); loadImpl.accept("io.github.xanthic.cache.provider.infinispanjdk11.InfinispanProvider"); loadImpl.accept("io.github.xanthic.cache.provider.infinispan.InfinispanProvider"); loadImpl.accept("io.github.xanthic.cache.provider.expiringmap.ExpiringMapProvider"); diff --git a/provider-infinispan-java17/build.gradle.kts b/provider-infinispan-java17/build.gradle.kts new file mode 100644 index 00000000..6a1a8ebb --- /dev/null +++ b/provider-infinispan-java17/build.gradle.kts @@ -0,0 +1,22 @@ +dependencies { + api(project(":cache-core")) + + implementation(platform("org.infinispan:infinispan-bom:15.0.0.Final")) + + compileOnly("org.infinispan:infinispan-component-annotations") + implementation("org.infinispan:infinispan-core") + + testImplementation(testFixtures(project(":cache-core"))) +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +publishing.publications.withType { + pom { + name.set("Xanthic - Infinispan Provider Module for JDK 17") + description.set("Xanthic Provider dependency for Infinispan on JDK 17+") + } +} diff --git a/provider-infinispan-java17/src/main/java/io/github/xanthic/cache/provider/infinispanjdk17/InfinispanDelegate.java b/provider-infinispan-java17/src/main/java/io/github/xanthic/cache/provider/infinispanjdk17/InfinispanDelegate.java new file mode 100644 index 00000000..a095314a --- /dev/null +++ b/provider-infinispan-java17/src/main/java/io/github/xanthic/cache/provider/infinispanjdk17/InfinispanDelegate.java @@ -0,0 +1,87 @@ +package io.github.xanthic.cache.provider.infinispanjdk17; + +import io.github.xanthic.cache.api.Cache; +import lombok.Value; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; + +@Value +class InfinispanDelegate implements Cache { + org.infinispan.Cache cache; + + @Override + public V get(@NotNull K key) { + return cache.get(key); + } + + @Override + public V put(@NotNull K key, @NotNull V value) { + return cache.put(key, value); + } + + @Override + public V remove(@NotNull K key) { + return cache.remove(key); + } + + @Override + public void clear() { + cache.clear(); + } + + @Override + public long size() { + return cache.size(); + } + + @Nullable + @Override + public V compute(@NotNull K key, @NotNull BiFunction computeFunc) { + return cache.compute(key, computeFunc); + } + + @Override + public V computeIfAbsent(@NotNull K key, @NotNull Function computeFunc) { + return cache.computeIfAbsent(key, computeFunc); + } + + @Override + public V computeIfPresent(@NotNull K key, @NotNull BiFunction computeFunc) { + return cache.computeIfPresent(key, computeFunc); + } + + @Override + public V putIfAbsent(@NotNull K key, @NotNull V value) { + return cache.putIfAbsent(key, value); + } + + @Override + public V merge(@NotNull K key, @NotNull V value, @NotNull BiFunction mergeFunc) { + return cache.merge(key, value, mergeFunc); + } + + @Override + public boolean replace(@NotNull K key, @NotNull V value) { + return cache.replace(key, value) != null; + } + + @Override + public boolean replace(@NotNull K key, @NotNull V oldValue, @NotNull V newValue) { + return cache.replace(key, oldValue, newValue); + } + + @Override + public void putAll(@NotNull Map map) { + cache.putAll(map); + } + + @Override + public void forEach(@NotNull BiConsumer action) { + cache.forEach(action); + } +} diff --git a/provider-infinispan-java17/src/main/java/io/github/xanthic/cache/provider/infinispanjdk17/InfinispanListener.java b/provider-infinispan-java17/src/main/java/io/github/xanthic/cache/provider/infinispanjdk17/InfinispanListener.java new file mode 100644 index 00000000..b5b8b9ae --- /dev/null +++ b/provider-infinispan-java17/src/main/java/io/github/xanthic/cache/provider/infinispanjdk17/InfinispanListener.java @@ -0,0 +1,76 @@ +package io.github.xanthic.cache.provider.infinispanjdk17; + +import io.github.xanthic.cache.api.RemovalListener; +import io.github.xanthic.cache.api.domain.RemovalCause; +import lombok.Value; +import org.infinispan.notifications.Listener; +import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted; +import org.infinispan.notifications.cachelistener.annotation.CacheEntryExpired; +import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated; +import org.infinispan.notifications.cachelistener.annotation.CacheEntryModified; +import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved; +import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent; +import org.infinispan.notifications.cachelistener.event.CacheEntryExpiredEvent; +import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent; +import org.infinispan.notifications.cachelistener.event.CacheEntryModifiedEvent; +import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent; +import org.infinispan.notifications.cachelistener.event.Event; + +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.EnumSet; +import java.util.IdentityHashMap; +import java.util.Set; + +@Value +@Listener +class InfinispanListener { + static final Set EVENTS; + static final Set> ANNOTATIONS; + + RemovalListener removalListener; + + @CacheEntriesEvicted + public void onPostEvictions(CacheEntriesEvictedEvent event) { + if (!event.isPre()) + event.getEntries().forEach((k, v) -> removalListener.onRemoval(k, v, RemovalCause.SIZE)); + } + + @CacheEntryExpired + public void onExpiry(CacheEntryExpiredEvent event) { + removalListener.onRemoval(event.getKey(), event.getValue(), RemovalCause.TIME); + } + + @CacheEntryInvalidated + public void onInvalidation(CacheEntryInvalidatedEvent event) { + removalListener.onRemoval(event.getKey(), event.getValue(), RemovalCause.OTHER); + } + + @CacheEntryModified + public void onPostModifyExisting(CacheEntryModifiedEvent event) { + if (!event.isCreated() && !event.isPre()) + removalListener.onRemoval(event.getKey(), event.getOldValue(), RemovalCause.REPLACED); + } + + @CacheEntryRemoved + public void onPostRemoval(CacheEntryRemovedEvent event) { + if (!event.isPre()) + removalListener.onRemoval(event.getKey(), event.getOldValue(), RemovalCause.MANUAL); + } + + static { + EVENTS = EnumSet.noneOf(Event.Type.class); + EVENTS.add(Event.Type.CACHE_ENTRY_EVICTED); + EVENTS.add(Event.Type.CACHE_ENTRY_EXPIRED); + EVENTS.add(Event.Type.CACHE_ENTRY_INVALIDATED); + EVENTS.add(Event.Type.CACHE_ENTRY_MODIFIED); + EVENTS.add(Event.Type.CACHE_ENTRY_REMOVED); + + ANNOTATIONS = Collections.newSetFromMap(new IdentityHashMap<>()); + ANNOTATIONS.add(CacheEntriesEvicted.class); + ANNOTATIONS.add(CacheEntryExpired.class); + ANNOTATIONS.add(CacheEntryInvalidated.class); + ANNOTATIONS.add(CacheEntryModified.class); + ANNOTATIONS.add(CacheEntryRemoved.class); + } +} diff --git a/provider-infinispan-java17/src/main/java/io/github/xanthic/cache/provider/infinispanjdk17/InfinispanProvider.java b/provider-infinispan-java17/src/main/java/io/github/xanthic/cache/provider/infinispanjdk17/InfinispanProvider.java new file mode 100644 index 00000000..1cd587cc --- /dev/null +++ b/provider-infinispan-java17/src/main/java/io/github/xanthic/cache/provider/infinispanjdk17/InfinispanProvider.java @@ -0,0 +1,50 @@ +package io.github.xanthic.cache.provider.infinispanjdk17; + +import io.github.xanthic.cache.api.Cache; +import io.github.xanthic.cache.api.ICacheSpec; +import io.github.xanthic.cache.api.domain.ExpiryType; +import io.github.xanthic.cache.core.AbstractCacheProvider; +import org.infinispan.commons.api.CacheContainerAdmin; +import org.infinispan.configuration.cache.ConfigurationBuilder; +import org.infinispan.manager.DefaultCacheManager; +import org.infinispan.manager.EmbeddedCacheManager; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * Provides {@link Cache} instances using Infinispan's {@link org.infinispan.Cache} in heap-mode. + *

+ * Implements size and time-based expiry. + */ +public final class InfinispanProvider extends AbstractCacheProvider { + private static final EmbeddedCacheManager MANAGER = new DefaultCacheManager(); + + @Override + public Cache build(ICacheSpec spec) { + ConfigurationBuilder builder = new ConfigurationBuilder(); + builder.simpleCache(true); + if (spec.maxSize() != null) builder.memory().maxCount(spec.maxSize()); + handleExpiration(spec.expiryTime(), spec.expiryType(), (time, type) -> { + if (type == ExpiryType.POST_WRITE) + builder.expiration().lifespan(time.toNanos(), TimeUnit.NANOSECONDS); + else + builder.expiration().maxIdle(time.toNanos(), TimeUnit.NANOSECONDS); + }); + + org.infinispan.Cache cache = MANAGER.administration() + .withFlags(CacheContainerAdmin.AdminFlag.VOLATILE) + .createCache(UUID.randomUUID().toString(), builder.build()); + + if (spec.removalListener() != null) { + cache.addFilteredListener( + new InfinispanListener<>(spec.removalListener()), + (key, oldValue, oldMeta, newValue, newMeta, eventType) -> eventType != null && InfinispanListener.EVENTS.contains(eventType.getType()), + null, + InfinispanListener.ANNOTATIONS + ); + } + + return new InfinispanDelegate<>(cache); + } +} diff --git a/provider-infinispan-java17/src/test/java/io/github/xanthic/cache/provider/infinispanjdk17/InfinispanJava17ProviderTest.java b/provider-infinispan-java17/src/test/java/io/github/xanthic/cache/provider/infinispanjdk17/InfinispanJava17ProviderTest.java new file mode 100644 index 00000000..5917fd0e --- /dev/null +++ b/provider-infinispan-java17/src/test/java/io/github/xanthic/cache/provider/infinispanjdk17/InfinispanJava17ProviderTest.java @@ -0,0 +1,11 @@ +package io.github.xanthic.cache.provider.infinispanjdk17; + +import io.github.xanthic.cache.core.provider.ProviderTestBase; + +public class InfinispanJava17ProviderTest extends ProviderTestBase { + + public InfinispanJava17ProviderTest() { + super(new InfinispanProvider()); + } + +} diff --git a/settings.gradle.kts b/settings.gradle.kts index de55c181..a1c00ca1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,6 +17,7 @@ include( ":provider-guava", ":provider-infinispan", ":provider-infinispan-java11", + ":provider-infinispan-java17", ) project(":bom").name = "cache-bom" @@ -35,3 +36,4 @@ project(":provider-expiringmap").name = "cache-provider-expiringmap" project(":provider-guava").name = "cache-provider-guava" project(":provider-infinispan").name = "cache-provider-infinispan" project(":provider-infinispan-java11").name = "cache-provider-infinispan-java11" +project(":provider-infinispan-java17").name = "cache-provider-infinispan-java17"