001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.actuate.cache; 018 019import java.util.LinkedHashMap; 020import java.util.List; 021import java.util.Map; 022import java.util.Objects; 023import java.util.function.Predicate; 024import java.util.stream.Collectors; 025 026import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; 027import org.springframework.boot.actuate.endpoint.annotation.Endpoint; 028import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; 029import org.springframework.boot.actuate.endpoint.annotation.Selector; 030import org.springframework.cache.Cache; 031import org.springframework.cache.CacheManager; 032import org.springframework.lang.Nullable; 033 034/** 035 * {@link Endpoint} to expose available {@link Cache caches}. 036 * 037 * @author Johannes Edmeier 038 * @author Stephane Nicoll 039 * @since 2.1.0 040 */ 041@Endpoint(id = "caches") 042public class CachesEndpoint { 043 044 private final Map<String, CacheManager> cacheManagers; 045 046 /** 047 * Create a new endpoint with the {@link CacheManager} instances to use. 048 * @param cacheManagers the cache managers to use, indexed by name 049 */ 050 public CachesEndpoint(Map<String, CacheManager> cacheManagers) { 051 this.cacheManagers = new LinkedHashMap<>(cacheManagers); 052 } 053 054 /** 055 * Return a {@link CachesReport} of all available {@link Cache caches}. 056 * @return a caches reports 057 */ 058 @ReadOperation 059 public CachesReport caches() { 060 Map<String, Map<String, CacheDescriptor>> descriptors = new LinkedHashMap<>(); 061 getCacheEntries(matchAll(), matchAll()).forEach((entry) -> { 062 String cacheName = entry.getName(); 063 String cacheManager = entry.getCacheManager(); 064 Map<String, CacheDescriptor> cacheManagerDescriptors = descriptors 065 .computeIfAbsent(cacheManager, (key) -> new LinkedHashMap<>()); 066 cacheManagerDescriptors.put(cacheName, 067 new CacheDescriptor(entry.getTarget())); 068 }); 069 Map<String, CacheManagerDescriptor> cacheManagerDescriptors = new LinkedHashMap<>(); 070 descriptors.forEach((name, entries) -> cacheManagerDescriptors.put(name, 071 new CacheManagerDescriptor(entries))); 072 return new CachesReport(cacheManagerDescriptors); 073 } 074 075 /** 076 * Return a {@link CacheDescriptor} for the specified cache. 077 * @param cache the name of the cache 078 * @param cacheManager the name of the cacheManager (can be {@code null} 079 * @return the descriptor of the cache or {@code null} if no such cache exists 080 * @throws NonUniqueCacheException if more than one cache with that name exists and no 081 * {@code cacheManager} was provided to identify a unique candidate 082 */ 083 @ReadOperation 084 public CacheEntry cache(@Selector String cache, @Nullable String cacheManager) { 085 return extractUniqueCacheEntry(cache, 086 getCacheEntries((name) -> name.equals(cache), isNameMatch(cacheManager))); 087 } 088 089 /** 090 * Clear all the available {@link Cache caches}. 091 */ 092 @DeleteOperation 093 public void clearCaches() { 094 getCacheEntries(matchAll(), matchAll()).forEach(this::clearCache); 095 } 096 097 /** 098 * Clear the specific {@link Cache}. 099 * @param cache the name of the cache 100 * @param cacheManager the name of the cacheManager (can be {@code null} to match all) 101 * @return {@code true} if the cache was cleared or {@code false} if no such cache 102 * exists 103 * @throws NonUniqueCacheException if more than one cache with that name exists and no 104 * {@code cacheManager} was provided to identify a unique candidate 105 */ 106 @DeleteOperation 107 public boolean clearCache(@Selector String cache, @Nullable String cacheManager) { 108 CacheEntry entry = extractUniqueCacheEntry(cache, 109 getCacheEntries((name) -> name.equals(cache), isNameMatch(cacheManager))); 110 return (entry != null && clearCache(entry)); 111 } 112 113 private List<CacheEntry> getCacheEntries(Predicate<String> cacheNamePredicate, 114 Predicate<String> cacheManagerNamePredicate) { 115 return this.cacheManagers.keySet().stream().filter(cacheManagerNamePredicate) 116 .flatMap((cacheManagerName) -> getCacheEntries(cacheManagerName, 117 cacheNamePredicate).stream()) 118 .collect(Collectors.toList()); 119 } 120 121 private List<CacheEntry> getCacheEntries(String cacheManagerName, 122 Predicate<String> cacheNamePredicate) { 123 CacheManager cacheManager = this.cacheManagers.get(cacheManagerName); 124 return cacheManager.getCacheNames().stream().filter(cacheNamePredicate) 125 .map(cacheManager::getCache).filter(Objects::nonNull) 126 .map((cache) -> new CacheEntry(cache, cacheManagerName)) 127 .collect(Collectors.toList()); 128 } 129 130 private CacheEntry extractUniqueCacheEntry(String cache, List<CacheEntry> entries) { 131 if (entries.size() > 1) { 132 throw new NonUniqueCacheException(cache, 133 entries.stream().map(CacheEntry::getCacheManager).distinct() 134 .collect(Collectors.toList())); 135 } 136 return (!entries.isEmpty() ? entries.get(0) : null); 137 } 138 139 private boolean clearCache(CacheEntry entry) { 140 String cacheName = entry.getName(); 141 String cacheManager = entry.getCacheManager(); 142 Cache cache = this.cacheManagers.get(cacheManager).getCache(cacheName); 143 if (cache != null) { 144 cache.clear(); 145 return true; 146 } 147 return false; 148 } 149 150 private Predicate<String> isNameMatch(String name) { 151 return (name != null) ? ((requested) -> requested.equals(name)) : matchAll(); 152 } 153 154 private Predicate<String> matchAll() { 155 return (name) -> true; 156 } 157 158 /** 159 * A report of available {@link Cache caches}, primarily intended for serialization to 160 * JSON. 161 */ 162 public static final class CachesReport { 163 164 private final Map<String, CacheManagerDescriptor> cacheManagers; 165 166 public CachesReport(Map<String, CacheManagerDescriptor> cacheManagers) { 167 this.cacheManagers = cacheManagers; 168 } 169 170 public Map<String, CacheManagerDescriptor> getCacheManagers() { 171 return this.cacheManagers; 172 } 173 174 } 175 176 /** 177 * Description of a {@link CacheManager}, primarily intended for serialization to 178 * JSON. 179 */ 180 public static final class CacheManagerDescriptor { 181 182 private final Map<String, CacheDescriptor> caches; 183 184 public CacheManagerDescriptor(Map<String, CacheDescriptor> caches) { 185 this.caches = caches; 186 } 187 188 public Map<String, CacheDescriptor> getCaches() { 189 return this.caches; 190 } 191 192 } 193 194 /** 195 * Basic description of a {@link Cache}, primarily intended for serialization to JSON. 196 */ 197 public static class CacheDescriptor { 198 199 private final String target; 200 201 public CacheDescriptor(String target) { 202 this.target = target; 203 } 204 205 /** 206 * Return the fully qualified name of the native cache. 207 * @return the fully qualified name of the native cache 208 */ 209 public String getTarget() { 210 return this.target; 211 } 212 213 } 214 215 /** 216 * Description of a {@link Cache}, primarily intended for serialization to JSON. 217 */ 218 public static final class CacheEntry extends CacheDescriptor { 219 220 private final String name; 221 222 private final String cacheManager; 223 224 public CacheEntry(Cache cache, String cacheManager) { 225 super(cache.getNativeCache().getClass().getName()); 226 this.name = cache.getName(); 227 this.cacheManager = cacheManager; 228 } 229 230 public String getName() { 231 return this.name; 232 } 233 234 public String getCacheManager() { 235 return this.cacheManager; 236 } 237 238 } 239 240}