From bcff4138729be0773d1463e053a05796bbe32319 Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Sat, 22 Nov 2025 12:15:24 +0100 Subject: [PATCH] HTTPCLIENT-2406: MemcachedHttpCacheStorage to support per entry expiry setting --- .../AbstractSerializingCacheStorage.java | 11 +++- .../memcached/MemcachedHttpCacheStorage.java | 52 +++++++++++++++---- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractSerializingCacheStorage.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractSerializingCacheStorage.java index 108f9a6210..b0a24d3280 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractSerializingCacheStorage.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AbstractSerializingCacheStorage.java @@ -26,6 +26,7 @@ */ package org.apache.hc.client5.http.impl.cache; +import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -60,6 +61,13 @@ public AbstractSerializingCacheStorage(final int maxUpdateRetries, final HttpCac protected abstract void store(String storageKey, T storageObject) throws ResourceIOException; + /** + * @since 5.6 + */ + protected void store(final String storageKey, final Instant expectedExpiry, final T storageObject) throws ResourceIOException { + store(storageKey, storageObject); + } + protected abstract T restore(String storageKey) throws ResourceIOException; protected abstract CAS getForUpdateCAS(String storageKey) throws ResourceIOException; @@ -76,7 +84,8 @@ public AbstractSerializingCacheStorage(final int maxUpdateRetries, final HttpCac public final void putEntry(final String key, final HttpCacheEntry entry) throws ResourceIOException { final String storageKey = digestToStorageKey(key); final T storageObject = serializer.serialize(new HttpCacheStorageEntry(key, entry)); - store(storageKey, storageObject); + final Instant expires = entry.getExpires(); + store(storageKey, expires, storageObject); } @Override diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpCacheStorage.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpCacheStorage.java index 84ec2aa37b..3b62d22829 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpCacheStorage.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/MemcachedHttpCacheStorage.java @@ -28,11 +28,19 @@ import java.io.IOException; import java.net.InetSocketAddress; +import java.time.Duration; +import java.time.Instant; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CancellationException; +import java.util.function.BiFunction; +import net.spy.memcached.CASResponse; +import net.spy.memcached.CASValue; +import net.spy.memcached.MemcachedClient; +import net.spy.memcached.MemcachedClientIF; +import net.spy.memcached.OperationTimeoutException; import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer; import org.apache.hc.client5.http.cache.ResourceIOException; import org.apache.hc.client5.http.impl.cache.AbstractBinaryCacheStorage; @@ -40,12 +48,6 @@ import org.apache.hc.client5.http.impl.cache.HttpByteArrayCacheEntrySerializer; import org.apache.hc.core5.util.Args; -import net.spy.memcached.CASResponse; -import net.spy.memcached.CASValue; -import net.spy.memcached.MemcachedClient; -import net.spy.memcached.MemcachedClientIF; -import net.spy.memcached.OperationTimeoutException; - /** *

* This class is a storage backend that uses an external memcached @@ -88,6 +90,7 @@ public class MemcachedHttpCacheStorage extends AbstractBinaryCacheStorage expiryResolver; /** * Create a storage backend talking to a memcached instance @@ -119,7 +122,7 @@ public MemcachedHttpCacheStorage(final MemcachedClient cache) { * @since 5.2 */ public MemcachedHttpCacheStorage(final MemcachedClientIF cache) { - this(cache, CacheConfig.DEFAULT, HttpByteArrayCacheEntrySerializer.INSTANCE, SHA256KeyHashingScheme.INSTANCE); + this(cache, CacheConfig.DEFAULT, HttpByteArrayCacheEntrySerializer.INSTANCE, SHA256KeyHashingScheme.INSTANCE, null); } /** @@ -137,7 +140,18 @@ public MemcachedHttpCacheStorage( final CacheConfig config, final HttpCacheEntrySerializer serializer, final KeyHashingScheme keyHashingScheme) { - this((MemcachedClientIF) client, config, serializer, keyHashingScheme); + this(client, config, serializer, keyHashingScheme, null); + } + + /** + * @since 5.2 + */ + public MemcachedHttpCacheStorage( + final MemcachedClientIF client, + final CacheConfig config, + final HttpCacheEntrySerializer serializer, + final KeyHashingScheme keyHashingScheme) { + this(client, config, serializer, keyHashingScheme, null); } /** @@ -150,17 +164,20 @@ public MemcachedHttpCacheStorage( * @param serializer alternative serialization mechanism * @param keyHashingScheme how to map higher-level logical "storage keys" * onto "cache keys" suitable for use with memcached - * @since 5.2 + * @param expiryResolver resolver for cache entry expiry + * @since 5.6 */ public MemcachedHttpCacheStorage( final MemcachedClientIF client, final CacheConfig config, final HttpCacheEntrySerializer serializer, - final KeyHashingScheme keyHashingScheme) { + final KeyHashingScheme keyHashingScheme, + final BiFunction expiryResolver) { super((config != null ? config : CacheConfig.DEFAULT).getMaxUpdateRetries(), serializer != null ? serializer : HttpByteArrayCacheEntrySerializer.INSTANCE); this.client = Args.notNull(client, "Memcached client"); this.keyHashingScheme = keyHashingScheme; + this.expiryResolver = expiryResolver; } @Override @@ -170,8 +187,21 @@ protected String digestToStorageKey(final String key) { @Override protected void store(final String storageKey, final byte[] storageObject) throws ResourceIOException { + store(storageKey, null, storageObject); + } + + @Override + protected void store(final String storageKey, final Instant expectedExpiry, final byte[] storageObject) throws ResourceIOException { + int exp = 0; + final Duration validityduration = expiryResolver != null ? expiryResolver.apply(storageKey, expectedExpiry) : null; + if (validityduration != null) { + final long expSeconds = validityduration.getSeconds(); + if (expSeconds > 0) { + exp = (int) expSeconds; + } + } try { - client.set(storageKey, 0, storageObject); + client.set(storageKey, exp, storageObject); } catch (final CancellationException ex) { throw new MemcachedOperationCancellationException(ex); }