All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgroups.blocks.Cache Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 32.0.0.Final
Show newest version
package org.jgroups.blocks;

import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.annotations.Experimental;
import org.jgroups.annotations.Unsupported;
import org.jgroups.annotations.ManagedAttribute;
import org.jgroups.annotations.ManagedOperation;
import org.jgroups.util.Util;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.*;
import java.io.*;

/**
 * Simple cache which maintains keys and value. A reaper can be enabled which periodically evicts expired entries.
 * Also, when the cache is configured to be bounded, entries in excess of the max size will be evicted on put().
 * @author Bela Ban
 */
@Experimental
@Unsupported
public class Cache {
    private static final Log log=LogFactory.getLog(Cache.class);
    private final ConcurrentMap> map=Util.createConcurrentMap();
    private ScheduledThreadPoolExecutor timer=new ScheduledThreadPoolExecutor(1);
    private Future task=null;
    private final AtomicBoolean is_reaping=new AtomicBoolean(false);

    private Set change_listeners=new HashSet<>();

    /** The maximum number of keys, When this value is exceeded we evict older entries, until we drop below this 
     * mark again. This effectively maintains a bounded cache. A value of 0 means don't bound the cache.
     */
    @ManagedAttribute(writable=true)
    private int max_num_entries=0;

    public int getMaxNumberOfEntries() {
        return max_num_entries;
    }

    public void setMaxNumberOfEntries(int max_num_entries) {
        this.max_num_entries=max_num_entries;
    }

    public void addChangeListener(ChangeListener l) {
        change_listeners.add(l);
    }

    public void removeChangeListener(ChangeListener l) {
        change_listeners.remove(l);
    }

    @ManagedAttribute
    public int getSize() {
        return map.size();
    }

    @ManagedAttribute
    public boolean isReapingEnabled() {
        return task != null && !task.isCancelled();
    }

    /** Runs the reaper every interval ms, evicts expired items */
    @ManagedOperation
    public void enableReaping(long interval) {
        if(task != null)
            task.cancel(false);
        task=timer.scheduleWithFixedDelay(new Reaper(), 0, interval, TimeUnit.MILLISECONDS);
    }

    @ManagedOperation
    public void disableReaping() {
        if(task != null) {
            task.cancel(false);
            task=null;
        }
    }

    @ManagedOperation
    public void start() {
        if(timer == null)
            timer=new ScheduledThreadPoolExecutor(1);
    }

    @ManagedOperation
    public void stop() {
        if(timer != null)
            timer.shutdown();
        timer=null;
    }

    /**
     *
     * @param key
     * @param val
     * @param caching_time Number of milliseconds to keep an entry in the cache. -1 means don't cache (if reaping
     * is enabled, we'll evict an entry with -1 caching time), 0 means never evict. In the latter case, we can still
     * evict an entry with 0 caching time: when we have a bounded cache, we evict in order of insertion no matter
     * what the caching time is.
     */
    @ManagedOperation
    public V put(K key, V val, long caching_time) {
        if(log.isTraceEnabled())
            log.trace("put(" + key + ", " + val + ", " + caching_time + ")");
        Value value=new Value<>(val, caching_time);
        Value retval=map.put(key, value);

        if(max_num_entries > 0 && map.size() > max_num_entries) {
            boolean rc=is_reaping.compareAndSet(false, true);
            if(rc) {
                if(log.isTraceEnabled())
                    log.trace("reaping: max_num_entries=" + max_num_entries + ", size=" + map.size());
                timer.execute(new Runnable() {
                    public void run() {
                        if(max_num_entries > 0) {
                            try {
                                if(map.size() > max_num_entries) {
                                    evict(); // see if we can gracefully evict expired items
                                }
                                if(map.size() > max_num_entries) {
                                    // still too many entries: now evict entries based on insertion time: oldest first
                                    int diff=map.size() - max_num_entries; // we have to evict diff entries
                                    SortedMap tmp=new TreeMap<>();
                                    for(Map.Entry> entry: map.entrySet()) {
                                        tmp.put(entry.getValue().insertion_time, entry.getKey());
                                    }

                                    Collection vals=tmp.values();
                                    for(K k: vals) {
                                        if(diff-- > 0) {
                                            Value v=map.remove(k);
                                            if(log.isTraceEnabled())
                                                log.trace("evicting " + k + ": " + v.value);
                                        }
                                        else
                                            break;
                                    }
                                }
                                if(log.isTraceEnabled())
                                    log.trace("done reaping (size=" + map.size() + ")");
                            }
                            finally {
                                is_reaping.set(false);
                            }
                        }
                    }
                });
            }
        }

        return retval != null? retval.value : null;
    }

