• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ben-manes / caffeine / #5651

18 Jul 2026 03:26AM UTC coverage: 66.303% (-33.7%) from 100.0%
#5651

push

github

ben-manes
dependency updates

2809 of 4164 branches covered (67.46%)

5584 of 8422 relevant lines covered (66.3%)

0.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheFactory.java
1
/*
2
 * Copyright 2015 Ben Manes. All Rights Reserved.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package com.github.benmanes.caffeine.jcache;
17

18
import static java.util.Objects.requireNonNull;
19

20
import java.time.Duration;
21
import java.util.ArrayList;
22
import java.util.Optional;
23
import java.util.concurrent.Executor;
24
import java.util.concurrent.TimeUnit;
25

26
import javax.cache.CacheManager;
27
import javax.cache.configuration.CacheEntryListenerConfiguration;
28
import javax.cache.configuration.CompleteConfiguration;
29
import javax.cache.configuration.Configuration;
30
import javax.cache.configuration.Factory;
31
import javax.cache.expiry.EternalExpiryPolicy;
32
import javax.cache.expiry.ExpiryPolicy;
33
import javax.cache.integration.CacheLoader;
34

35
import org.jspecify.annotations.Nullable;
36

37
import com.github.benmanes.caffeine.cache.Caffeine;
38
import com.github.benmanes.caffeine.cache.Expiry;
39
import com.github.benmanes.caffeine.cache.Scheduler;
40
import com.github.benmanes.caffeine.cache.Ticker;
41
import com.github.benmanes.caffeine.cache.Weigher;
42
import com.github.benmanes.caffeine.jcache.configuration.CaffeineConfiguration;
43
import com.github.benmanes.caffeine.jcache.configuration.TypesafeConfigurator;
44
import com.github.benmanes.caffeine.jcache.copy.Copier;
45
import com.github.benmanes.caffeine.jcache.event.EventDispatcher;
46
import com.github.benmanes.caffeine.jcache.event.JCacheEvictionListener;
47
import com.github.benmanes.caffeine.jcache.integration.JCacheLoaderAdapter;
48
import com.github.benmanes.caffeine.jcache.management.JCacheStatisticsMXBean;
49
import com.google.errorprone.annotations.Var;
50
import com.typesafe.config.Config;
51

52
/**
53
 * A factory for creating a cache from the configuration.
54
 *
55
 * @author ben.manes@gmail.com (Ben Manes)
56
 */
