/* * * Copyright 2016 Robert Winkler * * 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 io.github.resilience4j.cache.internal; import io.github.resilience4j.cache.Cache; import io.github.resilience4j.cache.event.CacheEvent; import io.github.resilience4j.cache.event.CacheOnErrorEvent; import io.github.resilience4j.cache.event.CacheOnHitEvent; import io.github.resilience4j.cache.event.CacheOnMissEvent; import io.reactivex.Flowable; import io.reactivex.processors.FlowableProcessor; import io.reactivex.processors.PublishProcessor; import io.vavr.CheckedFunction0; import io.vavr.control.Option; import io.vavr.control.Try; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.atomic.LongAdder; import java.util.function.Supplier; public class CacheContext<K, V> implements Cache<K,V> { private static final Logger LOG = LoggerFactory.getLogger(CacheContext.class); private final javax.cache.Cache<K, V> cache; private final FlowableProcessor<CacheEvent> eventPublisher; private final CacheMetrics metrics; public CacheContext(javax.cache.Cache<K, V> cache) { this.cache = cache; PublishProcessor<CacheEvent> publisher = PublishProcessor.create(); this.eventPublisher = publisher.toSerialized(); this.metrics = new CacheMetrics(); } @Override public String getName() { return cache.getName(); } @Override public Metrics getMetrics() { return metrics; } @Override public V computeIfAbsent(K cacheKey, CheckedFunction0<V> supplier) { return getValueFromCache(cacheKey) .getOrElse(() -> computeAndPut(cacheKey, supplier)); } private V computeAndPut(K cacheKey, CheckedFunction0<V> supplier) { return Try.of(supplier) .andThen(value -> putValueIntoCache(cacheKey, value)) .get(); } private Option<V> getValueFromCache(K cacheKey){ try { Option<V> result = Option.of(cache.get(cacheKey)); if (result.isDefined()) { onCacheHit(cacheKey); return result; } else { onCacheMiss(cacheKey); return result; } }catch (Exception exception){ LOG.warn(String.format("Failed to get a value from Cache %s", getName()), exception); onError(exception); return Option.none(); } } private void putValueIntoCache(K cacheKey, V value) { try { if(value != null) { cache.put(cacheKey, value); } } catch (Exception exception){ LOG.warn(String.format("Failed to put a value into Cache %s", getName()), exception); onError(exception); } } private void onError(Throwable throwable) { publishCacheEvent(() -> new CacheOnErrorEvent(cache.getName(), throwable)); } private void onCacheMiss(K cacheKey) { metrics.onCacheMiss(); publishCacheEvent(() -> new CacheOnMissEvent<>(cache.getName(), cacheKey)); } private void onCacheHit(K cacheKey) { metrics.onCacheHit(); publishCacheEvent(() -> new CacheOnHitEvent<>(cache.getName(), cacheKey)); } private void publishCacheEvent(Supplier<CacheEvent> event) { if(eventPublisher.hasSubscribers()) { eventPublisher.onNext(event.get()); } } @Override public Flowable<CacheEvent> getEventStream() { return eventPublisher; } private final class CacheMetrics implements Metrics { private final LongAdder cacheMisses; private final LongAdder cacheHits; private CacheMetrics() { cacheMisses = new LongAdder(); cacheHits = new LongAdder(); } public void onCacheMiss(){ cacheMisses.increment(); } public void onCacheHit(){ cacheHits.increment(); } @Override public long getNumberOfCacheHits() { return cacheHits.longValue(); } @Override public long getNumberOfCacheMisses() { return cacheMisses.longValue(); } } }