10 changed files with 436 additions and 27 deletions
@ -0,0 +1,308 @@ |
|||||
|
package org.crazycake.shiro; |
||||
|
|
||||
|
import org.apache.shiro.cache.Cache; |
||||
|
import org.apache.shiro.cache.CacheException; |
||||
|
import org.apache.shiro.subject.PrincipalCollection; |
||||
|
import org.apache.shiro.util.CollectionUtils; |
||||
|
import org.crazycake.shiro.exception.CacheManagerPrincipalIdNotAssignedException; |
||||
|
import org.crazycake.shiro.exception.PrincipalIdNullException; |
||||
|
import org.crazycake.shiro.exception.PrincipalInstanceException; |
||||
|
import org.crazycake.shiro.exception.SerializationException; |
||||
|
import org.crazycake.shiro.serializer.RedisSerializer; |
||||
|
import org.crazycake.shiro.serializer.StringSerializer; |
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
|
||||
|
import java.lang.reflect.InvocationTargetException; |
||||
|
import java.lang.reflect.Method; |
||||
|
import java.util.*; |
||||
|
|
||||
|
/** |
||||
|
* Used for setting/getting authorization information from Redis |
||||
|
* @param <K> |
||||
|
* @param <V> |
||||
|
*/ |
||||
|
public class RedisCache<K, V> implements Cache<K, V> { |
||||
|
|
||||
|
private static Logger logger = LoggerFactory.getLogger(RedisCache.class); |
||||
|
|
||||
|
private RedisSerializer keySerializer; |
||||
|
private RedisSerializer valueSerializer; |
||||
|
private IRedisManager redisManager; |
||||
|
private String keyPrefix = RedisCacheManager.DEFAULT_CACHE_KEY_PREFIX; |
||||
|
private int expire; |
||||
|
private String principalIdFieldName = RedisCacheManager.DEFAULT_PRINCIPAL_ID_FIELD_NAME; |
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
* @param redisManager redisManager |
||||
|
* @param keySerializer keySerializer |
||||
|
* @param valueSerializer valueSerializer |
||||
|
* @param prefix authorization prefix |
||||
|
* @param expire expire |
||||
|
* @param principalIdFieldName id field name of principal object |
||||
|
*/ |
||||
|
public RedisCache(IRedisManager redisManager, RedisSerializer keySerializer, RedisSerializer valueSerializer, String prefix, int expire, String principalIdFieldName) { |
||||
|
if (redisManager == null) { |
||||
|
throw new IllegalArgumentException("redisManager cannot be null."); |
||||
|
} |
||||
|
this.redisManager = redisManager; |
||||
|
if (keySerializer == null) { |
||||
|
throw new IllegalArgumentException("keySerializer cannot be null."); |
||||
|
} |
||||
|
this.keySerializer = keySerializer; |
||||
|
if (valueSerializer == null) { |
||||
|
throw new IllegalArgumentException("valueSerializer cannot be null."); |
||||
|
} |
||||
|
this.valueSerializer = valueSerializer; |
||||
|
if (prefix != null && !"".equals(prefix)) { |
||||
|
this.keyPrefix = prefix; |
||||
|
} |
||||
|
this.expire = expire; |
||||
|
if (principalIdFieldName != null) { |
||||
|
this.principalIdFieldName = principalIdFieldName; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* get shiro authorization redis key-value |
||||
|
* @param key key |
||||
|
* @return value |
||||
|
* @throws CacheException get cache exception |
||||
|
*/ |
||||
|
@Override |
||||
|
public V get(K key) throws CacheException { |
||||
|
logger.debug("get key [" + key + "]"); |
||||
|
|
||||
|
if (key == null) { |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
Object redisCacheKey = getRedisCacheKey(key); |
||||
|
byte[] rawValue = redisManager.get(keySerializer.serialize(redisCacheKey)); |
||||
|
if (rawValue == null) { |
||||
|
return null; |
||||
|
} |
||||
|
V value = (V) valueSerializer.deserialize(rawValue); |
||||
|
return value; |
||||
|
} catch (SerializationException e) { |
||||
|
throw new CacheException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public V put(K key, V value) throws CacheException { |
||||
|
if (key == null) { |
||||
|
logger.warn("Saving a null key is meaningless, return value directly without call Redis."); |
||||
|
return value; |
||||
|
} |
||||
|
try { |
||||
|
Object redisCacheKey = getRedisCacheKey(key); |
||||
|
logger.debug("put key [" + redisCacheKey + "]"); |
||||
|
redisManager.set(keySerializer.serialize(redisCacheKey), value != null ? valueSerializer.serialize(value) : null, expire); |
||||
|
return value; |
||||
|
} catch (SerializationException e) { |
||||
|
throw new CacheException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public V remove(K key) throws CacheException { |
||||
|
logger.debug("remove key [" + key + "]"); |
||||
|
if (key == null) { |
||||
|
return null; |
||||
|
} |
||||
|
try { |
||||
|
Object redisCacheKey = getRedisCacheKey(key); |
||||
|
byte[] rawValue = redisManager.get(keySerializer.serialize(redisCacheKey)); |
||||
|
V previous = (V) valueSerializer.deserialize(rawValue); |
||||
|
redisManager.del(keySerializer.serialize(redisCacheKey)); |
||||
|
return previous; |
||||
|
} catch (SerializationException e) { |
||||
|
throw new CacheException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* get the full Redis key including prefix by Redis key |
||||
|
* @param key |
||||
|
* @return |
||||
|
*/ |
||||
|
private Object getRedisCacheKey(K key) { |
||||
|
if (key == null) { |
||||
|
return null; |
||||
|
} |
||||
|
if (keySerializer instanceof StringSerializer) { |
||||
|
return this.keyPrefix + getStringRedisKey(key); |
||||
|
} |
||||
|
return key; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* get Redis key (not including prefix) |
||||
|
* @param key |
||||
|
* @return |
||||
|
*/ |
||||
|
private String getStringRedisKey(K key) { |
||||
|
String redisKey; |
||||
|
if (key instanceof PrincipalCollection) { |
||||
|
redisKey = getRedisKeyFromPrincipalIdField((PrincipalCollection) key); |
||||
|
} else { |
||||
|
redisKey = key.toString(); |
||||
|
} |
||||
|
return redisKey; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* get the Redis key (not including prefix) by PrincipalCollection |
||||
|
* @param key |
||||
|
* @return |
||||
|
*/ |
||||
|
private String getRedisKeyFromPrincipalIdField(PrincipalCollection key) { |
||||
|
Object principalObject = key.getPrimaryPrincipal(); |
||||
|
if (principalObject instanceof String) { |
||||
|
return principalObject.toString(); |
||||
|
} |
||||
|
Method pincipalIdGetter = getPrincipalIdGetter(principalObject); |
||||
|
return getIdObj(principalObject, pincipalIdGetter); |
||||
|
} |
||||
|
|
||||
|
private String getIdObj(Object principalObject, Method pincipalIdGetter) { |
||||
|
String redisKey; |
||||
|
try { |
||||
|
Object idObj = pincipalIdGetter.invoke(principalObject); |
||||
|
if (idObj == null) { |
||||
|
throw new PrincipalIdNullException(principalObject.getClass(), this.principalIdFieldName); |
||||
|
} |
||||
|
redisKey = idObj.toString(); |
||||
|
} catch (IllegalAccessException e) { |
||||
|
throw new PrincipalInstanceException(principalObject.getClass(), this.principalIdFieldName, e); |
||||
|
} catch (InvocationTargetException e) { |
||||
|
throw new PrincipalInstanceException(principalObject.getClass(), this.principalIdFieldName, e); |
||||
|
} |
||||
|
return redisKey; |
||||
|
} |
||||
|
|
||||
|
private Method getPrincipalIdGetter(Object principalObject) { |
||||
|
Method pincipalIdGetter = null; |
||||
|
String principalIdMethodName = this.getPrincipalIdMethodName(); |
||||
|
try { |
||||
|
pincipalIdGetter = principalObject.getClass().getMethod(principalIdMethodName); |
||||
|
} catch (NoSuchMethodException e) { |
||||
|
throw new PrincipalInstanceException(principalObject.getClass(), this.principalIdFieldName); |
||||
|
} |
||||
|
return pincipalIdGetter; |
||||
|
} |
||||
|
|
||||
|
private String getPrincipalIdMethodName() { |
||||
|
if (this.principalIdFieldName == null || "".equals(this.principalIdFieldName)) { |
||||
|
throw new CacheManagerPrincipalIdNotAssignedException(); |
||||
|
} |
||||
|
return "get" + this.principalIdFieldName.substring(0, 1).toUpperCase() + this.principalIdFieldName.substring(1); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public void clear() throws CacheException { |
||||
|
logger.debug("clear cache"); |
||||
|
Set<byte[]> keys = null; |
||||
|
try { |
||||
|
keys = redisManager.keys(keySerializer.serialize(this.keyPrefix + "*")); |
||||
|
} catch (SerializationException e) { |
||||
|
logger.error("get keys error", e); |
||||
|
} |
||||
|
if (keys == null || keys.size() == 0) { |
||||
|
return; |
||||
|
} |
||||
|
for (byte[] key: keys) { |
||||
|
redisManager.del(key); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* get all authorization key-value quantity |
||||
|
* @return key-value size |
||||
|
*/ |
||||
|
@Override |
||||
|
public int size() { |
||||
|
Long longSize = 0L; |
||||
|
try { |
||||
|
longSize = new Long(redisManager.dbSize(keySerializer.serialize(this.keyPrefix + "*"))); |
||||
|
} catch (SerializationException e) { |
||||
|
logger.error("get keys error", e); |
||||
|
} |
||||
|
return longSize.intValue(); |
||||
|
} |
||||
|
|
||||
|
@SuppressWarnings("unchecked") |
||||
|
@Override |
||||
|
public Set<K> keys() { |
||||
|
Set<byte[]> keys = null; |
||||
|
try { |
||||
|
keys = redisManager.keys(keySerializer.serialize(this.keyPrefix + "*")); |
||||
|
} catch (SerializationException e) { |
||||
|
logger.error("get keys error", e); |
||||
|
return Collections.emptySet(); |
||||
|
} |
||||
|
|
||||
|
if (CollectionUtils.isEmpty(keys)) { |
||||
|
return Collections.emptySet(); |
||||
|
} |
||||
|
|
||||
|
Set<K> convertedKeys = new HashSet<K>(); |
||||
|
for (byte[] key:keys) { |
||||
|
try { |
||||
|
convertedKeys.add((K) keySerializer.deserialize(key)); |
||||
|
} catch (SerializationException e) { |
||||
|
logger.error("deserialize keys error", e); |
||||
|
} |
||||
|
} |
||||
|
return convertedKeys; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Collection<V> values() { |
||||
|
Set<byte[]> keys = null; |
||||
|
try { |
||||
|
keys = redisManager.keys(keySerializer.serialize(this.keyPrefix + "*")); |
||||
|
} catch (SerializationException e) { |
||||
|
logger.error("get values error", e); |
||||
|
return Collections.emptySet(); |
||||
|
} |
||||
|
|
||||
|
if (CollectionUtils.isEmpty(keys)) { |
||||
|
return Collections.emptySet(); |
||||
|
} |
||||
|
|
||||
|
List<V> values = new ArrayList<V>(keys.size()); |
||||
|
for (byte[] key : keys) { |
||||
|
V value = null; |
||||
|
try { |
||||
|
value = (V) valueSerializer.deserialize(redisManager.get(key)); |
||||
|
} catch (SerializationException e) { |
||||
|
logger.error("deserialize values= error", e); |
||||
|
} |
||||
|
if (value != null) { |
||||
|
values.add(value); |
||||
|
} |
||||
|
} |
||||
|
return Collections.unmodifiableList(values); |
||||
|
} |
||||
|
|
||||
|
public String getKeyPrefix() { |
||||
|
return keyPrefix; |
||||
|
} |
||||
|
|
||||
|
public void setKeyPrefix(String keyPrefix) { |
||||
|
this.keyPrefix = keyPrefix; |
||||
|
} |
||||
|
|
||||
|
public String getPrincipalIdFieldName() { |
||||
|
return principalIdFieldName; |
||||
|
} |
||||
|
|
||||
|
public void setPrincipalIdFieldName(String principalIdFieldName) { |
||||
|
this.principalIdFieldName = principalIdFieldName; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,99 @@ |
|||||
|
package org.crazycake.shiro; |
||||
|
|
||||
|
import org.apache.shiro.cache.Cache; |
||||
|
import org.apache.shiro.cache.CacheException; |
||||
|
import org.apache.shiro.cache.CacheManager; |
||||
|
import org.crazycake.shiro.serializer.ObjectSerializer; |
||||
|
import org.crazycake.shiro.serializer.RedisSerializer; |
||||
|
import org.crazycake.shiro.serializer.StringSerializer; |
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
|
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.concurrent.ConcurrentMap; |
||||
|
|
||||
|
public class RedisCacheManager implements CacheManager { |
||||
|
|
||||
|
private final Logger logger = LoggerFactory.getLogger(RedisCacheManager.class); |
||||
|
|
||||
|
// fast lookup by name map
|
||||
|
private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<>(); |
||||
|
private RedisSerializer keySerializer = new StringSerializer(); |
||||
|
private RedisSerializer valueSerializer = new ObjectSerializer(); |
||||
|
|
||||
|
private IRedisManager redisManager; |
||||
|
|
||||
|
// expire time in seconds
|
||||
|
public static final int DEFAULT_EXPIRE = 1800; |
||||
|
private int expire = DEFAULT_EXPIRE; |
||||
|
|
||||
|
/** |
||||
|
* The Redis key prefix for caches |
||||
|
*/ |
||||
|
public static final String DEFAULT_CACHE_KEY_PREFIX = "shiro:cache:"; |
||||
|
private String keyPrefix = DEFAULT_CACHE_KEY_PREFIX; |
||||
|
|
||||
|
public static final String DEFAULT_PRINCIPAL_ID_FIELD_NAME = "id"; |
||||
|
private String principalIdFieldName = DEFAULT_PRINCIPAL_ID_FIELD_NAME; |
||||
|
|
||||
|
@Override |
||||
|
public <K, V> Cache<K, V> getCache(String name) throws CacheException { |
||||
|
logger.debug("get cache, name=" + name); |
||||
|
|
||||
|
Cache<K, V> cache = caches.get(name); |
||||
|
|
||||
|
if (cache == null) { |
||||
|
cache = new RedisCache<K, V>(redisManager, keySerializer, valueSerializer, keyPrefix + name + ":", expire, principalIdFieldName); |
||||
|
caches.put(name, cache); |
||||
|
} |
||||
|
return cache; |
||||
|
} |
||||
|
|
||||
|
public IRedisManager getRedisManager() { |
||||
|
return redisManager; |
||||
|
} |
||||
|
|
||||
|
public void setRedisManager(IRedisManager redisManager) { |
||||
|
this.redisManager = redisManager; |
||||
|
} |
||||
|
|
||||
|
public String getKeyPrefix() { |
||||
|
return keyPrefix; |
||||
|
} |
||||
|
|
||||
|
public void setKeyPrefix(String keyPrefix) { |
||||
|
this.keyPrefix = keyPrefix; |
||||
|
} |
||||
|
|
||||
|
public RedisSerializer getKeySerializer() { |
||||
|
return keySerializer; |
||||
|
} |
||||
|
|
||||
|
public void setKeySerializer(RedisSerializer keySerializer) { |
||||
|
this.keySerializer = keySerializer; |
||||
|
} |
||||
|
|
||||
|
public RedisSerializer getValueSerializer() { |
||||
|
return valueSerializer; |
||||
|
} |
||||
|
|
||||
|
public void setValueSerializer(RedisSerializer valueSerializer) { |
||||
|
this.valueSerializer = valueSerializer; |
||||
|
} |
||||
|
|
||||
|
public int getExpire() { |
||||
|
return expire; |
||||
|
} |
||||
|
|
||||
|
public void setExpire(int expire) { |
||||
|
this.expire = expire; |
||||
|
} |
||||
|
|
||||
|
public String getPrincipalIdFieldName() { |
||||
|
return principalIdFieldName; |
||||
|
} |
||||
|
|
||||
|
public void setPrincipalIdFieldName(String principalIdFieldName) { |
||||
|
this.principalIdFieldName = principalIdFieldName; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue