From 146a0fb8b0e90f9196e569152f649baf60d6cc8f Mon Sep 17 00:00:00 2001
From: Joaquín Reñé <jrene@curisit.net>
Date: Tue, 07 Oct 2025 14:52:57 +0000
Subject: [PATCH] #4410 - Comments on classes
---
securis/src/main/java/net/curisit/securis/utils/CacheTTL.java | 248 ++++++++++++++++++++++++++++++++++++++----------
1 files changed, 194 insertions(+), 54 deletions(-)
diff --git a/securis/src/main/java/net/curisit/securis/utils/CacheTTL.java b/securis/src/main/java/net/curisit/securis/utils/CacheTTL.java
index 2b0146a..8376449 100644
--- a/securis/src/main/java/net/curisit/securis/utils/CacheTTL.java
+++ b/securis/src/main/java/net/curisit/securis/utils/CacheTTL.java
@@ -1,3 +1,6 @@
+/*
+* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
+*/
package net.curisit.securis.utils;
import java.util.ArrayList;
@@ -5,6 +8,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@@ -13,122 +17,258 @@
import org.apache.logging.log4j.Logger;
/**
- * Cache implementation with TTL (time To Live) The objects are removed from
- * cache when TTL is reached.
- *
- * @author roberto <roberto.sanchez@curisit.net>
- */
+* CacheTTL
+* <p>
+* Simple in-memory cache with per-entry TTL (time-to-live). A background
+* cleaning thread periodically removes expired entries.
+*
+* <p><b>Type-safety note:</b> Besides generic getters, this cache provides
+* {@link #getSet(String, Class)} to safely retrieve {@code Set<E>} values
+* without unchecked warnings at call sites. The method validates that all
+* elements match the requested {@code elementType}.
+*
+* <p><b>Threading:</b> This implementation is lightweight and uses a single
+* cleaner thread. The internal map is not synchronized beyond the remove loop,
+* which is acceptable for low-concurrency scenarios. For heavier usage,
+* consider switching to a {@code ConcurrentHashMap} and/or a scheduled executor.
+*
+* @author roberto
+* Last reviewed by JRA on Oct 5, 2025.
+*/
@ApplicationScoped
public class CacheTTL {
private static final Logger LOG = LogManager.getLogger(CacheTTL.class);
+ /** Default TTL (seconds) for entries when not specified. */
+ private static final int DEFAULT_CACHE_DURATION = 24 * 60 * 60;
+
+ /** Backing store: key → cached object + expiration. */
+ private final Map<String, CachedObject> data = new HashMap<>();
+
+ /** Background cleaner thread. */
+ private final Thread cleaningThread;
+
/**
- * Period before token expires, set in seconds.
- */
- private static int DEFAULT_CACHE_DURATION = 24 * 60 * 60;
-
- private Map<String, CachedObject> data = new HashMap<>();
-
- private Thread cleaningThread = null;
-
+ * CacheTTL<p>
+ * Construct a cache and start the background cleaner that removes expired
+ * entries every 60 seconds.
+ */
@Inject
public CacheTTL() {
- cleaningThread = new Thread(new Runnable() {
-
- @Override
- public void run() {
- while (CacheTTL.this.data != null) {
- try {
- // We check for expired object every 60 seconds
- Thread.sleep(60 * 1000);
- } catch (InterruptedException e) {
- LOG.error("Exiting from Cache Thread");
- data.clear();
- return;
- }
- Date now = new Date();
- List<String> keysToRemove = new ArrayList<>();
- for (String key : CacheTTL.this.data.keySet()) {
- CachedObject co = CacheTTL.this.data.get(key);
- if (now.after(co.getExpireAt())) {
- keysToRemove.add(key);
- }
- }
- for (String key : keysToRemove) {
- // If we try to remove directly in the previous loop an
- // exception is thrown
- // java.util.ConcurrentModificationException
- CacheTTL.this.data.remove(key);
+ cleaningThread = new Thread(() -> {
+ while (true) {
+ try {
+ // Check for expired objects every 60 seconds
+ Thread.sleep(60_000);
+ } catch (InterruptedException e) {
+ LOG.warn("Cache cleaner interrupted. Clearing cache and stopping.");
+ data.clear();
+ return;
+ }
+ Date now = new Date();
+ List<String> keysToRemove = new ArrayList<>();
+ for (String key : data.keySet()) {
+ CachedObject co = data.get(key);
+ if (co != null && now.after(co.getExpireAt())) {
+ keysToRemove.add(key);
}
}
+ for (String key : keysToRemove) {
+ // Avoid ConcurrentModificationException by removing after iteration
+ data.remove(key);
+ }
}
- });
+ }, "CacheTTL-Cleaner");
+ cleaningThread.setDaemon(true);
cleaningThread.start();
}
+ // ---------------------------------------------------------------------
+ // Putters
+ // ---------------------------------------------------------------------
+
/**
- *
- * @param key
- * @param obj
- * @param ttl
- * Time To Live in seconds
- */
+ * set<p>
+ * Store a value with an explicit TTL.
+ *
+ * @param key cache key
+ * @param obj value to store (may be any object, including collections)
+ * @param ttl TTL in seconds
+ */
public void set(String key, Object obj, int ttl) {
- Date expirationDate = new Date(new Date().getTime() + ttl * 1000);
+ Date expirationDate = new Date(System.currentTimeMillis() + (long) ttl * 1000L);
data.put(key, new CachedObject(expirationDate, obj));
}
+ /**
+ * set<p>
+ * Store a value with the default TTL.
+ *
+ * @param key cache key
+ * @param obj value to store
+ */
public void set(String key, Object obj) {
set(key, obj, DEFAULT_CACHE_DURATION);
}
+ // ---------------------------------------------------------------------
+ // Getters
+ // ---------------------------------------------------------------------
+
+ /**
+ * get<p>
+ * Retrieve a value as {@code Object}. Returns {@code null} if not present
+ * or expired (expired entries are eagerly removed by the cleaner).
+ *
+ * @param key cache key
+ * @return cached value or null
+ */
public Object get(String key) {
CachedObject co = data.get(key);
return co == null ? null : co.getObject();
}
+ /**
+ * get<p>
+ * Retrieve a value and cast it to the requested type. The cast is unchecked
+ * due to type erasure, but localized within the cache implementation.
+ *
+ * @param key cache key
+ * @param type expected value type
+ * @param <T> generic type
+ * @return cached value typed or null
+ */
public <T> T get(String key, Class<T> type) {
CachedObject co = data.get(key);
return co == null ? null : co.getObject(type);
}
+ /**
+ * getSet<p>
+ * Retrieve a {@code Set<E>} in a type-safe way without unchecked warnings
+ * at the call site. The method validates that the cached value is a
+ * {@code Set} and that <b>all</b> elements are instances of {@code elementType}.
+ * If any element does not match, the method returns {@code null} and logs a warning.
+ *
+ * @param key cache key
+ * @param elementType class of the set elements (e.g., {@code Integer.class})
+ * @return typed set or null if missing/type-mismatch
+ */
+ @SuppressWarnings("unchecked")
+ public <E> Set<E> getSet(String key, Class<E> elementType) {
+ Object obj = get(key);
+ if (obj == null) return null;
+ if (!(obj instanceof Set<?> raw)) {
+ LOG.warn("Cache key '{}' expected Set<{}> but found {}", key, elementType.getSimpleName(), obj.getClass().getName());
+ return null;
+ }
+ // Validate element types to avoid ClassCastException later
+ for (Object el : raw) {
+ if (el != null && !elementType.isInstance(el)) {
+ LOG.warn("Cache key '{}' contains element of type {}, expected {}", key,
+ el.getClass().getName(), elementType.getName());
+ return null;
+ }
+ }
+ // Safe due to element-wise validation
+ return (Set<E>) raw;
+ }
+
+ // ---------------------------------------------------------------------
+ // Removers & maintenance
+ // ---------------------------------------------------------------------
+
+ /**
+ * remove<p>
+ * Remove and return a value typed.
+ *
+ * @param key cache key
+ * @param type expected type
+ * @return removed value or null
+ */
public <T> T remove(String key, Class<T> type) {
CachedObject co = data.remove(key);
return co == null ? null : co.getObject(type);
}
+ /**
+ * remove<p>
+ * Remove and return a value as {@code Object}.
+ *
+ * @param key cache key
+ * @return removed value or null
+ */
public Object remove(String key) {
CachedObject co = data.remove(key);
return co == null ? null : co.getObject();
}
+ /**
+ * clear<p>
+ * Remove all entries from the cache.
+ */
public void clear() {
data.clear();
}
- private class CachedObject {
- Date expireAt;
- Object object;
+ // ---------------------------------------------------------------------
+ // Internal structure
+ // ---------------------------------------------------------------------
+ /**
+ * CachedObject
+ * <p>
+ * Internal wrapper that pairs an arbitrary object with its expiration date.
+ */
+ private static class CachedObject {
+ private final Date expireAt;
+ private final Object object;
+
+ /**
+ * Constructor<p>
+ * Set expiration and payload.
+ *
+ * @param date
+ * @param object
+ */
public CachedObject(Date date, Object obj) {
- expireAt = date;
- object = obj;
+ this.expireAt = date;
+ this.object = obj;
}
+ /**
+ * getExpireAt<p>
+ * Return expiration date.
+ *
+ * @return date
+ */
public Date getExpireAt() {
return expireAt;
}
+ /**
+ * getObject<p>
+ * Return payload as {@code Object}.
+ *
+ * @return object
+ */
public Object getObject() {
return object;
}
+ /**
+ * getObject<p>
+ * Return payload cast to the requested type. Cast is localized here.
+ *
+ * @param type requested type
+ * @param <T> generic type
+ * @return typed payload
+ */
@SuppressWarnings("unchecked")
public <T> T getObject(Class<T> type) {
return (T) object;
}
-
}
-
}
+
--
Gitblit v1.3.2