Improve cache api for usage in plugins (#1540)

This change allows usage of Cache as Shiro authentication and authorization cache. It will also fix loading cache configurations from plugins.
This commit is contained in:
Sebastian Sdorra
2021-02-17 09:15:01 +01:00
committed by GitHub
parent 20c3faeb5b
commit d8427ed4ed
13 changed files with 197 additions and 460 deletions

View File

@@ -21,11 +21,13 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.cache;
//~--- JDK imports ------------------------------------------------------------
import com.google.inject.ImplementedBy;
import java.io.File;
import java.net.URL;
@@ -33,35 +35,14 @@ import java.net.URL;
import java.util.Iterator;
/**
*
* @author Sebastian Sdorra
*/
public interface CacheConfigurationLoader
{
@ImplementedBy(DefaultCacheConfigurationLoader.class)
public interface CacheConfigurationLoader {
/**
* Method description
*
*
* @return
*/
public URL getDefaultResource();
URL getDefaultResource();
/**
* Method description
*
*
* @return
*/
public File getManualFileResource();
File getManualFileResource();
/**
* Method description
*
*
* @param path
*
* @return
*/
public Iterator<URL> getModuleResources();
Iterator<URL> getModuleResources();
}

View File

@@ -1,108 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.cache;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.Iterators;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
import static java.util.Collections.emptyIterator;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
*/
public final class CacheConfigurations
{
/**
* the logger for CacheConfigurations
*/
private static final Logger logger =
LoggerFactory.getLogger(CacheConfigurations.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*/
private CacheConfigurations() {}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param loadingClass
* @param resource
*
* @return
*/
public static Iterator<URL> findModuleResources(Class<?> loadingClass,
String resource)
{
Iterator<URL> it = null;
try
{
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader == null)
{
classLoader = loadingClass.getClassLoader();
}
Enumeration<URL> enm = classLoader.getResources(resource);
if (enm != null)
{
it = Iterators.forEnumeration(enm);
}
}
catch (IOException ex)
{
logger.error("could not read module resources", ex);
}
if (it == null)
{
it = emptyIterator();
}
return it;
}
}

View File

@@ -21,92 +21,70 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.cache;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.Iterators;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContext;
import sonia.scm.plugin.PluginLoader;
//~--- JDK imports ------------------------------------------------------------
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
/**
*
* @author Sebastian Sdorra
*/
public class DefaultCacheConfigurationLoader implements CacheConfigurationLoader
{
import static java.util.Collections.emptyIterator;
/**
* Constructs ...
*
*
* @param defaultResource
* @param manualFileResource
* @param moduleResources
*/
public DefaultCacheConfigurationLoader(String defaultResource,
String manualFileResource, String moduleResources)
{
this.defaultResource = defaultResource;
this.manualFileResource = manualFileResource;
this.moduleResources = moduleResources;
public class DefaultCacheConfigurationLoader implements CacheConfigurationLoader {
private static final Logger LOG = LoggerFactory.getLogger(DefaultCacheConfigurationLoader.class);
private static final String DEFAULT = "/config/gcache.xml";
private static final String MANUAL_RESOURCE = "ext".concat(File.separator).concat("gcache.xml");
private static final String MODULE_RESOURCES = "META-INF/scm/gcache.xml";
private final ClassLoader classLoader;
@Inject
public DefaultCacheConfigurationLoader(PluginLoader pluginLoader) {
this(pluginLoader.getUberClassLoader());
}
//~--- get methods ----------------------------------------------------------
public DefaultCacheConfigurationLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Method description
*
*
* @return
*/
@Override
public URL getDefaultResource()
{
return DefaultCacheConfigurationLoader.class.getResource(defaultResource);
public URL getDefaultResource() {
return DefaultCacheConfigurationLoader.class.getResource(DEFAULT);
}
/**
* Method description
*
*
* @return
*/
@Override
public File getManualFileResource()
{
return new File(SCMContext.getContext().getBaseDirectory(),
manualFileResource);
public File getManualFileResource() {
return new File(SCMContext.getContext().getBaseDirectory(), MANUAL_RESOURCE);
}
/**
* Method description
*
*
* @return
*/
@Override
public Iterator<URL> getModuleResources()
{
return CacheConfigurations.findModuleResources(
DefaultCacheConfigurationLoader.class, moduleResources);
public Iterator<URL> getModuleResources() {
try {
Enumeration<URL> enm = classLoader.getResources(MODULE_RESOURCES);
if (enm != null) {
return Iterators.forEnumeration(enm);
}
} catch (IOException ex) {
LOG.error("could not read module resources", ex);
}
return emptyIterator();
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final String defaultResource;
/** Field description */
private final String manualFileResource;
/** Field description */
private final String moduleResources;
}

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.cache;
//~--- non-JDK imports --------------------------------------------------------
@@ -47,8 +47,7 @@ import java.util.concurrent.atomic.AtomicLong;
* @param <K>
* @param <V>
*/
public class GuavaCache<K, V>
implements Cache<K, V>, org.apache.shiro.cache.Cache<K, V>
public class GuavaCache<K, V> implements Cache<K, V>
{
/**
@@ -106,7 +105,7 @@ public class GuavaCache<K, V>
this.copyStrategy = CopyStrategy.NONE;
}
}
//~--- methods --------------------------------------------------------------
/**
@@ -196,6 +195,7 @@ public class GuavaCache<K, V>
* @return
*/
@Override
@SuppressWarnings("java:S4738") // we have to use guava predicate for compatibility
public Iterable<V> removeAll(Predicate<K> filter)
{
Set<V> removedValues = Sets.newHashSet();

View File

@@ -21,12 +21,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.cache;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
@@ -44,25 +43,15 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
/**
*
* @author Sebastian Sdorra
*/
public class GuavaCacheConfigurationReader
{
/** Field description */
private static final String DEFAULT = "/config/gcache.xml";
/** Field description */
private static final String MANUAL_RESOURCE =
"ext".concat(File.separator).concat("gcache.xml");
/** Field description */
private static final String MODULE_RESOURCES = "META-INF/scm/gcache.xml";
public class GuavaCacheConfigurationReader {
/**
* the logger for CacheConfigurationReader
@@ -75,118 +64,69 @@ public class GuavaCacheConfigurationReader
/**
* Constructs ...
*
*
* @param loader
*/
@VisibleForTesting
GuavaCacheConfigurationReader(CacheConfigurationLoader loader)
{
@Inject
public GuavaCacheConfigurationReader(CacheConfigurationLoader loader) {
this.loader = loader;
try
{
this.context =
JAXBContext.newInstance(GuavaCacheManagerConfiguration.class);
}
catch (JAXBException ex)
{
throw new CacheException(
"could not create jaxb context for cache configuration", ex);
try {
this.context = JAXBContext.newInstance(GuavaCacheManagerConfiguration.class);
} catch (JAXBException ex) {
throw new CacheException("could not create jaxb context for cache configuration", ex);
}
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public static GuavaCacheManagerConfiguration read()
{
return new GuavaCacheConfigurationReader(
new DefaultCacheConfigurationLoader(
DEFAULT, MANUAL_RESOURCE, MODULE_RESOURCES)).doRead();
}
/**
* Method description
*
*
* @return
*/
@VisibleForTesting
GuavaCacheManagerConfiguration doRead()
{
public GuavaCacheManagerConfiguration read() {
URL defaultConfigUrl = loader.getDefaultResource();
if (defaultConfigUrl == null)
{
if (defaultConfigUrl == null) {
throw new IllegalStateException(
"could not find default cache configuration");
}
GuavaCacheManagerConfiguration config = readConfiguration(defaultConfigUrl,
true);
GuavaCacheManagerConfiguration config = readConfiguration(defaultConfigUrl, true);
Iterator<URL> it = loader.getModuleResources();
if (!it.hasNext())
{
if (!it.hasNext()) {
logger.debug("no module configuration found");
}
while (it.hasNext())
{
while (it.hasNext()) {
GuavaCacheManagerConfiguration moduleConfig =
readConfiguration(it.next(), false);
if (moduleConfig != null)
{
if (moduleConfig != null) {
config = merge(config, moduleConfig);
}
}
File manualFile = loader.getManualFileResource();
if (manualFile.exists())
{
try
{
GuavaCacheManagerConfiguration manualConfig =
readConfiguration(manualFile.toURI().toURL(), false);
config = merge(config, manualConfig);
}
catch (MalformedURLException ex)
{
if (manualFile.exists()) {
try {
GuavaCacheManagerConfiguration manualConfig = readConfiguration(manualFile.toURI().toURL(), false);
if (manualConfig != null) {
config = merge(config, manualConfig);
}
} catch (MalformedURLException ex) {
logger.error("malformed url", ex);
}
}
else
{
} else {
logger.warn("could not find manual configuration at {}", manualFile);
}
return config;
}
/**
* Method description
*
*
* @param caches
*
* @return
*/
private Map<String, GuavaNamedCacheConfiguration> createNamedCacheMap(
List<GuavaNamedCacheConfiguration> caches)
{
List<GuavaNamedCacheConfiguration> caches) {
Map<String, GuavaNamedCacheConfiguration> map = Maps.newLinkedHashMap();
for (GuavaNamedCacheConfiguration ncc : caches)
{
for (GuavaNamedCacheConfiguration ncc : caches) {
map.put(ncc.getName(), ncc);
}
@@ -196,28 +136,23 @@ public class GuavaCacheConfigurationReader
/**
* Method description
*
*
* @param config
* @param other
*
* @return
*/
private GuavaCacheManagerConfiguration merge(
GuavaCacheManagerConfiguration config, GuavaCacheManagerConfiguration other)
{
GuavaCacheManagerConfiguration config, GuavaCacheManagerConfiguration other) {
GuavaCacheConfiguration defaultCache = config.getDefaultCache();
Map<String, GuavaNamedCacheConfiguration> namedCaches =
createNamedCacheMap(config.getCaches());
if (other.getDefaultCache() != null)
{
if (other.getDefaultCache() != null) {
defaultCache = other.getDefaultCache();
}
List<GuavaNamedCacheConfiguration> otherNamedCaches = other.getCaches();
for (GuavaNamedCacheConfiguration ncc : otherNamedCaches)
{
for (GuavaNamedCacheConfiguration ncc : otherNamedCaches) {
namedCaches.put(ncc.getName(), ncc);
}
@@ -228,34 +163,22 @@ public class GuavaCacheConfigurationReader
/**
* Method description
*
*
* @param url
* @param fail
*
* @return
*/
private GuavaCacheManagerConfiguration readConfiguration(URL url,
boolean fail)
{
private GuavaCacheManagerConfiguration readConfiguration(URL url, boolean fail) {
logger.debug("read cache configuration from {}", url);
GuavaCacheManagerConfiguration config = null;
try
{
config =
(GuavaCacheManagerConfiguration) context.createUnmarshaller().unmarshal(
url);
}
catch (JAXBException ex)
{
if (fail)
{
throw new CacheException("could not unmarshall cache configuration",
ex);
}
else
{
try {
Unmarshaller unmarshaller = context.createUnmarshaller();
config = (GuavaCacheManagerConfiguration) unmarshaller.unmarshal(url);
} catch (JAXBException ex) {
if (fail) {
throw new CacheException("could not unmarshall cache configuration", ex);
} else {
logger.warn("could not unmarshall cache configuration", ex);
}
}
@@ -265,9 +188,13 @@ public class GuavaCacheConfigurationReader
//~--- fields ---------------------------------------------------------------
/** Field description */
/**
* Field description
*/
private JAXBContext context;
/** Field description */
/**
* Field description
*/
private CacheConfigurationLoader loader;
}

View File

@@ -21,128 +21,75 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.cache;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Maps;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//~--- JDK imports ------------------------------------------------------------
import javax.inject.Inject;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
//~--- JDK imports ------------------------------------------------------------
/**
* Guava based implementation of {@link CacheManager} and {@link org.apache.shiro.cache.CacheManager}.
*
*
* @author Sebastian Sdorra
*/
@Singleton
public class GuavaCacheManager
implements CacheManager, org.apache.shiro.cache.CacheManager
{
public class GuavaCacheManager implements CacheManager, org.apache.shiro.cache.CacheManager {
/**
* the logger for GuavaCacheManager
*/
private static final Logger logger =
LoggerFactory.getLogger(GuavaCacheManager.class);
private static final Logger LOG = LoggerFactory.getLogger(GuavaCacheManager.class);
//~--- constructors ---------------------------------------------------------
@SuppressWarnings({"java:S3740", "rawtypes"})
private final ConcurrentHashMap<String, GuavaCache> caches = new ConcurrentHashMap<>();
/**
* Constructs ...
*
*/
public GuavaCacheManager()
{
this(GuavaCacheConfigurationReader.read());
private final GuavaCacheConfiguration defaultConfiguration;
@Inject
public GuavaCacheManager(GuavaCacheConfigurationReader configurationReader) {
this(configurationReader.read());
}
/**
* Constructs ...
*
*
* @param config
*/
@VisibleForTesting
protected GuavaCacheManager(GuavaCacheManagerConfiguration config)
{
protected GuavaCacheManager(GuavaCacheManagerConfiguration config) {
defaultConfiguration = config.getDefaultCache();
for (GuavaNamedCacheConfiguration ncc : config.getCaches())
{
logger.debug("create cache {} from configured configuration {}",
ncc.getName(), ncc);
cacheMap.put(ncc.getName(), new GuavaCache(ncc));
for (GuavaNamedCacheConfiguration ncc : config.getCaches()) {
LOG.debug("create cache {} from configured configuration {}", ncc.getName(), ncc);
caches.put(ncc.getName(), new GuavaCache<>(ncc));
}
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @throws IOException
*/
@Override
public void close() throws IOException
{
logger.info("close guava cache manager");
@SuppressWarnings("unchecked")
public <K, V> GuavaCache<K, V> getCache(String name) {
LOG.trace("try to retrieve cache {}", name);
for (Cache c : cacheMap.values())
{
return caches.computeIfAbsent(name, cacheName -> {
LOG.debug(
"cache {} does not exists, creating a new instance from default configuration: {}",
cacheName, defaultConfiguration
);
return new GuavaCache<>(defaultConfiguration, cacheName);
});
}
@Override
public void close() throws IOException {
LOG.info("close guava cache manager");
for (Cache<?, ?> c : caches.values()) {
c.clear();
}
cacheMap.clear();
caches.clear();
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param name
* @param <K>
* @param <V>
*
* @return
*/
@Override
@SuppressWarnings("unchecked")
public synchronized <K, V> GuavaCache<K, V> getCache(String name)
{
logger.trace("try to retrieve cache {}", name);
GuavaCache<K, V> cache = cacheMap.get(name);
if (cache == null)
{
logger.debug(
"cache {} does not exists, creating a new instance from default configuration: {}",
name, defaultConfiguration);
cache = new GuavaCache<K, V>(defaultConfiguration, name);
cacheMap.put(name, cache);
}
return cache;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private volatile Map<String, GuavaCache> cacheMap = Maps.newHashMap();
/** Field description */
private GuavaCacheConfiguration defaultConfiguration;
}

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.cache;
//~--- JDK imports ------------------------------------------------------------
@@ -76,7 +76,7 @@ public class GuavaCacheManagerConfiguration
{
if (caches == null)
{
caches = Collections.EMPTY_LIST;
caches = Collections.emptyList();
}
return caches;

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.cache;
//~--- non-JDK imports --------------------------------------------------------
@@ -67,7 +67,7 @@ public final class GuavaCaches
*
* @return
*/
public static com.google.common.cache.Cache create(
public static <K,V> com.google.common.cache.Cache<K, V> create(
GuavaCacheConfiguration configuration, String name)
{
CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();