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.metrics.cache;
018
019import java.util.Collection;
020import java.util.Objects;
021
022import io.micrometer.core.instrument.MeterRegistry;
023import io.micrometer.core.instrument.Tag;
024import io.micrometer.core.instrument.Tags;
025import io.micrometer.core.instrument.binder.MeterBinder;
026
027import org.springframework.boot.util.LambdaSafe;
028import org.springframework.cache.Cache;
029import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
030import org.springframework.util.ClassUtils;
031
032/**
033 * Register supported {@link Cache} to a {@link MeterRegistry}.
034 *
035 * @author Stephane Nicoll
036 * @since 2.0.0
037 */
038public class CacheMetricsRegistrar {
039
040        private final MeterRegistry registry;
041
042        private final Collection<CacheMeterBinderProvider<?>> binderProviders;
043
044        /**
045         * Creates a new registrar.
046         * @param registry the {@link MeterRegistry} to use
047         * @param binderProviders the {@link CacheMeterBinderProvider} instances that should
048         * be used to detect compatible caches
049         */
050        public CacheMetricsRegistrar(MeterRegistry registry,
051                        Collection<CacheMeterBinderProvider<?>> binderProviders) {
052                this.registry = registry;
053                this.binderProviders = binderProviders;
054        }
055
056        /**
057         * Attempt to bind the specified {@link Cache} to the registry. Return {@code true} if
058         * the cache is supported and was bound to the registry, {@code false} otherwise.
059         * @param cache the cache to handle
060         * @param tags the tags to associate with the metrics of that cache
061         * @return {@code true} if the {@code cache} is supported and was registered
062         */
063        public boolean bindCacheToRegistry(Cache cache, Tag... tags) {
064                MeterBinder meterBinder = getMeterBinder(unwrapIfNecessary(cache), Tags.of(tags));
065                if (meterBinder != null) {
066                        meterBinder.bindTo(this.registry);
067                        return true;
068                }
069                return false;
070        }
071
072        @SuppressWarnings({ "unchecked" })
073        private MeterBinder getMeterBinder(Cache cache, Tags tags) {
074                Tags cacheTags = tags.and(getAdditionalTags(cache));
075                return LambdaSafe
076                                .callbacks(CacheMeterBinderProvider.class, this.binderProviders, cache)
077                                .withLogger(CacheMetricsRegistrar.class)
078                                .invokeAnd((binderProvider) -> binderProvider.getMeterBinder(cache,
079                                                cacheTags))
080                                .filter(Objects::nonNull).findFirst().orElse(null);
081        }
082
083        /**
084         * Return additional {@link Tag tags} to be associated with the given {@link Cache}.
085         * @param cache the cache
086         * @return a list of additional tags to associate to that {@code cache}.
087         */
088        protected Iterable<Tag> getAdditionalTags(Cache cache) {
089                return Tags.of("name", cache.getName());
090        }
091
092        private Cache unwrapIfNecessary(Cache cache) {
093                if (ClassUtils.isPresent(
094                                "org.springframework.cache.transaction.TransactionAwareCacheDecorator",
095                                getClass().getClassLoader())) {
096                        return TransactionAwareCacheDecoratorHandler.unwrapIfNecessary(cache);
097                }
098                return cache;
099        }
100
101        private static class TransactionAwareCacheDecoratorHandler {
102
103                private static Cache unwrapIfNecessary(Cache cache) {
104                        try {
105                                if (cache instanceof TransactionAwareCacheDecorator) {
106                                        return ((TransactionAwareCacheDecorator) cache).getTargetCache();
107                                }
108                        }
109                        catch (NoClassDefFoundError ex) {
110                                // Ignore
111                        }
112                        return cache;
113                }
114
115        }
116
117}