package com.github.ddth.cacheadapter.cacheimpl.redis;

import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.ddth.cacheadapter.ICacheFactory;
import com.github.ddth.commons.redis.JedisConnector;

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol;

/**
 * Clustered Redis implementation of
 * {@link ICacheFactory} that creates {@link ClusteredRedisCache} objects.
 *
 * @author Thanh Ba Nguyen <btnguyen2k@gmail.com>
 * @since 0.4.1
 */
public class ClusteredRedisCacheFactory extends BaseRedisCacheFactory {

    private final Logger LOGGER = LoggerFactory.getLogger(ClusteredRedisCacheFactory.class);

    public final static int DEFAULT_MAX_ATTEMPTS = 3;

    /**
     * Creates a new {@link JedisCluster}, with default timeout.
     *
     * @param hostsAndPorts
     *            format {@code host1:port1,host2:port2...}
     * @param password
     * @return
     */
    public static JedisCluster newJedisCluster(String hostsAndPorts, String password) {
        return newJedisCluster(hostsAndPorts, password, Protocol.DEFAULT_TIMEOUT);
    }

    /**
     * Creates a new {@link JedisCluster}.
     *
     * @param hostsAndPorts
     *            format {@code host1:port1,host2:port2...}
     * @param password
     * @param timeoutMs
     * @return
     */
    public static JedisCluster newJedisCluster(String hostsAndPorts, String password,
            long timeoutMs) {
        final int maxTotal = Runtime.getRuntime().availableProcessors();
        final int maxIdle = maxTotal / 2;

        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(maxTotal);
        poolConfig.setMinIdle(1);
        poolConfig.setMaxIdle(maxIdle > 0 ? maxIdle : 1);
        poolConfig.setMaxWaitMillis(timeoutMs + 1000);
        // poolConfig.setTestOnBorrow(true);
        poolConfig.setTestWhileIdle(true);

        Set<HostAndPort> clusterNodes = new HashSet<>();
        String[] hapList = hostsAndPorts.split("[,;\\s]+");
        for (String hostAndPort : hapList) {
            String[] tokens = hostAndPort.split(":");
            String host = tokens.length > 0 ? tokens[0] : Protocol.DEFAULT_HOST;
            int port = tokens.length > 1 ? Integer.parseInt(tokens[1]) : Protocol.DEFAULT_PORT;
            clusterNodes.add(new HostAndPort(host, port));
        }

        JedisCluster jedisCluster = new JedisCluster(clusterNodes, (int) timeoutMs, (int) timeoutMs,
                DEFAULT_MAX_ATTEMPTS, password, poolConfig);
        return jedisCluster;
    }

    private String redisHostsAndPorts = Protocol.DEFAULT_HOST + ":" + Protocol.DEFAULT_PORT;

    /**
     * Redis' hosts and ports scheme (format
     * {@code host1:port1,host2:port2,host3:port3}).
     *
     * @return
     */
    public String getRedisHostsAndPorts() {
        return redisHostsAndPorts;
    }

