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

ben-manes / caffeine / #5707

02 Aug 2026 12:45AM UTC coverage: 0.106% (-99.9%) from 100.0%
#5707

push

github

ben-manes
fix wikibench trace reader after overly strict audit fixes

0 of 4235 branches covered (0.0%)

9 of 8528 relevant lines covered (0.11%)

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/configuration/TypesafeConfigurator.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.configuration;
17

18
import static java.util.Objects.requireNonNull;
19
import static java.util.concurrent.TimeUnit.MILLISECONDS;
20
import static java.util.concurrent.TimeUnit.NANOSECONDS;
21

22
import java.io.File;
23
import java.lang.System.Logger;
24
import java.lang.System.Logger.Level;
25
import java.net.MalformedURLException;
26
import java.net.URI;
27
import java.util.Collections;
28
import java.util.LinkedHashSet;
29
import java.util.Objects;
30
import java.util.Optional;
31
import java.util.OptionalLong;
32
import java.util.Set;
33
import java.util.concurrent.atomic.AtomicReference;
34
import java.util.function.Supplier;
35

36
import javax.cache.CacheException;
37
import javax.cache.CacheManager;
38
import javax.cache.configuration.Factory;
39
import javax.cache.configuration.FactoryBuilder;
40
import javax.cache.configuration.MutableCacheEntryListenerConfiguration;
41
import javax.cache.event.CacheEntryEventFilter;
42
import javax.cache.event.CacheEntryListener;
43
import javax.cache.expiry.Duration;
44
import javax.cache.expiry.EternalExpiryPolicy;
45
import javax.cache.expiry.ExpiryPolicy;
46

47
import org.jspecify.annotations.NullMarked;
48
import org.jspecify.annotations.Nullable;
49

50
import com.github.benmanes.caffeine.jcache.expiry.JCacheExpiryPolicy;
51
import com.google.errorprone.annotations.Var;
52
import com.typesafe.config.Config;
53
import com.typesafe.config.ConfigException;
54
import com.typesafe.config.ConfigFactory;
55
import com.typesafe.config.ConfigParseOptions;
56
import com.typesafe.config.ConfigSyntax;
57

58
import jakarta.inject.Inject;
59

60
/**
61
 * Static utility methods pertaining to externalized {@link CaffeineConfiguration} entries using the
62
 * Typesafe Config library.
63
 *
64
 * @author ben.manes@gmail.com (Ben Manes)
65
 */
