mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-01-03 14:19:47 +01:00
Caches for internal stores and files
This adds optional caches for configuration stores and backing data files for data stores. These stores can be enabled using the system properties `scm.storeCache.enabled=true` and `scm.cache.dataFileCache.enabled=true`. In addition, this adds the possibility to overwrite cache configurations from the guice cache (see file `gcache.xml`) with system properties. The maximum size of the external group cache for example can be overwritten with the system property `scm.cache.externalGroups.maximumSize`. Co-authored-by: René Pfeuffer <rene.pfeuffer@cloudogu.com>
This commit is contained in:
2
gradle/changelog/store_cache.yaml
Normal file
2
gradle/changelog/store_cache.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- type: added
|
||||
description: Optional caching for stores and data files
|
||||
@@ -26,6 +26,7 @@ package sonia.scm.store;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
@@ -67,6 +68,7 @@ public final class TypedStoreParametersBuilder<T,S> {
|
||||
@Getter
|
||||
@Setter(AccessLevel.PRIVATE)
|
||||
@RequiredArgsConstructor
|
||||
@EqualsAndHashCode(exclude = {"classLoader", "adapters"})
|
||||
private static class TypedStoreParametersImpl<T> implements TypedStoreParameters<T> {
|
||||
private final Class<T> type;
|
||||
private String name;
|
||||
|
||||
@@ -55,11 +55,16 @@ public final class CopyOnWrite {
|
||||
}
|
||||
|
||||
public static void withTemporaryFile(FileWriter writer, Path targetFile) {
|
||||
withTemporaryFile(writer, targetFile, () -> {});
|
||||
}
|
||||
|
||||
public static void withTemporaryFile(FileWriter writer, Path targetFile, Runnable onSuccess) {
|
||||
validateInput(targetFile);
|
||||
execute(() -> {
|
||||
Path temporaryFile = createTemporaryFile(targetFile);
|
||||
executeCallback(writer, targetFile, temporaryFile);
|
||||
replaceOriginalFile(targetFile, temporaryFile);
|
||||
onSuccess.run();
|
||||
}).withLockedFileForWrite(targetFile);
|
||||
}
|
||||
|
||||
|
||||
142
scm-dao-xml/src/main/java/sonia/scm/store/DataFileCache.java
Normal file
142
scm-dao-xml/src/main/java/sonia/scm/store/DataFileCache.java
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.store;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.cache.Cache;
|
||||
import sonia.scm.cache.CacheManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.File;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Singleton
|
||||
public class DataFileCache {
|
||||
|
||||
private static final String CACHE_NAME = "sonia.cache.dataFileCache";
|
||||
private static final String ENABLE_CACHE_PROPERTY_NAME = "scm.cache.dataFileCache.enabled";
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DataFileCache.class);
|
||||
|
||||
private static final NoDataFileCacheInstance NO_CACHE = new NoDataFileCacheInstance();
|
||||
|
||||
private final Cache<File, Object> cache;
|
||||
private final boolean cacheEnabled;
|
||||
|
||||
@Inject
|
||||
DataFileCache(CacheManager cacheManager) {
|
||||
this(cacheManager.getCache(CACHE_NAME), Boolean.getBoolean(ENABLE_CACHE_PROPERTY_NAME));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
DataFileCache(Cache<File, Object> cache, boolean cacheEnabled) {
|
||||
this.cache = cache;
|
||||
this.cacheEnabled = cacheEnabled;
|
||||
}
|
||||
|
||||
DataFileCacheInstance instanceFor(Class<?> type) {
|
||||
if (cacheEnabled) {
|
||||
return new GCacheDataFileCacheInstance(type);
|
||||
} else {
|
||||
return NO_CACHE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
long size = cache.keys().stream().map(File::length).reduce(0L, Long::sum);
|
||||
return String.format("data file cache, %s entries, %s bytes cached", cache.size(), size);
|
||||
}
|
||||
|
||||
interface DataFileCacheInstance {
|
||||
<T> void put(File file, T item);
|
||||
|
||||
<T> T get(File file, Supplier<T> reader);
|
||||
|
||||
void remove(File file);
|
||||
}
|
||||
|
||||
private static class NoDataFileCacheInstance implements DataFileCacheInstance {
|
||||
|
||||
@Override
|
||||
public <T> void put(File file, T item) {
|
||||
// nothing to do without cache
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T get(File file, Supplier<T> reader) {
|
||||
return reader.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(File file) {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
||||
|
||||
private class GCacheDataFileCacheInstance implements DataFileCacheInstance {
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
GCacheDataFileCacheInstance(Class<?> type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public <T> void put(File file, T item) {
|
||||
LOG.trace("put '{}' in {}", file, DataFileCache.this);
|
||||
if (item != null) {
|
||||
cache.put(file, item);
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T get(File file, Supplier<T> reader) {
|
||||
LOG.trace("get of '{}' from {}", file, DataFileCache.this);
|
||||
File absoluteFile = file.getAbsoluteFile();
|
||||
if (cache.contains(absoluteFile)) {
|
||||
T t = (T) cache.get(absoluteFile);
|
||||
if (t == null || type.isAssignableFrom(t.getClass())) {
|
||||
return t;
|
||||
} else {
|
||||
LOG.info("discarding cached entry with wrong type (expected: {}, got {})", type, t.getClass());
|
||||
cache.remove(file);
|
||||
}
|
||||
}
|
||||
|
||||
T item = reader.get();
|
||||
if (item != null) {
|
||||
cache.put(absoluteFile, item);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
public void remove(File file) {
|
||||
LOG.trace("remove of '{}' from {}", file, DataFileCache.this);
|
||||
cache.remove(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,6 @@
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
@@ -33,8 +31,6 @@ import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.repository.RepositoryReadOnlyChecker;
|
||||
import sonia.scm.security.KeyGenerator;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@@ -44,14 +40,22 @@ public class JAXBConfigurationEntryStoreFactory extends FileBasedStoreFactory
|
||||
|
||||
private final KeyGenerator keyGenerator;
|
||||
|
||||
private final StoreCache<ConfigurationEntryStore<?>> storeCache;
|
||||
|
||||
@Inject
|
||||
public JAXBConfigurationEntryStoreFactory(SCMContextProvider contextProvider, RepositoryLocationResolver repositoryLocationResolver, KeyGenerator keyGenerator, RepositoryReadOnlyChecker readOnlyChecker) {
|
||||
super(contextProvider, repositoryLocationResolver, Store.CONFIG, readOnlyChecker);
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.storeCache = new StoreCache<>(this::createStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> ConfigurationEntryStore<T> getStore(TypedStoreParameters<T> storeParameters) {
|
||||
return (ConfigurationEntryStore<T>) storeCache.getStore(storeParameters);
|
||||
}
|
||||
|
||||
private <T> ConfigurationEntryStore<T> createStore(TypedStoreParameters<T> storeParameters) {
|
||||
return new JAXBConfigurationEntryStore<>(
|
||||
getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION),
|
||||
storeParameters.getType(),
|
||||
|
||||
@@ -42,6 +42,8 @@ public class JAXBConfigurationStoreFactory extends FileBasedStoreFactory impleme
|
||||
|
||||
private final Set<ConfigurationStoreDecoratorFactory> decoratorFactories;
|
||||
|
||||
private final StoreCache<ConfigurationStore<?>> storeCache;
|
||||
|
||||
/**
|
||||
* Constructs a new instance.
|
||||
*
|
||||
@@ -51,10 +53,16 @@ public class JAXBConfigurationStoreFactory extends FileBasedStoreFactory impleme
|
||||
public JAXBConfigurationStoreFactory(SCMContextProvider contextProvider, RepositoryLocationResolver repositoryLocationResolver, RepositoryReadOnlyChecker readOnlyChecker, Set<ConfigurationStoreDecoratorFactory> decoratorFactories) {
|
||||
super(contextProvider, repositoryLocationResolver, Store.CONFIG, readOnlyChecker);
|
||||
this.decoratorFactories = decoratorFactories;
|
||||
this.storeCache = new StoreCache<>(this::createStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> ConfigurationStore<T> getStore(TypedStoreParameters<T> storeParameters) {
|
||||
return (ConfigurationStore<T>) storeCache.getStore(storeParameters);
|
||||
}
|
||||
|
||||
private <T> ConfigurationStore<T> createStore(TypedStoreParameters<T> storeParameters) {
|
||||
TypedStoreContext<T> context = TypedStoreContext.of(storeParameters);
|
||||
|
||||
ConfigurationStore<T> store = new JAXBConfigurationStore<>(
|
||||
|
||||
@@ -40,9 +40,8 @@ import static sonia.scm.store.CopyOnWrite.compute;
|
||||
/**
|
||||
* Jaxb implementation of {@link DataStore}.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*
|
||||
* @param <T> type of stored data.
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class JAXBDataStore<T> extends FileBasedStore<T> implements DataStore<T> {
|
||||
|
||||
@@ -53,10 +52,12 @@ public class JAXBDataStore<T> extends FileBasedStore<T> implements DataStore<T>
|
||||
|
||||
private final KeyGenerator keyGenerator;
|
||||
private final TypedStoreContext<T> context;
|
||||
private final DataFileCache.DataFileCacheInstance cache;
|
||||
|
||||
JAXBDataStore(KeyGenerator keyGenerator, TypedStoreContext<T> context, File directory, boolean readOnly) {
|
||||
JAXBDataStore(KeyGenerator keyGenerator, TypedStoreContext<T> context, File directory, boolean readOnly, DataFileCache.DataFileCacheInstance cache) {
|
||||
super(directory, StoreConstants.FILE_EXTENSION, readOnly);
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.cache = cache;
|
||||
this.directory = directory;
|
||||
this.context = context;
|
||||
}
|
||||
@@ -75,10 +76,10 @@ public class JAXBDataStore<T> extends FileBasedStore<T> implements DataStore<T>
|
||||
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
|
||||
CopyOnWrite.withTemporaryFile(
|
||||
temp -> marshaller.marshal(item, temp.toFile()),
|
||||
file.toPath()
|
||||
file.toPath(),
|
||||
() -> cache.put(file, item)
|
||||
);
|
||||
}
|
||||
catch (JAXBException ex) {
|
||||
} catch (JAXBException ex) {
|
||||
throw new StoreException("could not write object with id ".concat(id),
|
||||
ex);
|
||||
}
|
||||
@@ -106,14 +107,22 @@ public class JAXBDataStore<T> extends FileBasedStore<T> implements DataStore<T>
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void remove(File file) {
|
||||
cache.remove(file);
|
||||
super.remove(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T read(File file) {
|
||||
return compute(() -> {
|
||||
if (file.exists()) {
|
||||
LOG.trace("try to read {}", file);
|
||||
return context.unmarshall(file);
|
||||
}
|
||||
return null;
|
||||
}).withLockedFileForRead(file);
|
||||
return cache.get(file, () ->
|
||||
compute(() -> {
|
||||
if (file.exists()) {
|
||||
LOG.trace("try to read {}", file);
|
||||
return context.unmarshall(file);
|
||||
}
|
||||
return null;
|
||||
}).withLockedFileForRead(file)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
@@ -46,16 +44,33 @@ public class JAXBDataStoreFactory extends FileBasedStoreFactory
|
||||
|
||||
private final KeyGenerator keyGenerator;
|
||||
|
||||
private final StoreCache<DataStore<?>> storeCache;
|
||||
|
||||
private final DataFileCache dataFileCache;
|
||||
|
||||
@Inject
|
||||
public JAXBDataStoreFactory(SCMContextProvider contextProvider , RepositoryLocationResolver repositoryLocationResolver, KeyGenerator keyGenerator, RepositoryReadOnlyChecker readOnlyChecker) {
|
||||
public JAXBDataStoreFactory(SCMContextProvider contextProvider , RepositoryLocationResolver repositoryLocationResolver, KeyGenerator keyGenerator, RepositoryReadOnlyChecker readOnlyChecker, DataFileCache dataFileCache) {
|
||||
super(contextProvider, repositoryLocationResolver, Store.DATA, readOnlyChecker);
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.dataFileCache = dataFileCache;
|
||||
this.storeCache = new StoreCache<>(this::createStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> DataStore<T> getStore(TypedStoreParameters<T> storeParameters) {
|
||||
return (DataStore<T>) storeCache.getStore(storeParameters);
|
||||
}
|
||||
|
||||
private <T> DataStore<T> createStore(TypedStoreParameters<T> storeParameters) {
|
||||
File storeLocation = getStoreLocation(storeParameters);
|
||||
IOUtil.mkdirs(storeLocation);
|
||||
return new JAXBDataStore<>(keyGenerator, TypedStoreContext.of(storeParameters), storeLocation, mustBeReadOnly(storeParameters));
|
||||
return new JAXBDataStore<>(
|
||||
keyGenerator,
|
||||
TypedStoreContext.of(storeParameters),
|
||||
storeLocation,
|
||||
mustBeReadOnly(storeParameters),
|
||||
dataFileCache.instanceFor(storeParameters.getType())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
57
scm-dao-xml/src/main/java/sonia/scm/store/StoreCache.java
Normal file
57
scm-dao-xml/src/main/java/sonia/scm/store/StoreCache.java
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.store;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.util.Collections.synchronizedMap;
|
||||
|
||||
class StoreCache <S> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(StoreCache.class);
|
||||
|
||||
public static final String ENABLE_STORE_CACHE_PROPERTY = "scm.storeCache.enabled";
|
||||
|
||||
private final Function<TypedStoreParameters<?>, S> cachingStoreCreator;
|
||||
|
||||
StoreCache(Function<TypedStoreParameters<?>, S> storeCreator) {
|
||||
if (Boolean.getBoolean(ENABLE_STORE_CACHE_PROPERTY)) {
|
||||
LOG.info("store cache enabled");
|
||||
Map<TypedStoreParameters<?>, S> storeCache = synchronizedMap(new HashMap<>());
|
||||
cachingStoreCreator = storeParameters -> storeCache.computeIfAbsent(storeParameters, storeCreator);
|
||||
} else {
|
||||
cachingStoreCreator = storeCreator;
|
||||
}
|
||||
}
|
||||
|
||||
S getStore(TypedStoreParameters<?> storeParameters) {
|
||||
return cachingStoreCreator.apply(storeParameters);
|
||||
}
|
||||
}
|
||||
@@ -86,6 +86,10 @@ final class TypedStoreContext<T> {
|
||||
withClassLoader(consumer, unmarshaller);
|
||||
}
|
||||
|
||||
Class<?> getType() {
|
||||
return parameters.getType();
|
||||
}
|
||||
|
||||
private <C> void withClassLoader(ThrowingConsumer<C> consumer, C consume) {
|
||||
ClassLoader contextClassLoader = null;
|
||||
Optional<ClassLoader> classLoader = parameters.getClassLoader();
|
||||
|
||||
141
scm-dao-xml/src/test/java/sonia/scm/store/DataFileCacheTest.java
Normal file
141
scm-dao-xml/src/test/java/sonia/scm/store/DataFileCacheTest.java
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.store;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.cache.MapCache;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.in;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DataFileCacheTest {
|
||||
|
||||
@Nested
|
||||
class WithActivatedCache {
|
||||
|
||||
private final MapCache<File, Object> backingCache = new MapCache<>();
|
||||
private final DataFileCache dataFileCache = new DataFileCache(backingCache, true);
|
||||
|
||||
@Test
|
||||
void shouldReturnCachedData() {
|
||||
File file = new File("/some.string");
|
||||
backingCache.put(file, "some string");
|
||||
|
||||
DataFileCache.DataFileCacheInstance instance = dataFileCache.instanceFor(String.class);
|
||||
|
||||
Object result = instance.get(file, () -> {
|
||||
throw new RuntimeException("should not be read");
|
||||
});
|
||||
|
||||
assertThat(result).isSameAs("some string");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReadDataIfNotCached() {
|
||||
File file = new File("/some.string");
|
||||
|
||||
DataFileCache.DataFileCacheInstance instance = dataFileCache.instanceFor(String.class);
|
||||
|
||||
Object result = instance.get(file, () -> "some string");
|
||||
|
||||
assertThat(result).isSameAs("some string");
|
||||
assertThat(backingCache.get(file)).isSameAs("some string");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReadDataAnewIfOfDifferentType() {
|
||||
File file = new File("/some.string");
|
||||
backingCache.put(file, 42);
|
||||
|
||||
DataFileCache.DataFileCacheInstance instance = dataFileCache.instanceFor(String.class);
|
||||
|
||||
Object result = instance.get(file, () -> "some string");
|
||||
|
||||
assertThat(result).isSameAs("some string");
|
||||
assertThat(backingCache.get(file)).isSameAs("some string");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRemoveOutdatedDataIfOfDifferentType() {
|
||||
File file = new File("/some.string");
|
||||
backingCache.put(file, 42);
|
||||
|
||||
DataFileCache.DataFileCacheInstance instance = dataFileCache.instanceFor(String.class);
|
||||
|
||||
Object result = instance.get(file, () -> null);
|
||||
|
||||
assertThat(result).isNull();
|
||||
assertThat(backingCache.get(file)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCacheNewData() {
|
||||
File file = new File("/some.string");
|
||||
|
||||
DataFileCache.DataFileCacheInstance instance = dataFileCache.instanceFor(String.class);
|
||||
|
||||
instance.put(file, "some string");
|
||||
|
||||
assertThat(backingCache.get(file)).isSameAs("some string");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRemoveDataFromCache() {
|
||||
File file = new File("/some.string");
|
||||
backingCache.put(file, "some string");
|
||||
|
||||
DataFileCache.DataFileCacheInstance instance = dataFileCache.instanceFor(String.class);
|
||||
|
||||
instance.remove(file);
|
||||
|
||||
assertThat(backingCache.get(file)).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WithDeactivatedCache {
|
||||
|
||||
private final DataFileCache dataFileCache = new DataFileCache(null, false);
|
||||
|
||||
@Test
|
||||
void shouldReadData() {
|
||||
File file = new File("/some.string");
|
||||
|
||||
DataFileCache.DataFileCacheInstance instance = dataFileCache.instanceFor(String.class);
|
||||
|
||||
Object result = instance.get(file, () -> "some string");
|
||||
|
||||
assertThat(result).isSameAs("some string");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,8 @@
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import org.junit.Test;
|
||||
import sonia.scm.cache.MapCache;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryReadOnlyChecker;
|
||||
import sonia.scm.security.UUIDKeyGenerator;
|
||||
@@ -47,7 +46,13 @@ public class JAXBDataStoreTest extends DataStoreTestBase {
|
||||
@Override
|
||||
protected DataStoreFactory createDataStoreFactory()
|
||||
{
|
||||
return new JAXBDataStoreFactory(contextProvider, repositoryLocationResolver, new UUIDKeyGenerator(), readOnlyChecker);
|
||||
return new JAXBDataStoreFactory(
|
||||
contextProvider,
|
||||
repositoryLocationResolver,
|
||||
new UUIDKeyGenerator(),
|
||||
readOnlyChecker,
|
||||
new DataFileCache(null, false)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,10 +21,8 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.cache;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
package sonia.scm.cache;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
|
||||
@@ -35,215 +33,138 @@ import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
import java.io.Serializable;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@XmlRootElement(name = "cache")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class GuavaCacheConfiguration implements Serializable
|
||||
{
|
||||
@XmlAccessorType(XmlAccessType.PROPERTY)
|
||||
public class GuavaCacheConfiguration implements Serializable {
|
||||
|
||||
/** Field description */
|
||||
private static final long serialVersionUID = -8734373158089010603L;
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
private Integer concurrencyLevel;
|
||||
private CopyStrategy copyStrategy;
|
||||
private Long expireAfterAccess;
|
||||
private Long expireAfterWrite;
|
||||
private Integer initialCapacity;
|
||||
private Long maximumSize;
|
||||
private Long maximumWeight;
|
||||
private Boolean recordStats;
|
||||
private Boolean softValues;
|
||||
private Boolean weakKeys;
|
||||
private Boolean weakValues;
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
//J-
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("concurrencyLevel", concurrencyLevel)
|
||||
.add("copyStrategy", copyStrategy)
|
||||
.add("expireAfterAccess", expireAfterAccess)
|
||||
.add("expireAfterWrite", expireAfterWrite)
|
||||
.add("initialCapacity", initialCapacity)
|
||||
.add("maximumSize", maximumSize)
|
||||
.add("maximumWeight", maximumWeight)
|
||||
.add("recordStats", recordStats)
|
||||
.add("softValues", softValues)
|
||||
.add("weakKeys", weakKeys)
|
||||
.add("weakValues", weakValues)
|
||||
.omitNullValues().toString();
|
||||
//J+
|
||||
.add("concurrencyLevel", concurrencyLevel)
|
||||
.add("copyStrategy", copyStrategy)
|
||||
.add("expireAfterAccess", expireAfterAccess)
|
||||
.add("expireAfterWrite", expireAfterWrite)
|
||||
.add("initialCapacity", initialCapacity)
|
||||
.add("maximumSize", maximumSize)
|
||||
.add("maximumWeight", maximumWeight)
|
||||
.add("recordStats", recordStats)
|
||||
.add("softValues", softValues)
|
||||
.add("weakKeys", weakKeys)
|
||||
.add("weakValues", weakValues)
|
||||
.omitNullValues().toString();
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Integer getConcurrencyLevel()
|
||||
{
|
||||
public Integer getConcurrencyLevel() {
|
||||
return concurrencyLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public CopyStrategy getCopyStrategy()
|
||||
{
|
||||
public CopyStrategy getCopyStrategy() {
|
||||
return copyStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Long getExpireAfterAccess()
|
||||
{
|
||||
public Long getExpireAfterAccess() {
|
||||
return expireAfterAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Long getExpireAfterWrite()
|
||||
{
|
||||
public Long getExpireAfterWrite() {
|
||||
return expireAfterWrite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Integer getInitialCapacity()
|
||||
{
|
||||
public Integer getInitialCapacity() {
|
||||
return initialCapacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Long getMaximumSize()
|
||||
{
|
||||
public Long getMaximumSize() {
|
||||
return maximumSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Long getMaximumWeight()
|
||||
{
|
||||
public Long getMaximumWeight() {
|
||||
return maximumWeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Boolean getRecordStats()
|
||||
{
|
||||
public Boolean getRecordStats() {
|
||||
return recordStats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Boolean getSoftValues()
|
||||
{
|
||||
public Boolean getSoftValues() {
|
||||
return softValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Boolean getWeakKeys()
|
||||
{
|
||||
public Boolean getWeakKeys() {
|
||||
return weakKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Boolean getWeakValues()
|
||||
{
|
||||
public Boolean getWeakValues() {
|
||||
return weakValues;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
@XmlAttribute
|
||||
private Integer concurrencyLevel;
|
||||
void setConcurrencyLevel(Integer concurrencyLevel) {
|
||||
this.concurrencyLevel = concurrencyLevel;
|
||||
}
|
||||
|
||||
/** Field description */
|
||||
@XmlAttribute
|
||||
@XmlJavaTypeAdapter(XmlCopyStrategyAdapter.class)
|
||||
private CopyStrategy copyStrategy;
|
||||
void setCopyStrategy(CopyStrategy copyStrategy) {
|
||||
this.copyStrategy = copyStrategy;
|
||||
}
|
||||
|
||||
/** Field description */
|
||||
@XmlAttribute
|
||||
private Long expireAfterAccess;
|
||||
void setExpireAfterAccess(Long expireAfterAccess) {
|
||||
this.expireAfterAccess = expireAfterAccess;
|
||||
}
|
||||
|
||||
/** Field description */
|
||||
@XmlAttribute
|
||||
private Long expireAfterWrite;
|
||||
void setExpireAfterWrite(Long expireAfterWrite) {
|
||||
this.expireAfterWrite = expireAfterWrite;
|
||||
}
|
||||
|
||||
/** Field description */
|
||||
@XmlAttribute
|
||||
private Integer initialCapacity;
|
||||
void setInitialCapacity(Integer initialCapacity) {
|
||||
this.initialCapacity = initialCapacity;
|
||||
}
|
||||
|
||||
/** Field description */
|
||||
@XmlAttribute
|
||||
private Long maximumSize;
|
||||
void setMaximumSize(Long maximumSize) {
|
||||
this.maximumSize = maximumSize;
|
||||
}
|
||||
|
||||
/** Field description */
|
||||
@XmlAttribute
|
||||
private Long maximumWeight;
|
||||
void setMaximumWeight(Long maximumWeight) {
|
||||
this.maximumWeight = maximumWeight;
|
||||
}
|
||||
|
||||
/** Field description */
|
||||
@XmlAttribute
|
||||
private Boolean recordStats;
|
||||
void setRecordStats(Boolean recordStats) {
|
||||
this.recordStats = recordStats;
|
||||
}
|
||||
|
||||
/** Field description */
|
||||
@XmlAttribute
|
||||
private Boolean softValues;
|
||||
void setSoftValues(Boolean softValues) {
|
||||
this.softValues = softValues;
|
||||
}
|
||||
|
||||
/** Field description */
|
||||
@XmlAttribute
|
||||
private Boolean weakKeys;
|
||||
void setWeakKeys(Boolean weakKeys) {
|
||||
this.weakKeys = weakKeys;
|
||||
}
|
||||
|
||||
/** Field description */
|
||||
@XmlAttribute
|
||||
private Boolean weakValues;
|
||||
void setWeakValues(Boolean weakValues) {
|
||||
this.weakValues = weakValues;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,44 +21,133 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.cache;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@XmlRootElement(name = "cache")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class GuavaNamedCacheConfiguration extends GuavaCacheConfiguration
|
||||
{
|
||||
@XmlAccessorType(XmlAccessType.PROPERTY)
|
||||
public class GuavaNamedCacheConfiguration extends GuavaCacheConfiguration {
|
||||
|
||||
/** Field description */
|
||||
private static final long serialVersionUID = -624795324874828475L;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GuavaNamedCacheConfiguration.class);
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
@XmlAttribute
|
||||
private String name;
|
||||
private void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setConcurrencyLevel(Integer concurrencyLevel) {
|
||||
setIntegerValue("concurrencyLevel", concurrencyLevel, super::setConcurrencyLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setCopyStrategy(CopyStrategy copyStrategy) {
|
||||
setValue("copyStrategy", copyStrategy, super::setCopyStrategy, propertyName -> of(System.getProperty(propertyName)).map(CopyStrategy::valueOf).orElse(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
void setExpireAfterAccess(Long expireAfterAccess) {
|
||||
setLongValue("expireAfterAccess", expireAfterAccess, super::setExpireAfterAccess);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setExpireAfterWrite(Long expireAfterWrite) {
|
||||
setLongValue("expireAfterWrite", expireAfterWrite, super::setExpireAfterWrite);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setInitialCapacity(Integer initialCapacity) {
|
||||
setIntegerValue("initialCapacity", initialCapacity, super::setInitialCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setMaximumSize(Long maximumSize) {
|
||||
setLongValue("maximumSize", maximumSize, super::setMaximumSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setMaximumWeight(Long maximumWeight) {
|
||||
setLongValue("maximumWeight", maximumWeight, super::setMaximumWeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setRecordStats(Boolean recordStats) {
|
||||
setBooleanValue("recordStats", recordStats, super::setRecordStats);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setSoftValues(Boolean softValues) {
|
||||
setBooleanValue("softValues", softValues, super::setSoftValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setWeakKeys(Boolean weakKeys) {
|
||||
setBooleanValue("weakKeys", weakKeys, super::setWeakKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setWeakValues(Boolean weakValues) {
|
||||
setBooleanValue("weakValues", weakValues, super::setWeakValues);
|
||||
}
|
||||
|
||||
private void setIntegerValue(String propertyName, Integer originalValue, Consumer<Integer> setter) {
|
||||
setValue(propertyName, originalValue, setter, Integer::getInteger);
|
||||
}
|
||||
|
||||
private void setLongValue(String propertyName, Long originalValue, Consumer<Long> setter) {
|
||||
setValue(propertyName, originalValue, setter, Long::getLong);
|
||||
}
|
||||
|
||||
private void setBooleanValue(String propertyName, Boolean originalValue, Consumer<Boolean> setter) {
|
||||
setValue(propertyName, originalValue, setter, Boolean::getBoolean);
|
||||
}
|
||||
|
||||
private <T> void setValue(String propertyName, T originalValue, Consumer<T> setter, Function<String, T> systemPropertyReader) {
|
||||
setter.accept(originalValue);
|
||||
createPropertyName(propertyName)
|
||||
.map(systemPropertyReader)
|
||||
.ifPresent(value -> {
|
||||
logOverwrite(propertyName, originalValue, value);
|
||||
setter.accept(value);
|
||||
});
|
||||
}
|
||||
|
||||
private void logOverwrite(String propertyName, Object originalValue, Object overwrittenValue) {
|
||||
LOG.debug("overwrite {} of cache '{}' with system property value {} (original value: {})", propertyName, name, overwrittenValue, originalValue);
|
||||
}
|
||||
|
||||
private Optional<String> createPropertyName(String fieldName) {
|
||||
if (name == null) {
|
||||
LOG.warn("failed to overwrite cache configuration with system properties, because name has not been set yet");
|
||||
return empty();
|
||||
}
|
||||
if (name.startsWith("sonia.cache.")) {
|
||||
return of("scm.cache." + name.substring("sonia.cache.".length()) + "." + fieldName);
|
||||
}
|
||||
return empty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,14 +28,19 @@ import com.google.inject.AbstractModule;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.multibindings.Multibinder;
|
||||
import com.google.inject.throwingproviders.ThrowingProviderBinder;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.cache.CacheManager;
|
||||
import sonia.scm.cache.GuavaCacheManager;
|
||||
import sonia.scm.io.DefaultFileSystem;
|
||||
import sonia.scm.io.FileSystem;
|
||||
import sonia.scm.lifecycle.DefaultRestarter;
|
||||
import sonia.scm.lifecycle.Restarter;
|
||||
import sonia.scm.metrics.MeterRegistryProvider;
|
||||
import sonia.scm.metrics.MonitoringSystem;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.repository.DefaultRepositoryExportingCheck;
|
||||
import sonia.scm.repository.EventDrivenRepositoryArchiveCheck;
|
||||
@@ -121,6 +126,14 @@ public class BootstrapModule extends AbstractModule {
|
||||
bind(RepositoryUpdateIterator.class, FileRepositoryUpdateIterator.class);
|
||||
bind(StoreUpdateStepUtilFactory.class, FileStoreUpdateStepUtilFactory.class);
|
||||
bind(new TypeLiteral<UpdateStepRepositoryMetadataAccess<Path>>() {}).to(new TypeLiteral<MetadataStore>() {});
|
||||
|
||||
// bind metrics
|
||||
bind(MeterRegistry.class).toProvider(MeterRegistryProvider.class).asEagerSingleton();
|
||||
Multibinder.newSetBinder(binder(), MonitoringSystem.class);
|
||||
|
||||
// bind cache
|
||||
bind(CacheManager.class, GuavaCacheManager.class);
|
||||
bind(org.apache.shiro.cache.CacheManager.class, GuavaCacheManager.class);
|
||||
}
|
||||
|
||||
private <T> void bind(Class<T> clazz, Class<? extends T> defaultImplementation) {
|
||||
|
||||
@@ -30,7 +30,6 @@ import com.google.inject.multibindings.Multibinder;
|
||||
import com.google.inject.servlet.RequestScoped;
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
import com.google.inject.throwingproviders.ThrowingProviderBinder;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.Default;
|
||||
@@ -39,14 +38,13 @@ import sonia.scm.PushStateDispatcher;
|
||||
import sonia.scm.PushStateDispatcherProvider;
|
||||
import sonia.scm.RootURL;
|
||||
import sonia.scm.Undecorated;
|
||||
import sonia.scm.admin.ScmConfigurationStore;
|
||||
import sonia.scm.api.rest.ObjectMapperProvider;
|
||||
import sonia.scm.api.v2.resources.BranchLinkProvider;
|
||||
import sonia.scm.api.v2.resources.DefaultBranchLinkProvider;
|
||||
import sonia.scm.api.v2.resources.DefaultRepositoryLinkProvider;
|
||||
import sonia.scm.api.v2.resources.RepositoryLinkProvider;
|
||||
import sonia.scm.auditlog.AuditLogConfigurationStoreDecoratorFactory;
|
||||
import sonia.scm.cache.CacheManager;
|
||||
import sonia.scm.cache.GuavaCacheManager;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.group.DefaultGroupCollector;
|
||||
@@ -65,7 +63,6 @@ import sonia.scm.initialization.InitializationCookieIssuer;
|
||||
import sonia.scm.initialization.InitializationFinisher;
|
||||
import sonia.scm.io.ContentTypeResolver;
|
||||
import sonia.scm.io.DefaultContentTypeResolver;
|
||||
import sonia.scm.metrics.MeterRegistryProvider;
|
||||
import sonia.scm.migration.MigrationDAO;
|
||||
import sonia.scm.net.SSLContextProvider;
|
||||
import sonia.scm.net.TrustManagerProvider;
|
||||
@@ -134,7 +131,6 @@ import sonia.scm.user.UserManager;
|
||||
import sonia.scm.user.UserManagerProvider;
|
||||
import sonia.scm.user.xml.XmlUserDAO;
|
||||
import sonia.scm.util.DebugServlet;
|
||||
import sonia.scm.admin.ScmConfigurationStore;
|
||||
import sonia.scm.web.UserAgentParser;
|
||||
import sonia.scm.web.cgi.CGIExecutorFactory;
|
||||
import sonia.scm.web.cgi.DefaultCGIExecutorFactory;
|
||||
@@ -200,8 +196,6 @@ class ScmServletModule extends ServletModule {
|
||||
// bind extensions
|
||||
pluginLoader.getExtensionProcessor().processAutoBindExtensions(binder());
|
||||
|
||||
// bind metrics
|
||||
bind(MeterRegistry.class).toProvider(MeterRegistryProvider.class).asEagerSingleton();
|
||||
|
||||
// bind security stuff
|
||||
bind(LoginAttemptHandler.class).to(ConfigurableLoginAttemptHandler.class);
|
||||
@@ -210,10 +204,6 @@ class ScmServletModule extends ServletModule {
|
||||
bind(SecuritySystem.class).to(DefaultSecuritySystem.class);
|
||||
bind(AdministrationContext.class, DefaultAdministrationContext.class);
|
||||
|
||||
// bind cache
|
||||
bind(CacheManager.class, GuavaCacheManager.class);
|
||||
bind(org.apache.shiro.cache.CacheManager.class, GuavaCacheManager.class);
|
||||
|
||||
// bind dao
|
||||
bind(GroupDAO.class, XmlGroupDAO.class);
|
||||
bind(UserDAO.class, XmlUserDAO.class);
|
||||
|
||||
@@ -163,4 +163,12 @@ Search cache for repository changesets
|
||||
maximumSize="500"
|
||||
/>
|
||||
|
||||
<!--
|
||||
data files cache
|
||||
-->
|
||||
<cache
|
||||
name="sonia.cache.dataFile"
|
||||
maximumSize="10000"
|
||||
/>
|
||||
|
||||
</caches>
|
||||
|
||||
86
scm-webapp/src/test/java/sonia/scm/cache/GuavaNamedCacheConfigurationTest.java
vendored
Normal file
86
scm-webapp/src/test/java/sonia/scm/cache/GuavaNamedCacheConfigurationTest.java
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import java.io.StringReader;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GuavaNamedCacheConfigurationTest {
|
||||
|
||||
public static final String CACHE_CONFIGURATION =
|
||||
"<caches>" +
|
||||
" <cache" +
|
||||
" name=\"sonia.cache.externalGroups\"" +
|
||||
" maximumSize=\"1000\"" +
|
||||
" />" +
|
||||
"</caches>";
|
||||
|
||||
@Test
|
||||
void shouldTakeValueFromConfigurationFile() throws JAXBException {
|
||||
GuavaCacheManagerConfiguration configuration = readConfiguration();
|
||||
|
||||
assertThat(configuration.getCaches().get(0).getName()).isEqualTo("sonia.cache.externalGroups");
|
||||
assertThat(configuration.getCaches().get(0).getMaximumSize()).isEqualTo(1000);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WithProperty {
|
||||
|
||||
@BeforeEach
|
||||
void setProperty() {
|
||||
System.setProperty("scm.cache.externalGroups.maximumSize", "42");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void removeProperty() {
|
||||
System.clearProperty("scm.cache.externalGroups.maximumSize");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTakeValueFromPropertyIfPresent() throws JAXBException {
|
||||
GuavaCacheManagerConfiguration configuration = readConfiguration();
|
||||
|
||||
assertThat(configuration.getCaches().get(0).getMaximumSize()).isEqualTo(42);
|
||||
}
|
||||
}
|
||||
|
||||
private static GuavaCacheManagerConfiguration readConfiguration() throws JAXBException {
|
||||
JAXBContext context = JAXBContext.newInstance(GuavaCacheManagerConfiguration.class);
|
||||
return (GuavaCacheManagerConfiguration) context
|
||||
.createUnmarshaller()
|
||||
.unmarshal(new StringReader(CACHE_CONFIGURATION));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user