Skip to content

Commit

Permalink
Allow jcache to load its configuration from a uri (fixes #877)
Browse files Browse the repository at this point in the history
  • Loading branch information
ben-manes committed Mar 5, 2023
1 parent 03e9926 commit 391a0cc
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 33 deletions.
15 changes: 15 additions & 0 deletions config/spotbugs/exclude.xml
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,21 @@
<Method name="deserialize"/>
<Bug pattern="OBJECT_DESERIALIZATION"/>
</Match>
<Match>
<Class name="com.github.benmanes.caffeine.jcache.configuration.TypesafeConfigurator"/>
<Method name="resolveConfig"/>
<Bug pattern="PATH_TRAVERSAL_IN"/>
</Match>
<Match>
<Class name="com.github.benmanes.caffeine.jcache.configuration.TypesafeConfigurator"/>
<Method name="resolveConfig"/>
<Bug pattern="IMPROPER_UNICODE"/>
</Match>
<Match>
<Class name="com.github.benmanes.caffeine.jcache.configuration.TypesafeConfigurator"/>
<Method name="isResource"/>
<Bug pattern="IMPROPER_UNICODE"/>
</Match>
<Match>
<Class name="com.github.benmanes.caffeine.jcache.configuration.TypesafeConfigurator$Configurator"/>
<Method name="addLazyExpiration"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <K, V> CacheProxy<K, V> tryToCreateFromExternalSettings(
CacheManager cacheManager, String cacheName) {
return TypesafeConfigurator.<K, V>from(rootConfig(), cacheName)
return TypesafeConfigurator.<K, V>from(rootConfig(cacheManager), cacheName)
.map(configuration -> createCache(cacheManager, cacheName, configuration))
.orElse(null);
}
Expand All @@ -87,24 +88,25 @@ public static boolean isDefinedExternally(String cacheName) {
*/
public static <K, V> CacheProxy<K, V> createCache(CacheManager cacheManager,
String cacheName, Configuration<K, V> configuration) {
CaffeineConfiguration<K, V> config = resolveConfigurationFor(configuration);
CaffeineConfiguration<K, V> 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 <K, V> CaffeineConfiguration<K, V> resolveConfigurationFor(
Configuration<K, V> configuration) {
CacheManager cacheManager, Configuration<K, V> configuration) {
if (configuration instanceof CaffeineConfiguration<?, ?>) {
return new CaffeineConfiguration<>((CaffeineConfiguration<K, V>) configuration);
}

CaffeineConfiguration<K, V> template = TypesafeConfigurator.defaults(rootConfig());
CaffeineConfiguration<K, V> template = TypesafeConfigurator.defaults(rootConfig(cacheManager));
if (configuration instanceof CompleteConfiguration<?, ?>) {
CompleteConfiguration<K, V> complete = (CompleteConfiguration<K, V>) configuration;
template.setReadThrough(complete.isReadThrough());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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);
Expand Down Expand Up @@ -102,7 +99,7 @@ public <K, V, C extends Configuration<K, V>> Cache<K, V> 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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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 [email protected] (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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@
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;
import java.util.OptionalLong;
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;
Expand All @@ -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
Expand All @@ -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<Config> configSource = ConfigFactory::load;

private TypesafeConfigurator() {}

Expand Down Expand Up @@ -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<Config> 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<Config> 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<K, V> {
final CaffeineConfiguration<K, V> configuration;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
});
}
}
Expand Down Expand Up @@ -267,10 +266,5 @@ public Enumeration<URL> getResources(String name) throws IOException {
@SuppressWarnings("unused")
private void activate() {
isOsgiComponent = true;
TypesafeConfigurator.setConfigSource(() -> ConfigFactory.load(DEFAULT_CLASS_LOADER));
}

public boolean isOsgiComponent() {
return isOsgiComponent;
}
}
Loading

0 comments on commit 391a0cc

Please sign in to comment.