package me.hao0.antares.store.dao.impl;
import com.google.common.base.Function;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import me.hao0.antares.common.anno.RedisModel;
import me.hao0.antares.common.model.Model;
import me.hao0.antares.common.util.CollectionUtil;
import me.hao0.antares.common.util.Names;
import me.hao0.antares.store.dao.BaseDao;
import me.hao0.antares.store.support.RedisIds;
import me.hao0.antares.store.support.RedisKeys;
import me.hao0.antares.store.util.Maps;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import javax.annotation.Nullable;
import java.lang.reflect.ParameterizedType;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* Author: haolin
* Email: haolin.h0@gmail.com
*/
@SuppressWarnings("unchecked")
public class RedisDao<T extends Model> implements BaseDao<T> {
protected static final Integer DELETE_BATCH_SIZE = 100;
@SuppressWarnings("unchecked")
protected final Class<?> genericClazz =
(Class<T>)((ParameterizedType)getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
protected final String OBJECT_PREFIX = getObjectPrefix();
private String getObjectPrefix() {
RedisModel redisModel = genericClazz.getAnnotation(RedisModel.class);
if (redisModel != null){
return redisModel.prefix();
}
return Names.toUnderScore(genericClazz.getSimpleName()) + "s";
}
protected final String IDS_KEY = RedisKeys.keyOfIds(OBJECT_PREFIX);
protected final String IDG_KEY = RedisKeys.keyOfIdGenerator(OBJECT_PREFIX);
@Autowired
protected StringRedisTemplate redis;
@Autowired
private RedisIds ids;
@Override
public Boolean save(Model m) {
Date now = new Date();
boolean isCreated = false;
if (m.getId() == null){
// create
isCreated = true;
m.setId(ids.generate(IDG_KEY));
m.setCtime(now);
}
m.setUtime(now);
// save object
String objKey = objectKey(m.getId());
Map<?, ?> objMap = Maps.toMap(m);
redis.opsForHash().putAll(objKey, objMap);
// bind ids if create
if (isCreated){
redis.opsForList().leftPush(IDS_KEY, m.getId().toString());
}
return Boolean.TRUE;
}
@Override
public T findById(Long id) {
String objKey = objectKey(id);
Map objectMap = redis.opsForHash().entries(objKey);
if (objectMap == null || objectMap.isEmpty()){
return null;
}
return (T)Maps.fromMap(objectMap, genericClazz);
}
@Override
public Boolean delete(Long id) {
// delete id in ids
redis.opsForList().remove(IDS_KEY, 1, String.valueOf(id));
// delete object
String objKey = objectKey(id);
redis.delete(objKey);
return Boolean.TRUE;
}
protected void deleteBatch(String listKey) {
int offset = 0;
List<String> strIds;
// delete all instances
while (true){
strIds = listStr(listKey, offset, DELETE_BATCH_SIZE);
if (CollectionUtil.isNullOrEmpty(strIds)){
break;
}
for (String id : strIds){
delete(Long.valueOf(id));
}
offset += DELETE_BATCH_SIZE;
}
}
@Override
public List<T> findByIds(final List<Long> ids) {
List<T> objs = Lists.newArrayListWithExpectedSize(ids.size());
// iterate instead of the pipeline
// so that support the redis proxy or cluster environment
T t;
for (Long id : ids){
t = findById(id);
if (t != null){
objs.add(t);
}
}
return objs;
}
@Override
public Long findMaxId() {
return findMaxId(IDS_KEY);
}
@Override
public Long findMaxId(String listKey) {
String idStr = redis.opsForList().index(listKey, 0);
if (Strings.isNullOrEmpty(idStr)){
return null;
}
return Long.valueOf(idStr);
}
@Override
public T findLatest() {
Long maxId = findMaxId();
if (maxId == null){
return null;
}
return findById(maxId);
}
@Override
public Long count() {
return count(IDS_KEY);
}
@Override
public Long count(String listKey) {
return redis.opsForList().size(listKey);
}
@Override
public List<T> list(Integer offset, Integer limit) {
return list(IDS_KEY, offset, limit);
}
@Override
public List<String> listStr(String listKey, Integer offset, Integer limit) {
return redis.opsForList().range(listKey, offset, offset + limit - 1);
}
@Override
public List<T> list(String idsKey, Integer offset, Integer limit) {
final List<String> ids = redis.opsForList().range(idsKey, offset, offset + limit - 1);
if (ids == null || ids.isEmpty()){
return Collections.emptyList();
}
List<Long> idsLong = Lists.transform(ids, new Function<String, Long>() {
@Override
public Long apply(String s) {
return Long.valueOf(s);
}
});
return findByIds(idsLong);
}
@Override
public List<Long> listIds(String listKey, Integer offset, Integer limit) {
List<String> listStr = listStr(listKey, offset, limit);
if (CollectionUtil.isNullOrEmpty(listStr)){
return Collections.emptyList();
}
return Lists.transform(listStr, new Function<String, Long>() {
@Override
public Long apply(String idStr) {
return Long.valueOf(idStr);
}
});
}
@Override
public Integer getIntegerField(Long id, String fieldName) {
String objectKey = objectKey(id);
Object fieldValue = redis.opsForHash().get(objectKey, fieldName);
return fieldValue == null ? null : Integer.valueOf(fieldValue.toString());
}
@Override
public Boolean updateField(Long id, String fieldName, Object fieldValue) {
String objectKey = objectKey(id);
redis.opsForHash().put(objectKey, fieldName, fieldValue);
return Boolean.TRUE;
}
/**
* Default use id as unique key
*/
protected String objectKey(Object id) {
return RedisKeys.format(OBJECT_PREFIX, id.toString());
}
}