66
@NullMarked
67
public final class TypesafeConfigurator {
68
  static final Logger logger = System.getLogger(TypesafeConfigurator.class.getName());
×
69

70
  /** Sections nested under {@code caffeine.jcache} that are reserved and not cache definitions. */
71
  static final Set<String> RESERVED_NAMES = Set.of("default", "listeners");
×
72

73
  static final AtomicReference<ConfigSource> configSource =
×
74
      new AtomicReference<>(TypesafeConfigurator::resolveConfig);
75
  static final AtomicReference<FactoryCreator> factoryCreator =
×
76
      new AtomicReference<>(FactoryBuilder::factoryOf);
77

78
  private TypesafeConfigurator() {}
79

80
  /**
81
   * Retrieves the names of the caches defined in the configuration resource.
82
   *
83
   * @param config the configuration resource
84
   * @return the names of the configured caches
85
   */
86
  public static Set<String> cacheNames(Config config) {
87
    if (!config.hasPath("caffeine.jcache")) {
×
88
      return Collections.emptySet();
×
89
    }
90
    var names = new LinkedHashSet<>(config.getObject("caffeine.jcache").keySet());
×
91
    names.removeAll(RESERVED_NAMES);
×
92
    return Collections.unmodifiableSet(names);
×
93
  }
94

95
  /**
96
   * Retrieves the default cache settings from the configuration resource.
97
   *
98
   * @param config the configuration resource
99
   * @param <K> the type of keys maintained the cache
100
   * @param <V> the type of cached values
101
   * @return the default configuration for a cache
102
   */
103
  public static <K, V> CaffeineConfiguration<K, V> defaults(Config config) {
104
    return new Configurator<K, V>(config, "default").configure();
×
105
  }
106

107
  /**
108
   * Retrieves the cache's settings from the configuration resource if defined.
109
   *
110
   * @param config the configuration resource
111
   * @param cacheName the name of the cache
112
   * @param <K> the type of keys maintained the cache
113
   * @param <V> the type of cached values
114
   * @return the configuration for the cache
115
   */
116
  public static <K, V> Optional<CaffeineConfiguration<K, V>> from(Config config, String cacheName) {
117
    try {
118
      requireNonNull(cacheName);
×
119
      return (!RESERVED_NAMES.contains(cacheName) && config.hasPath("caffeine.jcache." + cacheName))
×
120
          ? Optional.of(new Configurator<K, V>(config, cacheName).configure())
×
121
          : Optional.empty();
×
122
    } catch (ConfigException.BadPath e) {
×
123
      logger.log(Level.WARNING, "Failed to load cache configuration", e);
×
124
      return Optional.empty();
×
125
    } catch (ConfigException e) {
×
126
      throw new CacheException("Failed to load the configuration for cache " + cacheName, e);
×
127
    }
128
  }
129

130
  /**
131
   * Specifies how {@link Factory} instances are created for a given class name. The default
132
   * strategy uses {@link Class#newInstance()} and requires the class has a no-args constructor.
133
   *
134
   * @param factoryCreator the strategy for creating a factory
135
   */
136
  @Inject
137
  @SuppressWarnings({"deprecation", "UnnecessarilyVisible"})
138
  public static void setFactoryCreator(FactoryCreator factoryCreator) {
139
    TypesafeConfigurator.factoryCreator.set(requireNonNull(factoryCreator));
×
140
  }
×
141

142
  /** Returns the strategy for how factory instances are created. */
143
  public static FactoryCreator factoryCreator() {
144
    return requireNonNull(factoryCreator.get());
×
145
  }
146

147
  /**
148
   * Specifies how the {@link Config} instance should be loaded. The default strategy uses the uri
149
   * provided by {@link CacheManager#getURI()} as an optional override location to parse from a
150
   * file system or classpath resource, or else returns {@link ConfigFactory#load(ClassLoader)}.
151
   * The configuration is retrieved on-demand, allowing for it to be reloaded, and it is assumed
152
   * that the source caches it as needed.
153
   *
154
   * @param configSource the strategy for loading the configuration
155
   */
156
  public static void setConfigSource(Supplier<Config> configSource) {
157
    requireNonNull(configSource);
×
158
    setConfigSource((uri, classloader) -> configSource.get());
×
159
  }
×
160

161
  /**
162
   * Specifies how the {@link Config} instance should be loaded. The default strategy uses the uri
163
   * provided by {@link CacheManager#getURI()} as an optional override location to parse from a
164
   * file system or classpath resource, or else returns {@link ConfigFactory#load(ClassLoader)}.
165
   * The configuration is retrieved on-demand, allowing for it to be reloaded, and it is assumed
166
   * that the source caches it as needed.
167
   *
168
   * @param configSource the strategy for loading the configuration from a URI
169
   */
170
  public static void setConfigSource(ConfigSource configSource) {
171
    TypesafeConfigurator.configSource.set(requireNonNull(configSource));
×
172
  }
×
173

174
  /** Returns the strategy for loading the configuration. */
175
  public static ConfigSource configSource() {
176
    return requireNonNull(configSource.get());
×
177
  }
178

179
  /** Returns the configuration by applying the default strategy. */
180
  private static Config resolveConfig(URI uri, ClassLoader classloader) {
181
    requireNonNull(uri);
×
182
    requireNonNull(classloader);
×
183
    var options = ConfigParseOptions.defaults()
×
184
        .setClassLoader(classloader)
×
185
        .setAllowMissing(false);
×
186
    if ((uri.getScheme() != null) && uri.getScheme().equalsIgnoreCase("file")) {
×
187
      return ConfigFactory.defaultOverrides(classloader)
×
188
          .withFallback(ConfigFactory.parseFile(new File(uri), options))
×
189
          .withFallback(ConfigFactory.defaultReferenceUnresolved(classloader))
×
190
          .resolve();
×
191
    } else if ((uri.getScheme() != null) && uri.getScheme().equalsIgnoreCase("jar")) {
×
192
      try {
193
        return ConfigFactory.defaultOverrides(classloader)
×
194
            .withFallback(ConfigFactory.parseURL(uri.toURL(), options))
×
195
            .withFallback(ConfigFactory.defaultReferenceUnresolved(classloader))
×
196
            .resolve();
×
197
      } catch (MalformedURLException e) {
×
198
        throw new ConfigException.BadPath(uri.toString(), "Failed to load cache configuration", e);
×
199
      }
200
    } else if (isResource(uri)) {
×
201
      return ConfigFactory.defaultOverrides(classloader)
×
202
          .withFallback(ConfigFactory.parseResources(uri.getSchemeSpecificPart(), options))
×
203
          .withFallback(ConfigFactory.defaultReferenceUnresolved(classloader))
×
204
          .resolve();
×
205
    }
206
    return ConfigFactory.load(classloader);
×
207
  }
208

209
  /** Returns if the uri is a file or classpath resource. */
210
  private static boolean isResource(URI uri) {
211
    if ((uri.getScheme() != null) && !uri.getScheme().equalsIgnoreCase("classpath")) {
×
212
      return false;
×
213
    }
214
    var path = uri.getSchemeSpecificPart();
×
215
    int dotIndex = path.lastIndexOf('.');
×
216
    if (dotIndex != -1) {
×
217
      var extension = path.substring(dotIndex + 1);
×
218
      for (var format : ConfigSyntax.values()) {
×
219
        if (format.toString().equalsIgnoreCase(extension)) {
×
220
          return true;
×
221
        }
222
      }
223
    }
224
    return false;
×
225
  }
226

227
  /** A one-shot builder for creating a configuration instance. */
228
  static final class Configurator<K, V> {
229
    final CaffeineConfiguration<K, V> configuration;
230
    final Config customized;
231
    final Config merged;
232
    final Config root;
233

234
    Configurator(Config config, String cacheName) {
×
235
      this.root = requireNonNull(config);
×
236
      this.configuration = new CaffeineConfiguration<>();
×
237
      this.customized = root.getConfig("caffeine.jcache." + requireNonNull(cacheName));
×
238
      this.merged = customized.withFallback(root.getConfig("caffeine.jcache.default"));
×
239
    }
×
240

241
    /** Returns a configuration built from the external settings. */
242
    CaffeineConfiguration<K, V> configure() {
243
      addKeyValueTypes();
×
244
      addStoreByValue();
×
245
      addExecutor();
×
246
      addScheduler();
×
247
      addListeners();
×
248
      addReadThrough();
×
249
      addWriteThrough();
×
250
      addMonitoring();
×
251
      addLazyExpiration();
×
252
      addEagerExpiration();
×
253
      addRefresh();
×
254
      addMaximum();
×
255

256
      return configuration;
×
257
    }
258

259
    /** Adds the key and value class types. */
260
    private void addKeyValueTypes() {
261
      @Var ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
×
262
      if (classLoader == null) {
×
263
        classLoader = getClass().getClassLoader();
×
264
      }
265
      try {
266
        @SuppressWarnings("unchecked")
267
        var keyType = (Class<K>) Class.forName(merged.getString("key-type"), true, classLoader);
×
268
        @SuppressWarnings("unchecked")
269
        var valueType = (Class<V>) Class.forName(merged.getString("value-type"), true, classLoader);
×
270
        configuration.setTypes(keyType, valueType);
×
271
      } catch (ClassNotFoundException e) {
×
272
        throw new IllegalStateException(e);
×
273
      }
×
274
    }
×
275

276
    /** Adds the store-by-value settings. */
277
    private void addStoreByValue() {
278
      configuration.setStoreByValue(merged.getBoolean("store-by-value.enabled"));
×
279
      if (isSet("store-by-value.strategy")) {
×
280
        configuration.setCopierFactory(factoryCreator().factoryOf(
×
281
            merged.getString("store-by-value.strategy")));
×
282
      }
283
    }
×
284

285
    /** Adds the executor settings. */
286
    public void addExecutor() {
287
      if (isSet("executor")) {
×
288
        configuration.setExecutorFactory(factoryCreator()
×
289
            .factoryOf(merged.getString("executor")));
×
290
      }
291
    }
×
292

293
    /** Adds the scheduler settings. */
294
    public void addScheduler() {
295
      if (isSet("scheduler")) {
×
296
        configuration.setSchedulerFactory(factoryCreator()
×
297
            .factoryOf(merged.getString("scheduler")));
×
298
      }
299
    }
×
300

301
    /** Adds the entry listeners settings. */
302
    private void addListeners() {
303
      for (String path : merged.getStringList("listeners")) {
×
304
        Config listener = root.getConfig(path);
×
305

306
        Factory<? extends CacheEntryListener<? super K, ? super V>> listenerFactory =
307
            factoryCreator().factoryOf(listener.getString("class"));
×
308
        @Var Factory<? extends CacheEntryEventFilter<? super K, ? super V>> filterFactory = null;
×
309
        if (listener.hasPath("filter")) {
×
310
          filterFactory = factoryCreator().factoryOf(listener.getString("filter"));
×
311
        }
312
        boolean oldValueRequired = listener.getBoolean("old-value-required");
×
313
        boolean synchronous = listener.getBoolean("synchronous");
×
314
        configuration.addCacheEntryListenerConfiguration(
×
315
            new MutableCacheEntryListenerConfiguration<>(
316
                listenerFactory, filterFactory, oldValueRequired, synchronous));
317
      }
×
318
    }
×
319

320
    /** Adds the read through settings. */
321
    private void addReadThrough() {
322
      configuration.setReadThrough(merged.getBoolean("read-through.enabled"));
×
323
      if (isSet("read-through.loader")) {
×
324
        configuration.setCacheLoaderFactory(factoryCreator().factoryOf(
×
325
            merged.getString("read-through.loader")));
×
326
      }
327
    }
×
328

329
    /** Adds the write-through settings. */
330
    private void addWriteThrough() {
331
      configuration.setWriteThrough(merged.getBoolean("write-through.enabled"));
×
332
      if (isSet("write-through.writer")) {
×
333
        configuration.setCacheWriterFactory(factoryCreator().factoryOf(
×
334
            merged.getString("write-through.writer")));
×
335
      }
336
    }
×
337

338
    /** Adds the monitoring settings. */
339
    private void addMonitoring() {
340
      configuration.setNativeStatisticsEnabled(merged.getBoolean("monitoring.native-statistics"));
×
341
      configuration.setStatisticsEnabled(merged.getBoolean("monitoring.statistics"));
×
342
      configuration.setManagementEnabled(merged.getBoolean("monitoring.management"));
×
343
    }
×
344

345
    /** Adds the JCache specification's lazy expiration settings. */
346
    public void addLazyExpiration() {
347
      Duration creation = getDurationFor("policy.lazy-expiration.creation");
×
348
      Duration update = getDurationFor("policy.lazy-expiration.update");
×
349
      Duration access = getDurationFor("policy.lazy-expiration.access");
×
350
      requireNonNull(creation, "policy.lazy-expiration.creation may not be null");
×
351

352
      boolean eternal = Objects.equals(creation, Duration.ETERNAL)
×
353
          && Objects.equals(update, Duration.ETERNAL)
×
354
          && Objects.equals(access, Duration.ETERNAL);
×
355
      Factory<? extends ExpiryPolicy> factory = eternal
×
356
          ? EternalExpiryPolicy.factoryOf()
×
357
          : FactoryBuilder.factoryOf(new JCacheExpiryPolicy(creation, update, access));
×
358
      configuration.setExpiryPolicyFactory(factory);
×
359
    }
×
360

361
    /** Returns the duration for the expiration time. */
362
    private @Nullable Duration getDurationFor(String path) {
363
      if (!isSet(path)) {
×
364
        return null;
×
365
      }
366
      if (merged.getString(path).equalsIgnoreCase("eternal")) {
×
367
        return Duration.ETERNAL;
×
368
      }
369
      long millis = merged.getDuration(path, MILLISECONDS);
×
370
      return new Duration(MILLISECONDS, millis);
×
371
    }
372

373
    /** Adds the Caffeine eager expiration settings. */
374
    public void addEagerExpiration() {
375
      if (isSet("policy.eager-expiration.after-write")) {
×
376
        long nanos = merged.getDuration("policy.eager-expiration.after-write", NANOSECONDS);
×
377
        configuration.setExpireAfterWrite(OptionalLong.of(nanos));
×
378
      }
379
      if (isSet("policy.eager-expiration.after-access")) {
×
380
        long nanos = merged.getDuration("policy.eager-expiration.after-access", NANOSECONDS);
×
381
        configuration.setExpireAfterAccess(OptionalLong.of(nanos));
×
382
      }
383
      if (isSet("policy.eager-expiration.variable")) {
×
384
        configuration.setExpiryFactory(Optional.of(factoryCreator().factoryOf(
×
385
            merged.getString("policy.eager-expiration.variable"))));
×
386
      }
387
    }
×
388

389
    /** Adds the Caffeine refresh settings. */
390
    public void addRefresh() {
391
      if (isSet("policy.refresh.after-write")) {
×
392
        long nanos = merged.getDuration("policy.refresh.after-write", NANOSECONDS);
×
393
        configuration.setRefreshAfterWrite(OptionalLong.of(nanos));
×
394
      }
395
    }
×
396

397
    /** Adds the maximum size and weight bounding settings. */
398
    private void addMaximum() {
399
      if (isSet("policy.maximum.size")) {
×
400
        configuration.setMaximumSize(OptionalLong.of(merged.getLong("policy.maximum.size")));
×
401
      }
402
      if (isSet("policy.maximum.weight")) {
×
403
        configuration.setMaximumWeight(OptionalLong.of(merged.getLong("policy.maximum.weight")));
×
404
      }
405
      if (isSet("policy.maximum.weigher")) {
×
406
        configuration.setWeigherFactory(Optional.of(
×
407
            factoryCreator().factoryOf(merged.getString("policy.maximum.weigher"))));
×
408
      }
409
    }
×
410

411
    /** Returns if the value is present (not unset by the cache configuration). */
412
    private boolean isSet(String path) {
413
      if (!merged.hasPath(path)) {
×
414
        return false;
×
415
      } else if (customized.hasPathOrNull(path)) {
×
416
        return !customized.getIsNull(path);
×
417
      }
418
      return true;
×
419
    }
420
  }
421
}
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