001/*
002 * Copyright (C) 2022-present The Prometheus jmx_exporter Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package io.prometheus.jmx.common.http.authenticator;
018
019import java.nio.charset.StandardCharsets;
020import java.util.LinkedHashMap;
021import java.util.LinkedList;
022
023/**
024 * Class to implement a Credentials cache that is size constrained
025 *
026 * <p>The cache will purge old entries to make size for a cacheable credential
027 *
028 * <p>A credential that exceeds maximumCacheSizeBytes is not cached
029 */
030public class CredentialsCache {
031
032    private final int maximumCacheSizeBytes;
033    private final LinkedHashMap<Credentials, Byte> linkedHashMap;
034    private final LinkedList<Credentials> linkedList;
035
036    private int currentCacheSizeBytes;
037
038    /**
039     * Constructor
040     *
041     * @param maximumCacheSizeBytes maximum cache size in bytes
042     */
043    public CredentialsCache(int maximumCacheSizeBytes) {
044        this.maximumCacheSizeBytes = maximumCacheSizeBytes;
045        linkedHashMap = new LinkedHashMap<>();
046        linkedList = new LinkedList<>();
047    }
048
049    /**
050     * Method to add a Credentials to the cache
051     *
052     * <p>A credential that exceeds maximumCacheSizeBytes is not cached
053     *
054     * @param credentials credential
055     */
056    public synchronized void add(Credentials credentials) {
057        int credentialSizeBytes = credentials.toString().getBytes(StandardCharsets.UTF_8).length;
058
059        // Don't cache the entry since it's bigger than the maximum cache size
060        // Don't invalidate other entries
061        if (credentialSizeBytes > maximumCacheSizeBytes) {
062            return;
063        }
064
065        // Purge old cache entries until we have space or the cache is empty
066        while (((currentCacheSizeBytes + credentialSizeBytes) > maximumCacheSizeBytes)
067                && (currentCacheSizeBytes > 0)) {
068            Credentials c = linkedList.removeLast();
069            linkedHashMap.remove(c);
070            currentCacheSizeBytes -= credentialSizeBytes;
071            if (currentCacheSizeBytes < 0) {
072                currentCacheSizeBytes = 0;
073            }
074        }
075
076        linkedHashMap.put(credentials, (byte) 1);
077        linkedList.addFirst(credentials);
078        currentCacheSizeBytes += credentialSizeBytes;
079    }
080
081    /**
082     * Method to return whether the cache contains the Credentials
083     *
084     * @param credentials credentials
085     * @return true if the set contains the Credential, else false
086     */
087    public synchronized boolean contains(Credentials credentials) {
088        return linkedHashMap.containsKey(credentials);
089    }
090
091    /**
092     * Method to remove a Credentials from the cache
093     *
094     * @param credentials credentials
095     * @return true if the Credentials existed and was removed, else false
096     */
097    public synchronized boolean remove(Credentials credentials) {
098        if (linkedHashMap.remove(credentials) != null) {
099            linkedList.remove(credentials);
100            currentCacheSizeBytes -= credentials.toString().getBytes(StandardCharsets.UTF_8).length;
101            return true;
102        } else {
103            return false;
104        }
105    }
106
107    /**
108     * Method to get the maximum cache size in bytes
109     *
110     * @return the maximum cache size in bytes
111     */
112    public int getMaximumCacheSizeBytes() {
113        return maximumCacheSizeBytes;
114    }
115
116    /**
117     * Method to get the current cache size in bytes
118     *
119     * @return the current cache size in bytes
120     */
121    public synchronized int getCurrentCacheSizeBytes() {
122        return currentCacheSizeBytes;
123    }
124}