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