ca.exprofesso.guava.jcache.GuavaCache.java Source code

Java tutorial

Introduction

Here is the source code for ca.exprofesso.guava.jcache.GuavaCache.java

Source

/*
 * Copyright 2016 ExProfesso.
 *
 * 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 ca.exprofesso.guava.jcache;

import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.cache.CacheException;
import javax.cache.CacheManager;
import javax.cache.configuration.CacheEntryListenerConfiguration;
import javax.cache.configuration.CompleteConfiguration;
import javax.cache.configuration.Configuration;
import javax.cache.configuration.Factory;
import javax.cache.event.CacheEntryCreatedListener;
import javax.cache.event.CacheEntryEvent;
import javax.cache.event.CacheEntryExpiredListener;
import javax.cache.event.CacheEntryListener;
import javax.cache.event.CacheEntryListenerException;
import javax.cache.event.CacheEntryRemovedListener;
import javax.cache.event.CacheEntryUpdatedListener;
import javax.cache.event.EventType;
import javax.cache.expiry.Duration;
import javax.cache.expiry.ExpiryPolicy;
import javax.cache.expiry.ModifiedExpiryPolicy;
import javax.cache.expiry.TouchedExpiryPolicy;
import javax.cache.integration.CacheLoader;
import javax.cache.integration.CompletionListener;
import javax.cache.processor.EntryProcessor;
import javax.cache.processor.EntryProcessorException;
import javax.cache.processor.EntryProcessorResult;
import javax.management.MBeanException;
import javax.management.ObjectName;
import javax.management.OperationsException;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheBuilderSpec;
import com.google.common.cache.CacheStats;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.collect.Sets;

public class GuavaCache<K, V> implements javax.cache.Cache<K, V>, RemovalListener<K, V> {
    private final String cacheName;
    private final CompleteConfiguration<K, V> configuration;
    private final CacheManager cacheManager;

    private final Cache<K, V> cache;
    private final ConcurrentMap<K, V> view;

    private final Set<CacheEntryListenerConfiguration<K, V>> cacheEntryListenerConfigurations;

    private final AtomicBoolean closed = new AtomicBoolean();

    public GuavaCache(String cacheName, CompleteConfiguration<K, V> configuration, CacheManager cacheManager) {
        this.cacheName = cacheName;
        this.configuration = configuration;
        this.cacheManager = cacheManager;

        String properties = cacheManager.getProperties().toString();

        CacheBuilderSpec cacheBuilderSpec = CacheBuilderSpec
                .parse(properties.substring(1, properties.length() - 1));

        CacheBuilder cacheBuilder = CacheBuilder.from(cacheBuilderSpec);

        ExpiryPolicy expiryPolicy = configuration.getExpiryPolicyFactory().create();

        if (expiryPolicy instanceof ModifiedExpiryPolicy) // == Guava expire after write
        {
            Duration d = expiryPolicy.getExpiryForUpdate();

            cacheBuilder.expireAfterWrite(d.getDurationAmount(), d.getTimeUnit());
        } else if (expiryPolicy instanceof TouchedExpiryPolicy) // == Guava expire after access
        {
            Duration d = expiryPolicy.getExpiryForAccess();

            cacheBuilder.expireAfterAccess(d.getDurationAmount(), d.getTimeUnit());
        }

        this.cacheEntryListenerConfigurations = Sets
                .newHashSet(configuration.getCacheEntryListenerConfigurations());

        if (!this.cacheEntryListenerConfigurations.isEmpty()) {
            cacheBuilder = cacheBuilder.removalListener(this);
        }

        if (configuration.isManagementEnabled()) {
            GuavaCacheMXBean bean = new GuavaCacheMXBean(this);

            try {
                ManagementFactory.getPlatformMBeanServer().registerMBean(bean,
                        new ObjectName(bean.getObjectName()));
            } catch (OperationsException | MBeanException e) {
                throw new CacheException(e);
            }
        }

        if (configuration.isStatisticsEnabled()) {
            GuavaCacheStatisticsMXBean bean = new GuavaCacheStatisticsMXBean(this);

            try {
                ManagementFactory.getPlatformMBeanServer().registerMBean(bean,
                        new ObjectName(bean.getObjectName()));
            } catch (OperationsException | MBeanException e) {
                throw new CacheException(e);
            }

            cacheBuilder.recordStats();
        }

        if (configuration.isReadThrough()) {
            Factory<CacheLoader<K, V>> factory = configuration.getCacheLoaderFactory();

            this.cache = (Cache<K, V>) cacheBuilder.build(new GuavaCacheLoader<>(factory.create()));
        } else {
            this.cache = (Cache<K, V>) cacheBuilder.build();
        }

        this.view = cache.asMap();
    }

    @Override
    public V get(K key) {
        checkState();

        if (key == null) {
            throw new NullPointerException();
        }

        if (configuration.isReadThrough()) {
            try {
                return ((LoadingCache<K, V>) cache).get(key);
            } catch (ExecutionException e) {
                throw new CacheException(e);
            }
        }

        return cache.getIfPresent(key);
    }

    @Override
    public Map<K, V> getAll(Set<? extends K> keys) {
        checkState();

        if (keys == null || keys.contains(null)) {
            throw new NullPointerException();
        }

        if (configuration.isReadThrough()) {
            try {
                return ((LoadingCache<K, V>) cache).getAll(keys);
            } catch (ExecutionException e) {
                throw new CacheException(e);
            }
        }

        return cache.getAllPresent(keys);
    }

    @Override
    public boolean containsKey(K key) {
        checkState();

        if (key == null) {
            throw new NullPointerException();
        }

        return view.containsKey(key);
    }

    @Override
    public void loadAll(final Set<? extends K> keys, final boolean replaceExistingValues,
            final CompletionListener cl) {
        checkState();

        if (keys == null || cl == null) {
            throw new NullPointerException();
        }

        ExecutorService executorService = Executors.newSingleThreadExecutor();

        executorService.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    if (cache instanceof LoadingCache) {
                        LoadingCache<K, V> loadingCache = (LoadingCache<K, V>) cache;

                        for (K key : keys) {
                            if (!view.containsKey(key)) {
                                loadingCache.get(key);
                            } else if (replaceExistingValues) {
                                loadingCache.refresh(key);
                            }
                        }
                    }
                } catch (ExecutionException e) {
                    cl.onException(e);
                } finally {
                    cl.onCompletion();
                }
            }
        });
    }

    @Override
    public void put(K key, V value) {
        checkState();

        if (key == null || value == null) {
            throw new NullPointerException();
        }

        cache.put(key, value);
    }

    @Override
    public V getAndPut(K key, V value) {
        checkState();

        if (key == null || value == null) {
            throw new NullPointerException();
        }

        return view.put(key, value);
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> map) {
        checkState();

        if (map == null || map.containsKey(null) || map.containsValue(null)) {
            throw new NullPointerException();
        }

        view.putAll(map);
    }

    @Override
    public boolean putIfAbsent(K key, V value) {
        checkState();

        if (key == null || value == null) {
            throw new NullPointerException();
        }

        return (view.putIfAbsent(key, value) == null);
    }

    @Override
    public boolean remove(K key) {
        checkState();

        if (key == null) {
            throw new NullPointerException();
        }

        return (view.remove(key) != null);
    }

    @Override
    public boolean remove(K key, V oldValue) {
        checkState();

        if (key == null || oldValue == null) {
            throw new NullPointerException();
        }

        return view.remove(key, oldValue);
    }

    @Override
    public V getAndRemove(K key) {
        checkState();

        if (key == null) {
            throw new NullPointerException();
        }

        return view.remove(key);
    }

    @Override
    public boolean replace(K key, V oldValue, V newValue) {
        checkState();

        if (key == null || oldValue == null || newValue == null) {
            throw new NullPointerException();
        }

        return view.replace(key, oldValue, newValue);
    }

    @Override
    public boolean replace(K key, V value) {
        checkState();

        if (key == null || value == null) {
            throw new NullPointerException();
        }

        return (view.replace(key, value) != null);
    }

    @Override
    public V getAndReplace(K key, V value) {
        checkState();

        if (key == null || value == null) {
            throw new NullPointerException();
        }

        return view.replace(key, value);
    }

    @Override
    public void removeAll(Set<? extends K> keys) {
        checkState();

        if (keys == null || keys.contains(null)) {
            throw new NullPointerException();
        }

        cache.invalidateAll(keys);
    }

    @Override
    public void removeAll() {
        checkState();

        cache.invalidateAll();
    }

    @Override
    public void clear() {
        checkState();

        view.clear();
    }

    @Override
    public <C extends Configuration<K, V>> C getConfiguration(Class<C> clazz) {
        return (C) configuration;
    }

    @Override
    public <T> T invoke(K key, EntryProcessor<K, V, T> entryProcessor, Object... arguments)
            throws EntryProcessorException {
        checkState();

        if (key == null || entryProcessor == null) {
            throw new NullPointerException();
        }

        V value = get(key);

        GuavaMutableEntry<K, V> entry = new GuavaMutableEntry<>(key, value);

        T t = entryProcessor.process(entry, arguments);

        if (entry.isUpdated()) {
            replace(key, entry.getValue());
        }

        if (entry.isRemoved()) {
            remove(key);
        }

        return t;
    }

    @Override
    public <T> Map<K, EntryProcessorResult<T>> invokeAll(Set<? extends K> keys,
            EntryProcessor<K, V, T> entryProcessor, Object... arguments) {
        Map<K, EntryProcessorResult<T>> results = new HashMap<>();

        for (K key : keys) {
            results.put(key, new GuavaEntryProcessorResult<>(invoke(key, entryProcessor, arguments)));
        }

        return results;
    }

    @Override
    public String getName() {
        return cacheName;
    }

    @Override
    public CacheManager getCacheManager() {
        return cacheManager;
    }

    @Override
    public void close() {
        if (closed.compareAndSet(false, true)) {
            cache.invalidateAll();
            cache.cleanUp();

            ((GuavaCacheManager) cacheManager).close(this);
        }
    }

    @Override
    public boolean isClosed() {
        return closed.get();
    }

    @Override
    public <T> T unwrap(Class<T> clazz) {
        if (!clazz.isAssignableFrom(getClass())) {
            throw new IllegalArgumentException();
        }

        return clazz.cast(this);
    }

    @Override
    public void registerCacheEntryListener(CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void deregisterCacheEntryListener(
            CacheEntryListenerConfiguration<K, V> cacheEntryListenerConfiguration) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Iterator<Entry<K, V>> iterator() {
        checkState();

        List<javax.cache.Cache.Entry<K, V>> list = new ArrayList<>();

        for (final Map.Entry<K, V> entry : view.entrySet()) {
            list.add(new javax.cache.Cache.Entry<K, V>() {
                @Override
                public K getKey() {
                    return entry.getKey();
                }

                @Override
                public V getValue() {
                    return entry.getValue();
                }

                @Override
                public <T> T unwrap(Class<T> clazz) {
                    return clazz.cast(entry);
                }
            });
        }

        return Collections.unmodifiableList(list).iterator();
    }

    @Override
    public void onRemoval(RemovalNotification<K, V> notification) {
        switch (notification.getCause()) {
        case EXPIRED:
            notifyListeners(new GuavaCacheEntryEvent<>(this, EventType.EXPIRED, notification));
            break;

        case EXPLICIT:
            notifyListeners(new GuavaCacheEntryEvent<>(this, EventType.REMOVED, notification));
            break;

        case REPLACED:
            notifyListeners(new GuavaCacheEntryEvent<>(this, EventType.UPDATED, notification));
            break;
        }
    }

    public void cleanUp() {
        cache.cleanUp();
    }

    public long size() {
        return cache.size();
    }

    public CacheStats stats() {
        return cache.stats();
    }

    private void checkState() {
        if (isClosed()) {
            throw new IllegalStateException("This cache is closed!");
        }
    }

    private void notifyListeners(CacheEntryEvent<K, V> event) {
        for (CacheEntryListenerConfiguration<K, V> listenerConfiguration : cacheEntryListenerConfigurations) {
            boolean invokeListener = true;

            if (listenerConfiguration.getCacheEntryEventFilterFactory() != null) {
                invokeListener = listenerConfiguration.getCacheEntryEventFilterFactory().create().evaluate(event);
            }

            if (invokeListener) {
                CacheEntryListener<?, ?> cel = listenerConfiguration.getCacheEntryListenerFactory().create();

                switch (event.getEventType()) {
                case CREATED:
                    if (cel instanceof CacheEntryCreatedListener) {
                        throw new CacheEntryListenerException("Not supported!");
                    }
                    break;

                case EXPIRED:
                    if (cel instanceof CacheEntryExpiredListener) {
                        ((CacheEntryExpiredListener) cel).onExpired(Sets.newHashSet(event));
                    }
                    break;

                case REMOVED:
                    if (cel instanceof CacheEntryRemovedListener) {
                        ((CacheEntryRemovedListener) cel).onRemoved(Sets.newHashSet(event));
                    }
                    break;

                case UPDATED:
                    if (cel instanceof CacheEntryUpdatedListener) {
                        ((CacheEntryUpdatedListener) cel).onUpdated(Sets.newHashSet(event));
                    }
                    break;
                }
            }
        }
    }
}