org.flite.cach3.aop.LogicalCacheImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.flite.cach3.aop.LogicalCacheImpl.java

Source

/*
 * Copyright (c) 2011-2013 Flite, Inc
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package org.flite.cach3.aop;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.flite.cach3.annotations.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.*;

import java.security.InvalidParameterException;
import java.util.*;
import java.util.concurrent.TimeUnit;

public class LogicalCacheImpl implements LogicalCacheIF, InitializingBean {
    private static final Logger LOG = LoggerFactory.getLogger(LogicalCacheImpl.class);

    /*default*/ static final Set<Duration> DURATION_SET = EnumSet.complementOf(EnumSet.of(Duration.UNDEFINED));
    private Cache<String, Duration> nanny;
    private Map<Duration, Cache<String, Object>> caches = new HashMap<Duration, Cache<String, Object>>(10);

    public void afterPropertiesSet() throws Exception {
        this.nanny = CacheBuilder.newBuilder().maximumSize(10000).expireAfterWrite(5, TimeUnit.MINUTES).build();

        // TODO: Refactor to use a configurable set of cache sizes.
        for (final Duration duration : DURATION_SET) {
            final Cache<String, Object> single = CacheBuilder.newBuilder().maximumSize(1000)
                    .expireAfterWrite(duration.getMillis(), TimeUnit.MILLISECONDS).build();
            caches.put(duration, single);
        }
    }

    /**
     * Returns only the id & object pairs that are actually in the cache.
     * @param ids
     * @param duration
     * @return
     */
    public Map<String, Object> getBulk(Collection<String> ids, Duration duration) {
        if (duration == null || Duration.UNDEFINED == duration) {
            throw new InvalidParameterException("UNDEFINED is not an allowed value");
        }
        //        warnOfDuplication(ids, duration);

        final Map<String, Object> partial = caches.get(duration).getAllPresent(ids);
        if (partial.size() == 0) {
            return partial;
        }

        final Map<String, Object> results = new HashMap<String, Object>(partial.size());
        for (Map.Entry<String, Object> entry : partial.entrySet()) {
            final Object value = entry.getValue() instanceof PertinentNegativeNull ? null : entry.getValue();
            results.put(entry.getKey(), value);
        }
        return results;
    }

    public void invalidateBulk(final Collection<String> ids) {
        if (ids == null || ids.size() == 0) {
            return;
        }

        for (final Cache<String, Object> cache : caches.values()) {
            if (cache != null) {
                cache.invalidateAll(ids);
            }
        }
    }

    public void setBulk(Map<String, Object> contents, Duration duration) {
        if (duration == null || Duration.UNDEFINED == duration) {
            throw new InvalidParameterException("UNDEFINED is not an allowed value");
        }
        if (contents.size() == 0) {
            return;
        }

        // Only do this on SET, because we want the GET to be super-optimized.
        warnOfDuplication(contents.keySet(), duration);

        final Cache<String, Object> cache = caches.get(duration);
        for (final Map.Entry<String, Object> entry : contents.entrySet()) {
            final String key = entry.getKey();
            final Object value = entry.getValue() == null ? new PertinentNegativeNull() : entry.getValue();
            //            System.out.println("Key: " + key + "; Value: " + value.toString() + "; Duration: " + duration);
            nanny.put(key, duration);
            cache.put(key, value);
        }
    }

    /*default*/ static final String WARNING = "WARNING! Conflicting expiration durations for a given key will result in "
            + "duplicate cache entries. Evidence of conflicts found for the given key(s):";

    /*default*/ String warnOfDuplication(final Collection<String> ids, final Duration duration) {
        if (ids == null || ids.size() < 1) {
            return null;
        }
        final Set<String> results = checkIdsForDuplication(ids, duration);
        if (results.size() == 0) {
            return null;
        }
        final StringBuilder sb = new StringBuilder(WARNING);
        for (final String single : results) {
            sb.append('\n').append(single);
        }
        final String resultString = sb.toString();
        LOG.warn(resultString);
        return resultString;
    }

    /*default*/ Set<String> checkIdsForDuplication(final Collection<String> ids, final Duration duration) {
        if (ids == null || ids.size() < 1) {
            return Collections.EMPTY_SET;
        }
        final Map<String, Duration> history = nanny.getAllPresent(ids);
        if (history == null || history.size() < 1) {
            return Collections.EMPTY_SET;
        }

        final Set<String> results = new HashSet<String>(ids.size());
        for (final Map.Entry<String, Duration> entry : history.entrySet()) {
            final Duration previous = entry.getValue();
            if (duration != previous) {
                results.add(duration.name() + " vs " + previous.name() + ": " + entry.getKey());
            }
        }

        return results;
    }
}