Package org.springframework.data.redis.cache

Source Code of org.springframework.data.redis.cache.RedisCache

/*
* Copyright 2011-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.data.redis.cache;

import java.util.Arrays;
import java.util.Set;

import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.Assert;

/**
* Cache implementation on top of Redis.
*
* @author Costin Leau
* @author Christoph Strobl
* @author Thomas Darimont
*/
@SuppressWarnings("unchecked")
class RedisCache implements Cache {

  private static final int PAGE_SIZE = 128;
  private final String name;
  @SuppressWarnings("rawtypes") private final RedisTemplate template;
  private final byte[] prefix;
  private final byte[] setName;
  private final byte[] cacheLockName;
  private long WAIT_FOR_LOCK = 300;
  private final long expiration;

  /**
   * Constructs a new <code>RedisCache</code> instance.
   *
   * @param name cache name
   * @param prefix
   * @param template
   * @param expiration
   */
  RedisCache(String name, byte[] prefix, RedisTemplate<? extends Object, ? extends Object> template, long expiration) {

    Assert.hasText(name, "non-empty cache name is required");
    this.name = name;
    this.template = template;
    this.prefix = prefix;
    this.expiration = expiration;

    StringRedisSerializer stringSerializer = new StringRedisSerializer();

    // name of the set holding the keys
    this.setName = stringSerializer.serialize(name + "~keys");
    this.cacheLockName = stringSerializer.serialize(name + "~lock");
  }

  public String getName() {
    return name;
  }

  /**
   * {@inheritDoc} This implementation simply returns the RedisTemplate used for configuring the cache, giving access to
   * the underlying Redis store.
   */
  public Object getNativeCache() {
    return template;
  }

  public ValueWrapper get(final Object key) {
    return (ValueWrapper) template.execute(new RedisCallback<ValueWrapper>() {

      public ValueWrapper doInRedis(RedisConnection connection) throws DataAccessException {
        waitForLock(connection);
        byte[] bs = connection.get(computeKey(key));
        Object value = template.getValueSerializer() != null ? template.getValueSerializer().deserialize(bs) : bs;
        return (bs == null ? null : new SimpleValueWrapper(value));
      }
    }, true);
  }

  /**
   * Return the value to which this cache maps the specified key, generically specifying a type that return value will
   * be cast to.
   *
   * @param key
   * @param type
   * @return
   * @see DATAREDIS-243
   */
  public <T> T get(Object key, Class<T> type) {

    ValueWrapper wrapper = get(key);
    return wrapper == null ? null : (T) wrapper.get();
  }

  public void put(final Object key, final Object value) {

    final byte[] keyBytes = computeKey(key);
    final byte[] valueBytes = convertToBytesIfNecessary(template.getValueSerializer(), value);

    template.execute(new RedisCallback<Object>() {
      public Object doInRedis(RedisConnection connection) throws DataAccessException {

        waitForLock(connection);

        connection.multi();

        connection.set(keyBytes, valueBytes);
        connection.zAdd(setName, 0, keyBytes);

        if (expiration > 0) {
          connection.expire(keyBytes, expiration);
          // update the expiration of the set of keys as well
          connection.expire(setName, expiration);
        }
        connection.exec();

        return null;
      }
    }, true);
  }

  public ValueWrapper putIfAbsent(Object key, final Object value) {

    final byte[] keyBytes = computeKey(key);
    final byte[] valueBytes = convertToBytesIfNecessary(template.getValueSerializer(), value);

    return toWrapper(template.execute(new RedisCallback<Object>() {
      public Object doInRedis(RedisConnection connection) throws DataAccessException {

        waitForLock(connection);

        Object resultValue = value;
        boolean valueWasSet = connection.setNX(keyBytes, valueBytes);
        if (valueWasSet) {
          connection.zAdd(setName, 0, keyBytes);
          if (expiration > 0) {
            connection.expire(keyBytes, expiration);
            // update the expiration of the set of keys as well
            connection.expire(setName, expiration);
          }
        } else {
          resultValue = deserializeIfNecessary(template.getValueSerializer(), connection.get(keyBytes));
        }

        return resultValue;
      }
    }, true));
  }

  private ValueWrapper toWrapper(Object value) {
    return (value != null ? new SimpleValueWrapper(value) : null);
  }

  public void evict(Object key) {
    final byte[] k = computeKey(key);

    template.execute(new RedisCallback<Object>() {
      public Object doInRedis(RedisConnection connection) throws DataAccessException {
        connection.del(k);
        // remove key from set
        connection.zRem(setName, k);
        return null;
      }
    }, true);
  }

  public void clear() {
    // need to del each key individually
    template.execute(new RedisCallback<Object>() {
      public Object doInRedis(RedisConnection connection) throws DataAccessException {
        // another clear is on-going
        if (connection.exists(cacheLockName)) {
          return null;
        }

        try {
          connection.set(cacheLockName, cacheLockName);

          int offset = 0;
          boolean finished = false;

          do {
            // need to paginate the keys
            Set<byte[]> keys = connection.zRange(setName, (offset) * PAGE_SIZE, (offset + 1) * PAGE_SIZE - 1);
            finished = keys.size() < PAGE_SIZE;
            offset++;
            if (!keys.isEmpty()) {
              connection.del(keys.toArray(new byte[keys.size()][]));
            }
          } while (!finished);

          connection.del(setName);
          return null;

        } finally {
          connection.del(cacheLockName);
        }
      }
    }, true);
  }

  private byte[] computeKey(Object key) {

    byte[] keyBytes = convertToBytesIfNecessary(template.getKeySerializer(), key);

    if (prefix == null || prefix.length == 0) {
      return keyBytes;
    }

    byte[] result = Arrays.copyOf(prefix, prefix.length + keyBytes.length);
    System.arraycopy(keyBytes, 0, result, prefix.length, keyBytes.length);

    return result;
  }

  private boolean waitForLock(RedisConnection connection) {

    boolean retry;
    boolean foundLock = false;
    do {
      retry = false;
      if (connection.exists(cacheLockName)) {
        foundLock = true;
        try {
          Thread.sleep(WAIT_FOR_LOCK);
        } catch (InterruptedException ex) {
          Thread.currentThread().interrupt();
        }
        retry = true;
      }
    } while (retry);

    return foundLock;
  }

  private byte[] convertToBytesIfNecessary(RedisSerializer<Object> serializer, Object value) {

    if (serializer == null && value instanceof byte[]) {
      return (byte[]) value;
    }

    return serializer.serialize(value);
  }

  private Object deserializeIfNecessary(RedisSerializer<byte[]> serializer, byte[] value) {

    if (serializer != null) {
      return serializer.deserialize(value);
    }

    return value;
  }

}
TOP

Related Classes of org.springframework.data.redis.cache.RedisCache

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.