    /**
     * Redis' hosts and ports scheme (format
     * {@code host1:port1,host2:port2,host3:port3}).
     *
     * @param redisHostsAndPorts
     * @return
     */
    public ClusteredRedisCacheFactory setRedisHostsAndPorts(String redisHostsAndPorts) {
        this.redisHostsAndPorts = redisHostsAndPorts;
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ClusteredRedisCacheFactory init() {
        super.init();

        if (getJedisConnector() == null) {
            try {
                JedisConnector jedisConnector = new JedisConnector();
                jedisConnector.setRedisHostsAndPorts(redisHostsAndPorts)
                        .setRedisPassword(getRedisPassword()).init();
                setJedisConnector(jedisConnector, true);
            } catch (Exception e) {
                LOGGER.warn(e.getMessage(), e);
            }
        }

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected ClusteredRedisCache createCacheInternal(String name, long capacity,
            long expireAfterWrite, long expireAfterAccess, Properties cacheProps) {
        ClusteredRedisCache cache = new ClusteredRedisCache(keyMode);
        cache.setName(name).setCapacity(capacity).setExpireAfterAccess(expireAfterAccess)
                .setExpireAfterWrite(expireAfterWrite).setCacheProperties(cacheProps);
        cache.setRedisHostsAndPorts(redisHostsAndPorts).setRedisPassword(getRedisPassword());
        cache.setJedisConnector(getJedisConnector());
        return cache;
    }

}


package com.github.ddth.cacheadapter.cacheimpl.memcached;

import java.util.concurrent.TimeoutException;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.ddth.cacheadapter.AbstractCacheFactory;
import com.github.ddth.cacheadapter.AbstractSerializingCache;
import com.github.ddth.cacheadapter.CacheEntry;
import com.github.ddth.cacheadapter.CacheException;
import com.github.ddth.cacheadapter.ICache;
import com.github.ddth.cacheadapter.ICacheEntrySerializer;
import com.github.ddth.cacheadapter.ICacheLoader;

import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.MemcachedClientCallable;
import net.rubyeye.xmemcached.exception.MemcachedException;

/**
 * Memcached implementation of {@link ICache}
 * using XMemcached
 * library.
 *
 * @author Thanh Ba Nguyen <btnguyen2k@gmail.com>
 * @since 0.6.0
 */
public class XMemcachedCache extends AbstractSerializingCache {

    private final Logger LOGGER = LoggerFactory.getLogger(XMemcachedCache.class);

    /**
     * Defines how cache key/entries are grouped together.
     */
    public enum KeyMode {
        /**
         * Cache entries are grouped into namespaces: cache keys are prefixed
         * with cache's name. So more than one {@link XMemcachedCache} instances
         * can share one memcached server.
         */
        NAMESPACE(0),

        /**
         * Assuming the whole memcached server is dedicated to the
         * {@link XMemcachedCache} instance. Cache keys are kept as-is.
         */
        MONOPOLISTIC(1),

        /**
         * Cache entries are grouped into namespaces: using XMemcached's
         * namespace mechanism.
         */
        XNAMESPACE(2);

        public final int value;

        KeyMode(int value) {
            this.value = value;
        }
    }

    /**
     * To override the {@link #keyMode} setting.
     *
     * @since 0.6.1
     */
    public final static String CACHE_PROP_KEY_MODE = "cache.key_mode";

    /**
     * To override the {@link #setMemcachedHostsAndPorts(String)} setting.
     *
     * @since 0.6.1
     */
    public final static String CACHE_PROP_MEMCACHED_HOSTS_AND_PORTS = "cache.hosts_and_ports";

    /**
     * To override the {@link #timeToLiveSeconds} setting.
     *
     * @since 0.6.1
     */
    public final static String CACHE_PROP_TTL_SECONDS = "cache.ttl_seconds";

    private KeyMode keyMode;

    private XMemcachedConnector memcachedConnector;
    private boolean myOwnMemcachedConnector = true;
    private String memcachedHostsAndPorts = "localhost:11211";
    private long timeToLiveSeconds = -1;

    public XMemcachedCache(KeyMode keyMode) {
        super();
        this.keyMode = keyMode;
    }

    public XMemcachedCache(KeyMode keyMode, String name, AbstractCacheFactory cacheFactory,
            ICacheEntrySerializer cacheEntrySerializer) {
        super(name, cacheFactory, cacheEntrySerializer);
        this.keyMode = keyMode;
    }

    public XMemcachedCache(KeyMode keyMode, String name, AbstractCacheFactory cacheFactory,
            long capacity, ICacheEntrySerializer cacheEntrySerializer) {
        super(name, cacheFactory, capacity, cacheEntrySerializer);
        this.keyMode = keyMode;
    }

    public XMemcachedCache(KeyMode keyMode, String name, AbstractCacheFactory cacheFactory,
            long capacity, long expireAfterWrite, long expireAfterAccess,
            ICacheEntrySerializer cacheEntrySerializer) {
        super(name, cacheFactory, capacity, expireAfterWrite, expireAfterAccess,
                cacheEntrySerializer);
        this.keyMode = keyMode;
    }

    public XMemcachedCache(KeyMode keyMode, String name, AbstractCacheFactory cacheFactory,
            long capacity, long expireAfterWrite, long expireAfterAccess, ICacheLoader cacheLoader,
            ICacheEntrySerializer cacheEntrySerializer) {
        super(name, cacheFactory, capacity, expireAfterWrite, expireAfterAccess, cacheLoader,
                cacheEntrySerializer);
        this.keyMode = keyMode;
    }

    public XMemcachedCache(KeyMode keyMode, String name, AbstractCacheFactory cacheFactory,
            long capacity, long expireAfterWrite, long expireAfterAccess,
            ICacheLoader cacheLoader) {
        super(name, cacheFactory, capacity, expireAfterWrite, expireAfterAccess, cacheLoader);
        this.keyMode = keyMode;
    }

    public XMemcachedCache(KeyMode keyMode, String name, AbstractCacheFactory cacheFactory,
            long capacity, long expireAfterWrite, long expireAfterAccess) {
        super(name, cacheFactory, capacity, expireAfterWrite, expireAfterAccess);
        this.keyMode = keyMode;
    }

    public XMemcachedCache(KeyMode keyMode, String name, AbstractCacheFactory cacheFactory,
            long capacity) {
        super(name, cacheFactory, capacity);
        this.keyMode = keyMode;
    }

    public XMemcachedCache(KeyMode keyMode, String name, AbstractCacheFactory cacheFactory) {
        super(name, cacheFactory);
        this.keyMode = keyMode;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isCapacitySupported() {
        return false;
    }

    /**
     *
     * @return
     * @see KeyMode
     */
    public KeyMode getKeyMode() {
        return keyMode;
    }

    /**
     * Get Memcached' hosts and ports scheme (format
     * {@code host1:port1,host2:port2}).
     *
     * @return
     */
    public String getMemcachedHostsAndPorts() {
        return memcachedHostsAndPorts;
    }

    /**
     * Set Memcached' hosts and ports scheme (format
     * {@code host1:port1,host2:port2}).
     *
     * @param memcachedHostsAndPorts
     * @return
     */
    public XMemcachedCache setMemcachedHostsAndPorts(String memcachedHostsAndPorts) {
        this.memcachedHostsAndPorts = memcachedHostsAndPorts;
        return this;
    }

    /**
     * @return
     */
    protected MemcachedClient getMemcachedClient() {
        return memcachedConnector.getMemcachedClient();
    }

    /**
     * @return
     * @since 0.6.3
     */
    public XMemcachedConnector getMemcachedConnector() {
        return memcachedConnector;
    }

    /**
     * @param memcachedConnector
     * @return
     * @since 0.6.3
     */
    public XMemcachedCache setMemcachedConnector(XMemcachedConnector memcachedConnector) {
        return setMemcachedConnector(memcachedConnector, false);
    }

    /**
     * Attach a {@link XMemcachedConnector} to this cache.
     *
     * @param memcachedConnector
     * @param setMyOwnMemcachedConnector
     *            set {@link #myOwnMemcachedConnector} to {@code true} or not.
     * @return
     * @since 0.6.3.3
     */
    protected XMemcachedCache setMemcachedConnector(XMemcachedConnector memcachedConnector,
            boolean setMyOwnMemcachedConnector) {
        if (myOwnMemcachedConnector && this.memcachedConnector != null) {
            this.memcachedConnector.close();
        }
        this.memcachedConnector = memcachedConnector;
        myOwnMemcachedConnector = setMyOwnMemcachedConnector;
        return this;
    }

    /**
     * Build and initialize an instance of {@link XMemcachedConnector}.
     *
     * @return
     * @since 0.6.3.3
     */
    @SuppressWarnings("resource")
    protected XMemcachedConnector buildXmemcachedConnector() {
        return new XMemcachedConnector().setMemcachedHostsAndPorts(getMemcachedHostsAndPorts())
                .init();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public XMemcachedCache init() {
        super.init();

        long expireAfterWrite = getExpireAfterWrite();
        long expireAfterAccess = getExpireAfterAccess();
        if (expireAfterAccess > 0 || expireAfterWrite > 0) {
            timeToLiveSeconds = expireAfterAccess > 0 ? expireAfterAccess : expireAfterWrite;
        } else {
            timeToLiveSeconds = -1;
        }

        /*
         * Parse custom property: key-mode
         */
        KeyMode oldKeyMode = this.keyMode;
        try {
            this.keyMode = KeyMode.valueOf(getCacheProperty(CACHE_PROP_KEY_MODE).toUpperCase());
        } catch (Exception e) {
            this.keyMode = oldKeyMode;
            if (getCacheProperty(CACHE_PROP_KEY_MODE) != null) {
                LOGGER.warn(e.getMessage(), e);
            }
        }

        /*
         * Parse custom property: ttl-seconds
         */
        long oldTTL = this.timeToLiveSeconds;
        try {
            this.timeToLiveSeconds = Long.parseLong(getCacheProperty(CACHE_PROP_TTL_SECONDS));
        } catch (Exception e) {
            this.timeToLiveSeconds = oldTTL;
            if (getCacheProperty(CACHE_PROP_TTL_SECONDS) != null) {
                LOGGER.warn(e.getMessage(), e);
            }
        }

        /*
         * Parse custom property: memcached-hosts-and-ports
         */
        String hostsAndPorts = getCacheProperty(CACHE_PROP_MEMCACHED_HOSTS_AND_PORTS);
        if (!StringUtils.isBlank(hostsAndPorts)) {
            this.memcachedHostsAndPorts = hostsAndPorts;
        }

        if (memcachedConnector == null) {
            try {
                memcachedConnector = buildXmemcachedConnector();
            } catch (Exception e) {
                LOGGER.warn(e.getMessage(), e);
            }
            myOwnMemcachedConnector = true;
        }

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void destroy() {
        try {
            super.destroy();
        } finally {
            if (memcachedConnector != null && myOwnMemcachedConnector) {
                try {
                    memcachedConnector.destroy();
                } catch (Exception e) {
                    LOGGER.warn(e.getMessage(), e);
                } finally {
                    memcachedConnector = null;
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long getSize() {
        return -1;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void set(String key, Object entry) {
        if (entry instanceof CacheEntry) {
            CacheEntry ce = (CacheEntry) entry;
            set(key, ce, ce.getExpireAfterWrite(), ce.getExpireAfterAccess());
        } else {
            set(key, entry, getExpireAfterWrite(), getExpireAfterAccess());
        }
    }

    public final static long TTL_NO_CHANGE = 0;
    public final static long TTL_FOREVER = -1;

    protected String calcCacheKeyNamespace(String key) {
        return getName() + ":" + key;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void set(String key, Object entry, long expireAfterWrite, long expireAfterAccess) {
        long ttl = TTL_NO_CHANGE;
        if (!(entry instanceof CacheEntry)) {
            CacheEntry ce = new CacheEntry(key, entry, expireAfterWrite, expireAfterAccess);
            entry = ce;
            ttl = expireAfterAccess > 0 ? expireAfterAccess
                    : (expireAfterWrite > 0 ? expireAfterWrite
                            : (timeToLiveSeconds > 0 ? timeToLiveSeconds : 0));
        } else {
            CacheEntry ce = (CacheEntry) entry;
            ttl = ce.getExpireAfterAccess();
        }
        byte[] _data = serializeCacheEntry((CacheEntry) entry);
        String data = Base64.encodeBase64String(_data);

        final int TTL = (int) ttl;
        switch (keyMode) {
        case MONOPOLISTIC:
        case NAMESPACE: {
            final String KEY = keyMode == KeyMode.MONOPOLISTIC ? key : calcCacheKeyNamespace(key);
            try {
                getMemcachedClient().set(KEY, TTL, data);
            } catch (Exception e) {
                throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
            }
            break;
        }
        case XNAMESPACE: {
            try {
                getMemcachedClient().withNamespace(getName(), new MemcachedClientCallable<Void>() {
                    @Override
                    public Void call(MemcachedClient client)
                            throws MemcachedException, InterruptedException, TimeoutException {
                        client.set(key, TTL, data);
                        return null;
                    }

                });
            } catch (Exception e) {
                throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
            }
            break;
        }
        default:
            throw new IllegalStateException("Invalid key mode: " + keyMode);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void delete(String key) {
        switch (keyMode) {
        case MONOPOLISTIC:
        case NAMESPACE:
            final String KEY = keyMode == KeyMode.MONOPOLISTIC ? key : calcCacheKeyNamespace(key);
            try {
                getMemcachedClient().deleteWithNoReply(KEY);
            } catch (Exception e) {
                throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
            }
            break;
        case XNAMESPACE:
            try {
                getMemcachedClient().withNamespace(getName(), new MemcachedClientCallable<Void>() {
                    @Override
                    public Void call(MemcachedClient client)
                            throws InterruptedException, MemcachedException {
                        client.deleteWithNoReply(key);
                        return null;
                    }

                });
            } catch (Exception e) {
                throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
            }
            break;
        default:
            throw new IllegalStateException("Invalid key mode: " + keyMode);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deleteAll() {
        switch (keyMode) {
        case NAMESPACE:
            throw new CacheException.OperationNotSupportedException(
                    "Key mode[" + keyMode + "] does not support flushall operation.");
        case MONOPOLISTIC:
            try {
                getMemcachedClient().flushAllWithNoReply();
            } catch (Exception e) {
                throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
            }
            break;
        case XNAMESPACE:
            try {
                getMemcachedClient().invalidateNamespace(getName());
            } catch (Exception e) {
                throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
            }
            break;
        default:
            throw new IllegalStateException("Invalid key mode: " + keyMode);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean exists(String key) {
        switch (keyMode) {
        case MONOPOLISTIC:
        case NAMESPACE:
            final String KEY = keyMode == KeyMode.MONOPOLISTIC ? key : calcCacheKeyNamespace(key);
            try {
                return getMemcachedClient().get(KEY) != null;
            } catch (Exception e) {
                throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
            }
        case XNAMESPACE:
            try {
                return getMemcachedClient().withNamespace(getName(),
                        new MemcachedClientCallable<Boolean>() {
                            @Override
                            public Boolean call(MemcachedClient client) throws InterruptedException,
                                    MemcachedException, TimeoutException {
                                return client.get(key) != null;
                            }

                        });
            } catch (Exception e) {
                throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
            }
        default:
            throw new IllegalStateException("Invalid key mode: " + keyMode);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Object internalGet(String key) {
        String _data = null;
        switch (keyMode) {
        case MONOPOLISTIC:
        case NAMESPACE:
            final String KEY = keyMode == KeyMode.MONOPOLISTIC ? key : calcCacheKeyNamespace(key);
            try {
                _data = getMemcachedClient().get(KEY);
            } catch (Exception e) {
                throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
            }
            break;
        case XNAMESPACE:
            try {
                _data = getMemcachedClient().withNamespace(getName(),
                        new MemcachedClientCallable<String>() {
                            @Override
                            public String call(MemcachedClient client) throws MemcachedException,
                                    InterruptedException, TimeoutException {
                                return client.get(key);
                            }

                        });
            } catch (Exception e) {
                throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
            }
            break;
        default:
            throw new IllegalStateException("Invalid key mode: " + keyMode);
        }
        if (_data != null) {
            byte[] data = Base64.decodeBase64(_data);
            CacheEntry ce = deserializeCacheEntry(data);
            if (ce != null && ce.touch()) {
                set(key, ce, ce.getExpireAfterWrite(), ce.getExpireAfterAccess());
            }
            return ce;
        }
        return null;
    }

    public static void main(String[] args) {
        System.out.println(KeyMode.valueOf("NAMESPACE"));
        System.out.println(KeyMode.valueOf("namespace"));
        System.out.println(KeyMode.valueOf("error"));
    }
}


package com.github.ddth.cacheadapter.cacheimpl.redis;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.ddth.cacheadapter.AbstractCacheFactory;
import com.github.ddth.cacheadapter.CacheEntry;
import com.github.ddth.cacheadapter.CacheException;
import com.github.ddth.cacheadapter.ICache;
import com.github.ddth.cacheadapter.ICacheEntrySerializer;
import com.github.ddth.cacheadapter.ICacheLoader;
import com.github.ddth.commons.redis.JedisConnector;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.Protocol;
import redis.clients.util.SafeEncoder;

/**
 * Clustered Redis implementation of
 * {@link ICache}.
 *
 *

 * This Redis-based cache implementation supports all types of {@link KeyMode}.
 *

 *
 * @author Thanh Ba Nguyen <btnguyen2k@gmail.com>
 * @since 0.4.1
 */
public class ClusteredRedisCache extends BaseRedisCache {

    private final Logger LOGGER = LoggerFactory.getLogger(ClusteredRedisCache.class);

    /**
     * To override the {@link #setRedisHostsAndPorts(String)} setting.
     *
     * @since 0.6.1
     */
    public final static String CACHE_PROP_REDIS_HOSTS_AND_PORTS = "cache.hosts_and_ports";

    private String redisHostsAndPorts = Protocol.DEFAULT_HOST + ":" + Protocol.DEFAULT_PORT;

    public ClusteredRedisCache(KeyMode keyMode) {
        super(keyMode);
    }

    public ClusteredRedisCache(KeyMode keyMode, String name, AbstractCacheFactory cacheFactory,
            ICacheEntrySerializer cacheEntrySerializer) {
        super(keyMode, name, cacheFactory, cacheEntrySerializer);
    }

    public ClusteredRedisCache(KeyMode keyMode, String name, AbstractCacheFactory cacheFactory,
            long capacity, ICacheEntrySerializer cacheEntrySerializer) {
        super(keyMode, name, cacheFactory, capacity, cacheEntrySerializer);
    }

    public ClusteredRedisCache(KeyMode keyMode, String name, AbstractCacheFactory cacheFactory,
            long capacity, long expireAfterWrite, long expireAfterAccess,
            ICacheEntrySerializer cacheEntrySerializer) {
        super(keyMode, name, cacheFactory, capacity, expireAfterWrite, expireAfterAccess,
                cacheEntrySerializer);
    }

    public ClusteredRedisCache(KeyMode keyMode, String name, AbstractCacheFactory cacheFactory,
            long capacity, long expireAfterWrite, long expireAfterAccess, ICacheLoader cacheLoader,
            ICacheEntrySerializer cacheEntrySerializer) {
        super(keyMode, name, cacheFactory, capacity, expireAfterWrite, expireAfterAccess,
                cacheLoader, cacheEntrySerializer);
    }

    public ClusteredRedisCache(KeyMode keyMode, String name, AbstractCacheFactory cacheFactory,
            long capacity, long expireAfterWrite, long expireAfterAccess,
            ICacheLoader cacheLoader) {
        super(keyMode, name, cacheFactory, capacity, expireAfterWrite, expireAfterAccess,
                cacheLoader);
    }

    public ClusteredRedisCache(KeyMode keyMode, String name, AbstractCacheFactory cacheFactory,
            long capacity, long expireAfterWrite, long expireAfterAccess) {
        super(keyMode, name, cacheFactory, capacity, expireAfterWrite, expireAfterAccess);
    }

    public ClusteredRedisCache(KeyMode keyMode, String name, AbstractCacheFactory cacheFactory,
            long capacity) {
        super(keyMode, name, cacheFactory, capacity);
    }

    public ClusteredRedisCache(KeyMode keyMode, String name, AbstractCacheFactory cacheFactory) {
        super(keyMode, name, cacheFactory);
    }

    /**
     * Redis' hosts and ports scheme (format
     * {@code host1:port1,host2:port2,host3:port3}).
     *
     * @return
     */
    public String getRedisHostsAndPorts() {
        return redisHostsAndPorts;
    }

    /**
     * Sets Redis' hosts and ports scheme (format
     * {@code host1:port1,host2:port2,host3:port3}).
     *
     * @param redisHostsAndPorts
     * @return
     */
    public ClusteredRedisCache setRedisHostsAndPorts(String redisHostsAndPorts) {
        this.redisHostsAndPorts = redisHostsAndPorts;
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ClusteredRedisCache init() {
        super.init();

        /*
         * Parse custom property: redis-hosts-and-ports
         */
        String hostsAndPorts = getCacheProperty(CACHE_PROP_REDIS_HOSTS_AND_PORTS);
        if (!StringUtils.isBlank(hostsAndPorts)) {
            this.redisHostsAndPorts = hostsAndPorts;
        }

        if (getJedisConnector() == null) {
            try {
                JedisConnector jedisConnector = new JedisConnector();
                jedisConnector.setRedisHostsAndPorts(redisHostsAndPorts).init();
                setJedisConnector(jedisConnector, true);
            } catch (Exception e) {
                LOGGER.warn(e.getMessage(), e);
            }
        }

        return this;
    }

    /**
     *
     * @return
     * @since 0.6.3
     */
    protected JedisCluster getJedis() {
        return getJedisConnector().getJedisCluster();
    }

    /**
     * {@inheritDoc}
     *
     *

     * Only {@link KeyMode#HASH} is supported. This method returns {@code -1}
     * for other key modes.
     *

     */
    @Override
    public long getSize() {
        switch (keyMode) {
        case HASH:
            try {
                return getJedis().hlen(getName());
            } catch (Exception e) {
                throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
            }
        default:
            return -1;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void set(String key, Object entry, long expireAfterWrite, long expireAfterAccess) {
        final String KEY = calcCacheKey(key);
        try {
            JedisCluster jedisCluster = getJedis();
            /*
             * Returns -1 if no expiry, -2 if key does not exist.
             */
            final long currentTTL = jedisCluster.ttl(KEY);
            long ttl = TTL_NO_CHANGE;
            if (!(entry instanceof CacheEntry)) {
                CacheEntry ce = new CacheEntry(key, entry, expireAfterWrite, expireAfterAccess);
                entry = ce;
                ttl = expireAfterAccess > 0 ? expireAfterAccess
                        : (expireAfterWrite > 0 ? expireAfterWrite
                                : (timeToLiveSeconds > 0 ? timeToLiveSeconds : 0));
            } else {
                CacheEntry ce = (CacheEntry) entry;
                ttl = ce.getExpireAfterAccess();
            }
            byte[] data = serializeCacheEntry((CacheEntry) entry);

            // TTL Rules:
            // 1. New item: TTL is calculated as formula(s) above.
            // 2. Existing item:
            // 2.1. If [keyMode=HASH]: extends the current TTL,
            // 2.2. Otherwise, extends the current TTL only when
            // expireAfterAccess >
            // 0
            if (currentTTL >= -1) {
                // existing item
                if (keyMode == KeyMode.HASH)
                    // rule 2.1
                    ttl = currentTTL > 0 ? currentTTL : TTL_NO_CHANGE;
                else
                    // rule 2.2
                    ttl = expireAfterAccess > 0 ? expireAfterAccess : TTL_NO_CHANGE;
            }

            if (keyMode == KeyMode.HASH) {
                jedisCluster.hset(SafeEncoder.encode(getName()), SafeEncoder.encode(KEY), data);
                if (ttl > 0) {
                    jedisCluster.expire(getName(), (int) ttl);
                } else if (ttl == TTL_FOREVER && currentTTL >= -1) {
                    jedisCluster.persist(getName());
                }
            } else {
                if (ttl > 0) {
                    jedisCluster.setex(SafeEncoder.encode(KEY), (int) ttl, data);
                } else {
                    jedisCluster.set(SafeEncoder.encode(KEY), data);
                    if (ttl == TTL_FOREVER) {
                        jedisCluster.persist(KEY);
                    } else if (currentTTL > 0) {
                        jedisCluster.expire(KEY, (int) currentTTL);
                    }
                }
            }
        } catch (Exception e) {
            throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void delete(String key) {
        final String KEY = calcCacheKey(key);
        try {
            if (keyMode == KeyMode.HASH) {
                getJedis().hdel(getName(), KEY);
            } else {
                getJedis().del(KEY);
            }
        } catch (Exception e) {
            throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deleteAll() {
        switch (keyMode) {
        case NAMESPACE:
            throw new CacheException.OperationNotSupportedException(
                    "Key mode[" + keyMode + "] does not support 'deleteAll' operation.");
        case MONOPOLISTIC:
            try {
                getJedis().getClusterNodes().forEach((name, pool) -> {
                    try (Jedis jedis = pool.getResource()) {
                        jedis.flushAll();
                    }
                });
            } catch (Exception e) {
                throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
            }
            break;
        case HASH:
            try {
                getJedis().del(getName());
            } catch (Exception e) {
                throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
            }
            break;
        default:
            throw new IllegalStateException("Invalid key mode: " + keyMode);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean exists(String key) {
        final String KEY = calcCacheKey(key);
        try {
            if (keyMode == KeyMode.HASH) {
                return getJedis().hget(SafeEncoder.encode(getName()),
                        SafeEncoder.encode(KEY)) != null;
            } else {
                return getJedis().get(SafeEncoder.encode(KEY)) != null;
            }
        } catch (Exception e) {
            throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
        }
    }

    /**
     * Refresh TTL of a cache entry.
     *
     * @param key
     * @param ce
     * @since 0.6.2
     */
    protected void refreshTTL(String key, CacheEntry ce) {
        final String KEY = calcCacheKey(key);
        final long TTL = ce.getExpireAfterAccess();
        if (TTL > 0) {
            try {
                if (keyMode == KeyMode.HASH) {
                    getJedis().expire(getName(), (int) TTL);
                } else {
                    getJedis().expire(SafeEncoder.encode(KEY), (int) TTL);
                }
            } catch (Exception e) {
                throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Object internalGet(String key) {
        final String KEY = calcCacheKey(key);
        try {
            byte[] data = keyMode == KeyMode.HASH
                    ? getJedis().hget(SafeEncoder.encode(getName()), SafeEncoder.encode(KEY))
                    : getJedis().get(SafeEncoder.encode(KEY));
            if (data != null) {
                CacheEntry ce = deserializeCacheEntry(data);
                if (ce != null && ce.touch()) {
                    /*
                     * Since v0.6.2: use refreshTTL() instead of set() to save
                     * traffic between client & Redis server
                     */
                    // set(key, ce, ce.getExpireAfterWrite(),
                    // ce.getExpireAfterAccess());
                    refreshTTL(key, ce);
                }
                return ce;
            }
            return null;
        } catch (Exception e) {
            throw e instanceof CacheException ? (CacheException) e : new CacheException(e);
        }
    }
}


package com.github.ddth.cacheadapter.cacheimpl.guava;

import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.ddth.cacheadapter.AbstractCache;
import com.github.ddth.cacheadapter.AbstractCacheFactory;
import com.github.ddth.cacheadapter.CacheEntry;
import com.github.ddth.cacheadapter.CacheException;
import com.github.ddth.cacheadapter.ICacheLoader;
import com.github.ddth.cacheadapter.utils.CacheUtils;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

/**
 * In-memory cache, use
 * Guava as the
 * underlying cache engine.
 *
 * @author Thanh Ba Nguyen <btnguyen2k@gmail.com>
 * @since 0.1.0
 */
public class GuavaCache extends AbstractCache {

    private final Logger LOGGER = LoggerFactory.getLogger(GuavaCache.class);

    /**
     * To override the {@link #setCloneCacheEntries(boolean)} setting.
     *
     * @since 0.6.1
     */
    public final static String CACHE_PROP_CLONE_CACHE_ENTRIES = "cache.clone_cache_entries";

    private LoadingCache<String, Object> cache;
    private boolean cloneCacheEntries = true;

    public GuavaCache() {
    }

    public GuavaCache(String name, AbstractCacheFactory cacheFactory) {
        super(name, cacheFactory);
    }

    public GuavaCache(String name, AbstractCacheFactory cacheFactory, long capacity) {
        super(name, cacheFactory, capacity);
    }

    public GuavaCache(String name, AbstractCacheFactory cacheFactory, long capacity,
            long expireAfterWrite, long expireAfterAccess) {
        super(name, cacheFactory, capacity, expireAfterWrite, expireAfterAccess);
    }

    public GuavaCache(String name, AbstractCacheFactory cacheFactory, long capacity,
            long expireAfterWrite, long expireAfterAccess, ICacheLoader cacheLoader) {
        super(name, cacheFactory, capacity, expireAfterWrite, expireAfterAccess, cacheLoader);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public GuavaCache init() {
        super.init();

        /*
         * Parse custom property: clone-cache-entries
         */
        boolean oldCloneCacheEntries = this.cloneCacheEntries;
        try {
            this.cloneCacheEntries = Boolean
                    .parseBoolean(getCacheProperty(CACHE_PROP_CLONE_CACHE_ENTRIES));
        } catch (Exception e) {
            this.cloneCacheEntries = oldCloneCacheEntries;
            if (getCacheProperty(CACHE_PROP_CLONE_CACHE_ENTRIES) != null) {
                LOGGER.warn(e.getMessage(), e);
            }
        }

        int numProcessores = Runtime.getRuntime().availableProcessors();
        CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder()
                .concurrencyLevel(numProcessores * 2);
        if (getCapacity() > 0) {
            cacheBuilder.maximumSize(getCapacity());
        }

        long expireAfterAccess = getExpireAfterAccess();
        long expireAfterWrite = getExpireAfterWrite();
        if (expireAfterAccess > 0) {
            cacheBuilder.expireAfterAccess(expireAfterAccess, TimeUnit.SECONDS);
        } else if (expireAfterWrite > 0) {
            cacheBuilder.expireAfterWrite(expireAfterWrite, TimeUnit.SECONDS);
        }

        CacheLoader<String, Object> guavaCacheLoader = new CacheLoader<String, Object>() {
            @Override
            public Object load(String key) throws Exception {
                ICacheLoader cacheLoader = getCacheLoader();
                Object result = cacheLoader != null ? cacheLoader.load(key) : null;
                if (result != null) {
                    return result;
                } else {
                    throw new CacheException.CacheEntryNotFoundException();
                }
            }
        };
        cache = cacheBuilder.build(guavaCacheLoader);

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void destroy() {
        try {
            super.destroy();
        } finally {
            try {
                if (cache != null) {
                    cache.invalidateAll();
                    cache = null;
                }
            } catch (Exception e) {
                // EMPTY
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isCapacitySupported() {
        return true;
    }

    /**
     * If {@code true}, cache entries are cloned when fetching out of cache,
     * default value is {@code false}.
     *
     * @return
     * @since 0.4.1.1
     */
    public boolean isCloneCacheEntries() {
        return cloneCacheEntries;
    }

    /**
     * If {@code true}, cache entries are cloned when fetching out of cache,
     * default value is {@code true}.
     *
     * @return
     * @since 0.4.1.1
     */
    public boolean getCloneCacheEntries() {
        return cloneCacheEntries;
    }

    /**
     * If {@code true}, cache entries are cloned when fetching out of cache.
     *
     * @param cloneCacheEntries
     * @return
     * @since 0.4.1.1
     */
    public GuavaCache setCloneCacheEntries(boolean cloneCacheEntries) {
        this.cloneCacheEntries = cloneCacheEntries;
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long getSize() {
        return cache.size();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void set(String key, Object entry) {
        cache.put(key, CacheUtils.tryClone(entry));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void set(String key, Object entry, long expireAfterWrite, long expireAfterAccess) {
        if (!(entry instanceof CacheEntry)) {
            CacheEntry ce = new CacheEntry(key, CacheUtils.tryClone(entry), expireAfterWrite,
                    expireAfterAccess);
            entry = ce;
        } else {
            entry = CacheUtils.tryClone(entry);
        }
        cache.put(key, entry);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void delete(String key) {
        cache.invalidate(key);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deleteAll() {
        cache.invalidateAll();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean exists(String key) {
        return cache.getIfPresent(key) != null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Object internalGet(String key) {
        try {
            Object result = cache.get(key);
            if (result instanceof CacheEntry) {
                // update entry's private TTL if needed.
                CacheEntry ce = (CacheEntry) result;
                if (ce.touch()) {
                    set(key, ce);
                }
            }
            return cloneCacheEntries ? CacheUtils.tryClone(result) : result;
        } catch (Exception e) {
            Throwable t = e.getCause();
            if (t instanceof CacheException.CacheEntryNotFoundException) {
                return null;
            } else {
                throw new RuntimeException(e);
            }
        }
    }

}


package com.github.ddth.cacheadapter.cacheimpl.localremote;

import java.util.Properties;

import com.github.ddth.cacheadapter.AbstractCache;
import com.github.ddth.cacheadapter.AbstractCacheFactory;
import com.github.ddth.cacheadapter.ICache;
import com.github.ddth.cacheadapter.ICacheFactory;

/**
 * Local-remote implementation of {@link ICacheFactory}. Item is first looked up
 * from "local" cache, then "remote" cache if "local" cache is a miss.
 *
 * @author Thanh Ba Nguyen <btnguyen2k@gmail.com>
 * @since 0.2.0
 */
public class LocalRemoteCacheFactory extends AbstractCacheFactory {

    private ICacheFactory localCacheFactory, remoteCacheFactory;

    public ICacheFactory getLocalCacheFactory() {
        return localCacheFactory;
    }

    public LocalRemoteCacheFactory setLocalCacheFactory(ICacheFactory localCacheFactory) {
        this.localCacheFactory = localCacheFactory;
        return this;
    }

    public ICacheFactory getRemoteCacheFactory() {
        return remoteCacheFactory;
    }

    public LocalRemoteCacheFactory setRemoteCacheFactory(ICacheFactory remoteCacheFactory) {
        this.remoteCacheFactory = remoteCacheFactory;
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected AbstractCache createCacheInternal(String name, long capacity, long expireAfterWrite,
            long expireAfterAccess, Properties cacheProps) {
        ICache localCache = localCacheFactory != null
                ? localCacheFactory.createCache(name, capacity, expireAfterWrite, expireAfterAccess)
                : null;
        ICache remoteCache = remoteCacheFactory != null ? remoteCacheFactory.createCache(name,
                capacity, expireAfterWrite, expireAfterAccess) : null;
        LocalRemoteCache cache = new LocalRemoteCache();
        cache.setName(name).setCapacity(capacity).setExpireAfterAccess(expireAfterAccess)
                .setExpireAfterWrite(expireAfterWrite).setCacheProperties(cacheProps);
        cache.setLocalCache(localCache).setRemoteCache(remoteCache);
        return cache;
    }

}


package com.github.ddth.cacheadapter;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import com.github.ddth.cacheadapter.utils.CacheUtils;
import com.github.ddth.commons.serialization.DeserializationException;
import com.github.ddth.commons.serialization.ISerializationSupport;
import com.github.ddth.commons.serialization.SerializationException;
import com.github.ddth.commons.utils.DPathUtils;
import com.github.ddth.commons.utils.SerializationUtils;

/**
 * Encapsulates a cache item with extra functionality.
 *
 * @author Thanh Ba Nguyen <btnguyen2k@gmail.com>
 * @since 0.1.0
 */
public class CacheEntry implements Serializable, Cloneable, ISerializationSupport {

    private final static long serialVersionUID = "0.5.1".hashCode();
    private final static long LAST_ACCESS_THRESHOLD_MS = 1000;

    private String key = "";
    private Object value = ArrayUtils.EMPTY_BYTE_ARRAY;

    private long creationTimestampMs = System.currentTimeMillis();
    private long lastAccessTimestampMs = System.currentTimeMillis();
    private long expireAfterWrite = -1;
    private long expireAfterAccess = -1;

    /**
     * {@inheritDoc}
     */
    public CacheEntry clone() {
        try {
            CacheEntry clone = (CacheEntry) super.clone();
            clone.value = CacheUtils.tryClone(value);
            return clone;
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }

    public CacheEntry() {
    }

    public CacheEntry(String key, Object value) {
        setKey(key);
        setValue(value);
    }

    public CacheEntry(String key, Object value, long expireAfterWrite, long expireAfterAccess) {
        setKey(key);
        setValue(value);
        setExpireAfterAccess(expireAfterAccess);
        setExpireAfterWrite(expireAfterWrite);
    }

    public boolean isExpired() {
        long now = System.currentTimeMillis();
        return expireAfterAccess > 0 ? lastAccessTimestampMs + expireAfterAccess * 1000L < now
                : expireAfterWrite > 0 ? creationTimestampMs + expireAfterWrite * 1000L < now
                        : false;
    }

    public String getKey() {
        return key;
    }

    public CacheEntry setKey(String key) {
        this.key = key;
        return this;
    }

    /**
     * Gets the wrapped value silently (do not check for expiry or update last
     * access timestamp).
     *
     * @return
     */
    public Object getValueSilent() {
        return value;
    }

    public Object getValue() {
        if (!isExpired()) {
            touch();
            return value;
        }
        return null;
    }

    public CacheEntry setValue(Object value) {
        this.value = value;
        return this;
    }

    public long getExpireAfterWrite() {
        return expireAfterWrite;
    }

    public CacheEntry setExpireAfterWrite(long expireAfterWriteSeconds) {
        this.expireAfterWrite = expireAfterWriteSeconds;
        return this;
    }

    public long getExpireAfterAccess() {
        return expireAfterAccess;
    }

    public CacheEntry setExpireAfterAccess(long expireAfterAccessSeconds) {
        this.expireAfterAccess = expireAfterAccessSeconds;
        return this;
    }

    public long getCreationTimestamp() {
        return creationTimestampMs;
    }

    public CacheEntry setCreationTimestamp(long creationTimestampMs) {
        this.creationTimestampMs = creationTimestampMs;
        return this;
    }

    public long getLastAccessTimestamp() {
        return lastAccessTimestampMs;
    }

    public CacheEntry setLastAccessTimestamp(long lastAccessTimestampMs) {
        this.lastAccessTimestampMs = lastAccessTimestampMs;
        return this;
    }

    /**
     * "Touch" the cache entry.
     *
     * @return
     * @since 0.2.1 entry can be touched only if {@code expireAfterAccess > 0}.
     */
    public boolean touch() {
        if (expireAfterAccess > 0) {
            long now = System.currentTimeMillis();
            if (lastAccessTimestampMs + LAST_ACCESS_THRESHOLD_MS < now) {
                lastAccessTimestampMs = now;
                return true;
            }
        }
        return false;
    }

    /**
     * {@inheritDoc}
     *
     * @since 0.2.2
     */
    @Override
    public String toString() {
        ToStringBuilder tsb = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
        tsb.append("key", this.key);
        tsb.append("value", this.value);
        tsb.append("timestampCreate", this.creationTimestampMs);
        tsb.append("timestampLastAccess", this.lastAccessTimestampMs);
        tsb.append("expireAfterAccess", this.expireAfterAccess);
        tsb.append("expireAfterWrite", this.expireAfterWrite);
        return tsb.toString();
    }

    /**
     * {@inheritDoc}
     *
     * @since 0.2.2
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj instanceof CacheEntry) {
            CacheEntry other = (CacheEntry) obj;
            EqualsBuilder eq = new EqualsBuilder();
            eq.append(this.key, other.key).append(this.value, other.value)
                    .append(this.creationTimestampMs, other.creationTimestampMs)
                    .append(this.expireAfterAccess, other.expireAfterAccess)
                    .append(this.expireAfterWrite, other.expireAfterWrite);
            return eq.isEquals();
        }
        return false;
    }

    /**
     * {@inheritDoc}
     *
     * @since 0.2.2
     */
    @Override
    public int hashCode() {
        HashCodeBuilder hcb = new HashCodeBuilder(19, 81);
        hcb.append(this.key).append(this.value).append(this.creationTimestampMs)
                .append(this.expireAfterAccess).append(this.expireAfterWrite);
        return hcb.hashCode();
    }

    /**
     * {@inheritDoc}
     *
     * @since 0.5.0
     */
    @Override
    public byte[] toBytes() throws SerializationException {
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("k", this.key);
        dataMap.put("tac", this.lastAccessTimestampMs);
        dataMap.put("tcr", this.creationTimestampMs);
        dataMap.put("eac", this.expireAfterAccess);
        dataMap.put("ewr", this.expireAfterWrite);

        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        if (value != null) {
            dataMap.put("_c_", value.getClass().getName());
            dataMap.put("v", SerializationUtils.toByteArray(this.value, cl));
        }

        return SerializationUtils.toByteArrayFst(dataMap, cl);
    }

    /**
     * {@inheritDoc}
     *
     * @since 0.5.0
     */
    @SuppressWarnings("unchecked")
    @Override
    public CacheEntry fromBytes(byte[] data) throws DeserializationException {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        Map<String, Object> dataMap = SerializationUtils.fromByteArrayFst(data, Map.class, cl);

        this.key = DPathUtils.getValue(dataMap, "k", String.class);

        Long tAccess = DPathUtils.getValue(dataMap, "tac", Long.class);
        this.lastAccessTimestampMs = tAccess != null ? tAccess.longValue()
                : System.currentTimeMillis();

        Long tCreate = DPathUtils.getValue(dataMap, "tcr", Long.class);
        this.creationTimestampMs = tCreate != null ? tCreate.longValue()
                : System.currentTimeMillis();

        Long eAccess = DPathUtils.getValue(dataMap, "eac", Long.class);
        this.expireAfterAccess = eAccess != null ? eAccess.longValue() : -1;

        Long eWrite = DPathUtils.getValue(dataMap, "ewr", Long.class);
        this.expireAfterWrite = eWrite != null ? eWrite.longValue() : -1;

        String clazzName = DPathUtils.getValue(dataMap, "_c_", String.class);
        if (!StringUtils.isBlank(clazzName)) {
            try {
                byte[] valueData = DPathUtils.getValue(dataMap, "v", byte[].class);
                Class<?> clazz = Class.forName(clazzName, true, cl);
                this.value = SerializationUtils.fromByteArray(valueData, clazz, cl);
            } catch (ClassNotFoundException e) {
                throw new DeserializationException(e);
            }
        }

        return this;
    }
}


/**
 * Autogenerated by Thrift Compiler (0.9.1)
 *
 * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
 *  @generated
 */
package com.github.ddth.cacheadapter.utils.thrift;

import org.apache.thrift.scheme.IScheme;
import org.apache.thrift.scheme.SchemeFactory;
import org.apache.thrift.scheme.StandardScheme;

import org.apache.thrift.scheme.TupleScheme;
import org.apache.thrift.protocol.TTupleProtocol;
import org.apache.thrift.protocol.TProtocolException;
import org.apache.thrift.EncodingUtils;
import org.apache.thrift.TException;
import org.apache.thrift.async.AsyncMethodCallback;
import org.apache.thrift.server.AbstractNonblockingServer.*;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.EnumMap;
import java.util.Set;
import java.util.HashSet;
import java.util.EnumSet;
import java.util.Collections;
import java.util.BitSet;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TCacheEntry implements org.apache.thrift.TBase<TCacheEntry, TCacheEntry._Fields>, java.io.Serializable, Cloneable, Comparable<TCacheEntry> {
  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("TCacheEntry");

  private static final org.apache.thrift.protocol.TField KEY_FIELD_DESC = new org.apache.thrift.protocol.TField("key", org.apache.thrift.protocol.TType.STRING, (short)1);
  private static final org.apache.thrift.protocol.TField VALUE_FIELD_DESC = new org.apache.thrift.protocol.TField("value", org.apache.thrift.protocol.TType.STRING, (short)2);
  private static final org.apache.thrift.protocol.TField VALUE_CLASS_FIELD_DESC = new org.apache.thrift.protocol.TField("valueClass", org.apache.thrift.protocol.TType.STRING, (short)3);
  private static final org.apache.thrift.protocol.TField CREATION_TIMESTAMP_MS_FIELD_DESC = new org.apache.thrift.protocol.TField("creationTimestampMs", org.apache.thrift.protocol.TType.I64, (short)4);
  private static final org.apache.thrift.protocol.TField LAST_ACCESS_TIMESTAMP_MS_FIELD_DESC = new org.apache.thrift.protocol.TField("lastAccessTimestampMs", org.apache.thrift.protocol.TType.I64, (short)5);
  private static final org.apache.thrift.protocol.TField EXPIRE_AFTER_WRITE_FIELD_DESC = new org.apache.thrift.protocol.TField("expireAfterWrite", org.apache.thrift.protocol.TType.I64, (short)6);
  private static final org.apache.thrift.protocol.TField EXPIRE_AFTER_ACCESS_FIELD_DESC = new org.apache.thrift.protocol.TField("expireAfterAccess", org.apache.thrift.protocol.TType.I64, (short)7);

  private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>();
  static {
    schemes.put(StandardScheme.class, new TCacheEntryStandardSchemeFactory());
    schemes.put(TupleScheme.class, new TCacheEntryTupleSchemeFactory());
  }

  public String key; // required
  public ByteBuffer value; // required
  public String valueClass; // required
  public long creationTimestampMs; // required
  public long lastAccessTimestampMs; // required
  public long expireAfterWrite; // required
  public long expireAfterAccess; // required

  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
    KEY((short)1, "key"),
    VALUE((short)2, "value"),
    VALUE_CLASS((short)3, "valueClass"),
    CREATION_TIMESTAMP_MS((short)4, "creationTimestampMs"),
    LAST_ACCESS_TIMESTAMP_MS((short)5, "lastAccessTimestampMs"),
    EXPIRE_AFTER_WRITE((short)6, "expireAfterWrite"),
    EXPIRE_AFTER_ACCESS((short)7, "expireAfterAccess");

    private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();

    static {
      for (_Fields field : EnumSet.allOf(_Fields.class)) {
        byName.put(field.getFieldName(), field);
      }
    }

    /**
     * Find the _Fields constant that matches fieldId, or null if its not found.
     */
    public static _Fields findByThriftId(int fieldId) {
      switch(fieldId) {
        case 1: // KEY
          return KEY;
        case 2: // VALUE
          return VALUE;
        case 3: // VALUE_CLASS
          return VALUE_CLASS;
        case 4: // CREATION_TIMESTAMP_MS
          return CREATION_TIMESTAMP_MS;
        case 5: // LAST_ACCESS_TIMESTAMP_MS
          return LAST_ACCESS_TIMESTAMP_MS;
        case 6: // EXPIRE_AFTER_WRITE
          return EXPIRE_AFTER_WRITE;
        case 7: // EXPIRE_AFTER_ACCESS
          return EXPIRE_AFTER_ACCESS;
        default:
          return null;
      }
    }

    /**
     * Find the _Fields constant that matches fieldId, throwing an exception
     * if it is not found.
     */
    public static _Fields findByThriftIdOrThrow(int fieldId) {
      _Fields fields = findByThriftId(fieldId);
      if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
      return fields;
    }

    /**
     * Find the _Fields constant that matches name, or null if its not found.
     */
    public static _Fields findByName(String name) {
      return byName.get(name);
    }

    private final short _thriftId;
    private final String _fieldName;

    _Fields(short thriftId, String fieldName) {
      _thriftId = thriftId;
      _fieldName = fieldName;
    }

    public short getThriftFieldId() {
      return _thriftId;
    }

    public String getFieldName() {
      return _fieldName;
    }
  }

  // isset id assignments
  private static final int __CREATIONTIMESTAMPMS_ISSET_ID = 0;
  private static final int __LASTACCESSTIMESTAMPMS_ISSET_ID = 1;
  private static final int __EXPIREAFTERWRITE_ISSET_ID = 2;
  private static final int __EXPIREAFTERACCESS_ISSET_ID = 3;
  private byte __isset_bitfield = 0;
  public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
  static {
    Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
    tmpMap.put(_Fields.KEY, new org.apache.thrift.meta_data.FieldMetaData("key", org.apache.thrift.TFieldRequirementType.DEFAULT,
        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
    tmpMap.put(_Fields.VALUE, new org.apache.thrift.meta_data.FieldMetaData("value", org.apache.thrift.TFieldRequirementType.DEFAULT,
        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING        , true)));
    tmpMap.put(_Fields.VALUE_CLASS, new org.apache.thrift.meta_data.FieldMetaData("valueClass", org.apache.thrift.TFieldRequirementType.DEFAULT,
        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
    tmpMap.put(_Fields.CREATION_TIMESTAMP_MS, new org.apache.thrift.meta_data.FieldMetaData("creationTimestampMs", org.apache.thrift.TFieldRequirementType.DEFAULT,
        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I64)));
    tmpMap.put(_Fields.LAST_ACCESS_TIMESTAMP_MS, new org.apache.thrift.meta_data.FieldMetaData("lastAccessTimestampMs", org.apache.thrift.TFieldRequirementType.DEFAULT,
        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I64)));
    tmpMap.put(_Fields.EXPIRE_AFTER_WRITE, new org.apache.thrift.meta_data.FieldMetaData("expireAfterWrite", org.apache.thrift.TFieldRequirementType.DEFAULT,
        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I64)));
    tmpMap.put(_Fields.EXPIRE_AFTER_ACCESS, new org.apache.thrift.meta_data.FieldMetaData("expireAfterAccess", org.apache.thrift.TFieldRequirementType.DEFAULT,
        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I64)));
    metaDataMap = Collections.unmodifiableMap(tmpMap);
    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(TCacheEntry.class, metaDataMap);
  }

  public TCacheEntry() {
    this.creationTimestampMs = 0L;

    this.lastAccessTimestampMs = 0L;

    this.expireAfterWrite = -1L;

    this.expireAfterAccess = -1L;

  }

  public TCacheEntry(
    String key,
    ByteBuffer value,
    String valueClass,
    long creationTimestampMs,
    long lastAccessTimestampMs,
    long expireAfterWrite,
    long expireAfterAccess)
  {
    this();
    this.key = key;
    this.value = value;
    this.valueClass = valueClass;
    this.creationTimestampMs = creationTimestampMs;
    setCreationTimestampMsIsSet(true);
    this.lastAccessTimestampMs = lastAccessTimestampMs;
    setLastAccessTimestampMsIsSet(true);
    this.expireAfterWrite = expireAfterWrite;
    setExpireAfterWriteIsSet(true);
    this.expireAfterAccess = expireAfterAccess;
    setExpireAfterAccessIsSet(true);
  }

  /**
   * Performs a deep copy on <i>other</i>.
   */
  public TCacheEntry(TCacheEntry other) {
    __isset_bitfield = other.__isset_bitfield;
    if (other.isSetKey()) {
      this.key = other.key;
    }
    if (other.isSetValue()) {
      this.value = org.apache.thrift.TBaseHelper.copyBinary(other.value);
;
    }
    if (other.isSetValueClass()) {
      this.valueClass = other.valueClass;
    }
    this.creationTimestampMs = other.creationTimestampMs;
    this.lastAccessTimestampMs = other.lastAccessTimestampMs;
    this.expireAfterWrite = other.expireAfterWrite;
    this.expireAfterAccess = other.expireAfterAccess;
  }

  public TCacheEntry deepCopy() {
    return new TCacheEntry(this);
  }

  @Override
  public void clear() {
    this.key = null;
    this.value = null;
    this.valueClass = null;
    this.creationTimestampMs = 0L;

    this.lastAccessTimestampMs = 0L;

    this.expireAfterWrite = -1L;

    this.expireAfterAccess = -1L;

  }

  public String getKey() {
    return this.key;
  }

  public TCacheEntry setKey(String key) {
    this.key = key;
    return this;
  }

  public void unsetKey() {
    this.key = null;
  }

  /** Returns true if field key is set (has been assigned a value) and false otherwise */
  public boolean isSetKey() {
    return this.key != null;
  }

  public void setKeyIsSet(boolean value) {
    if (!value) {
      this.key = null;
    }
  }

  public byte[] getValue() {
    setValue(org.apache.thrift.TBaseHelper.rightSize(value));
    return value == null ? null : value.array();
  }

  public ByteBuffer bufferForValue() {
    return value;
  }

  public TCacheEntry setValue(byte[] value) {
    setValue(value == null ? (ByteBuffer)null : ByteBuffer.wrap(value));
    return this;
  }

  public TCacheEntry setValue(ByteBuffer value) {
    this.value = value;
    return this;
  }

  public void unsetValue() {
    this.value = null;
  }

  /** Returns true if field value is set (has been assigned a value) and false otherwise */
  public boolean isSetValue() {
    return this.value != null;
  }

  public void setValueIsSet(boolean value) {
    if (!value) {
      this.value = null;
    }
  }

  public String getValueClass() {
    return this.valueClass;
  }

  public TCacheEntry setValueClass(String valueClass) {
    this.valueClass = valueClass;
    return this;
  }

  public void unsetValueClass() {
    this.valueClass = null;
  }

  /** Returns true if field valueClass is set (has been assigned a value) and false otherwise */
  public boolean isSetValueClass() {
    return this.valueClass != null;
  }

  public void setValueClassIsSet(boolean value) {
    if (!value) {
      this.valueClass = null;
    }
  }

  public long getCreationTimestampMs() {
    return this.creationTimestampMs;
  }

  public TCacheEntry setCreationTimestampMs(long creationTimestampMs) {
    this.creationTimestampMs = creationTimestampMs;
    setCreationTimestampMsIsSet(true);
    return this;
  }

  public void unsetCreationTimestampMs() {
    __isset_bitfield = EncodingUtils.clearBit(__isset_bitfield, __CREATIONTIMESTAMPMS_ISSET_ID);
  }

  /** Returns true if field creationTimestampMs is set (has been assigned a value) and false otherwise */
  public boolean isSetCreationTimestampMs() {
    return EncodingUtils.testBit(__isset_bitfield, __CREATIONTIMESTAMPMS_ISSET_ID);
  }

  public void setCreationTimestampMsIsSet(boolean value) {
    __isset_bitfield = EncodingUtils.setBit(__isset_bitfield, __CREATIONTIMESTAMPMS_ISSET_ID, value);
  }

  public long getLastAccessTimestampMs() {
    return this.lastAccessTimestampMs;
  }

  public TCacheEntry setLastAccessTimestampMs(long lastAccessTimestampMs) {
    this.lastAccessTimestampMs = lastAccessTimestampMs;
    setLastAccessTimestampMsIsSet(true);
    return this;
  }

  public void unsetLastAccessTimestampMs() {
    __isset_bitfield = EncodingUtils.clearBit(__isset_bitfield, __LASTACCESSTIMESTAMPMS_ISSET_ID);
  }

  /** Returns true if field lastAccessTimestampMs is set (has been assigned a value) and false otherwise */
  public boolean isSetLastAccessTimestampMs() {
    return EncodingUtils.testBit(__isset_bitfield, __LASTACCESSTIMESTAMPMS_ISSET_ID);
  }

  public void setLastAccessTimestampMsIsSet(boolean value) {
    __isset_bitfield = EncodingUtils.setBit(__isset_bitfield, __LASTACCESSTIMESTAMPMS_ISSET_ID, value);
  }

  public long getExpireAfterWrite() {
    return this.expireAfterWrite;
  }

  public TCacheEntry setExpireAfterWrite(long expireAfterWrite) {
    this.expireAfterWrite = expireAfterWrite;
    setExpireAfterWriteIsSet(true);
    return this;
  }

  public void unsetExpireAfterWrite() {
    __isset_bitfield = EncodingUtils.clearBit(__isset_bitfield, __EXPIREAFTERWRITE_ISSET_ID);
  }

  /** Returns true if field expireAfterWrite is set (has been assigned a value) and false otherwise */
  public boolean isSetExpireAfterWrite() {
    return EncodingUtils.testBit(__isset_bitfield, __EXPIREAFTERWRITE_ISSET_ID);
  }

  public void setExpireAfterWriteIsSet(boolean value) {
    __isset_bitfield = EncodingUtils.setBit(__isset_bitfield, __EXPIREAFTERWRITE_ISSET_ID, value);
  }

  public long getExpireAfterAccess() {
    return this.expireAfterAccess;
  }

  public TCacheEntry setExpireAfterAccess(long expireAfterAccess) {
    this.expireAfterAccess = expireAfterAccess;
    setExpireAfterAccessIsSet(true);
    return this;
  }

  public void unsetExpireAfterAccess() {
    __isset_bitfield = EncodingUtils.clearBit(__isset_bitfield, __EXPIREAFTERACCESS_ISSET_ID);
  }

  /** Returns true if field expireAfterAccess is set (has been assigned a value) and false otherwise */
  public boolean isSetExpireAfterAccess() {
    return EncodingUtils.testBit(__isset_bitfield, __EXPIREAFTERACCESS_ISSET_ID);
  }

  public void setExpireAfterAccessIsSet(boolean value) {
    __isset_bitfield = EncodingUtils.setBit(__isset_bitfield, __EXPIREAFTERACCESS_ISSET_ID, value);
  }

  public void setFieldValue(_Fields field, Object value) {
    switch (field) {
    case KEY:
      if (value == null) {
        unsetKey();
      } else {
        setKey((String)value);
      }
      break;

    case VALUE:
      if (value == null) {
        unsetValue();
      } else {
        setValue((ByteBuffer)value);
      }
      break;

    case VALUE_CLASS:
      if (value == null) {
        unsetValueClass();
      } else {
        setValueClass((String)value);
      }
      break;

    case CREATION_TIMESTAMP_MS:
      if (value == null) {
        unsetCreationTimestampMs();
      } else {
        setCreationTimestampMs((Long)value);
      }
      break;

    case LAST_ACCESS_TIMESTAMP_MS:
      if (value == null) {
        unsetLastAccessTimestampMs();
      } else {
        setLastAccessTimestampMs((Long)value);
      }
      break;

    case EXPIRE_AFTER_WRITE:
      if (value == null) {
        unsetExpireAfterWrite();
      } else {
        setExpireAfterWrite((Long)value);
      }
      break;

    case EXPIRE_AFTER_ACCESS:
      if (value == null) {
        unsetExpireAfterAccess();
      } else {
        setExpireAfterAccess((Long)value);
      }
      break;

    }
  }

  public Object getFieldValue(_Fields field) {
    switch (field) {
    case KEY:
      return getKey();

    case VALUE:
      return getValue();

    case VALUE_CLASS:
      return getValueClass();

    case CREATION_TIMESTAMP_MS:
      return Long.valueOf(getCreationTimestampMs());

    case LAST_ACCESS_TIMESTAMP_MS:
      return Long.valueOf(getLastAccessTimestampMs());

    case EXPIRE_AFTER_WRITE:
      return Long.valueOf(getExpireAfterWrite());

    case EXPIRE_AFTER_ACCESS:
      return Long.valueOf(getExpireAfterAccess());

    }
    throw new IllegalStateException();
  }

  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
  public boolean isSet(_Fields field) {
    if (field == null) {
      throw new IllegalArgumentException();
    }

    switch (field) {
    case KEY:
      return isSetKey();
    case VALUE:
      return isSetValue();
    case VALUE_CLASS:
      return isSetValueClass();
    case CREATION_TIMESTAMP_MS:
      return isSetCreationTimestampMs();
    case LAST_ACCESS_TIMESTAMP_MS:
      return isSetLastAccessTimestampMs();
    case EXPIRE_AFTER_WRITE:
      return isSetExpireAfterWrite();
    case EXPIRE_AFTER_ACCESS:
      return isSetExpireAfterAccess();
    }
    throw new IllegalStateException();
  }

  @Override
  public boolean equals(Object that) {
    if (that == null)
      return false;
    if (that instanceof TCacheEntry)
      return this.equals((TCacheEntry)that);
    return false;
  }

  public boolean equals(TCacheEntry that) {
    if (that == null)
      return false;

    boolean this_present_key = true && this.isSetKey();
    boolean that_present_key = true && that.isSetKey();
    if (this_present_key || that_present_key) {
      if (!(this_present_key && that_present_key))
        return false;
      if (!this.key.equals(that.key))
        return false;
    }

    boolean this_present_value = true && this.isSetValue();
    boolean that_present_value = true && that.isSetValue();
    if (this_present_value || that_present_value) {
      if (!(this_present_value && that_present_value))
        return false;
      if (!this.value.equals(that.value))
        return false;
    }

    boolean this_present_valueClass = true && this.isSetValueClass();
    boolean that_present_valueClass = true && that.isSetValueClass();
    if (this_present_valueClass || that_present_valueClass) {
      if (!(this_present_valueClass && that_present_valueClass))
        return false;
      if (!this.valueClass.equals(that.valueClass))
        return false;
    }

    boolean this_present_creationTimestampMs = true;
    boolean that_present_creationTimestampMs = true;
    if (this_present_creationTimestampMs || that_present_creationTimestampMs) {
      if (!(this_present_creationTimestampMs && that_present_creationTimestampMs))
        return false;
      if (this.creationTimestampMs != that.creationTimestampMs)
        return false;
    }

    boolean this_present_lastAccessTimestampMs = true;
    boolean that_present_lastAccessTimestampMs = true;
    if (this_present_lastAccessTimestampMs || that_present_lastAccessTimestampMs) {
      if (!(this_present_lastAccessTimestampMs && that_present_lastAccessTimestampMs))
        return false;
      if (this.lastAccessTimestampMs != that.lastAccessTimestampMs)
        return false;
    }

    boolean this_present_expireAfterWrite = true;
    boolean that_present_expireAfterWrite = true;
    if (this_present_expireAfterWrite || that_present_expireAfterWrite) {
      if (!(this_present_expireAfterWrite && that_present_expireAfterWrite))
        return false;
      if (this.expireAfterWrite != that.expireAfterWrite)
        return false;
    }

    boolean this_present_expireAfterAccess = true;
    boolean that_present_expireAfterAccess = true;
    if (this_present_expireAfterAccess || that_present_expireAfterAccess) {
      if (!(this_present_expireAfterAccess && that_present_expireAfterAccess))
        return false;
      if (this.expireAfterAccess != that.expireAfterAccess)
        return false;
    }

    return true;
  }

  @Override
  public int hashCode() {
    return 0;
  }

  @Override
  public int compareTo(TCacheEntry other) {
    if (!getClass().equals(other.getClass())) {
      return getClass().getName().compareTo(other.getClass().getName());
    }

    int lastComparison = 0;

    lastComparison = Boolean.valueOf(isSetKey()).compareTo(other.isSetKey());
    if (lastComparison != 0) {
      return lastComparison;
    }
    if (isSetKey()) {
      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.key, other.key);
      if (lastComparison != 0) {
        return lastComparison;
      }
    }
    lastComparison = Boolean.valueOf(isSetValue()).compareTo(other.isSetValue());
    if (lastComparison != 0) {
      return lastComparison;
    }
    if (isSetValue()) {
      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.value, other.value);
      if (lastComparison != 0) {
        return lastComparison;
      }
    }
    lastComparison = Boolean.valueOf(isSetValueClass()).compareTo(other.isSetValueClass());
    if (lastComparison != 0) {
      return lastComparison;
    }
    if (isSetValueClass()) {
      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.valueClass, other.valueClass);
      if (lastComparison != 0) {
        return lastComparison;
      }
    }
    lastComparison = Boolean.valueOf(isSetCreationTimestampMs()).compareTo(other.isSetCreationTimestampMs());
    if (lastComparison != 0) {
      return lastComparison;
    }
    if (isSetCreationTimestampMs()) {
      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.creationTimestampMs, other.creationTimestampMs);
      if (lastComparison != 0) {
        return lastComparison;
      }
    }
    lastComparison = Boolean.valueOf(isSetLastAccessTimestampMs()).compareTo(other.isSetLastAccessTimestampMs());
    if (lastComparison != 0) {
      return lastComparison;
    }
    if (isSetLastAccessTimestampMs()) {
      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.lastAccessTimestampMs, other.lastAccessTimestampMs);
      if (lastComparison != 0) {
        return lastComparison;
      }
    }
    lastComparison = Boolean.valueOf(isSetExpireAfterWrite()).compareTo(other.isSetExpireAfterWrite());
    if (lastComparison != 0) {
      return lastComparison;
    }
    if (isSetExpireAfterWrite()) {
      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.expireAfterWrite, other.expireAfterWrite);
      if (lastComparison != 0) {
        return lastComparison;
      }
    }
    lastComparison = Boolean.valueOf(isSetExpireAfterAccess()).compareTo(other.isSetExpireAfterAccess());
    if (lastComparison != 0) {
      return lastComparison;
    }
    if (isSetExpireAfterAccess()) {
      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.expireAfterAccess, other.expireAfterAccess);
      if (lastComparison != 0) {
        return lastComparison;
      }
    }
    return 0;
  }

  public _Fields fieldForId(int fieldId) {
    return _Fields.findByThriftId(fieldId);
  }

  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
    schemes.get(iprot.getScheme()).getScheme().read(iprot, this);
  }

  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
    schemes.get(oprot.getScheme()).getScheme().write(oprot, this);
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder("TCacheEntry(");
    boolean first = true;

    sb.append("key:");
    if (this.key == null) {
      sb.append("null");
    } else {
      sb.append(this.key);
    }
    first = false;
    if (!first) sb.append(", ");
    sb.append("value:");
    if (this.value == null) {
      sb.append("null");
    } else {
      org.apache.thrift.TBaseHelper.toString(this.value, sb);
    }
    first = false;
    if (!first) sb.append(", ");
    sb.append("valueClass:");
    if (this.valueClass == null) {
      sb.append("null");
    } else {
      sb.append(this.valueClass);
    }
    first = false;
    if (!first) sb.append(", ");
    sb.append("creationTimestampMs:");
    sb.append(this.creationTimestampMs);
    first = false;
    if (!first) sb.append(", ");
    sb.append("lastAccessTimestampMs:");
    sb.append(this.lastAccessTimestampMs);
    first = false;
    if (!first) sb.append(", ");
    sb.append("expireAfterWrite:");
    sb.append(this.expireAfterWrite);
    first = false;
    if (!first) sb.append(", ");
    sb.append("expireAfterAccess:");
    sb.append(this.expireAfterAccess);
    first = false;
    sb.append(")");
    return sb.toString();
  }

  public void validate() throws org.apache.thrift.TException {
    // check for required fields
    // check for sub-struct validity
  }

  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
    try {
      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
    } catch (org.apache.thrift.TException te) {
      throw new java.io.IOException(te);
    }
  }

  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
    try {
      // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
      __isset_bitfield = 0;
      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
    } catch (org.apache.thrift.TException te) {
      throw new java.io.IOException(te);
    }
  }

  private static class TCacheEntryStandardSchemeFactory implements SchemeFactory {
    public TCacheEntryStandardScheme getScheme() {
      return new TCacheEntryStandardScheme();
    }
  }

  private static class TCacheEntryStandardScheme extends StandardScheme<TCacheEntry> {

    public void read(org.apache.thrift.protocol.TProtocol iprot, TCacheEntry struct) throws org.apache.thrift.TException {
      org.apache.thrift.protocol.TField schemeField;
      iprot.readStructBegin();
      while (true)
      {
        schemeField = iprot.readFieldBegin();
        if (schemeField.type == org.apache.thrift.protocol.TType.STOP) {
          break;
        }
        switch (schemeField.id) {
          case 1: // KEY
            if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
              struct.key = iprot.readString();
              struct.setKeyIsSet(true);
            } else {
              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
            }
            break;
          case 2: // VALUE
            if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
              struct.value = iprot.readBinary();
              struct.setValueIsSet(true);
            } else {
              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
            }
            break;
          case 3: // VALUE_CLASS
            if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
              struct.valueClass = iprot.readString();
              struct.setValueClassIsSet(true);
            } else {
              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
            }
            break;
          case 4: // CREATION_TIMESTAMP_MS
            if (schemeField.type == org.apache.thrift.protocol.TType.I64) {
              struct.creationTimestampMs = iprot.readI64();
              struct.setCreationTimestampMsIsSet(true);
            } else {
              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
            }
            break;
          case 5: // LAST_ACCESS_TIMESTAMP_MS
            if (schemeField.type == org.apache.thrift.protocol.TType.I64) {
              struct.lastAccessTimestampMs = iprot.readI64();
              struct.setLastAccessTimestampMsIsSet(true);
            } else {
              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
            }
            break;
          case 6: // EXPIRE_AFTER_WRITE
            if (schemeField.type == org.apache.thrift.protocol.TType.I64) {
              struct.expireAfterWrite = iprot.readI64();
              struct.setExpireAfterWriteIsSet(true);
            } else {
              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
            }
            break;
          case 7: // EXPIRE_AFTER_ACCESS
            if (schemeField.type == org.apache.thrift.protocol.TType.I64) {
              struct.expireAfterAccess = iprot.readI64();
              struct.setExpireAfterAccessIsSet(true);
            } else {
              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
            }
            break;
          default:
            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
        }
        iprot.readFieldEnd();
      }
      iprot.readStructEnd();

      // check for required fields of primitive type, which can't be checked in the validate method
      struct.validate();
    }

    public void write(org.apache.thrift.protocol.TProtocol oprot, TCacheEntry struct) throws org.apache.thrift.TException {
      struct.validate();

      oprot.writeStructBegin(STRUCT_DESC);
      if (struct.key != null) {
        oprot.writeFieldBegin(KEY_FIELD_DESC);
        oprot.writeString(struct.key);
        oprot.writeFieldEnd();
      }
      if (struct.value != null) {
        oprot.writeFieldBegin(VALUE_FIELD_DESC);
        oprot.writeBinary(struct.value);
        oprot.writeFieldEnd();
      }
      if (struct.valueClass != null) {
        oprot.writeFieldBegin(VALUE_CLASS_FIELD_DESC);
        oprot.writeString(struct.valueClass);
        oprot.writeFieldEnd();
      }
      oprot.writeFieldBegin(CREATION_TIMESTAMP_MS_FIELD_DESC);
      oprot.writeI64(struct.creationTimestampMs);
      oprot.writeFieldEnd();
      oprot.writeFieldBegin(LAST_ACCESS_TIMESTAMP_MS_FIELD_DESC);
      oprot.writeI64(struct.lastAccessTimestampMs);
      oprot.writeFieldEnd();
      oprot.writeFieldBegin(EXPIRE_AFTER_WRITE_FIELD_DESC);
      oprot.writeI64(struct.expireAfterWrite);
      oprot.writeFieldEnd();
      oprot.writeFieldBegin(EXPIRE_AFTER_ACCESS_FIELD_DESC);
      oprot.writeI64(struct.expireAfterAccess);
      oprot.writeFieldEnd();
      oprot.writeFieldStop();
      oprot.writeStructEnd();
    }

  }

  private static class TCacheEntryTupleSchemeFactory implements SchemeFactory {
    public TCacheEntryTupleScheme getScheme() {
      return new TCacheEntryTupleScheme();
    }
  }

  private static class TCacheEntryTupleScheme extends TupleScheme<TCacheEntry> {

    @Override
    public void write(org.apache.thrift.protocol.TProtocol prot, TCacheEntry struct) throws org.apache.thrift.TException {
      TTupleProtocol oprot = (TTupleProtocol) prot;
      BitSet optionals = new BitSet();
      if (struct.isSetKey()) {
        optionals.set(0);
      }
      if (struct.isSetValue()) {
        optionals.set(1);
      }
      if (struct.isSetValueClass()) {
        optionals.set(2);
      }
      if (struct.isSetCreationTimestampMs()) {
        optionals.set(3);
      }
      if (struct.isSetLastAccessTimestampMs()) {
        optionals.set(4);
      }
      if (struct.isSetExpireAfterWrite()) {
        optionals.set(5);
      }
      if (struct.isSetExpireAfterAccess()) {
        optionals.set(6);
      }
      oprot.writeBitSet(optionals, 7);
      if (struct.isSetKey()) {
        oprot.writeString(struct.key);
      }
      if (struct.isSetValue()) {
        oprot.writeBinary(struct.value);
      }
      if (struct.isSetValueClass()) {
        oprot.writeString(struct.valueClass);
      }
      if (struct.isSetCreationTimestampMs()) {
        oprot.writeI64(struct.creationTimestampMs);
      }
      if (struct.isSetLastAccessTimestampMs()) {
        oprot.writeI64(struct.lastAccessTimestampMs);
      }
      if (struct.isSetExpireAfterWrite()) {
        oprot.writeI64(struct.expireAfterWrite);
      }
      if (struct.isSetExpireAfterAccess()) {
        oprot.writeI64(struct.expireAfterAccess);
      }
    }

    @Override
    public void read(org.apache.thrift.protocol.TProtocol prot, TCacheEntry struct) throws org.apache.thrift.TException {
      TTupleProtocol iprot = (TTupleProtocol) prot;
      BitSet incoming = iprot.readBitSet(7);
      if (incoming.get(0)) {
        struct.key = iprot.readString();
        struct.setKeyIsSet(true);
      }
      if (incoming.get(1)) {
        struct.value = iprot.readBinary();
        struct.setValueIsSet(true);
      }
      if (incoming.get(2)) {
        struct.valueClass = iprot.readString();
        struct.setValueClassIsSet(true);
      }
      if (incoming.get(3)) {
        struct.creationTimestampMs = iprot.readI64();
        struct.setCreationTimestampMsIsSet(true);
      }
      if (incoming.get(4)) {
        struct.lastAccessTimestampMs = iprot.readI64();
        struct.setLastAccessTimestampMsIsSet(true);
      }
      if (incoming.get(5)) {
        struct.expireAfterWrite = iprot.readI64();
        struct.setExpireAfterWriteIsSet(true);
      }
      if (incoming.get(6)) {
        struct.expireAfterAccess = iprot.readI64();
        struct.setExpireAfterAccessIsSet(true);
      }
    }
  }

}


package com.github.ddth.cacheadapter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.ddth.cacheadapter.utils.compressor.JdkDeflateCompressor;

/**
 * Abstract implementation of {@link ICacheEntrySerializer}.
 *
 * @author Thanh Nguyen <btnguyen2k@gmail.com>
 * @since 0.3.2
 */
public abstract class AbstractCacheEntrySerializer implements ICacheEntrySerializer {

    private final Logger LOGGER = LoggerFactory.getLogger(AbstractCacheEntrySerializer.class);
    private ICompressor compressor = JdkDeflateCompressor.instance;

    public AbstractCacheEntrySerializer init() {
        return this;
    }

    public void destroy() {
    }

    /**
     *
     * @return
     * @since 0.4.1
     */
    public ICompressor getCompressor() {
        return compressor;
    }

    /**
     *
     * @param compressor
     * @return
     * @since 0.4.1
     */
    public AbstractCacheEntrySerializer setCompressor(ICompressor compressor) {
        this.compressor = compressor;
        return this;
    }

    /**
     * Sub-class should implement this method.
     *
     * @param ce
     * @return
     * @since 0.4.1
     * @throws Exception
     */
    protected abstract byte[] doSerialize(CacheEntry ce) throws Exception;

    /**
     * Sub-class should implement this method.
     *
     * @param data
     * @return
     * @since 0.4.1
     * @throws Exception
     */
    protected abstract CacheEntry doDeserialize(byte[] data) throws Exception;

    /**
     * {@inheritDoc}
     */
    @Override
    public byte[] serialize(CacheEntry ce) {
        try {
            byte[] data = doSerialize(ce);
            return data != null ? (compressor != null ? compressor.compress(data) : data) : null;
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public CacheEntry deserialize(byte[] _data) {
        try {
            byte[] data = _data != null
                    ? (compressor != null ? compressor.decompress(_data) : _data) : null;
            return data != null ? doDeserialize(data) : null;
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
            return null;
        }
    }
}


package com.github.ddth.cacheadapter;

import java.util.Properties;

import com.github.ddth.cacheadapter.utils.ces.DefaultCacheEntrySerializer;

/**
 * Abstract implementation of {@link ICacheFactory} that creates
 * {@link AbstractSerializingCache} instances.
 *
 * @author Thanh Ba Nguyen <btnguyen2k@gmail.com>
 * @since 0.3.0
 */
public abstract class AbstractSerializingCacheFactory extends AbstractCacheFactory {

    private ICacheEntrySerializer cacheEntrySerializer;

    public AbstractSerializingCacheFactory() {
    }

    protected ICacheEntrySerializer getCacheEntrySerializer() {
        return cacheEntrySerializer;
    }

    public AbstractSerializingCacheFactory setCacheEntrySerializer(
            ICacheEntrySerializer cacheEntrySerializer) {
        this.cacheEntrySerializer = cacheEntrySerializer;
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public AbstractSerializingCache createCache(String name) {
        return (AbstractSerializingCache) super.createCache(name);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public AbstractSerializingCache createCache(String name, ICacheLoader cacheLoader) {
        return (AbstractSerializingCache) super.createCache(name, cacheLoader);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public AbstractSerializingCache createCache(String name, long capacity) {
        return (AbstractSerializingCache) super.createCache(name, capacity);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public AbstractSerializingCache createCache(String name, long capacity,
            ICacheLoader cacheLoader) {
        return (AbstractSerializingCache) super.createCache(name, capacity, cacheLoader);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public AbstractSerializingCache createCache(String name, long capacity, long expireAfterWrite,
            long expireAfterAccess) {
        return (AbstractSerializingCache) super.createCache(name, capacity, expireAfterWrite,
                expireAfterAccess);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public AbstractSerializingCache createCache(final String name, final long capacity,
            final long expireAfterWrite, final long expireAfterAccess,
            final ICacheLoader cacheLoader) {
        return (AbstractSerializingCache) super.createCache(name, capacity, expireAfterWrite,
                expireAfterAccess, cacheLoader);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected AbstractSerializingCache createAndInitCacheInstance(String name, long capacity,
            long expireAfterWrite, long expireAfterAccess, ICacheLoader cacheLoader,
            Properties cacheProps) {
        AbstractSerializingCache cache = createCacheInternal(name, capacity, expireAfterWrite,
                expireAfterAccess, cacheProps);
        cache.setCacheEntrySerializer(cacheEntrySerializer).setCacheLoader(cacheLoader)
                .setCacheFactory(this);
        cache.init();
        return cache;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected abstract AbstractSerializingCache createCacheInternal(String name, long capacity,
            long expireAfterWrite, long expireAfterAccess, Properties cacheProps);

    /**
     * {@inheritDoc}
     */
    @Override
    public AbstractSerializingCacheFactory init() {
        super.init();
        if (cacheEntrySerializer == null) {
            cacheEntrySerializer = DefaultCacheEntrySerializer.instance;
        }
        return this;
    }
}