CacheFactory.java

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package org.apache.doris.common;

import com.github.benmanes.caffeine.cache.AsyncCacheLoader;
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.RemovalListener;
import com.github.benmanes.caffeine.cache.Ticker;
import org.jetbrains.annotations.NotNull;

import java.time.Duration;
import java.util.OptionalLong;
import java.util.concurrent.ExecutorService;

/**
 * Factory to create Caffeine cache.
 * <p>
 * This class is used to create Caffeine cache with specified parameters.
 * It is used to create both sync and async cache.
 * The cache is created with the following parameters:
 * - expireAfterWriteSec: The duration after which the cache entries will expire.
 * - refreshAfterWriteSec: The duration after which the cache entries will be refreshed.
 * - maxSize: The maximum size of the cache.
 * - enableStats: Whether to enable stats for the cache.
 * - ticker: The ticker to use for the cache.
 * The cache can be created with the above parameters using the buildCache and buildAsyncCache methods.
 * </p>
 */
public class CacheFactory {

    private OptionalLong expireAfterWriteSec;
    private OptionalLong refreshAfterWriteSec;
    private long maxSize;
    private boolean enableStats;
    // Ticker is used to provide a time source for the cache.
    // Only used for test, to provide a fake time source.
    // If not provided, the system time is used.
    private Ticker ticker;

    public CacheFactory(
            OptionalLong expireAfterWriteSec,
            OptionalLong refreshAfterWriteSec,
            long maxSize,
            boolean enableStats,
            Ticker ticker) {
        this.expireAfterWriteSec = expireAfterWriteSec;
        this.refreshAfterWriteSec = refreshAfterWriteSec;
        this.maxSize = maxSize;
        this.enableStats = enableStats;
        this.ticker = ticker;
    }

    // Build a loading cache, without executor, it will use fork-join pool for refresh
    public <K, V> LoadingCache<K, V> buildCache(CacheLoader<K, V> cacheLoader) {
        Caffeine<Object, Object> builder = buildWithParams();
        return builder.build(cacheLoader);
    }

    // Build a loading cache, with executor, it will use given executor for refresh
    public <K, V> LoadingCache<K, V> buildCache(CacheLoader<K, V> cacheLoader,
            RemovalListener<K, V> removalListener, ExecutorService executor) {
        Caffeine<Object, Object> builder = buildWithParams();
        builder.executor(executor);
        if (removalListener != null) {
            builder.removalListener(removalListener);
        }
        return builder.build(cacheLoader);
    }

    // Build an async loading cache
    public <K, V> AsyncLoadingCache<K, V> buildAsyncCache(AsyncCacheLoader<K, V> cacheLoader,
            ExecutorService executor) {
        Caffeine<Object, Object> builder = buildWithParams();
        builder.executor(executor);
        return builder.buildAsync(cacheLoader);
    }

    @NotNull
    private Caffeine<Object, Object> buildWithParams() {
        Caffeine<Object, Object> builder = Caffeine.newBuilder();
        builder.maximumSize(maxSize);

        if (expireAfterWriteSec.isPresent()) {
            builder.expireAfterWrite(Duration.ofSeconds(expireAfterWriteSec.getAsLong()));
        }
        if (refreshAfterWriteSec.isPresent()) {
            builder.refreshAfterWrite(Duration.ofSeconds(refreshAfterWriteSec.getAsLong()));
        }

        if (enableStats) {
            builder.recordStats();
        }

        if (ticker != null) {
            builder.ticker(ticker);
        }
        return builder;
    }
}