    @ManagedOperation
    public V get(K key) {
        if(log.isTraceEnabled())
            log.trace("get(" + key + ")");
        Value val=map.get(key);
        if(val == null)
            return null;
        if(val.timeout == -1 ||
                (val.timeout > 0 && val.timeout < System.currentTimeMillis())) {
            map.remove(key);
            return null;
        }
        return val.value;
    }

    /**
     * This method should not be used to add or remove elements ! It was just added because ReplCacheDemo
     * requires it for its data model
     * @return
     */
    public ConcurrentMap> getInternalMap() {
        return map;
    }

    public Value getEntry(K key) {
        if(log.isTraceEnabled())
            log.trace("getEntry(" + key + ")");
        return map.get(key);
    }

    public V remove(K key) {
        if(log.isTraceEnabled())
            log.trace("remove(" + key + ")");
        Value val=map.remove(key);
        return val != null? val.value : null;
    }

    public Set>> entrySet() {
        return map.entrySet();
    }

    @ManagedOperation
    public String toString() {
        StringBuilder sb=new StringBuilder();
        for(Map.Entry> entry: map.entrySet()) {
            Value val=entry.getValue();
            sb.append(entry.getKey()).append(": ").append(entry.getValue().getValue());
            sb.append(" (expiration_time: ");
            long expiration_time=val.getTimeout();
            if(expiration_time <= 0)
                sb.append(expiration_time);
            else {
                sb.append(new Date(expiration_time));
            }
            sb.append(")\n");
        }
        return sb.toString();
    }


    public String dump() {
        StringBuilder sb=new StringBuilder();
        for(Map.Entry> entry: map.entrySet()) {
            sb.append(entry.getKey()).append(": ");
            V val=entry.getValue().getValue();
            if(val != null) {
                if(val instanceof byte[])
                    sb.append(" (" + ((byte[])val).length).append(" bytes)");
                else
                    sb.append(val);
            }
            sb.append("\n");
        }
        return sb.toString();
    }

    private void evict() {
        boolean evicted=false;
        for(Iterator>> it=map.entrySet().iterator(); it.hasNext();) {
            Map.Entry> entry=it.next();
            Value val=entry.getValue();
            if(val != null) {
                if(val.timeout == -1 || (val.timeout > 0 && System.currentTimeMillis() > val.insertion_time + val.timeout)) {
                    if(log.isTraceEnabled())
                        log.trace("evicting " + entry.getKey() + ": " + entry.getValue().value);
                    it.remove();
                    evicted=true;
                }
            }
        }
        if(evicted)
            notifyChangeListeners();
    }

    private void notifyChangeListeners() {
        for(ChangeListener l: change_listeners) {
            try {
                l.changed();
            }
            catch(Throwable t) {
                log.error("failed notifying change listener", t);
            }
        }
    }

    

    public static class Value implements Externalizable {
        private V value;

        private long insertion_time=System.currentTimeMillis();
        
        /** When the value can be reaped (in ms) */
        private transient long timeout;
        private static final long serialVersionUID=-3445944261826378608L;


        public Value(V value, long timeout) {
            this.value=value;
            this.timeout=timeout;
        }

        public Value() {
        }

        public V getValue() {return value;}
        public long getInsertionTime() {return insertion_time;}
        public long getTimeout() {return timeout;}

        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeLong(timeout);
            out.writeObject(value);
        }

        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            insertion_time=System.currentTimeMillis();
            timeout=in.readLong();
            value=(V)in.readObject();
        }
    }
    

    private class Reaper implements Runnable {

        public void run() {
            evict();
        }
    }

    public interface ChangeListener {
        void changed();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy