KubernetesClientCache.java

package org.entando.kubernetes.service;

import io.fabric8.kubernetes.client.KubernetesClient;
import java.time.Instant;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

@SuppressWarnings("java:S2160")
//because this class should never be checked for equality. It is conceptually a singleton
public class KubernetesClientCache extends ConcurrentHashMap<String, KubernetesClient> {

    final transient Timer timer = new Timer();
    final transient ConcurrentHashMap<String, Instant> accessTimes = new ConcurrentHashMap<>();
    private final int maximumAgeSeconds;
    private transient Function<String, KubernetesClient> kubernetesClientSupplier;

    //For tests
    public KubernetesClientCache(Function<String, KubernetesClient> kubernetesClientSupplier, int maximumAgeSeconds, long scanInterval) {
        //Remove after 1 hour
        //Scan every minute
        this(maximumAgeSeconds, scanInterval);
        this.kubernetesClientSupplier = kubernetesClientSupplier;
    }

    public KubernetesClientCache(Function<String, KubernetesClient> kubernetesClientSupplier) {
        //Remove after 1 hour
        //Scan every minute
        this(kubernetesClientSupplier, 3600, 60 * 1000L);
    }

    private KubernetesClientCache(int maximumAgeSeconds, long scanInterval) {
        super();
        this.maximumAgeSeconds = maximumAgeSeconds;
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                removeStaleEntries();
            }

        }, 0, scanInterval);
    }

    @Override
    public KubernetesClient get(Object tokenAsObject) {
        final String token = (String) tokenAsObject;
        accessTimes.put(token,Instant.now());
        return super.computeIfAbsent(token, this.kubernetesClientSupplier);
    }

    private void removeStaleEntries() {
        Instant cutoffInstant = Instant.now().minusSeconds(maximumAgeSeconds);
        keySet().stream().filter(s -> accessTimes.get(s).isBefore(cutoffInstant)).forEach(token -> {
            //Synchronization risk here is minimal
            accessTimes.remove(token);
            remove(token).close();
        });
    }
}