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}