org.elasticsoftware.elasticactors.cache.CacheManager.java Source code

Java tutorial

Introduction

Here is the source code for org.elasticsoftware.elasticactors.cache.CacheManager.java

Source

/*
 * Copyright 2013 - 2017 The Original 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.elasticsoftware.elasticactors.cache;

import com.google.common.cache.*;
import com.google.common.collect.*;

import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;

/**
 * @author Joost van de Wijgerd
 */
public class CacheManager<K, V> {
    private final Cache<CacheKey, V> backingCache;
    private final Multimap<Object, CacheKey> segmentIndex;
    private final GlobalRemovalListener globalRemovalListener = new GlobalRemovalListener();
    private final ConcurrentMap<Object, EvictionListener<V>> evictionListeners = new ConcurrentHashMap<Object, EvictionListener<V>>();

    public CacheManager(int maximumSize) {
        backingCache = CacheBuilder.newBuilder().maximumSize(maximumSize).removalListener(globalRemovalListener)
                .build();
        segmentIndex = Multimaps.synchronizedSetMultimap(HashMultimap.<Object, CacheKey>create());
    }

    public final Cache<K, V> create(Object cacheKey, EvictionListener<V> evictionListener) {
        if (evictionListener != null) {
            evictionListeners.put(cacheKey, evictionListener);
        }
        return new SegmentedCache(cacheKey);
    }

    public final void destroy(Cache<K, V> cache) {
        if (SegmentedCache.class.isInstance(cache)) {
            Object segmentKey = ((SegmentedCache) cache).segmentKey;
            backingCache.invalidateAll(segmentIndex.removeAll(segmentKey));
            evictionListeners.remove(segmentKey);
        }
    }

    private final class SegmentedCache extends AbstractCache<K, V> {
        private final Object segmentKey;

        private SegmentedCache(Object segmentKey) {
            this.segmentKey = segmentKey;
        }

        @Override
        public V getIfPresent(Object key) {
            return backingCache.getIfPresent(new CacheKey(segmentKey, key));
        }

        @Override
        public V get(K key, Callable<? extends V> valueLoader) throws ExecutionException {
            CacheKey cacheKey = new CacheKey(segmentKey, key);
            V value = backingCache.get(cacheKey, valueLoader);
            segmentIndex.put(segmentKey, cacheKey);
            return value;
        }

        @Override
        public void invalidate(Object key) {
            CacheKey cacheKey = new CacheKey(segmentKey, key);
            backingCache.invalidate(new CacheKey(segmentKey, key));
            segmentIndex.remove(segmentKey, cacheKey);
        }

        @Override
        public void put(K key, V value) {
            CacheKey cacheKey = new CacheKey(segmentKey, key);
            backingCache.put(cacheKey, value);
            segmentIndex.put(segmentKey, cacheKey);
        }

        @Override
        public ImmutableMap<K, V> getAllPresent(Iterable<?> keys) {
            Map<K, V> result = Maps.newLinkedHashMap();
            for (Object key : keys) {
                V value = backingCache.getIfPresent(new CacheKey(segmentKey, key));
                if (value != null) {
                    result.put((K) key, value);
                }
            }
            return ImmutableMap.copyOf(result);
        }

        @Override
        public void putAll(Map<? extends K, ? extends V> m) {
            for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
                put(entry.getKey(), entry.getValue());
            }
        }

        @Override
        public void cleanUp() {
            backingCache.cleanUp();
        }

        @Override
        public long size() {
            return segmentIndex.get(segmentKey).size();
        }

        @Override
        public void invalidateAll(Iterable<?> keys) {
            for (Object key : keys) {
                invalidate(key);
            }
        }

        @Override
        public void invalidateAll() {
            backingCache.invalidateAll(segmentIndex.removeAll(segmentKey));
        }

        @Override
        public CacheStats stats() {
            return backingCache.stats();
        }
    }

    private static final class CacheKey {
        private final Object segmentKey;
        private final Object cacheKey;
        private final int hashCode;

        private CacheKey(Object segmentKey, Object cacheKey) {
            this.segmentKey = segmentKey;
            this.cacheKey = cacheKey;
            this.hashCode = (segmentKey.hashCode() * 31) + cacheKey.hashCode();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            CacheKey cacheKey1 = (CacheKey) o;

            if (!cacheKey.equals(cacheKey1.cacheKey))
                return false;
            if (!segmentKey.equals(cacheKey1.segmentKey))
                return false;

            return true;
        }

        @Override
        public int hashCode() {
            return this.hashCode;
        }
    }

    private final class GlobalRemovalListener implements RemovalListener<CacheManager.CacheKey, V> {
        @Override
        public void onRemoval(RemovalNotification<CacheKey, V> notification) {
            if (notification.getKey() != null && notification.wasEvicted()) {
                segmentIndex.remove(notification.getKey().segmentKey, notification.getKey());
            }
            EvictionListener<V> evictionListener = evictionListeners.get(notification.getKey().segmentKey);
            // only notify when it was not evicted explicitly (when a entry was deleted)
            // otherwise the prePassivate will run
            if (evictionListener != null && notification.wasEvicted()) {
                evictionListener.onEvicted(notification.getValue());
            }
        }
    }
}