Polish "Add cache actuator endpoint"
This commit improves the initial proposal by providing a by name read operation that returns the detail of a particular cache. It also adds more tests and complete API documentation for the feature. Closes gh-12216
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,103 +17,202 @@
|
||||
package org.springframework.boot.actuate.cache;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
/**
|
||||
* {@link Endpoint} to expose cache operations.
|
||||
* {@link Endpoint} to expose available {@link Cache caches}.
|
||||
*
|
||||
* @author Johannes Edmeuer
|
||||
* @since 2.0.2
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.1.0
|
||||
*/
|
||||
@Endpoint(id = "caches")
|
||||
public class CachesEndpoint {
|
||||
private final ApplicationContext context;
|
||||
|
||||
public CachesEndpoint(ApplicationContext context) {
|
||||
this.context = context;
|
||||
private final Map<String, CacheManager> cacheManagers;
|
||||
|
||||
/**
|
||||
* Create a new endpoint with the {@link CacheManager} instances to use.
|
||||
* @param cacheManagers the cache managers to use, indexed by name
|
||||
*/
|
||||
public CachesEndpoint(Map<String, CacheManager> cacheManagers) {
|
||||
this.cacheManagers = new LinkedHashMap<>(cacheManagers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link CachesReport} of all available {@link Cache caches}.
|
||||
* @return a caches reports
|
||||
*/
|
||||
@ReadOperation
|
||||
public CachesDescriptor caches() {
|
||||
List<CacheDescriptor> caches = new ArrayList<>();
|
||||
this.context.getBeansOfType(CacheManager.class)
|
||||
.forEach((name, cacheManager) -> caches.addAll(
|
||||
getCacheDescriptors(name, cacheManager.getCacheNames())));
|
||||
return new CachesDescriptor(caches);
|
||||
public CachesReport caches() {
|
||||
Map<String, Map<String, CacheDescriptor>> descriptors = new LinkedHashMap<>();
|
||||
getCacheEntries((name) -> true, (cacheManager) -> true).forEach((entry) -> {
|
||||
Map<String, CacheDescriptor> cmDescriptors = descriptors.computeIfAbsent(
|
||||
entry.getCacheManager(), (key) -> new LinkedHashMap<>());
|
||||
String cache = entry.getName();
|
||||
cmDescriptors.put(cache, new CacheDescriptor(entry.getTarget()));
|
||||
});
|
||||
return new CachesReport(descriptors);
|
||||
}
|
||||
|
||||
private Collection<? extends CacheDescriptor> getCacheDescriptors(String cacheManager,
|
||||
Collection<String> cacheNames) {
|
||||
return cacheNames.stream().map(cacheName -> new CacheDescriptor(cacheName, cacheManager)).collect(toList());
|
||||
/**
|
||||
* Return a {@link CacheDescriptor} for the specified cache.
|
||||
* @param cache then name of the cache
|
||||
* @param cacheManager the name of the cacheManager (can be {@code null}
|
||||
* @return the descriptor of the cache or {@code null} if no such cache exists
|
||||
* @throws NonUniqueCacheException if more than one cache with that name exist and no
|
||||
* {@code cacheManager} was provided to identify a unique candidate
|
||||
*/
|
||||
@ReadOperation
|
||||
public CacheEntry cache(@Selector String cache, @Nullable String cacheManager) {
|
||||
return extractUniqueCacheEntry(cache, getCacheEntries(
|
||||
(name) -> name.equals(cache), safeEqual(cacheManager)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the available {@link Cache caches}.
|
||||
*/
|
||||
@DeleteOperation
|
||||
public void clearCaches(@Nullable String cacheManager, @Nullable String cacheName) {
|
||||
if (cacheManager == null) {
|
||||
this.context.getBeansOfType(CacheManager.class)
|
||||
.forEach((name, manager) -> this.clearCaches(manager, cacheName));
|
||||
} else {
|
||||
this.clearCaches(this.context.getBean(cacheManager, CacheManager.class), cacheName);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearCaches(CacheManager cacheManager, String cacheName) {
|
||||
if (cacheName == null) {
|
||||
cacheManager.getCacheNames().forEach(cn -> cacheManager.getCache(cn).clear());
|
||||
} else {
|
||||
Cache cache = cacheManager.getCache(cacheName);
|
||||
if (cache != null) {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
public void clearCaches() {
|
||||
getCacheEntries((name) -> true, (cacheManagerName) -> true)
|
||||
.forEach(this::clearCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Description of an application context's caches, primarily
|
||||
* intended for serialization to JSON.
|
||||
* Clear the specific {@link Cache}.
|
||||
* @param cache then name of the cache
|
||||
* @param cacheManager the name of the cacheManager (can be {@code null}
|
||||
* @return {@code true} if the cache was cleared or {@code false} if no such cache exists
|
||||
* @throws NonUniqueCacheException if more than one cache with that name exist and no
|
||||
*/
|
||||
public static final class CachesDescriptor {
|
||||
private final List<CacheDescriptor> caches;
|
||||
@DeleteOperation
|
||||
public boolean clearCache(@Selector String cache, @Nullable String cacheManager) {
|
||||
CacheEntry entry = extractUniqueCacheEntry(cache, getCacheEntries(
|
||||
(name) -> name.equals(cache), safeEqual(cacheManager)));
|
||||
return (entry != null && clearCache(entry));
|
||||
}
|
||||
|
||||
private CachesDescriptor(List<CacheDescriptor> caches) {
|
||||
this.caches = caches;
|
||||
}
|
||||
private List<CacheEntry> getCacheEntries(
|
||||
Predicate<String> cacheNamePredicate,
|
||||
Predicate<String> cacheManagerNamePredicate) {
|
||||
List<CacheEntry> entries = new ArrayList<>();
|
||||
this.cacheManagers.keySet().stream().filter(cacheManagerNamePredicate)
|
||||
.forEach((cacheManagerName) -> entries.addAll(
|
||||
getCacheEntries(cacheManagerName, cacheNamePredicate)));
|
||||
return entries;
|
||||
}
|
||||
|
||||
public List<CacheDescriptor> getCaches() {
|
||||
return this.caches;
|
||||
private List<CacheEntry> getCacheEntries(String cacheManagerName,
|
||||
Predicate<String> cacheNamePredicate) {
|
||||
CacheManager cacheManager = this.cacheManagers.get(cacheManagerName);
|
||||
List<CacheEntry> entries = new ArrayList<>();
|
||||
cacheManager.getCacheNames().stream().filter(cacheNamePredicate)
|
||||
.map(cacheManager::getCache).filter(Objects::nonNull)
|
||||
.forEach((cache) -> entries.add(
|
||||
new CacheEntry(cache, cacheManagerName)));
|
||||
return entries;
|
||||
}
|
||||
|
||||
private CacheEntry extractUniqueCacheEntry(String cache,
|
||||
List<CacheEntry> entries) {
|
||||
if (entries.size() > 1) {
|
||||
throw new NonUniqueCacheException(cache, entries.stream()
|
||||
.map(CacheEntry::getCacheManager).distinct()
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
return (entries.isEmpty() ? null : entries.get(0));
|
||||
}
|
||||
|
||||
private boolean clearCache(CacheEntry entry) {
|
||||
String cacheName = entry.getName();
|
||||
Cache cache = this.cacheManagers.get(entry.getCacheManager()).getCache(cacheName);
|
||||
if (cache != null) {
|
||||
cache.clear();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Predicate<String> safeEqual(String name) {
|
||||
return (name != null ? ((requested) -> requested.equals(name))
|
||||
: ((requested) -> true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Description of a {@link Cache}, primarily intended for serialization to
|
||||
* JSON.
|
||||
* A report of available {@link Cache caches}, primarily intended for serialization
|
||||
* to JSON.
|
||||
*/
|
||||
public static final class CacheDescriptor {
|
||||
public static final class CachesReport {
|
||||
|
||||
private final Map<String, Map<String, CacheDescriptor>> cacheManagers;
|
||||
|
||||
public CachesReport(Map<String, Map<String, CacheDescriptor>> cacheManagers) {
|
||||
this.cacheManagers = cacheManagers;
|
||||
}
|
||||
|
||||
public Map<String, Map<String, CacheDescriptor>> getCacheManagers() {
|
||||
return this.cacheManagers;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic description of a {@link Cache}, primarily intended for serialization to JSON.
|
||||
*/
|
||||
public static class CacheDescriptor {
|
||||
|
||||
private final String target;
|
||||
|
||||
public CacheDescriptor(String target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fully qualified name of the native cache.
|
||||
* @return the fully qualified name of the native cache
|
||||
*/
|
||||
public String getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Description of a {@link Cache}, primarily intended for serialization to JSON.
|
||||
*/
|
||||
public static final class CacheEntry extends CacheDescriptor {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String cacheManager;
|
||||
|
||||
public CacheDescriptor(String name, String cacheManager) {
|
||||
this.name = name;
|
||||
public CacheEntry(Cache cache, String cacheManager) {
|
||||
super(cache.getNativeCache().getClass().getName());
|
||||
this.name = cache.getName();
|
||||
this.cacheManager = cacheManager;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getCacheManager() {
|
||||
return cacheManager;
|
||||
return this.cacheManager;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.boot.actuate.cache;
|
||||
|
||||
import org.springframework.boot.actuate.cache.CachesEndpoint.CacheEntry;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link EndpointWebExtension} for the {@link CachesEndpoint}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.1.0
|
||||
*/
|
||||
@EndpointWebExtension(endpoint = CachesEndpoint.class)
|
||||
public class CachesEndpointWebExtension {
|
||||
|
||||
private final CachesEndpoint delegate;
|
||||
|
||||
public CachesEndpointWebExtension(CachesEndpoint delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public WebEndpointResponse<CacheEntry> cache(@Selector String cache,
|
||||
@Nullable String cacheManager) {
|
||||
try {
|
||||
CacheEntry entry = this.delegate.cache(cache, cacheManager);
|
||||
int status = (entry != null ? WebEndpointResponse.STATUS_OK
|
||||
: WebEndpointResponse.STATUS_NOT_FOUND);
|
||||
return new WebEndpointResponse<>(entry, status);
|
||||
}
|
||||
catch (NonUniqueCacheException ex) {
|
||||
return new WebEndpointResponse<>(WebEndpointResponse.STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteOperation
|
||||
public WebEndpointResponse<Void> clearCache(@Selector String cache,
|
||||
@Nullable String cacheManager) {
|
||||
try {
|
||||
boolean cleared = this.delegate.clearCache(cache, cacheManager);
|
||||
int status = (cleared ? WebEndpointResponse.STATUS_NO_CONTENT
|
||||
: WebEndpointResponse.STATUS_NOT_FOUND);
|
||||
return new WebEndpointResponse<>(status);
|
||||
}
|
||||
catch (NonUniqueCacheException ex) {
|
||||
return new WebEndpointResponse<>(WebEndpointResponse.STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.boot.actuate.cache;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Exception thrown when multiple caches exist with the same name.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public class NonUniqueCacheException extends RuntimeException {
|
||||
|
||||
private final String cacheName;
|
||||
|
||||
private final Collection<String> cacheManagerNames;
|
||||
|
||||
public NonUniqueCacheException(String cacheName,
|
||||
Collection<String> cacheManagerNames) {
|
||||
super(String.format("Multiple caches named %s found, specify the 'cacheManager' "
|
||||
+ "to use: %s", cacheName, cacheManagerNames));
|
||||
this.cacheName = cacheName;
|
||||
this.cacheManagerNames = Collections.unmodifiableCollection(cacheManagerNames);
|
||||
}
|
||||
|
||||
public String getCacheName() {
|
||||
return this.cacheName;
|
||||
}
|
||||
|
||||
public Collection<String> getCacheManagerNames() {
|
||||
return this.cacheManagerNames;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -36,6 +36,11 @@ public final class WebEndpointResponse<T> {
|
||||
*/
|
||||
public static final int STATUS_OK = 200;
|
||||
|
||||
/**
|
||||
* {@code 204 No Content}.
|
||||
*/
|
||||
public static final int STATUS_NO_CONTENT = 204;
|
||||
|
||||
/**
|
||||
* {@code 400 Bad Request}.
|
||||
*/
|
||||
|
||||
@@ -16,102 +16,201 @@
|
||||
|
||||
package org.springframework.boot.actuate.cache;
|
||||
|
||||
import org.junit.Test;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.boot.actuate.cache.CachesEndpoint.CacheDescriptor;
|
||||
import org.springframework.boot.actuate.cache.CachesEndpoint.CacheEntry;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCache;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
|
||||
import org.springframework.cache.support.SimpleCacheManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link CachesEndpoint}.
|
||||
*
|
||||
* @author Johannes Edmeier
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class CachesEndpointTests {
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(
|
||||
AutoConfigurations.of(CacheAutoConfiguration.class));
|
||||
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void cacheReportIsReturned() {
|
||||
//@formatter:off
|
||||
this.contextRunner.withUserConfiguration(Config.class)
|
||||
.run(context -> assertThat(context.getBean(CachesEndpoint.class).caches().getCaches())
|
||||
.hasSize(2)
|
||||
.anySatisfy(cache -> {
|
||||
assertThat(cache.getName()).isEqualTo("first");
|
||||
assertThat(cache.getCacheManager()).isEqualTo("cacheManager");
|
||||
}).anySatisfy(cache -> {
|
||||
assertThat(cache.getName()).isEqualTo("second");
|
||||
assertThat(cache.getCacheManager()).isEqualTo("cacheManager");
|
||||
})
|
||||
);
|
||||
//@formatter:on
|
||||
public void allCachesWithSingleCacheManager() {
|
||||
CachesEndpoint endpoint = new CachesEndpoint(Collections.singletonMap(
|
||||
"test", new ConcurrentMapCacheManager("a", "b")));
|
||||
Map<String, Map<String, CacheDescriptor>> allDescriptors = endpoint.caches()
|
||||
.getCacheManagers();
|
||||
assertThat(allDescriptors).containsOnlyKeys("test");
|
||||
Map<String, CacheDescriptor> descriptors = allDescriptors.get("test");
|
||||
assertThat(descriptors).containsOnlyKeys("a", "b");
|
||||
assertThat(descriptors.get("a").getTarget()).isEqualTo(
|
||||
ConcurrentHashMap.class.getName());
|
||||
assertThat(descriptors.get("b").getTarget()).isEqualTo(
|
||||
ConcurrentHashMap.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cacheIsCleared() {
|
||||
this.contextRunner.withUserConfiguration(Config.class).run((context) -> {
|
||||
Cache firstCache = context.getBean("firstCache", Cache.class);
|
||||
firstCache.put("key", "vale");
|
||||
Cache secondCache = context.getBean("secondCache", Cache.class);
|
||||
secondCache.put("key", "value");
|
||||
context.getBean(CachesEndpoint.class).clearCaches(null, null);
|
||||
assertThat(firstCache.get("key", String.class)).isNull();
|
||||
assertThat(secondCache.get("key", String.class)).isNull();
|
||||
});
|
||||
public void allCachesWithSeveralCacheManagers() {
|
||||
Map<String, CacheManager> cacheManagers = new LinkedHashMap<>();
|
||||
cacheManagers.put("test", new ConcurrentMapCacheManager("a", "b"));
|
||||
cacheManagers.put("another", new ConcurrentMapCacheManager("a", "c"));
|
||||
CachesEndpoint endpoint = new CachesEndpoint(cacheManagers);
|
||||
Map<String, Map<String, CacheDescriptor>> allDescriptors = endpoint.caches()
|
||||
.getCacheManagers();
|
||||
assertThat(allDescriptors).containsOnlyKeys("test", "another");
|
||||
assertThat(allDescriptors.get("test")).containsOnlyKeys("a", "b");
|
||||
assertThat(allDescriptors.get("another")).containsOnlyKeys("a", "c");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void namedCacheIsCleared() {
|
||||
this.contextRunner.withUserConfiguration(Config.class).run((context) -> {
|
||||
Cache firstCache = context.getBean("firstCache", Cache.class);
|
||||
firstCache.put("key", "value");
|
||||
Cache secondCache = context.getBean("secondCache", Cache.class);
|
||||
secondCache.put("key", "value");
|
||||
context.getBean(CachesEndpoint.class).clearCaches(null, "first");
|
||||
assertThat(firstCache.get("key", String.class)).isNull();
|
||||
assertThat(secondCache.get("key", String.class)).isEqualTo("value");
|
||||
});
|
||||
public void namedCacheWithSingleCacheManager() {
|
||||
CachesEndpoint endpoint = new CachesEndpoint(Collections.singletonMap(
|
||||
"test", new ConcurrentMapCacheManager("b", "a")));
|
||||
CacheEntry entry = endpoint.cache("a", null);
|
||||
assertThat(entry).isNotNull();
|
||||
assertThat(entry.getCacheManager()).isEqualTo("test");
|
||||
assertThat(entry.getName()).isEqualTo("a");
|
||||
assertThat(entry.getTarget()).isEqualTo(ConcurrentHashMap.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unknwonCache() {
|
||||
this.contextRunner.withUserConfiguration(Config.class).run((context) -> {
|
||||
Cache firstCache = context.getBean("firstCache", Cache.class);
|
||||
firstCache.put("key", "value");
|
||||
Cache secondCache = context.getBean("secondCache", Cache.class);
|
||||
secondCache.put("key", "value");
|
||||
context.getBean(CachesEndpoint.class).clearCaches(null, "UNKNWON");
|
||||
assertThat(firstCache.get("key", String.class)).isEqualTo("value");
|
||||
assertThat(secondCache.get("key", String.class)).isEqualTo("value");
|
||||
});
|
||||
public void namedCacheWithSeveralCacheManagers() {
|
||||
Map<String, CacheManager> cacheManagers = new LinkedHashMap<>();
|
||||
cacheManagers.put("test", new ConcurrentMapCacheManager("b", "dupe-cache"));
|
||||
cacheManagers.put("another", new ConcurrentMapCacheManager("c", "dupe-cache"));
|
||||
CachesEndpoint endpoint = new CachesEndpoint(cacheManagers);
|
||||
this.thrown.expect(NonUniqueCacheException.class);
|
||||
this.thrown.expectMessage("dupe-cache");
|
||||
this.thrown.expectMessage("test");
|
||||
this.thrown.expectMessage("another");
|
||||
endpoint.cache("dupe-cache", null);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public static class Config {
|
||||
@Bean
|
||||
public Cache firstCache() {
|
||||
return new ConcurrentMapCache("first");
|
||||
}
|
||||
@Test
|
||||
public void namedCacheWithUnknownCache() {
|
||||
CachesEndpoint endpoint = new CachesEndpoint(Collections.singletonMap(
|
||||
"test", new ConcurrentMapCacheManager("b", "a")));
|
||||
CacheEntry entry = endpoint.cache("unknown", null);
|
||||
assertThat(entry).isNull();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Cache secondCache() {
|
||||
return new ConcurrentMapCache("second");
|
||||
}
|
||||
@Test
|
||||
public void namedCacheWithWrongCacheManager() {
|
||||
Map<String, CacheManager> cacheManagers = new LinkedHashMap<>();
|
||||
cacheManagers.put("test", new ConcurrentMapCacheManager("b", "a"));
|
||||
cacheManagers.put("another", new ConcurrentMapCacheManager("c", "a"));
|
||||
CachesEndpoint endpoint = new CachesEndpoint(cacheManagers);
|
||||
CacheEntry entry = endpoint.cache("c", "test");
|
||||
assertThat(entry).isNull();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CachesEndpoint endpoint(ApplicationContext context) {
|
||||
return new CachesEndpoint(context);
|
||||
}
|
||||
@Test
|
||||
public void namedCacheWithSeveralCacheManagersWithCacheManagerFilter() {
|
||||
Map<String, CacheManager> cacheManagers = new LinkedHashMap<>();
|
||||
cacheManagers.put("test", new ConcurrentMapCacheManager("b", "a"));
|
||||
cacheManagers.put("another", new ConcurrentMapCacheManager("c", "a"));
|
||||
CachesEndpoint endpoint = new CachesEndpoint(cacheManagers);
|
||||
CacheEntry entry = endpoint.cache("a", "test");
|
||||
assertThat(entry).isNotNull();
|
||||
assertThat(entry.getCacheManager()).isEqualTo("test");
|
||||
assertThat(entry.getName()).isEqualTo("a");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearAllCaches() {
|
||||
Cache a = mockCache("a");
|
||||
Cache b = mockCache("b");
|
||||
CachesEndpoint endpoint = new CachesEndpoint(Collections.singletonMap(
|
||||
"test", cacheManager(a, b)));
|
||||
endpoint.clearCaches();
|
||||
verify(a).clear();
|
||||
verify(b).clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearCache() {
|
||||
Cache a = mockCache("a");
|
||||
Cache b = mockCache("b");
|
||||
CachesEndpoint endpoint = new CachesEndpoint(Collections.singletonMap(
|
||||
"test", cacheManager(a, b)));
|
||||
assertThat(endpoint.clearCache("a", null)).isTrue();
|
||||
verify(a).clear();
|
||||
verify(b, never()).clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearCacheWithSeveralCacheManagers() {
|
||||
Map<String, CacheManager> cacheManagers = new LinkedHashMap<>();
|
||||
cacheManagers.put("test", cacheManager(mockCache("dupe-cache"), mockCache("b")));
|
||||
cacheManagers.put("another", cacheManager(mockCache("dupe-cache")));
|
||||
CachesEndpoint endpoint = new CachesEndpoint(cacheManagers);
|
||||
|
||||
this.thrown.expectMessage("dupe-cache");
|
||||
this.thrown.expectMessage("test");
|
||||
this.thrown.expectMessage("another");
|
||||
endpoint.clearCache("dupe-cache", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearCacheWithSeveralCacheManagersWithCacheManagerFilter() {
|
||||
Map<String, CacheManager> cacheManagers = new LinkedHashMap<>();
|
||||
Cache a = mockCache("a");
|
||||
Cache b = mockCache("b");
|
||||
cacheManagers.put("test", cacheManager(a, b));
|
||||
Cache anotherA = mockCache("a");
|
||||
cacheManagers.put("another", cacheManager(anotherA));
|
||||
CachesEndpoint endpoint = new CachesEndpoint(cacheManagers);
|
||||
assertThat(endpoint.clearCache("a", "another")).isTrue();
|
||||
verify(a, never()).clear();
|
||||
verify(anotherA).clear();
|
||||
verify(b, never()).clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearCacheWithUnknownCache() {
|
||||
Cache a = mockCache("a");
|
||||
CachesEndpoint endpoint = new CachesEndpoint(Collections.singletonMap(
|
||||
"test", cacheManager(a)));
|
||||
assertThat(endpoint.clearCache("unknown", null)).isFalse();
|
||||
verify(a, never()).clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearCacheWithUnknownCacheManager() {
|
||||
Cache a = mockCache("a");
|
||||
CachesEndpoint endpoint = new CachesEndpoint(Collections.singletonMap(
|
||||
"test", cacheManager(a)));
|
||||
assertThat(endpoint.clearCache("a", "unknown")).isFalse();
|
||||
verify(a, never()).clear();
|
||||
}
|
||||
|
||||
private CacheManager cacheManager(Cache... caches) {
|
||||
SimpleCacheManager cacheManager = new SimpleCacheManager();
|
||||
cacheManager.setCaches(Arrays.asList(caches));
|
||||
cacheManager.afterPropertiesSet();
|
||||
return cacheManager;
|
||||
}
|
||||
|
||||
private Cache mockCache(String name) {
|
||||
Cache cache = mock(Cache.class);
|
||||
given(cache.getName()).willReturn(name);
|
||||
given(cache.getNativeCache()).willReturn(new Object());
|
||||
return cache;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.boot.actuate.cache;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointRunners;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link CachesEndpoint} exposed by Jersey, Spring MVC, and WebFlux.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@RunWith(WebEndpointRunners.class)
|
||||
public class CachesEndpointWebIntegrationTests {
|
||||
|
||||
private static WebTestClient client;
|
||||
|
||||
private static ConfigurableApplicationContext context;
|
||||
|
||||
@Test
|
||||
public void allCaches() {
|
||||
client.get().uri("/actuator/caches").exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("cacheManagers.one.a.target").isEqualTo(
|
||||
ConcurrentHashMap.class.getName())
|
||||
.jsonPath("cacheManagers.one.b.target").isEqualTo(
|
||||
ConcurrentHashMap.class.getName())
|
||||
.jsonPath("cacheManagers.two.a.target").isEqualTo(
|
||||
ConcurrentHashMap.class.getName())
|
||||
.jsonPath("cacheManagers.two.c.target").isEqualTo(
|
||||
ConcurrentHashMap.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void namedCache() {
|
||||
client.get().uri("/actuator/caches/b").exchange().expectStatus().isOk()
|
||||
.expectBody()
|
||||
.jsonPath("name").isEqualTo("b")
|
||||
.jsonPath("cacheManager").isEqualTo("one")
|
||||
.jsonPath("target").isEqualTo(ConcurrentHashMap.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void namedCacheWithUnknownName() {
|
||||
client.get().uri("/actuator/caches/does-not-exist").exchange().expectStatus()
|
||||
.isNotFound();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void namedCacheWithNonUniqueName() {
|
||||
client.get().uri("/actuator/caches/a").exchange().expectStatus()
|
||||
.isBadRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearNamedCache() {
|
||||
Cache b = context.getBean("one", CacheManager.class).getCache("b");
|
||||
b.put("test", "value");
|
||||
client.delete().uri("/actuator/caches/b").exchange().expectStatus().isNoContent();
|
||||
assertThat(b.get("test")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cleanNamedCacheWithUnknownName() {
|
||||
client.delete().uri("/actuator/caches/does-not-exist").exchange().expectStatus()
|
||||
.isNotFound();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearNamedCacheWithNonUniqueName() {
|
||||
client.get().uri("/actuator/caches/a").exchange().expectStatus()
|
||||
.isBadRequest();
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
public CacheManager one() {
|
||||
return new ConcurrentMapCacheManager("a", "b");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CacheManager two() {
|
||||
return new ConcurrentMapCacheManager("a", "c");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CachesEndpoint endpoint(Map<String, CacheManager> cacheManagers) {
|
||||
return new CachesEndpoint(cacheManagers);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CachesEndpointWebExtension cachesEndpointWebExtension(
|
||||
CachesEndpoint endpoint) {
|
||||
return new CachesEndpointWebExtension(endpoint);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user