57
final class CacheFactory {
58

59
  private CacheFactory() {}
60

61
  /**
62
   * Returns if the cache definition is found in the external settings file.
63
   *
64
   * @param cacheManager the owner
65
   * @param cacheName the name of the cache
66
   * @return {@code true} if a definition exists
67
   */
68
  public static boolean isDefinedExternally(CacheManager cacheManager, String cacheName) {
69
    return TypesafeConfigurator.cacheNames(rootConfig(cacheManager)).contains(cacheName);
×
70
  }
71

72
  /**
73
   * Returns a newly created cache instance if a definition is found in the external settings file.
74
   *
75
   * @param cacheManager the owner
76
   * @param cacheName the name of the cache
77
   * @return a new cache instance or null if the named cache is not defined in the settings file
78
   */
79
  @SuppressWarnings("resource")
80
  public static <K, V> @Nullable CacheProxy<K, V> tryToCreateFromExternalSettings(
81
      CacheManagerImpl cacheManager, String cacheName) {
82
    return TypesafeConfigurator.<K, V>from(rootConfig(cacheManager), cacheName)
×
83
        .map(configuration -> createCache(cacheManager, cacheName, configuration))
×
84
        .orElse(null);
×
85
  }
86

87
  /**
88
   * Returns a fully constructed cache based on the cache
89
   *
90
   * @param cacheManager the owner of the cache instance
91
   * @param cacheName the name of the cache
92
   * @param configuration the full cache definition
93
   * @return a newly constructed cache instance
94
   */
95
  public static <K, V> CacheProxy<K, V> createCache(CacheManagerImpl cacheManager,
96
      String cacheName, Configuration<K, V> configuration) {
97
    CaffeineConfiguration<K, V> config = resolveConfigurationFor(cacheManager, configuration);
×
98
    return new Builder<>(cacheManager, cacheName, config).build();
×
99
  }
100

101
  /** Returns the resolved configuration. */
102
  private static Config rootConfig(CacheManager cacheManager) {
103
    return requireNonNull(TypesafeConfigurator.configSource().get(
×
104
        cacheManager.getURI(), cacheManager.getClassLoader()));
×
105
  }
106

107
  /** Copies the configuration and overlays it on top of the default settings. */
108
  private static <K, V> CaffeineConfiguration<K, V> resolveConfigurationFor(
109
      CacheManager cacheManager, Configuration<K, V> configuration) {
110
    if (configuration instanceof CaffeineConfiguration<?, ?>) {
×
111
      return new CaffeineConfiguration<>((CaffeineConfiguration<K, V>) configuration);
×
112
    }
113

114
    CaffeineConfiguration<K, V> template = TypesafeConfigurator.defaults(rootConfig(cacheManager));
×
115
    if (configuration instanceof CompleteConfiguration<?, ?>) {
×
116
      var complete = (CompleteConfiguration<K, V>) configuration;
×
117
      template.setReadThrough(complete.isReadThrough());
×
118
      template.setWriteThrough(complete.isWriteThrough());
×
119
      template.setManagementEnabled(complete.isManagementEnabled());
×
120
      template.setStatisticsEnabled(complete.isStatisticsEnabled());
×
121
      var existing = new ArrayList<CacheEntryListenerConfiguration<K, V>>();
×
122
      template.getCacheEntryListenerConfigurations().forEach(existing::add);
×
123
      existing.forEach(template::removeCacheEntryListenerConfiguration);
×
124
      complete.getCacheEntryListenerConfigurations()
×
125
          .forEach(template::addCacheEntryListenerConfiguration);
×
126
      template.setCacheLoaderFactory(complete.getCacheLoaderFactory());
×
127
      template.setCacheWriterFactory(complete.getCacheWriterFactory());
×
128
      template.setExpiryPolicyFactory(complete.getExpiryPolicyFactory());
×
129
    }
130

131
    template.setTypes(configuration.getKeyType(), configuration.getValueType());
×
132
    template.setStoreByValue(configuration.isStoreByValue());
×
133
    return template;
×
134
  }
135

136
  /** A one-shot builder for creating a cache instance. */
137
  private static final class Builder<K, V> {
138
    final Copier copier;
139
    final Ticker ticker;
140
    final String cacheName;
141
    final Executor executor;
142
    final Scheduler scheduler;
143
    final ExpiryPolicy expiryPolicy;
144
    final CacheManagerImpl cacheManager;
145
    final EventDispatcher<K, V> dispatcher;
146
    final JCacheStatisticsMXBean statistics;
147
    final Caffeine<Object, Object> caffeine;
148
    final CaffeineConfiguration<K, V> config;
149

150
    Builder(CacheManagerImpl cacheManager, String cacheName, CaffeineConfiguration<K, V> config) {
×
151
      this.config = config;
×
152
      this.cacheName = cacheName;
×
153
      this.cacheManager = cacheManager;
×
154
      this.caffeine = Caffeine.newBuilder();
×
155
      this.statistics = new JCacheStatisticsMXBean();
×
156
      this.ticker = config.getTickerFactory().create();
×
157
      this.executor = config.getExecutorFactory().create();
×
158
      this.scheduler = config.getSchedulerFactory().create();
×
159
      this.expiryPolicy = config.getExpiryPolicyFactory().create();
×
160
      this.dispatcher = new EventDispatcher<>(executor);
×
161
      this.copier = config.isStoreByValue()
×
162
          ? config.getCopierFactory().create()
×
163
          : Copier.identity();
×
164

165
      caffeine.ticker(ticker);
×
166
      caffeine.executor(executor);
×
167
      caffeine.scheduler(scheduler);
×
168
      config.getCacheEntryListenerConfigurations().forEach(dispatcher::register);
×
169
    }
×
170

171
    /** Creates a configured cache. */
172
    CacheProxy<K, V> build() {
173
      @Var boolean evicts = false;
×
174
      evicts |= configureMaximumSize();
×
175
      evicts |= configureMaximumWeight();
×
176

177
      @Var boolean expires = false;
×
178
      expires |= configureExpireAfterWrite();
×
179
      expires |= configureExpireAfterAccess();
×
180
      expires |= configureExpireVariably();
×
181
      if (!expires) {
×
182
        expires = configureJCacheExpiry();
×
183
      }
184

185
      if (config.isNativeStatisticsEnabled()) {
×
186
        caffeine.recordStats();
×
187
      }
188

189
      @Var JCacheEvictionListener<K, V> evictionListener = null;
×
190
      if (evicts || expires) {
×
191
        evictionListener = new JCacheEvictionListener<>(dispatcher, statistics);
×
192
        caffeine.evictionListener(evictionListener);
×
193
      }
194

195
      CacheProxy<K, V> cache;
196
      if (isReadThrough()) {
×
197
        configureRefreshAfterWrite();
×
198
        cache = newLoadingCacheProxy();
×
199
      } else {
200
        cache = newCacheProxy();
×
201
      }
202

203
      if (evictionListener != null) {
×
204
        evictionListener.setCache(cache);
×
205
      }
206
      return cache;
×
207
    }
208

209
    /** Determines if the cache should operate in read through mode. */
210
    private boolean isReadThrough() {
211
      return config.isReadThrough() && (config.getCacheLoaderFactory() != null);
×
212
    }
213

214
    /** Creates a cache that does not read through on a cache miss. */
215
    private CacheProxy<K, V> newCacheProxy() {
216
      var cacheLoaderFactory = config.getCacheLoaderFactory();
×
217
      var cacheLoader = (cacheLoaderFactory == null) ? null : cacheLoaderFactory.create();
×
218
      return new CacheProxy<>(cacheName, executor, cacheManager, config, caffeine.build(),
×
219
          dispatcher, Optional.ofNullable(cacheLoader), expiryPolicy, ticker, statistics, copier);
×
220
    }
221

222
    /** Creates a cache that reads through on a cache miss. */
223
    private CacheProxy<K, V> newLoadingCacheProxy() {
224
      CacheLoader<K, V> cacheLoader = requireNonNull(config.getCacheLoaderFactory()).create();
×
225
      var adapter = new JCacheLoaderAdapter<>(cacheLoader, dispatcher, expiryPolicy, ticker,
×
226
          statistics, copier, requireNonNull(cacheManager.getClassLoader()));
×
227
      var cache = caffeine.build(adapter);
×
228
      var jcache = new LoadingCacheProxy<>(cacheName, executor, cacheManager, config,
×
229
          cache, dispatcher, cacheLoader, expiryPolicy, ticker, statistics, copier);
230
      adapter.setCache(jcache);
×
231
      return jcache;
×
232
    }
233

234
    /** Configures the maximum size and returns if set. */
235
    private boolean configureMaximumSize() {
236
      if (config.getMaximumSize().isPresent()) {
×
237
        caffeine.maximumSize(config.getMaximumSize().getAsLong());
×
238
      }
239
      return config.getMaximumSize().isPresent();
×
240
    }
241

242
    /** Configures the maximum weight and returns if set. */
243
    private boolean configureMaximumWeight() {
244
      if (config.getMaximumWeight().isPresent()) {
×
245
        caffeine.maximumWeight(config.getMaximumWeight().getAsLong());
×
246
        Weigher<K, V> weigher = config.getWeigherFactory().map(Factory::create)
×
247
            .orElseThrow(() -> new IllegalStateException("Weigher not configured"));
×
248
        caffeine.weigher((K key, Expirable<V> expirable) -> {
×
249
          return weigher.weigh(key, expirable.get());
×
250
        });
251
      }
252
      return config.getMaximumWeight().isPresent();
×
253
    }
254

255
    /** Configures write expiration and returns if set. */
256
    private boolean configureExpireAfterWrite() {
257
      if (config.getExpireAfterWrite().isEmpty()) {
×
258
        return false;
×
259
      }
260
      caffeine.expireAfterWrite(Duration.ofNanos(config.getExpireAfterWrite().getAsLong()));
×
261
      return true;
×
262
    }
263

264
    /** Configures access expiration and returns if set. */
265
    private boolean configureExpireAfterAccess() {
266
      if (config.getExpireAfterAccess().isEmpty()) {
×
267
        return false;
×
268
      }
269
      caffeine.expireAfterAccess(Duration.ofNanos(config.getExpireAfterAccess().getAsLong()));
×
270
      return true;
×
271
    }
272

273
    /** Configures the custom expiration and returns if set. */
274
    private boolean configureExpireVariably() {
275
      if (config.getExpiryFactory().isEmpty()) {
×
276
        return false;
×
277
      }
278
      caffeine.expireAfter(new ExpiryAdapter<>(config.getExpiryFactory().orElseThrow().create()));
×
279
      return true;
×
280
    }
281

282
    private boolean configureJCacheExpiry() {
283
      if (expiryPolicy instanceof EternalExpiryPolicy) {
×
284
        return false;
×
285
      }
286
      caffeine.expireAfter(new ExpirableToExpiry<>(ticker));
×
287
      return true;
×
288
    }
289

290
    private void configureRefreshAfterWrite() {
291
      if (config.getRefreshAfterWrite().isPresent()) {
×
292
        caffeine.refreshAfterWrite(Duration.ofNanos(config.getRefreshAfterWrite().getAsLong()));
×
293
      }
294
    }
×
295
  }
296

297
  private static final class ExpiryAdapter<K, V> implements Expiry<K, Expirable<V>> {
298
    private final Expiry<K, V> expiry;
299

300
    ExpiryAdapter(Expiry<K, V> expiry) {
×
301
      this.expiry = requireNonNull(expiry);
×
302
    }
×
303
    @Override public long expireAfterCreate(K key, Expirable<V> expirable, long currentTime) {
304
      return expiry.expireAfterCreate(key, expirable.get(), currentTime);
×
305
    }
306
    @Override public long expireAfterUpdate(K key, Expirable<V> expirable,
307
        long currentTime, long currentDuration) {
308
      return expiry.expireAfterUpdate(key, expirable.get(), currentTime, currentDuration);
×
309
    }
310
    @Override public long expireAfterRead(K key, Expirable<V> expirable,
311
        long currentTime, long currentDuration) {
312
      return expiry.expireAfterRead(key, expirable.get(), currentTime, currentDuration);
×
313
    }
314
  }
315

316
  private static final class ExpirableToExpiry<K, V> implements Expiry<K, Expirable<V>> {
317
    private final Ticker ticker;
318

319
    ExpirableToExpiry(Ticker ticker) {
×
320
      this.ticker = requireNonNull(ticker);
×
321
    }
×
322
    @Override public long expireAfterCreate(K key, Expirable<V> expirable, long currentTime) {
323
      return toNanos(expirable);
×
324
    }
325
    @Override public long expireAfterUpdate(K key, Expirable<V> expirable,
326
        long currentTime, long currentDuration) {
327
      return toNanos(expirable);
×
328
    }
329
    @Override public long expireAfterRead(K key, Expirable<V> expirable,
330
        long currentTime, long currentDuration) {
331
      return toNanos(expirable);
×
332
    }
333
    private long toNanos(Expirable<V> expirable) {
334
      if (expirable.getExpireTimeMillis() == 0L) {
×
335
        return -1L;
×
336
      } else if (expirable.isEternal()) {
×
337
        return Long.MAX_VALUE;
×
338
      }
339
      return TimeUnit.MILLISECONDS.toNanos(expirable.getExpireTimeMillis()) - ticker.read();
×
340
    }
341
  }
342
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc