001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.jboot.components.cache.redis;
017
018import com.jfinal.plugin.ehcache.IDataLoader;
019import io.jboot.Jboot;
020import io.jboot.components.cache.JbootCacheBase;
021import io.jboot.components.cache.JbootCacheConfig;
022import io.jboot.exception.JbootIllegalConfigException;
023import io.jboot.support.redis.JbootRedis;
024import io.jboot.support.redis.JbootRedisManager;
025import io.jboot.support.redis.RedisScanResult;
026import io.jboot.utils.StrUtil;
027
028import java.util.ArrayList;
029import java.util.List;
030import java.util.Set;
031
032
033public class JbootRedisCacheImpl extends JbootCacheBase {
034
035
036    private JbootRedis redis;
037    private JbootRedisCacheConfig cacheConfig;
038    private String redisCacheNamesKey = "jboot_cache_names";
039    private String globalKeyPrefix = "";
040
041
042    public JbootRedisCacheImpl(JbootCacheConfig config) {
043        super(config);
044
045        cacheConfig = Jboot.config(JbootRedisCacheConfig.class);
046
047        if (StrUtil.isNotBlank(cacheConfig.getGlobalKeyPrefix())) {
048            globalKeyPrefix = cacheConfig.getGlobalKeyPrefix() + ":";
049            redisCacheNamesKey = globalKeyPrefix + redisCacheNamesKey;
050        }
051
052
053        if (cacheConfig.isConfigOk()) {
054            redis = JbootRedisManager.me().getRedis(cacheConfig);
055        } else {
056            redis = Jboot.getRedis();
057        }
058
059        if (redis == null) {
060            throw new JbootIllegalConfigException("Can not get redis component in JbootRedisCacheImpl, Please check your jboot.properties " +
061                    "and config jboot.cache.redis.host or jboot.redis.host correct.");
062        }
063    }
064
065
066    @Override
067    public <T> T get(String cacheName, Object key) {
068        T value = redis.get(buildKey(cacheName, key));
069
070        if (config.isDevMode()) {
071            println("RedisCache GET: cacheName[" +buildCacheName(cacheName)+ "] cacheKey["+key+"] value:" + value);
072        }
073        return value;
074    }
075
076    @Override
077    public void put(String cacheName, Object key, Object value) {
078        if (value == null) {
079            remove(cacheName, key);
080            return;
081        }
082        redis.set(buildKey(cacheName, key), value);
083        redis.sadd(buildCacheName(redisCacheNamesKey), cacheName);
084
085        if (config.isDevMode()) {
086            println("RedisCache PUT: cacheName[" +buildCacheName(cacheName)+ "] cacheKey["+key+"] value:" + value);
087        }
088    }
089
090    @Override
091    public void put(String cacheName, Object key, Object value, int liveSeconds) {
092        if (value == null) {
093            remove(cacheName, key);
094            return;
095        }
096        if (liveSeconds <= 0) {
097            put(cacheName, key, value);
098            return;
099        }
100
101        redis.setex(buildKey(cacheName, key), liveSeconds, value);
102        redis.sadd(buildCacheName(redisCacheNamesKey), cacheName);
103
104        if (config.isDevMode()) {
105            println("RedisCache PUT: cacheName[" +buildCacheName(cacheName)+ "] cacheKey["+key+"] value:" + value);
106        }
107    }
108
109
110    @Override
111    public void remove(String cacheName, Object key) {
112        redis.del(buildKey(cacheName, key));
113
114        if (config.isDevMode()) {
115            println("RedisCache REMOVE: cacheName[" +buildCacheName(cacheName)+ "] cacheKey["+key+"]");
116        }
117    }
118
119
120    @Override
121    public void removeAll(String cacheName) {
122        String cursor = "0";
123        int scanCount = 1000;
124        boolean continueState = true;
125        String scanName = globalKeyPrefix + buildCacheName(cacheName);
126        do {
127            RedisScanResult<String> redisScanResult = redis.scan(scanName + ":*", cursor, scanCount);
128            List<String> scanKeys = redisScanResult.getResults();
129            cursor = redisScanResult.getCursor();
130
131            if (scanKeys != null && scanKeys.size() > 0) {
132                redis.del(scanKeys.toArray(new String[scanKeys.size()]));
133            }
134
135            if (redisScanResult.isCompleteIteration()) {
136                continueState = false;
137            }
138        } while (continueState);
139
140        redis.srem(buildCacheName(redisCacheNamesKey), cacheName);
141
142        if (config.isDevMode()) {
143            println("RedisCache REMOVEALL: cacheName[" +buildCacheName(cacheName)+ "]");
144        }
145    }
146
147
148    @Override
149    public <T> T get(String cacheName, Object key, IDataLoader dataLoader) {
150        Object data = get(cacheName, key);
151        if (data == null) {
152            data = dataLoader.load();
153            put(cacheName, key, data);
154        }
155
156        return (T) data;
157    }
158
159
160    private String buildKey(String cacheName, Object key) {
161        cacheName = buildCacheName(cacheName);
162        StringBuilder keyBuilder = new StringBuilder(globalKeyPrefix)
163                .append(cacheName).append(":");
164
165        if (key instanceof String) {
166            keyBuilder.append("S");
167        } else if (key instanceof Number) {
168            keyBuilder.append("I");
169        } else if (key == null) {
170            keyBuilder.append("S");
171            key = "null";
172        } else {
173            keyBuilder.append("O");
174        }
175        return keyBuilder.append(":").append(key).toString();
176    }
177
178    @Override
179    public <T> T get(String cacheName, Object key, IDataLoader dataLoader, int liveSeconds) {
180        if (liveSeconds <= 0) {
181            return get(cacheName, key, dataLoader);
182        }
183
184        Object data = get(cacheName, key);
185        if (data == null) {
186            data = dataLoader.load();
187            put(cacheName, key, data, liveSeconds);
188        }
189
190        return (T) data;
191    }
192
193
194    @Override
195    public Integer getTtl(String cacheName, Object key) {
196        Long ttl = redis.ttl(buildKey(cacheName, key));
197        return ttl != null ? ttl.intValue() : null;
198    }
199
200
201    @Override
202    public void setTtl(String cacheName, Object key, int seconds) {
203        redis.expire(buildKey(cacheName, key), seconds);
204
205        if (config.isDevMode()) {
206            println("RedisCache SETTTL: cacheName[" +buildCacheName(cacheName)+ "] cacheKey["+key+"] seconds:" + seconds);
207        }
208    }
209
210
211    @Override
212    public List getNames() {
213        String key = buildCacheName(redisCacheNamesKey);
214        Set set = redis.smembers(key);
215        return set == null ? null : new ArrayList(set);
216    }
217
218
219    @Override
220    public List getKeys(String cacheName) {
221        cacheName = globalKeyPrefix + buildCacheName(cacheName);
222        List<String> keys = new ArrayList<>();
223        String cursor = "0";
224        int scanCount = 1000;
225        boolean continueState = true;
226        do {
227            RedisScanResult<String> redisScanResult = redis.scan(cacheName + ":*", cursor, scanCount);
228            List<String> scanKeys = redisScanResult.getResults();
229            cursor = redisScanResult.getCursor();
230
231            if (scanKeys != null && scanKeys.size() > 0) {
232                for (String key : scanKeys) {
233                    keys.add(key.substring(cacheName.length() + 3));
234                }
235            }
236
237            if (redisScanResult.isCompleteIteration()) {
238                continueState = false;
239            }
240        } while (continueState);
241
242        return keys;
243    }
244
245    public JbootRedis getRedis() {
246        return redis;
247    }
248
249}