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

ben-manes / caffeine / #5173

29 Dec 2025 05:27AM UTC coverage: 0.0% (-100.0%) from 100.0%
#5173

push

github

ben-manes
speed up development ci build

0 of 3838 branches covered (0.0%)

0 of 7869 relevant lines covered (0.0%)

0.0 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.Optional;
22
import java.util.concurrent.Executor;
23
import java.util.concurrent.TimeUnit;
24

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

33
import org.jspecify.annotations.Nullable;
34

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

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

56
  private CacheFactory() {}
57

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

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

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

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

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

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

127
    template.setTypes(configuration.getKeyType(), configuration.getValueType());
×
128
    template.setStoreByValue(configuration.isStoreByValue());
×
129
    return template;
×
130
  }
131

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

145
    Builder(CacheManager cacheManager, String cacheName, CaffeineConfiguration<K, V> config) {
×
146
      this.config = config;
×
147
      this.cacheName = cacheName;
×
148
      this.cacheManager = cacheManager;
×
149
      this.caffeine = Caffeine.newBuilder();
×
150
      this.statistics = new JCacheStatisticsMXBean();
×
151
      this.ticker = config.getTickerFactory().create();
×
152
      this.executor = config.getExecutorFactory().create();
×
153
      this.scheduler = config.getSchedulerFactory().create();
×
154
      this.expiryPolicy = config.getExpiryPolicyFactory().create();
×
155
      this.dispatcher = new EventDispatcher<>(executor);
×
156

157
      caffeine.ticker(ticker);
×
158
      caffeine.executor(executor);
×
159
      caffeine.scheduler(scheduler);
×
160
      config.getCacheEntryListenerConfigurations().forEach(dispatcher::register);
×
161
    }
×
162

163
    /** Creates a configured cache. */
164
    CacheProxy<K, V> build() {
165
      @Var boolean evicts = false;
×
166
      evicts |= configureMaximumSize();
×
167
      evicts |= configureMaximumWeight();
×
168

169
      @Var boolean expires = false;
×
170
      expires |= configureExpireAfterWrite();
×
171
      expires |= configureExpireAfterAccess();
×
172
      expires |= configureExpireVariably();
×
173
      if (!expires) {
×
174
        expires = configureJCacheExpiry();
×
175
      }
176

177
      if (config.isNativeStatisticsEnabled()) {
×
178
        caffeine.recordStats();
×
179
      }
180

181
      @Var JCacheEvictionListener<K, V> evictionListener = null;
×
182
      if (evicts || expires) {
×
183
        evictionListener = new JCacheEvictionListener<>(dispatcher, statistics);
×
184
        caffeine.evictionListener(evictionListener);
×
185
      }
186

187
      CacheProxy<K, V> cache;
188
      if (isReadThrough()) {
×
189
        configureRefreshAfterWrite();
×
190
        cache = newLoadingCacheProxy();
×
191
      } else {
192
        cache = newCacheProxy();
×
193
      }
194

195
      if (evictionListener != null) {
×
196
        evictionListener.setCache(cache);
×
197
      }
198
      return cache;
×
199
    }
200

201
    /** Determines if the cache should operate in read through mode. */
202
    private boolean isReadThrough() {
203
      return config.isReadThrough() && (config.getCacheLoaderFactory() != null);
×
204
    }
205

206
    /** Creates a cache that does not read through on a cache miss. */
207
    private CacheProxy<K, V> newCacheProxy() {
208
      var cacheLoaderFactory = config.getCacheLoaderFactory();
×
209
      var cacheLoader = (cacheLoaderFactory == null) ? null : cacheLoaderFactory.create();
×
210
      return new CacheProxy<>(cacheName, executor, cacheManager, config, caffeine.build(),
×
211
          dispatcher, Optional.ofNullable(cacheLoader), expiryPolicy, ticker, statistics);
×
212
    }
213

214
    /** Creates a cache that reads through on a cache miss. */
