diff --git a/config/spotbugs/exclude.xml b/config/spotbugs/exclude.xml
index 280a0d94b8..b92dd26bf4 100644
--- a/config/spotbugs/exclude.xml
+++ b/config/spotbugs/exclude.xml
@@ -334,6 +334,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheFactory.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheFactory.java
index 2f22e1245c..021e2f196e 100644
--- a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheFactory.java
+++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheFactory.java
@@ -56,23 +56,24 @@ private CacheFactory() {}
/**
* Returns if the cache definition is found in the external settings file.
*
+ * @param cacheManager the owner
* @param cacheName the name of the cache
* @return {@code true} if a definition exists
*/
- public static boolean isDefinedExternally(String cacheName) {
- return TypesafeConfigurator.cacheNames(rootConfig()).contains(cacheName);
+ public static boolean isDefinedExternally(CacheManager cacheManager, String cacheName) {
+ return TypesafeConfigurator.cacheNames(rootConfig(cacheManager)).contains(cacheName);
}
/**
* Returns a newly created cache instance if a definition is found in the external settings file.
*
- * @param cacheManager the owner of the cache instance
+ * @param cacheManager the owner
* @param cacheName the name of the cache
* @return a new cache instance or null if the named cache is not defined in the settings file
*/
public static @Nullable CacheProxy tryToCreateFromExternalSettings(
CacheManager cacheManager, String cacheName) {
- return TypesafeConfigurator.from(rootConfig(), cacheName)
+ return TypesafeConfigurator.from(rootConfig(cacheManager), cacheName)
.map(configuration -> createCache(cacheManager, cacheName, configuration))
.orElse(null);
}
@@ -87,24 +88,25 @@ public static boolean isDefinedExternally(String cacheName) {
*/
public static CacheProxy createCache(CacheManager cacheManager,
String cacheName, Configuration configuration) {
- CaffeineConfiguration config = resolveConfigurationFor(configuration);
+ CaffeineConfiguration config = resolveConfigurationFor(cacheManager, configuration);
return new Builder<>(cacheManager, cacheName, config).build();
}
/** Returns the resolved configuration. */
- private static Config rootConfig() {
- return requireNonNull(TypesafeConfigurator.configSource().get());
+ private static Config rootConfig(CacheManager cacheManager) {
+ return requireNonNull(TypesafeConfigurator.configSource().get(
+ cacheManager.getURI(), cacheManager.getClassLoader()));
}
/** Copies the configuration and overlays it on top of the default settings. */
@SuppressWarnings("PMD.AccessorMethodGeneration")
private static CaffeineConfiguration resolveConfigurationFor(
- Configuration configuration) {
+ CacheManager cacheManager, Configuration configuration) {
if (configuration instanceof CaffeineConfiguration, ?>) {
return new CaffeineConfiguration<>((CaffeineConfiguration) configuration);
}
- CaffeineConfiguration template = TypesafeConfigurator.defaults(rootConfig());
+ CaffeineConfiguration template = TypesafeConfigurator.defaults(rootConfig(cacheManager));
if (configuration instanceof CompleteConfiguration, ?>) {
CompleteConfiguration complete = (CompleteConfiguration) configuration;
template.setReadThrough(complete.isReadThrough());
diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheManagerImpl.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheManagerImpl.java
index 4bd10addc8..0ea06e66b9 100644
--- a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheManagerImpl.java
+++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheManagerImpl.java
@@ -35,8 +35,6 @@
import org.checkerframework.checker.nullness.qual.Nullable;
-import com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider;
-
/**
* An implementation of JSR-107 {@link CacheManager} that manages Caffeine-based caches.
*
@@ -55,12 +53,11 @@ public final class CacheManagerImpl implements CacheManager {
private volatile boolean closed;
- public CacheManagerImpl(CachingProvider cacheProvider,
+ public CacheManagerImpl(CachingProvider cacheProvider, boolean runsAsAnOsgiBundle,
URI uri, ClassLoader classLoader, Properties properties) {
- this.runsAsAnOsgiBundle = (cacheProvider instanceof CaffeineCachingProvider)
- && ((CaffeineCachingProvider) cacheProvider).isOsgiComponent();
this.classLoaderReference = new WeakReference<>(requireNonNull(classLoader));
this.cacheProvider = requireNonNull(cacheProvider);
+ this.runsAsAnOsgiBundle = runsAsAnOsgiBundle;
this.properties = requireNonNull(properties);
this.caches = new ConcurrentHashMap<>();
this.uri = requireNonNull(uri);
@@ -102,7 +99,7 @@ public > Cache createCache(
CacheProxy, ?> cache = caches.compute(cacheName, (name, existing) -> {
if ((existing != null) && !existing.isClosed()) {
throw new CacheException("Cache " + cacheName + " already exists");
- } else if (CacheFactory.isDefinedExternally(cacheName)) {
+ } else if (CacheFactory.isDefinedExternally(this, cacheName)) {
throw new CacheException("Cache " + cacheName + " is configured externally");
}
return CacheFactory.createCache(this, cacheName, configuration);
diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/ConfigSource.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/ConfigSource.java
new file mode 100644
index 0000000000..4918b5ed8e
--- /dev/null
+++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/ConfigSource.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 Ben Manes. All Rights Reserved.
+ *
+ * Licensed 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 com.github.benmanes.caffeine.jcache.configuration;
+
+import java.net.URI;
+
+import com.typesafe.config.Config;
+
+/**
+ * A provider for the external configuration.
+ *
+ * @author ben.manes@gmail.com (Ben Manes)
+ */
+@FunctionalInterface
+public interface ConfigSource {
+
+ /**
+ * Returns a {@link Config} that provides the cache configurations.
+ *
+ * @param uri a uri that may assist in resolve to the resource
+ * @param classloader the classloader to load with
+ * @return a configuration
+ */
+ Config get(URI uri, ClassLoader classloader);
+}
diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/TypesafeConfigurator.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/TypesafeConfigurator.java
index a2c646ebee..4538f33aec 100644
--- a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/TypesafeConfigurator.java
+++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/TypesafeConfigurator.java
@@ -19,8 +19,10 @@
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import java.io.File;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
+import java.net.URI;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
@@ -28,6 +30,7 @@
import java.util.Set;
import java.util.function.Supplier;
+import javax.cache.CacheManager;
import javax.cache.configuration.Factory;
import javax.cache.configuration.FactoryBuilder;
import javax.cache.configuration.MutableCacheEntryListenerConfiguration;
@@ -44,6 +47,8 @@
import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigFactory;
+import com.typesafe.config.ConfigParseOptions;
+import com.typesafe.config.ConfigSyntax;
/**
* Static utility methods pertaining to externalized {@link CaffeineConfiguration} entries using the
@@ -55,8 +60,8 @@
public final class TypesafeConfigurator {
static final Logger logger = System.getLogger(TypesafeConfigurator.class.getName());
+ static ConfigSource configSource = TypesafeConfigurator::resolveConfig;
static FactoryCreator factoryCreator = FactoryBuilder::factoryOf;
- static Supplier configSource = ConfigFactory::load;
private TypesafeConfigurator() {}
@@ -118,21 +123,68 @@ public static void setFactoryCreator(FactoryCreator factoryCreator) {
}
/**
- * Specifies how the {@link Config} instance should be loaded. The default strategy uses
- * {@link ConfigFactory#load()}. The configuration is retrieved on-demand, allowing for it to be
- * reloaded, and it is assumed that the source caches it as needed.
+ * Specifies how the {@link Config} instance should be loaded. The default strategy uses the uri
+ * provided by {@link CacheManager#getURI()} as an optional override location to parse from a
+ * file system or classpath resource, or else returns {@link ConfigFactory#load(ClassLoader)}.
+ * The configuration is retrieved on-demand, allowing for it to be reloaded, and it is assumed
+ * that the source caches it as needed.
*
* @param configSource the strategy for loading the configuration
*/
public static void setConfigSource(Supplier configSource) {
+ requireNonNull(configSource);
+ setConfigSource((uri, classloader) -> configSource.get());
+ }
+
+ /**
+ * Specifies how the {@link Config} instance should be loaded. The default strategy uses the uri
+ * provided by {@link CacheManager#getURI()} as an optional override location to parse from a
+ * file system or classpath resource, or else returns {@link ConfigFactory#load(ClassLoader)}.
+ * The configuration is retrieved on-demand, allowing for it to be reloaded, and it is assumed
+ * that the source caches it as needed.
+ *
+ * @param configSource the strategy for loading the configuration from a uri
+ */
+ public static void setConfigSource(ConfigSource configSource) {
TypesafeConfigurator.configSource = requireNonNull(configSource);
}
/** Returns the strategy for loading the configuration. */
- public static Supplier configSource() {
+ public static ConfigSource configSource() {
return TypesafeConfigurator.configSource;
}
+ /** Returns the configuration by applying the default strategy. */
+ private static Config resolveConfig(URI uri, ClassLoader classloader) {
+ requireNonNull(uri);
+ requireNonNull(classloader);
+ var options = ConfigParseOptions.defaults().setAllowMissing(false);
+ if ((uri.getScheme() != null) && uri.getScheme().equalsIgnoreCase("file")) {
+ return ConfigFactory.parseFile(new File(uri), options);
+ } else if (isResource(uri)) {
+ return ConfigFactory.parseResources(uri.getSchemeSpecificPart(), options);
+ }
+ return ConfigFactory.load(classloader);
+ }
+
+ /** Returns if the uri is a file or classpath resource. */
+ private static boolean isResource(URI uri) {
+ if ((uri.getScheme() != null) && !uri.getScheme().equalsIgnoreCase("classpath")) {
+ return false;
+ }
+ var path = uri.getSchemeSpecificPart();
+ int dotIndex = path.lastIndexOf('.');
+ if (dotIndex != -1) {
+ var extension = path.substring(dotIndex + 1);
+ for (var format : ConfigSyntax.values()) {
+ if (format.toString().equalsIgnoreCase(extension)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
/** A one-shot builder for creating a configuration instance. */
private static final class Configurator {
final CaffeineConfiguration configuration;
diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/spi/CaffeineCachingProvider.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/spi/CaffeineCachingProvider.java
index 307199bffc..5dc3a2672d 100644
--- a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/spi/CaffeineCachingProvider.java
+++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/spi/CaffeineCachingProvider.java
@@ -39,9 +39,7 @@
import org.osgi.service.component.annotations.Component;
import com.github.benmanes.caffeine.jcache.CacheManagerImpl;
-import com.github.benmanes.caffeine.jcache.configuration.TypesafeConfigurator;
import com.google.errorprone.annotations.concurrent.GuardedBy;
-import com.typesafe.config.ConfigFactory;
/**
* A provider that produces a JCache implementation backed by Caffeine. Typically, this provider is
@@ -103,7 +101,8 @@ public CacheManager getCacheManager(URI uri, ClassLoader classLoader, Properties
managerClassLoader, any -> new HashMap<>());
return cacheManagersByURI.computeIfAbsent(managerURI, any -> {
Properties managerProperties = (properties == null) ? getDefaultProperties() : properties;
- return new CacheManagerImpl(this, managerURI, managerClassLoader, managerProperties);
+ return new CacheManagerImpl(this, isOsgiComponent,
+ managerURI, managerClassLoader, managerProperties);
});
}
}
@@ -267,10 +266,5 @@ public Enumeration getResources(String name) throws IOException {
@SuppressWarnings("unused")
private void activate() {
isOsgiComponent = true;
- TypesafeConfigurator.setConfigSource(() -> ConfigFactory.load(DEFAULT_CLASS_LOADER));
- }
-
- public boolean isOsgiComponent() {
- return isOsgiComponent;
}
}
diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/configuration/TypesafeConfigurationTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/configuration/TypesafeConfigurationTest.java
index 0eaa3c7567..864e769e46 100644
--- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/configuration/TypesafeConfigurationTest.java
+++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/configuration/TypesafeConfigurationTest.java
@@ -15,10 +15,13 @@
*/
package com.github.benmanes.caffeine.jcache.configuration;
+import static com.github.benmanes.caffeine.jcache.configuration.TypesafeConfigurator.configSource;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static org.junit.Assert.assertThrows;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.util.Optional;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
@@ -29,24 +32,100 @@
import javax.cache.expiry.Duration;
import javax.cache.expiry.ExpiryPolicy;
+import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.github.benmanes.caffeine.jcache.copy.JavaSerializationCopier;
import com.google.common.collect.Iterables;
import com.typesafe.config.Config;
+import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigFactory;
/**
* @author ben.manes@gmail.com (Ben Manes)
*/
public final class TypesafeConfigurationTest {
+ final ClassLoader classloader = Thread.currentThread().getContextClassLoader();
+ final ConfigSource defaultConfigSource = configSource();
+
+ @BeforeMethod
+ public void before() {
+ TypesafeConfigurator.setConfigSource(defaultConfigSource);
+ }
+
+ @Test
+ public void setConfigSource_supplier() {
+ TypesafeConfigurator.setConfigSource(() -> null);
+ assertThat(configSource()).isNotSameInstanceAs(defaultConfigSource);
+
+ assertThrows(NullPointerException.class, () ->
+ TypesafeConfigurator.setConfigSource((Supplier) null));
+ }
+
+ @Test
+ public void setConfigSource_function() {
+ TypesafeConfigurator.setConfigSource((uri, classloader) -> null);
+ assertThat(configSource()).isNotSameInstanceAs(defaultConfigSource);
+
+ assertThrows(NullPointerException.class, () ->
+ TypesafeConfigurator.setConfigSource((ConfigSource) null));
+ }
+
+ @Test
+ public void configSource_null() {
+ assertThrows(NullPointerException.class, () -> configSource().get(null, null));
+ assertThrows(NullPointerException.class, () -> configSource().get(null, classloader));
+ assertThrows(NullPointerException.class, () -> configSource().get(URI.create(""), null));
+ }
+
+ @Test
+ public void configSource_load() {
+ assertThat(configSource().get(URI.create(getClass().getName()), classloader))
+ .isSameInstanceAs(ConfigFactory.load());
+ assertThat(configSource().get(URI.create("rmi:/abc"), classloader))
+ .isSameInstanceAs(ConfigFactory.load());
+ }
+
+ @Test
+ public void configSource_classpath_present() {
+ var inferred = configSource().get(URI.create("custom.properties"), classloader);
+ assertThat(inferred.getInt("caffeine.jcache.classpath.policy.maximum.size")).isEqualTo(500);
+
+ var explicit = configSource().get(URI.create("classpath:custom.properties"), classloader);
+ assertThat(explicit.getInt("caffeine.jcache.classpath.policy.maximum.size")).isEqualTo(500);
+ }
+
+ @Test
+ public void configSource_classpath_absent() {
+ assertThrows(ConfigException.IO.class, () ->
+ configSource().get(URI.create("absent.conf"), classloader));
+ assertThrows(ConfigException.IO.class, () ->
+ configSource().get(URI.create("classpath:absent.conf"), classloader));
+ }
+
+ @Test
+ public void configSource_classpath_invalid() {
+ assertThrows(ConfigException.Parse.class, () ->
+ configSource().get(URI.create("invalid.conf"), classloader));
+ }
+
+ @Test
+ public void configSource_file() throws URISyntaxException {
+ var config = configSource().get(
+ getClass().getResource("/custom.properties").toURI(), classloader);
+ assertThat(config.getInt("caffeine.jcache.classpath.policy.maximum.size")).isEqualTo(500);
+ }
+
+ @Test
+ public void configSource_file_absent() {
+ assertThrows(ConfigException.IO.class, () ->
+ configSource().get(URI.create("file:/absent.conf"), classloader));
+ }
@Test
- public void configSource() {
- Config config = ConfigFactory.load();
- Supplier configSource = () -> config;
- TypesafeConfigurator.setConfigSource(configSource);
- assertThat(TypesafeConfigurator.configSource()).isEqualTo(configSource);
+ public void configSource_file_invalid() {
+ assertThrows(ConfigException.IO.class, () ->
+ configSource().get(URI.create("file:/invalid.conf"), classloader));
}
@Test
diff --git a/jcache/src/test/resources/custom.properties b/jcache/src/test/resources/custom.properties
new file mode 100644
index 0000000000..b5abd04779
--- /dev/null
+++ b/jcache/src/test/resources/custom.properties
@@ -0,0 +1 @@
+caffeine.jcache.classpath.policy.maximum.size = 500
diff --git a/jcache/src/test/resources/invalid.conf b/jcache/src/test/resources/invalid.conf
new file mode 100644
index 0000000000..66731a6443
--- /dev/null
+++ b/jcache/src/test/resources/invalid.conf
@@ -0,0 +1 @@
+This is not a hocon configuration file