• 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/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.Objects;
29
import java.util.Optional;
30
import java.util.OptionalLong;
31
import java.util.Set;
32
import java.util.concurrent.atomic.AtomicReference;
33
import java.util.function.Supplier;
34

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

45
import org.jspecify.annotations.NullMarked;
46
import org.jspecify.annotations.Nullable;
47

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

56
import jakarta.inject.Inject;
57

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

68
  static final AtomicReference<ConfigSource> configSource =
×
69
      new AtomicReference<>(TypesafeConfigurator::resolveConfig);
70
  static final AtomicReference<FactoryCreator> factoryCreator =
×
71
      new AtomicReference<>(FactoryBuilder::factoryOf);
72

73
  private TypesafeConfigurator() {}
74

75
  /**
76
   * Retrieves the names of the caches defined in the configuration resource.
77
   *
78
   * @param config the configuration resource
79
   * @return the names of the configured caches
80
   */
81
  public static Set<String> cacheNames(Config config) {
82
    return config.hasPath("caffeine.jcache")
×
83
        ? Collections.unmodifiableSet(config.getObject("caffeine.jcache").keySet())
×
84
        : Collections.emptySet();
×
85
  }
86

87
  /**
88
   * Retrieves the default cache settings from the configuration resource.
89
   *
90
   * @param config the configuration resource
91
   * @param <K> the type of keys maintained the cache
92
   * @param <V> the type of cached values
93
   * @return the default configuration for a cache
94
   */
95
  public static <K, V> CaffeineConfiguration<K, V> defaults(Config config) {
96
    return new Configurator<K, V>(config, "default").configure();
×
97
  }
98

99
  /**
100
   * Retrieves the cache's settings from the configuration resource if defined.
101
   *
102
   * @param config the configuration resource
103
   * @param cacheName the name of the cache
104
   * @param <K> the type of keys maintained the cache
105
   * @param <V> the type of cached values
106
   * @return the configuration for the cache
107
   */
108
  public static <K, V> Optional<CaffeineConfiguration<K, V>> from(Config config, String cacheName) {
109
    try {
110
      requireNonNull(cacheName);
×
111
      return config.hasPath("caffeine.jcache." + cacheName)
×
112
          ? Optional.of(new Configurator<K, V>(config, cacheName).configure())
×
113
          : Optional.empty();
×
114
    } catch (ConfigException.BadPath e) {
×
115
      logger.log(Level.WARNING, "Failed to load cache configuration", e);
×
116
      return Optional.empty();
×
117
    }
118
  }
119

120
  /**
121
   * Specifies how {@link Factory} instances are created for a given class name. The default
122
   * strategy uses {@link Class#newInstance()} and requires the class has a no-args constructor.
123
   *
124
   * @param factoryCreator the strategy for creating a factory
125
   */
126
  @Inject
127
  @SuppressWarnings({"deprecation", "UnnecessarilyVisible"})
128
  public static void setFactoryCreator(FactoryCreator factoryCreator) {
129
    TypesafeConfigurator.factoryCreator.set(requireNonNull(factoryCreator));
×
130
  }
×
131

132
  /** Returns the strategy for how factory instances are created. */
133
  public static FactoryCreator factoryCreator() {
134
    return requireNonNull(factoryCreator.get());
×
135
  }
136

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

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

164
  /** Returns the strategy for loading the configuration. */
165
  public static ConfigSource configSource() {
166
    return requireNonNull(configSource.get());
×
167
  }
168

169
  /** Returns the configuration by applying the default strategy. */
170
  private static Config resolveConfig(URI uri, ClassLoader classloader) {
171
    requireNonNull(uri);
×
172
    requireNonNull(classloader);
×
173
    var options = ConfigParseOptions.defaults()
×
174
        .setClassLoader(classloader)
×
175
        .setAllowMissing(false);
×
176
    if ((uri.getScheme() != null) && uri.getScheme().equalsIgnoreCase("file")) {
×
177
      return ConfigFactory.defaultOverrides(classloader)
×
178
          .withFallback(ConfigFactory.parseFile(new File(uri), options))
×
179
          .withFallback(ConfigFactory.defaultReferenceUnresolved(classloader));
×
180
    } else if ((uri.getScheme() != null) && uri.getScheme().equalsIgnoreCase("jar")) {
×
181
      try {
182
        return ConfigFactory.defaultOverrides(classloader)
×
183
            .withFallback(ConfigFactory.parseURL(uri.toURL(), options))
×
184
            .withFallback(ConfigFactory.defaultReferenceUnresolved(classloader));
×
185
      } catch (MalformedURLException e) {
×
186
        throw new ConfigException.BadPath(uri.toString(), "Failed to load cache configuration", e);
×
187
      }
188
    } else if (isResource(uri)) {
×
189
      return ConfigFactory.defaultOverrides(classloader)
×
190
          .withFallback(ConfigFactory.parseResources(uri.getSchemeSpecificPart(), options))
×
191
          .withFallback(ConfigFactory.defaultReferenceUnresolved(classloader));
×
192
    }
193
    return ConfigFactory.load(classloader);
×
194
  }
195

196
  /** Returns if the uri is a file or classpath resource. */
197
  private static boolean isResource(URI uri) {
198
    if ((uri.getScheme() != null) && !uri.getScheme().equalsIgnoreCase("classpath")) {
×
199
      return false;
×
200
    }
201
    var path = uri.getSchemeSpecificPart();
×
202
    int dotIndex = path.lastIndexOf('.');
×
203
    if (dotIndex != -1) {
×
204
      var extension = path.substring(dotIndex + 1);
×
205
      for (var format : ConfigSyntax.values()) {
×
206
        if (format.toString().equalsIgnoreCase(extension)) {
×
207
          return true;
×
208
        }
209
      }
210
    }
211
    return false;
×
212
  }
213

214
  /** A one-shot builder for creating a configuration instance. */
215
  static final class Configurator<K, V> {
216
    final CaffeineConfiguration<K, V> configuration;
217
    final Config customized;
218
    final Config merged;
219
    final Config root;
220

221
    Configurator(Config config, String cacheName) {
×
222
      this.root = requireNonNull(config);
×
223
      this.configuration = new CaffeineConfiguration<>();
×
224
      this.customized = root.getConfig("caffeine.jcache." + requireNonNull(cacheName));
×
225
      this.merged = customized.withFallback(root.getConfig("caffeine.jcache.default"));
×
226
    }
×
227

228
    /** Returns a configuration built from the external settings. */
229
    CaffeineConfiguration<K, V> configure() {
230
      addKeyValueTypes();
×
231
      addStoreByValue();
×
232
      addExecutor();
×
233
      addScheduler();
×
234
      addListeners();
×
235
      addReadThrough();
×
236
      addWriteThrough();
×
237
      addMonitoring();
×
238
      addLazyExpiration();
×
239
      addEagerExpiration();
×
240
      addRefresh();
×
241
      addMaximum();
×
242

243
      return configuration;
×
244
    }
245

246
    /** Adds the key and value class types. */
247
    private void addKeyValueTypes() {
248
      try {
249
        @SuppressWarnings("unchecked")
250
        var keyType = (Class<K>) Class.forName(merged.getString("key-type"));
×
251
        @SuppressWarnings("unchecked")
252
        var valueType = (Class<V>) Class.forName(merged.getString("value-type"));
×
253
        configuration.setTypes(keyType, valueType);
×
254
      } catch (ClassNotFoundException e) {
×
255
        throw new IllegalStateException(e);
×
256
      }
×
257
    }
×
258

259
    /** Adds the store-by-value settings. */
260
    private void addStoreByValue() {
261
      configuration.setStoreByValue(merged.getBoolean("store-by-value.enabled"));
×
262
      if (isSet("store-by-value.strategy")) {
×
263
        configuration.setCopierFactory(factoryCreator().factoryOf(
×
264
            merged.getString("store-by-value.strategy")));
×
265
      }
266
    }
×
267

268
    /** Adds the executor settings. */
269
    public void addExecutor() {
270
      if (isSet("executor")) {
×
271
        configuration.setExecutorFactory(factoryCreator()
×
272
            .factoryOf(merged.getString("executor")));
×
273
      }
274
    }
×
275

276
    /** Adds the scheduler settings. */
277
    public void addScheduler() {
278
      if (isSet("scheduler")) {
×
279
        configuration.setSchedulerFactory(factoryCreator()
×
280
            .factoryOf(merged.getString("scheduler")));
×
281
      }
282
    }
×
283

284
    /** Adds the entry listeners settings. */
285
    private void addListeners() {
286
      for (String path : merged.getStringList("listeners")) {
×
287
        Config listener = root.getConfig(path);
×
288

289
        Factory<? extends CacheEntryListener<? super K, ? super V>> listenerFactory =
290
            factoryCreator().factoryOf(listener.getString("class"));
×
291
        @Var Factory<? extends CacheEntryEventFilter<? super K, ? super V>> filterFactory = null;
×
292
        if (listener.hasPath("filter")) {
×
293
          filterFactory = factoryCreator().factoryOf(listener.getString("filter"));
×
294
        }
295
        boolean oldValueRequired = listener.getBoolean("old-value-required");
×
296
        boolean synchronous = listener.getBoolean("synchronous");
×
297
        configuration.addCacheEntryListenerConfiguration(
×
298
            new MutableCacheEntryListenerConfiguration<>(
299
                listenerFactory, filterFactory, oldValueRequired, synchronous));
300
      }
×
301
    }
×
302

303
    /** Adds the read through settings. */
304
    private void addReadThrough() {
305
      configuration.setReadThrough(merged.getBoolean("read-through.enabled"));
×
306
      if (isSet("read-through.loader")) {
×
307
        configuration.setCacheLoaderFactory(factoryCreator().factoryOf(
×
308
            merged.getString("read-through.loader")));
×
309
      }
310
    }
×
311

312
    /** Adds the write-through settings. */
313
    private void addWriteThrough() {
314
      configuration.setWriteThrough(merged.getBoolean("write-through.enabled"));
×
315
      if (isSet("write-through.writer")) {
×
316
        configuration.setCacheWriterFactory(factoryCreator().factoryOf(
×
317
            merged.getString("write-through.writer")));
×
318
      }
319
    }
×
320

321
    /** Adds the monitoring settings. */
322
    private void addMonitoring() {
323
      configuration.setNativeStatisticsEnabled(merged.getBoolean("monitoring.native-statistics"));
×
324
      configuration.setStatisticsEnabled(merged.getBoolean("monitoring.statistics"));
×
325
      configuration.setManagementEnabled(merged.getBoolean("monitoring.management"));
×
326
    }
×
327

328
    /** Adds the JCache specification's lazy expiration settings. */
329
    public void addLazyExpiration() {
330
      Duration creation = getDurationFor("policy.lazy-expiration.creation");
×
331
      Duration update = getDurationFor("policy.lazy-expiration.update");
×
332
      Duration access = getDurationFor("policy.lazy-expiration.access");
×
333
      requireNonNull(creation, "policy.lazy-expiration.creation may not be null");
×
334

335
      boolean eternal = Objects.equals(creation, Duration.ETERNAL)
×
336
          && Objects.equals(update, Duration.ETERNAL)
×
337
          && Objects.equals(access, Duration.ETERNAL);
×
338
      Factory<? extends ExpiryPolicy> factory = eternal
×
339
          ? EternalExpiryPolicy.factoryOf()
×
340
          : FactoryBuilder.factoryOf(new JCacheExpiryPolicy(creation, update, access));
×
341
      configuration.setExpiryPolicyFactory(factory);
×
342
    }
×
343

344
    /** Returns the duration for the expiration time. */
345
    private @Nullable Duration getDurationFor(String path) {
346
      if (!isSet(path)) {
×
347
        return null;
×
348
      }
349
      if (merged.getString(path).equalsIgnoreCase("eternal")) {
×
350
        return Duration.ETERNAL;
×
351
      }
352
      long millis = merged.getDuration(path, MILLISECONDS);
×
353
      return new Duration(MILLISECONDS, millis);
×
354
    }
355

356
    /** Adds the Caffeine eager expiration settings. */
357
    public void addEagerExpiration() {
358
      if (isSet("policy.eager-expiration.after-write")) {
×
359
        long nanos = merged.getDuration("policy.eager-expiration.after-write", NANOSECONDS);
×
360
        configuration.setExpireAfterWrite(OptionalLong.of(nanos));
×
361
      }
362
      if (isSet("policy.eager-expiration.after-access")) {
×
363
        long nanos = merged.getDuration("policy.eager-expiration.after-access", NANOSECONDS);
×
364
        configuration.setExpireAfterAccess(OptionalLong.of(nanos));
×
365
      }
366
      if (isSet("policy.eager-expiration.variable")) {
×
367
        configuration.setExpiryFactory(Optional.of(FactoryBuilder.factoryOf(
×
368
            merged.getString("policy.eager-expiration.variable"))));
×
369
      }
370
    }
×
371

372
    /** Adds the Caffeine refresh settings. */
373
    public void addRefresh() {
374
      if (isSet("policy.refresh.after-write")) {
×
375
        long nanos = merged.getDuration("policy.refresh.after-write", NANOSECONDS);
×
376
        configuration.setRefreshAfterWrite(OptionalLong.of(nanos));
×
377
      }
378
    }
×
379

380
    /** Adds the maximum size and weight bounding settings. */
381
    private void addMaximum() {
382
      if (isSet("policy.maximum.size")) {
×
383
        configuration.setMaximumSize(OptionalLong.of(merged.getLong("policy.maximum.size")));
×
384
      }
385
      if (isSet("policy.maximum.weight")) {
×
386
        configuration.setMaximumWeight(OptionalLong.of(merged.getLong("policy.maximum.weight")));
×
387
      }
388
      if (isSet("policy.maximum.weigher")) {
×
389
        configuration.setWeigherFactory(Optional.of(
×
390
            FactoryBuilder.factoryOf(merged.getString("policy.maximum.weigher"))));
×
391
      }
392
    }
×
393

394
    /** Returns if the value is present (not unset by the cache configuration). */
395
    private boolean isSet(String path) {
396
      if (!merged.hasPath(path)) {
×
397
        return false;
×
398
      } else if (customized.hasPathOrNull(path)) {
×
399
        return !customized.getIsNull(path);
×
400
      }
401
      return true;
×
402
    }
403
  }
404
}
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