215
    private CacheProxy<K, V> newLoadingCacheProxy() {
216
      @SuppressWarnings("NullAway")
217
      CacheLoader<K, V> cacheLoader = requireNonNull(config.getCacheLoaderFactory()).create();
×
218
      var adapter = new JCacheLoaderAdapter<>(
×
219
          cacheLoader, dispatcher, expiryPolicy, ticker, statistics);
220
      var cache = caffeine.build(adapter);
×
221
      var jcache = new LoadingCacheProxy<>(cacheName, executor, cacheManager, config,
×
222
          cache, dispatcher, cacheLoader, expiryPolicy, ticker, statistics);
223
      adapter.setCache(jcache);
×
224
      return jcache;
×
225
    }
226

227
    /** Configures the maximum size and returns if set. */
228
    private boolean configureMaximumSize() {
229
      if (config.getMaximumSize().isPresent()) {
×
230
        caffeine.maximumSize(config.getMaximumSize().getAsLong());
×
231
      }
232
      return config.getMaximumSize().isPresent();
×
233
    }
234

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

248
    /** Configures write expiration and returns if set. */
249
    private boolean configureExpireAfterWrite() {
250
      if (config.getExpireAfterWrite().isEmpty()) {
×
251
        return false;
×
252
      }
253
      caffeine.expireAfterWrite(Duration.ofNanos(config.getExpireAfterWrite().getAsLong()));
×
254
      return true;
×
255
    }
256

257
    /** Configures access expiration and returns if set. */
258
    private boolean configureExpireAfterAccess() {
259
      if (config.getExpireAfterAccess().isEmpty()) {
×
260
        return false;
×
261
      }
262
      caffeine.expireAfterAccess(Duration.ofNanos(config.getExpireAfterAccess().getAsLong()));
×
263
      return true;
×
264
    }
265

266
    /** Configures the custom expiration and returns if set. */
267
    private boolean configureExpireVariably() {
268
      if (config.getExpiryFactory().isEmpty()) {
×
269
        return false;
×
270
      }
271
      caffeine.expireAfter(new ExpiryAdapter<>(config.getExpiryFactory().orElseThrow().create()));
×
272
      return true;
×
273
    }
274

275
    private boolean configureJCacheExpiry() {
276
      if (expiryPolicy instanceof EternalExpiryPolicy) {
×
277
        return false;
×
278
      }
279
      caffeine.expireAfter(new ExpirableToExpiry<>(ticker));
×
280
      return true;
×
281
    }
282

283
    private void configureRefreshAfterWrite() {
284
      if (config.getRefreshAfterWrite().isPresent()) {
×
285
        caffeine.refreshAfterWrite(Duration.ofNanos(config.getRefreshAfterWrite().getAsLong()));
×
286
      }
287
    }
×
288
  }
289

290
  private static final class ExpiryAdapter<K, V> implements Expiry<K, Expirable<V>> {
291
    private final Expiry<K, V> expiry;
292

293
    ExpiryAdapter(Expiry<K, V> expiry) {
×
294
      this.expiry = requireNonNull(expiry);
×
295
    }
×
296
    @Override public long expireAfterCreate(K key, Expirable<V> expirable, long currentTime) {
297
      return expiry.expireAfterCreate(key, expirable.get(), currentTime);
×
298
    }
299
    @Override public long expireAfterUpdate(K key, Expirable<V> expirable,
300
        long currentTime, long currentDuration) {
301
      return expiry.expireAfterUpdate(key, expirable.get(), currentTime, currentDuration);
×
302
    }
303
    @Override public long expireAfterRead(K key, Expirable<V> expirable,
304
        long currentTime, long currentDuration) {
305
      return expiry.expireAfterRead(key, expirable.get(), currentTime, currentDuration);
×
306
    }
307
  }
308

309
  private static final class ExpirableToExpiry<K, V> implements Expiry<K, Expirable<V>> {
310
    private final Ticker ticker;
311

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

© 2026 Coveralls, Inc