diff --git a/pom.xml b/pom.xml
index 7e66651..a98b44b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -112,9 +112,9 @@
| test dependencies
-->
- org.junit.jupiter
- junit-jupiter-engine
- 5.7.0
+ junit
+ junit
+ 4.12
test
diff --git a/src/main/java/org/mybatis/caches/redis/JDKSerializer.java b/src/main/java/org/mybatis/caches/redis/JDKSerializer.java
index d0355f7..5464f73 100644
--- a/src/main/java/org/mybatis/caches/redis/JDKSerializer.java
+++ b/src/main/java/org/mybatis/caches/redis/JDKSerializer.java
@@ -17,9 +17,9 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
+import java.util.Arrays;
import org.apache.ibatis.cache.CacheException;
@@ -31,10 +31,12 @@ private JDKSerializer() {
// prevent instantiation
}
- public byte[] serialize(Object object) {
- try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ public byte[] serialize(long timestamp, Object object) {
+ try (
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(object);
+ baos.write(longToBytes(timestamp));
return baos.toByteArray();
} catch (Exception e) {
throw new CacheException(e);
@@ -42,10 +44,11 @@ public byte[] serialize(Object object) {
}
public Object unserialize(byte[] bytes) {
- if (bytes == null) {
+ if (bytes == null || bytes.length < 8) {
return null;
}
- try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+ try (
+ ByteArrayInputStream bais = new ByteArrayInputStream(Arrays.copyOf(bytes, bytes.length - 8));
ObjectInputStream ois = new ObjectInputStream(bais)) {
return ois.readObject();
} catch (Exception e) {
diff --git a/src/main/java/org/mybatis/caches/redis/KryoSerializer.java b/src/main/java/org/mybatis/caches/redis/KryoSerializer.java
index c12fd76..2bdca8a 100644
--- a/src/main/java/org/mybatis/caches/redis/KryoSerializer.java
+++ b/src/main/java/org/mybatis/caches/redis/KryoSerializer.java
@@ -1,5 +1,5 @@
/**
- * Copyright 2015-2018 the original author or authors.
+ * Copyright 2015-2020 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.
@@ -57,7 +57,7 @@ private KryoSerializer() {
fallbackSerializer = JDKSerializer.INSTANCE;// use JDKSerializer as fallback
}
- public byte[] serialize(Object object) {
+ public byte[] serialize(long timestamp, Object object) {
output.clear();
if (!unnormalClassSet.contains(object.getClass())) {
/**
@@ -66,30 +66,33 @@ public byte[] serialize(Object object) {
*/
try {
kryo.writeClassAndObject(output, object);
+ // Add 8 bytes of timestamp to the tail
+ output.write(longToBytes(timestamp));
return output.toBytes();
} catch (Exception e) {
// For unnormal class occurred for the first time, exception will be thrown
unnormalClassSet.add(object.getClass());
- return fallbackSerializer.serialize(object);// use fallback Serializer to resolve
+ return fallbackSerializer.serialize(timestamp, object);// use fallback Serializer to resolve
}
} else {
// For unnormal class
- return fallbackSerializer.serialize(object);
+ return fallbackSerializer.serialize(timestamp, object);
}
}
public Object unserialize(byte[] bytes) {
- if (bytes == null) {
+ if (bytes == null || bytes.length < 8) {
return null;
}
- int hashCode = Arrays.hashCode(bytes);
- if (!unnormalBytesHashCodeSet.contains(hashCode)) {
+ byte[] stripped = Arrays.copyOf(bytes, bytes.length - 8);
+ int hashCode = Arrays.hashCode(stripped);
+ if (!unnormalBytesHashCodeSet.contains(stripped)) {
/**
* In the following cases: 1. This bytes occurs for the first time. 2. This bytes have occurred and can be
* resolved by default kryo serializer
*/
try {
- input.setBuffer(bytes);
+ input.setBuffer(stripped);
return kryo.readClassAndObject(input);
} catch (Exception e) {
// For unnormal bytes occurred for the first time, exception will be thrown
diff --git a/src/main/java/org/mybatis/caches/redis/RedisCache.java b/src/main/java/org/mybatis/caches/redis/RedisCache.java
index 1401d6c..6deb26e 100644
--- a/src/main/java/org/mybatis/caches/redis/RedisCache.java
+++ b/src/main/java/org/mybatis/caches/redis/RedisCache.java
@@ -1,5 +1,5 @@
/**
- * Copyright 2015-2018 the original author or authors.
+ * Copyright 2015-2020 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.
@@ -20,8 +20,8 @@
import org.apache.ibatis.cache.Cache;
-import redis.clients.jedis.Jedis;
-import redis.clients.jedis.JedisPool;
+import org.mybatis.caches.redis.client.GenericRedisClient;
+import org.mybatis.caches.redis.client.RedisClientBuilder;
/**
* Cache adapter for Redis.
@@ -34,9 +34,9 @@ public final class RedisCache implements Cache {
private String id;
- private static JedisPool pool;
+ private static GenericRedisClient client;
- private final RedisConfig redisConfig;
+ private static RedisConfig redisConfig;
private Integer timeout;
@@ -45,20 +45,16 @@ public RedisCache(final String id) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
- redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();
- pool = new JedisPool(redisConfig, redisConfig.getHost(), redisConfig.getPort(), redisConfig.getConnectionTimeout(),
- redisConfig.getSoTimeout(), redisConfig.getPassword(), redisConfig.getDatabase(), redisConfig.getClientName(),
- redisConfig.isSsl(), redisConfig.getSslSocketFactory(), redisConfig.getSslParameters(),
- redisConfig.getHostnameVerifier());
- }
-
- // TODO Review this is UNUSED
- private Object execute(RedisCallback callback) {
- Jedis jedis = pool.getResource();
- try {
- return callback.doWithRedis(jedis);
- } finally {
- jedis.close();
+ synchronized (RedisCache.class) {
+ if (client == null) {
+ redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();
+ RedisClientBuilder builder = new RedisClientBuilder(redisConfig, redisConfig.getHosts(),
+ redisConfig.getConnectionTimeout(), redisConfig.getSoTimeout(), redisConfig.getMaxAttempts(),
+ redisConfig.getPassword(), redisConfig.getDatabase(), redisConfig.getClientName(), redisConfig.isSsl(),
+ redisConfig.getSslSocketFactory(), redisConfig.getSslParameters(), redisConfig.getHostnameVerifier(),
+ redisConfig.getHostAndPortMap());
+ client = builder.getClient();
+ }
}
}
@@ -69,60 +65,42 @@ public String getId() {
@Override
public int getSize() {
- return (Integer) execute(new RedisCallback() {
- @Override
- public Object doWithRedis(Jedis jedis) {
- Map result = jedis.hgetAll(id.getBytes());
- return result.size();
- }
- });
+ Map result = client.hgetAll(id.getBytes());
+ return result.size();
}
@Override
public void putObject(final Object key, final Object value) {
- execute(new RedisCallback() {
- @Override
- public Object doWithRedis(Jedis jedis) {
- final byte[] idBytes = id.getBytes();
- jedis.hset(idBytes, key.toString().getBytes(), redisConfig.getSerializer().serialize(value));
- if (timeout != null && jedis.ttl(idBytes) == -1) {
- jedis.expire(idBytes, timeout);
- }
- return null;
- }
- });
+ final byte[] idBytes = id.getBytes();
+ long ts = 0;
+ if (timeout != null) {
+ ts = System.currentTimeMillis() + timeout * 1000;
+ }
+ final byte[] objBytes = redisConfig.getSerializer().serialize(ts, value);
+ client.hset(idBytes, key.toString().getBytes(), objBytes);
}
@Override
public Object getObject(final Object key) {
- return execute(new RedisCallback() {
- @Override
- public Object doWithRedis(Jedis jedis) {
- return redisConfig.getSerializer().unserialize(jedis.hget(id.getBytes(), key.toString().getBytes()));
- }
- });
+ byte[] objBytes = client.hget(id.getBytes(), key.toString().getBytes());
+ if (objBytes == null || objBytes.length < 8) return null;
+ long ts = redisConfig.getSerializer().getTimestamp(objBytes);
+ if (ts > 0 && ts < System.currentTimeMillis()) {
+ client.hdel(id, key.toString());
+ return null;
+ } else {
+ return redisConfig.getSerializer().unserialize(objBytes);
+ }
}
@Override
public Object removeObject(final Object key) {
- return execute(new RedisCallback() {
- @Override
- public Object doWithRedis(Jedis jedis) {
- return jedis.hdel(id, key.toString());
- }
- });
+ return client.hdel(id, key.toString());
}
@Override
public void clear() {
- execute(new RedisCallback() {
- @Override
- public Object doWithRedis(Jedis jedis) {
- jedis.del(id);
- return null;
- }
- });
-
+ client.del(id);
}
@Override
diff --git a/src/main/java/org/mybatis/caches/redis/RedisCallback.java b/src/main/java/org/mybatis/caches/redis/RedisCallback.java
deleted file mode 100644
index e7ccd5a..0000000
--- a/src/main/java/org/mybatis/caches/redis/RedisCallback.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Copyright 2015-2018 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.mybatis.caches.redis;
-
-import redis.clients.jedis.Jedis;
-
-public interface RedisCallback {
-
- Object doWithRedis(Jedis jedis);
-}
diff --git a/src/main/java/org/mybatis/caches/redis/RedisConfig.java b/src/main/java/org/mybatis/caches/redis/RedisConfig.java
index 9138a25..c3b6d91 100644
--- a/src/main/java/org/mybatis/caches/redis/RedisConfig.java
+++ b/src/main/java/org/mybatis/caches/redis/RedisConfig.java
@@ -1,5 +1,5 @@
/**
- * Copyright 2015-2018 the original author or authors.
+ * Copyright 2015-2020 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.
@@ -19,15 +19,16 @@
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocketFactory;
+import redis.clients.jedis.JedisClusterHostAndPortMap;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol;
public class RedisConfig extends JedisPoolConfig {
- private String host = Protocol.DEFAULT_HOST;
- private int port = Protocol.DEFAULT_PORT;
+ private String hosts = Protocol.DEFAULT_HOST + ':' + Protocol.DEFAULT_PORT;
private int connectionTimeout = Protocol.DEFAULT_TIMEOUT;
private int soTimeout = Protocol.DEFAULT_TIMEOUT;
+ private int maxAttempts = 5;
private String password;
private int database = Protocol.DEFAULT_DATABASE;
private String clientName;
@@ -35,6 +36,7 @@ public class RedisConfig extends JedisPoolConfig {
private SSLSocketFactory sslSocketFactory;
private SSLParameters sslParameters;
private HostnameVerifier hostnameVerifier;
+ private JedisClusterHostAndPortMap hostAndPortMap;
private Serializer serializer = JDKSerializer.INSTANCE;
public boolean isSsl() {
@@ -69,23 +71,20 @@ public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
this.hostnameVerifier = hostnameVerifier;
}
- public String getHost() {
- return host;
+ public JedisClusterHostAndPortMap getHostAndPortMap() {
+ return hostAndPortMap;
}
- public void setHost(String host) {
- if (host == null || "".equals(host)) {
- host = Protocol.DEFAULT_HOST;
- }
- this.host = host;
+ public void setHostAndPortMap(JedisClusterHostAndPortMap hostAndPortMap) {
+ this.hostAndPortMap = hostAndPortMap;
}
- public int getPort() {
- return port;
+ public String getHosts() {
+ return hosts;
}
- public void setPort(int port) {
- this.port = port;
+ public void setHosts(String hosts) {
+ this.hosts = hosts;
}
public String getPassword() {
@@ -134,6 +133,14 @@ public void setSoTimeout(int soTimeout) {
this.soTimeout = soTimeout;
}
+ public int getMaxAttempts() {
+ return maxAttempts;
+ }
+
+ public void setMaxAttempts(int maxAttempts) {
+ this.maxAttempts = maxAttempts;
+ }
+
public Serializer getSerializer() {
return serializer;
}
diff --git a/src/main/java/org/mybatis/caches/redis/RedisConfigurationBuilder.java b/src/main/java/org/mybatis/caches/redis/RedisConfigurationBuilder.java
index 4f95ba1..a16fc08 100644
--- a/src/main/java/org/mybatis/caches/redis/RedisConfigurationBuilder.java
+++ b/src/main/java/org/mybatis/caches/redis/RedisConfigurationBuilder.java
@@ -1,5 +1,5 @@
/**
- * Copyright 2015-2018 the original author or authors.
+ * Copyright 2015-2020 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.
@@ -118,7 +118,8 @@ private void setConfigProperties(Properties properties, RedisConfig jedisConfig)
// Custom serializer is not supported yet.
throw new CacheException("Unknown serializer: '" + value + "'");
}
- } else if (Arrays.asList("sslSocketFactory", "sslParameters", "hostnameVerifier").contains(name)) {
+ } else if (Arrays.asList("sslSocketFactory", "sslParameters", "hostnameVerifier", "hostAndPortMap")
+ .contains(name)) {
setInstance(metaCache, name, value);
} else if (metaCache.hasSetter(name)) {
Class> type = metaCache.getSetterType(name);
diff --git a/src/main/java/org/mybatis/caches/redis/Serializer.java b/src/main/java/org/mybatis/caches/redis/Serializer.java
index 2520f11..040796d 100644
--- a/src/main/java/org/mybatis/caches/redis/Serializer.java
+++ b/src/main/java/org/mybatis/caches/redis/Serializer.java
@@ -1,5 +1,5 @@
/**
- * Copyright 2015-2018 the original author or authors.
+ * Copyright 2015-2020 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.
@@ -15,6 +15,8 @@
*/
package org.mybatis.caches.redis;
+import java.nio.ByteBuffer;
+
public interface Serializer {
/**
@@ -23,7 +25,19 @@ public interface Serializer {
* @param object
* @return serialized bytes
*/
- public byte[] serialize(Object object);
+ byte[] serialize(long timestamp, Object object);
+
+ /**
+ * Read the timestamp
+ */
+ default long getTimestamp(byte[] bytes) {
+ if (bytes == null || bytes.length < 8) {
+ return -1;
+ }
+ byte[] copy = new byte[8];
+ System.arraycopy(bytes, bytes.length - 8, copy, 0, 8);
+ return bytesToLong(copy);
+ }
/**
* Unserialize method
@@ -31,6 +45,18 @@ public interface Serializer {
* @param bytes
* @return unserialized object
*/
- public Object unserialize(byte[] bytes);
+ Object unserialize(byte[] bytes);
+
+ default byte[] longToBytes(long x) {
+ ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
+ buffer.putLong(x);
+ return buffer.array();
+ }
+ default long bytesToLong(byte[] bytes) {
+ ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
+ buffer.put(bytes);
+ buffer.flip();//need flip
+ return buffer.getLong();
+ }
}
diff --git a/src/main/java/org/mybatis/caches/redis/client/ClusterRedisClient.java b/src/main/java/org/mybatis/caches/redis/client/ClusterRedisClient.java
new file mode 100644
index 0000000..788bc08
--- /dev/null
+++ b/src/main/java/org/mybatis/caches/redis/client/ClusterRedisClient.java
@@ -0,0 +1,173 @@
+/**
+ * Copyright 2015-2020 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.mybatis.caches.redis.client;
+
+import redis.clients.jedis.JedisCluster;
+import redis.clients.jedis.params.SetParams;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Milton Lai(millton.lai@gmail.com)
+ */
+public class ClusterRedisClient implements GenericRedisClient {
+ private JedisCluster cluster;
+
+ public ClusterRedisClient(JedisCluster cluster) {
+ this.cluster = cluster;
+ }
+
+ @Override
+ public Object eval(String script, int keyCount, String... params) {
+ return cluster.eval(script, keyCount, params);
+ }
+
+ @Override
+ public List blpop(int timeout, String key) {
+ return cluster.blpop(timeout, key);
+ }
+
+ @Override
+ public List blpop(int timeout, String... keys) {
+ return cluster.blpop(timeout, keys);
+ }
+
+ @Override
+ public List blpop(int timeout, byte[]... keys) {
+ return cluster.blpop(timeout, keys);
+ }
+
+ @Override
+ public List brpop(int timeout, String key) {
+ return cluster.brpop(timeout, key);
+ }
+
+ @Override
+ public List brpop(int timeout, String... keys) {
+ return cluster.brpop(timeout, keys);
+ }
+
+ @Override
+ public List brpop(int timeout, byte[]... keys) {
+ return cluster.brpop(timeout, keys);
+ }
+
+ @Override
+ public Long del(String key) {
+ return cluster.del(key);
+ }
+
+ @Override
+ public Long del(String... keys) {
+ return cluster.del(keys);
+ }
+
+ @Override
+ public Long exists(String... keys) {
+ return cluster.exists(keys);
+ }
+
+ @Override
+ public Boolean exists(String key) {
+ return cluster.exists(key);
+ }
+
+ @Override
+ public Long expire(byte[] key, int seconds) {
+ return cluster.expire(key, seconds);
+ }
+
+ @Override
+ public String get(String key) {
+ return cluster.get(key);
+ }
+
+ @Override
+ public byte[] get(byte[] key) {
+ return cluster.get(key);
+ }
+
+ @Override
+ public Long hdel(String key, String... fields) {
+ return cluster.hdel(key, fields);
+ }
+
+ @Override
+ public byte[] hget(byte[] key, byte[] field) {
+ return cluster.hget(key, field);
+ }
+
+ @Override
+ public Map hgetAll(byte[] key) {
+ return cluster.hgetAll(key);
+ }
+
+ @Override
+ public Long hset(byte[] key, byte[] field, byte[] value) {
+ return cluster.hset(key, field, value);
+ }
+
+ @Override
+ public Long llen(String key) {
+ return cluster.llen(key);
+ }
+
+ @Override
+ public Long lpush(String key, String... strings) {
+ return cluster.lpush(key, strings);
+ }
+
+ @Override
+ public Long pttl(String key) {
+ return cluster.pttl(key);
+ }
+
+ @Override
+ public Long rpush(String key, String... strings) {
+ return cluster.rpush(key, strings);
+ }
+
+ @Override
+ public String set(String key, String value) {
+ return cluster.set(key, value);
+ }
+
+ @Override
+ public String set(byte[] key, byte[] value) {
+ return cluster.set(key, value);
+ }
+
+ @Override
+ public String set(String key, String value, SetParams params) {
+ return cluster.set(key, value, params);
+ }
+
+ @Override
+ public String set(byte[] key, byte[] value, SetParams params) {
+ return cluster.set(key, value, params);
+ }
+
+ @Override
+ public Long ttl(String key) {
+ return cluster.ttl(key);
+ }
+
+ @Override
+ public Long ttl(byte[] key) {
+ return cluster.ttl(key);
+ }
+}
diff --git a/src/main/java/org/mybatis/caches/redis/client/GenericRedisClient.java b/src/main/java/org/mybatis/caches/redis/client/GenericRedisClient.java
new file mode 100644
index 0000000..0c9245b
--- /dev/null
+++ b/src/main/java/org/mybatis/caches/redis/client/GenericRedisClient.java
@@ -0,0 +1,84 @@
+/**
+ * Copyright 2015-2020 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.mybatis.caches.redis.client;
+
+import redis.clients.jedis.params.SetParams;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Milton Lai(millton.lai@gmail.com)
+ */
+public interface GenericRedisClient {
+
+ Object eval(final String script, final int keyCount, final String... params);
+
+ List blpop(int timeout, String key);
+
+ List blpop(int timeout, String... keys);
+
+ List blpop(int timeout, byte[]... keys);
+
+ List brpop(int timeout, String key);
+
+ List brpop(int timeout, String... keys);
+
+ List brpop(int timeout, byte[]... keys);
+
+ Long del(String key);
+
+ Long del(String... keys);
+
+ Long exists(String... keys);
+
+ Boolean exists(String key);
+
+ Long expire(byte[] key, int seconds);
+
+ String get(String key);
+
+ byte[] get(byte[] key);
+
+ Long hdel(String key, String... fields);
+
+ byte[] hget(byte[] key, byte[] field);
+
+ Map hgetAll(byte[] key);
+
+ Long hset(byte[] key, byte[] field, byte[] value);
+
+ Long llen(String key);
+
+ Long lpush(String key, String... strings);
+
+ Long pttl(final String key);
+
+ Long rpush(String key, String... strings);
+
+ String set(String key, String value);
+
+ String set(byte[] key, byte[] value);
+
+ String set(String key, String value, SetParams params);
+
+ String set(byte[] key, byte[] value, SetParams params);
+
+ Long ttl(final String key);
+
+ Long ttl(byte[] key);
+
+}
diff --git a/src/main/java/org/mybatis/caches/redis/client/PooledRedisClient.java b/src/main/java/org/mybatis/caches/redis/client/PooledRedisClient.java
new file mode 100644
index 0000000..8410fd4
--- /dev/null
+++ b/src/main/java/org/mybatis/caches/redis/client/PooledRedisClient.java
@@ -0,0 +1,184 @@
+/**
+ * Copyright 2015-2020 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.mybatis.caches.redis.client;
+
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPoolAbstract;
+import redis.clients.jedis.params.SetParams;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Milton Lai(millton.lai@gmail.com)
+ */
+public class PooledRedisClient implements GenericRedisClient {
+ private JedisPoolAbstract pool;
+
+ public PooledRedisClient(JedisPoolAbstract pool) {
+ this.pool = pool;
+ }
+
+ @Override
+ public Object eval(String script, int keyCount, String... params) {
+ return execute((Jedis jedis) -> jedis.eval(script, keyCount, params));
+ }
+
+ @Override
+ public List blpop(int timeout, String key) {
+ return (List) execute((Jedis jedis) -> jedis.blpop(timeout, key));
+ }
+
+ @Override
+ public List blpop(int timeout, String... keys) {
+ return (List) execute((Jedis jedis) -> jedis.blpop(timeout, keys));
+ }
+
+ @Override
+ public List blpop(int timeout, byte[]... keys) {
+ return (List) execute((Jedis jedis) -> jedis.blpop(timeout, keys));
+ }
+
+ @Override
+ public List brpop(int timeout, String key) {
+ return (List) execute((Jedis jedis) -> jedis.brpop(timeout, key));
+ }
+
+ @Override
+ public List brpop(int timeout, String... keys) {
+ return (List) execute((Jedis jedis) -> jedis.brpop(timeout, keys));
+ }
+
+ @Override
+ public List brpop(int timeout, byte[]... keys) {
+ return (List) execute((Jedis jedis) -> jedis.brpop(timeout, keys));
+ }
+
+ @Override
+ public Long del(String key) {
+ return (Long) execute((Jedis jedis) -> jedis.del(key));
+ }
+
+ @Override
+ public Long del(String... keys) {
+ return (Long) execute((Jedis jedis) -> jedis.del(keys));
+ }
+
+ @Override
+ public Long exists(String... keys) {
+ return (Long) execute((Jedis jedis) -> jedis.exists(keys));
+ }
+
+ @Override
+ public Boolean exists(String key) {
+ return (Boolean) execute((Jedis jedis) -> jedis.exists(key));
+ }
+
+ @Override
+ public Long expire(byte[] key, int seconds) {
+ return (Long) execute((Jedis jedis) -> jedis.expire(key, seconds));
+ }
+
+ @Override
+ public String get(String key) {
+ return (String) execute((Jedis jedis) -> jedis.get(key));
+ }
+
+ @Override
+ public byte[] get(byte[] key) {
+ return (byte[]) execute((Jedis jedis) -> jedis.get(key));
+ }
+
+ @Override
+ public Long hdel(String key, String... fields) {
+ return (Long) execute((Jedis jedis) -> jedis.hdel(key, fields));
+ }
+
+ @Override
+ public byte[] hget(byte[] key, byte[] field) {
+ return (byte[]) execute((Jedis jedis) -> jedis.hget(key, field));
+ }
+
+ @Override
+ public Map hgetAll(byte[] key) {
+ return (Map) execute((Jedis jedis) -> jedis.hgetAll(key));
+ }
+
+ @Override
+ public Long hset(byte[] key, byte[] field, byte[] value) {
+ return (Long) execute((Jedis jedis) -> jedis.hset(key, field, value));
+ }
+
+ @Override
+ public Long llen(String key) {
+ return (Long) execute((Jedis jedis) -> jedis.llen(key));
+ }
+
+ @Override
+ public Long lpush(String key, String... strings) {
+ return (Long) execute((Jedis jedis) -> jedis.lpush(key, strings));
+ }
+
+ @Override
+ public Long pttl(String key) {
+ return (Long) execute((Jedis jedis) -> jedis.pttl(key));
+ }
+
+ @Override
+ public Long rpush(String key, String... strings) {
+ return (Long) execute((Jedis jedis) -> jedis.rpush(key, strings));
+ }
+
+ @Override
+ public String set(String key, String value) {
+ return (String) execute((Jedis jedis) -> jedis.set(key, value));
+ }
+
+ @Override
+ public String set(byte[] key, byte[] value) {
+ return (String) execute((Jedis jedis) -> jedis.set(key, value));
+ }
+
+ @Override
+ public String set(String key, String value, SetParams params) {
+ return (String) execute((Jedis jedis) -> jedis.set(key, value, params));
+ }
+
+ @Override
+ public String set(byte[] key, byte[] value, SetParams params) {
+ return (String) execute((Jedis jedis) -> jedis.set(key, value, params));
+ }
+
+ @Override
+ public Long ttl(String key) {
+ return (Long) execute((Jedis jedis) -> jedis.ttl(key));
+ }
+
+ @Override
+ public Long ttl(byte[] key) {
+ return (Long) execute((Jedis jedis) -> jedis.ttl(key));
+ }
+
+ private Object execute(RedisCallback callback) {
+ try (Jedis jedis = pool.getResource()) {
+ return callback.doWithRedis(jedis);
+ }
+ }
+
+ public interface RedisCallback {
+ Object doWithRedis(Jedis var1);
+ }
+}
diff --git a/src/main/java/org/mybatis/caches/redis/client/RedisClientBuilder.java b/src/main/java/org/mybatis/caches/redis/client/RedisClientBuilder.java
new file mode 100644
index 0000000..c50cd60
--- /dev/null
+++ b/src/main/java/org/mybatis/caches/redis/client/RedisClientBuilder.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright 2015-2020 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.mybatis.caches.redis.client;
+
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.JedisCluster;
+import redis.clients.jedis.JedisClusterHostAndPortMap;
+import redis.clients.jedis.JedisPool;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSocketFactory;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * @author Milton Lai(millton.lai@gmail.com)
+ */
+public class RedisClientBuilder {
+ private GenericRedisClient client;
+
+ public RedisClientBuilder(GenericObjectPoolConfig poolConfig, String hosts, int connectionTimeout, int soTimeout,
+ int maxAttempts, String password, int database, String clientName, boolean ssl, SSLSocketFactory sslSocketFactory,
+ SSLParameters sslParameters, HostnameVerifier hostnameVerifier, JedisClusterHostAndPortMap hostAndPortMap) {
+
+ if (hosts.indexOf(',') > 0) {
+ // cluster config
+ Set nodes = new LinkedHashSet<>();
+ for (String host : hosts.split(",[ ]*")) {
+ String[] hostParts = host.split(":");
+ if ((hostParts.length != 2) || !(hostParts[1].matches("\\d+"))) {
+ throw new RuntimeException("Invalid host name set for redis cluster: " + host);
+ }
+ nodes.add(new HostAndPort(hostParts[0], Integer.parseInt(hostParts[1])));
+ }
+ JedisCluster cluster = new JedisCluster(nodes, connectionTimeout, soTimeout, maxAttempts, password, clientName,
+ poolConfig, ssl, sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMap);
+ this.client = new ClusterRedisClient(cluster);
+
+ } else {
+ // pool config
+ password = (password == null || password.trim().length() == 0) ? null : password.trim();
+
+ String[] hostParts = hosts.split(":");
+ if ((hostParts.length != 2) || !(hostParts[1].matches("\\d+"))) {
+ throw new RuntimeException("Invalid host name set for redis cluster: " + hosts);
+ }
+ JedisPool pool = new JedisPool(poolConfig, hostParts[0], Integer.parseInt(hostParts[1]), connectionTimeout,
+ soTimeout, password, database, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
+ this.client = new PooledRedisClient(pool);
+ }
+ }
+
+ public GenericRedisClient getClient() {
+ return client;
+ }
+}
diff --git a/src/test/java/org/mybatis/caches/redis/RedisConfigurationBuilderTest.java b/src/test/java/org/mybatis/caches/redis/RedisConfigurationBuilderTest.java
index f5e6628..678a65b 100644
--- a/src/test/java/org/mybatis/caches/redis/RedisConfigurationBuilderTest.java
+++ b/src/test/java/org/mybatis/caches/redis/RedisConfigurationBuilderTest.java
@@ -1,5 +1,5 @@
/**
- * Copyright 2015-2018 the original author or authors.
+ * Copyright 2015-2020 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.
@@ -15,13 +15,9 @@
*/
package org.mybatis.caches.redis;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Test;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
import org.mybatis.caches.redis.sslconfig.TestHostnameVerifier;
import org.mybatis.caches.redis.sslconfig.TestSSLParameters;
import org.mybatis.caches.redis.sslconfig.TestSSLSocketFactory;
@@ -33,11 +29,11 @@ public void testDefaults() throws Exception {
System.setProperty(RedisConfigurationBuilder.SYSTEM_PROPERTY_REDIS_PROPERTIES_FILENAME, "no-such-file.properties");
RedisConfig redisConfig = RedisConfigurationBuilder.getInstance()
.parseConfiguration(this.getClass().getClassLoader());
- assertEquals(JDKSerializer.class, redisConfig.getSerializer().getClass());
- assertFalse(redisConfig.isSsl());
- assertNull(redisConfig.getSslSocketFactory());
- assertNull(redisConfig.getSslParameters());
- assertNull(redisConfig.getHostnameVerifier());
+ Assert.assertEquals(JDKSerializer.class, redisConfig.getSerializer().getClass());
+ Assert.assertFalse(redisConfig.isSsl());
+ Assert.assertNull(redisConfig.getSslSocketFactory());
+ Assert.assertNull(redisConfig.getSslParameters());
+ Assert.assertNull(redisConfig.getHostnameVerifier());
}
@Test
@@ -45,14 +41,14 @@ public void test1() throws Exception {
System.setProperty(RedisConfigurationBuilder.SYSTEM_PROPERTY_REDIS_PROPERTIES_FILENAME, "test1.properties");
RedisConfig redisConfig = RedisConfigurationBuilder.getInstance()
.parseConfiguration(this.getClass().getClassLoader());
- assertEquals(KryoSerializer.class, redisConfig.getSerializer().getClass());
- assertTrue(redisConfig.isSsl());
- assertEquals(TestSSLSocketFactory.class, redisConfig.getSslSocketFactory().getClass());
- assertEquals(TestSSLParameters.class, redisConfig.getSslParameters().getClass());
- assertEquals(TestHostnameVerifier.class, redisConfig.getHostnameVerifier().getClass());
+ Assert.assertEquals(KryoSerializer.class, redisConfig.getSerializer().getClass());
+ Assert.assertTrue(redisConfig.isSsl());
+ Assert.assertEquals(TestSSLSocketFactory.class, redisConfig.getSslSocketFactory().getClass());
+ Assert.assertEquals(TestSSLParameters.class, redisConfig.getSslParameters().getClass());
+ Assert.assertEquals(TestHostnameVerifier.class, redisConfig.getHostnameVerifier().getClass());
}
- @AfterEach
+ @After
public void after() {
System.setProperty(RedisConfigurationBuilder.SYSTEM_PROPERTY_REDIS_PROPERTIES_FILENAME,
RedisConfigurationBuilder.REDIS_RESOURCE);
diff --git a/src/test/java/org/mybatis/caches/redis/RedisTestCase.java b/src/test/java/org/mybatis/caches/redis/RedisTestCase.java
index 5d57a3e..1b63178 100644
--- a/src/test/java/org/mybatis/caches/redis/RedisTestCase.java
+++ b/src/test/java/org/mybatis/caches/redis/RedisTestCase.java
@@ -1,5 +1,5 @@
/**
- * Copyright 2015-2018 the original author or authors.
+ * Copyright 2015-2020 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.
@@ -15,13 +15,10 @@
*/
package org.mybatis.caches.redis;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
/**
* Test with Ubuntu sudo apt-get install redis-server execute the test
@@ -32,25 +29,27 @@ public final class RedisTestCase {
private static RedisCache cache;
- @BeforeAll
- public static void newCache() {
+ @Before
+ public void newCache() {
cache = new RedisCache(DEFAULT_ID);
+ cache.setTimeout(300);
}
@Test
public void shouldDemonstrateCopiesAreEqual() {
for (int i = 0; i < 1000; i++) {
cache.putObject(i, i);
- assertEquals(i, cache.getObject(i));
+
+ Assert.assertEquals(i, cache.getObject(i));
}
}
@Test
public void shouldRemoveItemOnDemand() {
cache.putObject(0, 0);
- assertNotNull(cache.getObject(0));
+ Assert.assertNotNull(cache.getObject(0));
cache.removeObject(0);
- assertNull(cache.getObject(0));
+ Assert.assertNull(cache.getObject(0));
}
@Test
@@ -58,28 +57,26 @@ public void shouldFlushAllItemsOnDemand() {
for (int i = 0; i < 5; i++) {
cache.putObject(i, i);
}
- assertNotNull(cache.getObject(0));
- assertNotNull(cache.getObject(4));
+ Assert.assertNotNull(cache.getObject(0));
+ Assert.assertNotNull(cache.getObject(4));
cache.clear();
- assertNull(cache.getObject(0));
- assertNull(cache.getObject(4));
+ Assert.assertNull(cache.getObject(0));
+ Assert.assertNull(cache.getObject(4));
}
- @Test
+ @Test(expected = IllegalArgumentException.class)
public void shouldNotCreateCache() {
- assertThrows(IllegalArgumentException.class, () -> {
- cache = new RedisCache(null);
- });
+ cache = new RedisCache(null);
}
@Test
public void shouldVerifyCacheId() {
- assertEquals("REDIS", cache.getId());
+ Assert.assertEquals("REDIS", cache.getId());
}
@Test
public void shouldVerifyToString() {
- assertEquals("Redis {REDIS}", cache.toString());
+ Assert.assertEquals("Redis {REDIS}", cache.toString());
}
@Test
@@ -90,20 +87,26 @@ public void shouldDeleteExpiredCache() throws Exception {
Thread.sleep(2000);
cache.putObject(1, 1);
// 2 secs : not expired yet
- assertEquals(0, cache.getObject(0));
+ Assert.assertEquals(0, cache.getObject(0));
Thread.sleep(2000);
- // 4 secs : should be expired
- assertNull(cache.getObject(0));
- assertNull(cache.getObject(1));
- // Make sure timeout is re-set
+ // 4 secs : (0) should be expired
+ Assert.assertNull(cache.getObject(0));
+ Assert.assertNotNull(cache.getObject(1));
+ Thread.sleep(2000);
+ // 4 secs : (1) should be expired
+ Assert.assertNull(cache.getObject(1));
+
cache.putObject(2, 2);
Thread.sleep(2000);
// 2 secs : not expired yet
cache.putObject(3, 3);
- assertEquals(2, cache.getObject(2));
+ Assert.assertEquals(2, cache.getObject(2));
+ Thread.sleep(2000);
+ // 4 secs : (2) should be expired
+ Assert.assertNull(cache.getObject(2));
+ Assert.assertNotNull(cache.getObject(3));
Thread.sleep(2000);
// 4 secs : should be expired
- assertNull(cache.getObject(2));
- assertNull(cache.getObject(3));
+ Assert.assertNull(cache.getObject(3));
}
}
diff --git a/src/test/java/org/mybatis/caches/redis/SerializerTestCase.java b/src/test/java/org/mybatis/caches/redis/SerializerTestCase.java
index fbf0c72..ff18127 100644
--- a/src/test/java/org/mybatis/caches/redis/SerializerTestCase.java
+++ b/src/test/java/org/mybatis/caches/redis/SerializerTestCase.java
@@ -1,5 +1,5 @@
/**
- * Copyright 2015-2018 the original author or authors.
+ * Copyright 2015-2020 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.
@@ -15,18 +15,19 @@
*/
package org.mybatis.caches.redis;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Arrays;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
public class SerializerTestCase {
@@ -35,7 +36,7 @@ public class SerializerTestCase {
Serializer kryoSerializer;
Serializer jdkSerializer;
- @BeforeEach
+ @Before
public void setup() {
kryoSerializer = KryoSerializer.INSTANCE;
jdkSerializer = JDKSerializer.INSTANCE;
@@ -44,30 +45,30 @@ public void setup() {
@Test
public void testKryoUnserializeNull() {
Object obj = kryoSerializer.unserialize(null);
- assertNull(obj);
+ Assert.assertNull(obj);
}
@Test
public void testJDKUnserializeNull() {
Object obj = jdkSerializer.unserialize(null);
- assertNull(obj);
+ Assert.assertNull(obj);
}
public void testKryoSerialize() {
SimpleBeanStudentInfo rawSimpleBean = new SimpleBeanStudentInfo();
for (int i = 0; i != max; ++i) {
- kryoSerializer.serialize(rawSimpleBean);
+ kryoSerializer.serialize(1, rawSimpleBean);
}
- byte[] serialBytes = kryoSerializer.serialize(rawSimpleBean);
+ byte[] serialBytes = kryoSerializer.serialize(1, rawSimpleBean);
SimpleBeanStudentInfo unserializeSimpleBean = (SimpleBeanStudentInfo) kryoSerializer.unserialize(serialBytes);
for (int i = 0; i != max; ++i) {
kryoSerializer.unserialize(serialBytes);
}
- assertEquals(rawSimpleBean, unserializeSimpleBean);
+ Assert.assertEquals(rawSimpleBean, unserializeSimpleBean);
}
@@ -75,23 +76,23 @@ public void testKryoSerialize() {
public void testKryoFallbackSerialize() throws IOException {
SimpleBeanStudentInfo rawSimpleBean = new SimpleBeanStudentInfo();
- byte[] serialBytes = jdkSerializer.serialize(rawSimpleBean);
+ byte[] serialBytes = jdkSerializer.serialize(1, rawSimpleBean);
SimpleBeanStudentInfo unserializeSimpleBean = (SimpleBeanStudentInfo) kryoSerializer.unserialize(serialBytes);
- assertEquals(rawSimpleBean, unserializeSimpleBean);
+ Assert.assertEquals(rawSimpleBean, unserializeSimpleBean);
}
@Test
public void testKryoUnserializeWithoutRegistry() throws IOException {
SimpleBeanStudentInfo rawSimpleBean = new SimpleBeanStudentInfo();
-
- byte[] serialBytes = kryoSerializer.serialize(rawSimpleBean);
+ byte[] serialBytes = kryoSerializer.serialize(1, rawSimpleBean);
Kryo kryoWithoutRegisty = new Kryo();
- Input input = new Input(serialBytes);
+ byte[] stripped = Arrays.copyOf(serialBytes, serialBytes.length - 8);
+ Input input = new Input(stripped);
SimpleBeanStudentInfo unserializeSimpleBean = (SimpleBeanStudentInfo) kryoWithoutRegisty.readClassAndObject(input);
- assertEquals(rawSimpleBean, unserializeSimpleBean);
+ Assert.assertEquals(rawSimpleBean, unserializeSimpleBean);
}
@@ -118,10 +119,11 @@ public void testKryoUnserializeWithoutRegistryWithFile() throws IOException {
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
+ buffer.write(kryoSerializer.longToBytes(System.currentTimeMillis()));
buffer.flush();
SimpleBeanCourseInfo unserializeSimpleBean = (SimpleBeanCourseInfo) kryoSerializer.unserialize(data);
- assertEquals(rawSimpleBean, unserializeSimpleBean);
+ Assert.assertEquals(rawSimpleBean, unserializeSimpleBean);
}
@@ -130,23 +132,26 @@ public void testJDKSerialize() {
SimpleBeanStudentInfo rawSimpleBean = new SimpleBeanStudentInfo();
for (int i = 0; i != max; ++i) {
- jdkSerializer.serialize(rawSimpleBean);
+ long ts = Long.MAX_VALUE - i;
+ byte[] bytes = jdkSerializer.serialize(ts, rawSimpleBean);
+ Assert.assertEquals(jdkSerializer.getTimestamp(bytes), ts);
+ Assert.assertEquals(rawSimpleBean, jdkSerializer.unserialize(bytes));
}
- byte[] serialBytes = jdkSerializer.serialize(rawSimpleBean);
+ byte[] serialBytes = jdkSerializer.serialize(1, rawSimpleBean);
SimpleBeanStudentInfo unserializeSimpleBean = (SimpleBeanStudentInfo) jdkSerializer.unserialize(serialBytes);
for (int i = 0; i != max; ++i) {
jdkSerializer.unserialize(serialBytes);
}
- assertEquals(rawSimpleBean, unserializeSimpleBean);
+ Assert.assertEquals(rawSimpleBean, unserializeSimpleBean);
}
@Test
public void testSerializeCofig() {
RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();
- assertEquals(JDKSerializer.class, redisConfig.getSerializer().getClass());
+ Assert.assertEquals(JDKSerializer.class, redisConfig.getSerializer().getClass());
}
}
diff --git a/src/test/resources/redis.properties b/src/test/resources/redis.properties
index ce57d8a..0d05cbd 100644
--- a/src/test/resources/redis.properties
+++ b/src/test/resources/redis.properties
@@ -1,5 +1,5 @@
#
-# Copyright 2015-2018 the original author or authors.
+# Copyright 2015-2020 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.
@@ -14,10 +14,10 @@
# limitations under the License.
#
-redis.host=localhost
-redis.port=6379
+redis.hosts=localhost:6379
redis.connectionTimeout=5000
redis.soTimeout=5000
+redis.maxAttempts=5
redis.password=
redis.database=0
redis.clientName=
@@ -30,3 +30,4 @@ redis.serializer=jdk
#redis.sslSocketFactory=
#redis.sslParameters=
#redis.hostnameVerifier=
+#redis.hostAndPortMap=