Skip to content

Commit 391a0cc

Browse files
committed
Allow jcache to load its configuration from a uri (fixes #877)
1 parent 03e9926 commit 391a0cc

File tree

9 files changed

+212
-33
lines changed

9 files changed

+212
-33
lines changed

config/spotbugs/exclude.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,21 @@
334334
<Method name="deserialize"/>
335335
<Bug pattern="OBJECT_DESERIALIZATION"/>
336336
</Match>
337+
<Match>
338+
<Class name="com.github.benmanes.caffeine.jcache.configuration.TypesafeConfigurator"/>
339+
<Method name="resolveConfig"/>
340+
<Bug pattern="PATH_TRAVERSAL_IN"/>
341+
</Match>
342+
<Match>
343+
<Class name="com.github.benmanes.caffeine.jcache.configuration.TypesafeConfigurator"/>
344+
<Method name="resolveConfig"/>
345+
<Bug pattern="IMPROPER_UNICODE"/>
346+
</Match>
347+
<Match>
348+
<Class name="com.github.benmanes.caffeine.jcache.configuration.TypesafeConfigurator"/>
349+
<Method name="isResource"/>
350+
<Bug pattern="IMPROPER_UNICODE"/>
351+
</Match>
337352
<Match>
338353
<Class name="com.github.benmanes.caffeine.jcache.configuration.TypesafeConfigurator$Configurator"/>
339354
<Method name="addLazyExpiration"/>

jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheFactory.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,23 +56,24 @@ private CacheFactory() {}
5656
/**
5757
* Returns if the cache definition is found in the external settings file.
5858
*
59+
* @param cacheManager the owner
5960
* @param cacheName the name of the cache
6061
* @return {@code true} if a definition exists
6162
*/
62-
public static boolean isDefinedExternally(String cacheName) {
63-
return TypesafeConfigurator.cacheNames(rootConfig()).contains(cacheName);
63+
public static boolean isDefinedExternally(CacheManager cacheManager, String cacheName) {
64+
return TypesafeConfigurator.cacheNames(rootConfig(cacheManager)).contains(cacheName);
6465
}
6566

6667
/**
6768
* Returns a newly created cache instance if a definition is found in the external settings file.
6869
*
69-
* @param cacheManager the owner of the cache instance
70+
* @param cacheManager the owner
7071
* @param cacheName the name of the cache
7172
* @return a new cache instance or null if the named cache is not defined in the settings file
7273
*/
7374
public static @Nullable <K, V> CacheProxy<K, V> tryToCreateFromExternalSettings(
7475
CacheManager cacheManager, String cacheName) {
75-
return TypesafeConfigurator.<K, V>from(rootConfig(), cacheName)
76+
return TypesafeConfigurator.<K, V>from(rootConfig(cacheManager), cacheName)
7677
.map(configuration -> createCache(cacheManager, cacheName, configuration))
7778
.orElse(null);
7879
}
@@ -87,24 +88,25 @@ public static boolean isDefinedExternally(String cacheName) {
8788
*/
8889
public static <K, V> CacheProxy<K, V> createCache(CacheManager cacheManager,
8990
String cacheName, Configuration<K, V> configuration) {
90-
CaffeineConfiguration<K, V> config = resolveConfigurationFor(configuration);
91+
CaffeineConfiguration<K, V> config = resolveConfigurationFor(cacheManager, configuration);
9192
return new Builder<>(cacheManager, cacheName, config).build();
9293
}
9394

9495
/** Returns the resolved configuration. */
95-
private static Config rootConfig() {
96-
return requireNonNull(TypesafeConfigurator.configSource().get());
96+
private static Config rootConfig(CacheManager cacheManager) {
97+
return requireNonNull(TypesafeConfigurator.configSource().get(
98+
cacheManager.getURI(), cacheManager.getClassLoader()));
9799
}
98100

99101
/** Copies the configuration and overlays it on top of the default settings. */
100102
@SuppressWarnings("PMD.AccessorMethodGeneration")
101103
private static <K, V> CaffeineConfiguration<K, V> resolveConfigurationFor(
102-
Configuration<K, V> configuration) {
104+
CacheManager cacheManager, Configuration<K, V> configuration) {
103105
if (configuration instanceof CaffeineConfiguration<?, ?>) {
104106
return new CaffeineConfiguration<>((CaffeineConfiguration<K, V>) configuration);
105107
}
106108

107-
CaffeineConfiguration<K, V> template = TypesafeConfigurator.defaults(rootConfig());
109+
CaffeineConfiguration<K, V> template = TypesafeConfigurator.defaults(rootConfig(cacheManager));
108110
if (configuration instanceof CompleteConfiguration<?, ?>) {
109111
CompleteConfiguration<K, V> complete = (CompleteConfiguration<K, V>) configuration;
110112
template.setReadThrough(complete.isReadThrough());

jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheManagerImpl.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@
3535

3636
import org.checkerframework.checker.nullness.qual.Nullable;
3737

38-
import com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider;
39-
4038
/**
4139
* An implementation of JSR-107 {@link CacheManager} that manages Caffeine-based caches.
4240
*
@@ -55,12 +53,11 @@ public final class CacheManagerImpl implements CacheManager {
5553

5654
private volatile boolean closed;
5755

58-
public CacheManagerImpl(CachingProvider cacheProvider,
56+
public CacheManagerImpl(CachingProvider cacheProvider, boolean runsAsAnOsgiBundle,
5957
URI uri, ClassLoader classLoader, Properties properties) {
60-
this.runsAsAnOsgiBundle = (cacheProvider instanceof CaffeineCachingProvider)
61-
&& ((CaffeineCachingProvider) cacheProvider).isOsgiComponent();
6258
this.classLoaderReference = new WeakReference<>(requireNonNull(classLoader));
6359
this.cacheProvider = requireNonNull(cacheProvider);
60+
this.runsAsAnOsgiBundle = runsAsAnOsgiBundle;
6461
this.properties = requireNonNull(properties);
6562
this.caches = new ConcurrentHashMap<>();
6663
this.uri = requireNonNull(uri);
@@ -102,7 +99,7 @@ public <K, V, C extends Configuration<K, V>> Cache<K, V> createCache(
10299
CacheProxy<?, ?> cache = caches.compute(cacheName, (name, existing) -> {
103100
if ((existing != null) && !existing.isClosed()) {
104101
throw new CacheException("Cache " + cacheName + " already exists");
105-
} else if (CacheFactory.isDefinedExternally(cacheName)) {
102+
} else if (CacheFactory.isDefinedExternally(this, cacheName)) {
106103
throw new CacheException("Cache " + cacheName + " is configured externally");
107104
}
108105
return CacheFactory.createCache(this, cacheName, configuration);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2023 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 java.net.URI;
19+
20+
import com.typesafe.config.Config;
21+
22+
/**
23+
* A provider for the external configuration.
24+
*
25+
* @author [email protected] (Ben Manes)
26+
*/
27+
@FunctionalInterface
28+
public interface ConfigSource {
29+
30+
/**
31+
* Returns a {@link Config} that provides the cache configurations.
32+
*
33+
* @param uri a uri that may assist in resolve to the resource
34+
* @param classloader the classloader to load with
35+
* @return a configuration
36+
*/
37+
Config get(URI uri, ClassLoader classloader);
38+
}

jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/TypesafeConfigurator.java

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,18 @@
1919
import static java.util.concurrent.TimeUnit.MILLISECONDS;
2020
import static java.util.concurrent.TimeUnit.NANOSECONDS;
2121

22+
import java.io.File;
2223
import java.lang.System.Logger;
2324
import java.lang.System.Logger.Level;
25+
import java.net.URI;
2426
import java.util.Collections;
2527
import java.util.Objects;
2628
import java.util.Optional;
2729
import java.util.OptionalLong;
2830
import java.util.Set;
2931
import java.util.function.Supplier;
3032

33+
import javax.cache.CacheManager;
3134
import javax.cache.configuration.Factory;
3235
import javax.cache.configuration.FactoryBuilder;
3336
import javax.cache.configuration.MutableCacheEntryListenerConfiguration;
@@ -44,6 +47,8 @@
4447
import com.typesafe.config.Config;
4548
import com.typesafe.config.ConfigException;
4649
import com.typesafe.config.ConfigFactory;
50+
import com.typesafe.config.ConfigParseOptions;
51+
import com.typesafe.config.ConfigSyntax;
4752

4853
/**
4954
* Static utility methods pertaining to externalized {@link CaffeineConfiguration} entries using the
@@ -55,8 +60,8 @@
5560
public final class TypesafeConfigurator {
5661
static final Logger logger = System.getLogger(TypesafeConfigurator.class.getName());
5762

63+
static ConfigSource configSource = TypesafeConfigurator::resolveConfig;
5864
static FactoryCreator factoryCreator = FactoryBuilder::factoryOf;
59-
static Supplier<Config> configSource = ConfigFactory::load;
6065

6166
private TypesafeConfigurator() {}
6267

@@ -118,21 +123,68 @@ public static void setFactoryCreator(FactoryCreator factoryCreator) {
118123
}
119124

120125
/**
121-
* Specifies how the {@link Config} instance should be loaded. The default strategy uses
122-
* {@link ConfigFactory#load()}. The configuration is retrieved on-demand, allowing for it to be
123-
* reloaded, and it is assumed that the source caches it as needed.
126+
* Specifies how the {@link Config} instance should be loaded. The default strategy uses the uri
127+
* provided by {@link CacheManager#getURI()} as an optional override location to parse from a
128+
* file system or classpath resource, or else returns {@link ConfigFactory#load(ClassLoader)}.
129+
* The configuration is retrieved on-demand, allowing for it to be reloaded, and it is assumed
130+
* that the source caches it as needed.
124131
*
125132
* @param configSource the strategy for loading the configuration
126133
*/
127134
public static void setConfigSource(Supplier<Config> configSource) {
135+
requireNonNull(configSource);
136+
setConfigSource((uri, classloader) -> configSource.get());
137+
}
138+
139+
/**
140+
* Specifies how the {@link Config} instance should be loaded. The default strategy uses the uri
141+
* provided by {@link CacheManager#getURI()} as an optional override location to parse from a
142+
* file system or classpath resource, or else returns {@link ConfigFactory#load(ClassLoader)}.
143+
* The configuration is retrieved on-demand, allowing for it to be reloaded, and it is assumed
144+
* that the source caches it as needed.
145+
*
146+
* @param configSource the strategy for loading the configuration from a uri
147+
*/
148+
public static void setConfigSource(ConfigSource configSource) {
128149
TypesafeConfigurator.configSource = requireNonNull(configSource);
129150
}
130151

131152
/** Returns the strategy for loading the configuration. */
132-
public static Supplier<Config> configSource() {
153+
public static ConfigSource configSource() {
133154
return TypesafeConfigurator.configSource;
134155
}
135156

157+
/** Returns the configuration by applying the default strategy. */
158+
private static Config resolveConfig(URI uri, ClassLoader classloader) {
159+
requireNonNull(uri);
160+
requireNonNull(classloader);
161+
var options = ConfigParseOptions.defaults().setAllowMissing(false);
162+
if ((uri.getScheme() != null) && uri.getScheme().equalsIgnoreCase("file")) {
163+
return ConfigFactory.parseFile(new File(uri), options);
164+
} else if (isResource(uri)) {
165+
return ConfigFactory.parseResources(uri.getSchemeSpecificPart(), options);
166+
}
167+
return ConfigFactory.load(classloader);
168+
}
169+
170+
/** Returns if the uri is a file or classpath resource. */
171+
private static boolean isResource(URI uri) {
172+
if ((uri.getScheme() != null) && !uri.getScheme().equalsIgnoreCase("classpath")) {
173+
return false;
174+
}
175+
var path = uri.getSchemeSpecificPart();
176+
int dotIndex = path.lastIndexOf('.');
177+
if (dotIndex != -1) {
178+
var extension = path.substring(dotIndex + 1);
179+
for (var format : ConfigSyntax.values()) {
180+
if (format.toString().equalsIgnoreCase(extension)) {
181+
return true;
182+
}
183+
}
184+
}
185+
return false;
186+
}
187+
136188
/** A one-shot builder for creating a configuration instance. */
137189
private static final class Configurator<K, V> {
138190
final CaffeineConfiguration<K, V> configuration;

jcache/src/main/java/com/github/benmanes/caffeine/jcache/spi/CaffeineCachingProvider.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,7 @@
3939
import org.osgi.service.component.annotations.Component;
4040

4141
import com.github.benmanes.caffeine.jcache.CacheManagerImpl;
42-
import com.github.benmanes.caffeine.jcache.configuration.TypesafeConfigurator;
4342
import com.google.errorprone.annotations.concurrent.GuardedBy;
44-
import com.typesafe.config.ConfigFactory;
4543

4644
/**
4745
* 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
103101
managerClassLoader, any -> new HashMap<>());
104102
return cacheManagersByURI.computeIfAbsent(managerURI, any -> {
105103
Properties managerProperties = (properties == null) ? getDefaultProperties() : properties;
106-
return new CacheManagerImpl(this, managerURI, managerClassLoader, managerProperties);
104+
return new CacheManagerImpl(this, isOsgiComponent,
105+
managerURI, managerClassLoader, managerProperties);
107106
});
108107
}
109108
}
@@ -267,10 +266,5 @@ public Enumeration<URL> getResources(String name) throws IOException {
267266
@SuppressWarnings("unused")
268267
private void activate() {
269268
isOsgiComponent = true;
270-
TypesafeConfigurator.setConfigSource(() -> ConfigFactory.load(DEFAULT_CLASS_LOADER));
271-
}
272-
273-
public boolean isOsgiComponent() {
274-
return isOsgiComponent;
275269
}
276270
}

0 commit comments

Comments
 (0)