diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java
index 702948003..5cdf6ee9e 100644
--- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java
+++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2015 the original author or authors.
+ * Copyright 2011-2016 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.
@@ -36,6 +36,7 @@ import org.springframework.data.redis.connection.convert.SetConverter;
import org.springframework.data.redis.core.ConvertingCursor;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
+import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@@ -730,6 +731,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection {
delegate.set(key, value);
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.redis.connection.RedisStringCommands#set(byte[], byte[], org.springframework.data.redis.core.types.Expiration, org.springframework.data.redis.connection.RedisStringCommands.SetOptions)
+ */
+ @Override
+ public void set(byte[] key, byte[] value, Expiration expiration, SetOption option) {
+ delegate.set(key, value, expiration, option);
+ }
+
public Boolean setBit(byte[] key, long offset, boolean value) {
return delegate.setBit(key, offset, value);
}
@@ -1860,6 +1870,14 @@ public class DefaultStringRedisConnection implements StringRedisConnection {
delegate.set(serialize(key), serialize(value));
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.redis.connection.StringRedisConnection#set(java.lang.String, java.lang.String, org.springframework.data.redis.core.types.Expiration, org.springframework.data.redis.connection.RedisStringCommands.SetOptions)
+ */
+ public void set(String key, String value, Expiration expiration, SetOption option) {
+ set(serialize(key), serialize(value), expiration, option);
+ }
+
public Boolean setBit(String key, long offset, boolean value) {
return delegate.setBit(serialize(key), offset, value);
}
diff --git a/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java
index 52afdc755..305629e9c 100644
--- a/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java
+++ b/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2014 the original author or authors.
+ * Copyright 2011-2016 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.
@@ -18,6 +18,8 @@ package org.springframework.data.redis.connection;
import java.util.List;
import java.util.Map;
+import org.springframework.data.redis.core.types.Expiration;
+
/**
* String/Value-specific commands supported by Redis.
*
@@ -71,6 +73,18 @@ public interface RedisStringCommands {
*/
void set(byte[] key, byte[] value);
+ /**
+ * Set {@code value} for {@code key} applying timeouts from {@code expiration} if set and inserting/updating values
+ * depending on {@code options}.
+ *
+ * @param key must not be {@literal null}.
+ * @param value must not be {@literal null}.
+ * @param expiration can be {@literal null}. Defaulted to {@link Expiration#persistent()}.
+ * @param option can be {@literal null}. Defaulted to {@link SetOption#UPSERT}.
+ * @since 1.7
+ */
+ void set(byte[] key, byte[] value, Expiration expiration, SetOption option);
+
/**
* Set {@code value} for {@code key}, only if {@code key} does not exist.
*
@@ -278,4 +292,61 @@ public interface RedisStringCommands {
* @return
*/
Long strLen(byte[] key);
+
+ /**
+ * {@code SET} command arguments for {@code NX}, {@code XX}.
+ *
+ * @author Christoph Strobl
+ * @since 1.7
+ */
+ public static enum SetOption {
+
+ /**
+ * Do not set any additional command argument.
+ *
+ * @return
+ */
+ UPSERT,
+
+ /**
+ * {@code NX}
+ *
+ * @return
+ */
+ SET_IF_ABSENT,
+
+ /**
+ * {@code XX}
+ *
+ * @return
+ */
+ SET_IF_PRESENT;
+
+ /**
+ * Do not set any additional command argument.
+ *
+ * @return
+ */
+ public static SetOption upsert() {
+ return UPSERT;
+ }
+
+ /**
+ * {@code XX}
+ *
+ * @return
+ */
+ public static SetOption ifPresent() {
+ return SET_IF_PRESENT;
+ }
+
+ /**
+ * {@code NX}
+ *
+ * @return
+ */
+ public static SetOption ifAbsent() {
+ return SET_IF_ABSENT;
+ }
+ }
}
diff --git a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java
index c3c5b6548..b776bceef 100644
--- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java
+++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java
@@ -24,6 +24,7 @@ import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.data.redis.serializer.RedisSerializer;
@@ -95,6 +96,18 @@ public interface StringRedisConnection extends RedisConnection {
void set(String key, String value);
+ /**
+ * Set {@code value} for {@code key} applying timeouts from {@code expiration} if set and inserting/updating values
+ * depending on {@code options}.
+ *
+ * @param key must not be {@literal null}.
+ * @param value must not be {@literal null}.
+ * @param expiration can be {@literal null}. Defaulted to {@link Expiration#persistent()}.
+ * @param option can be {@literal null}. Defaulted to {@link SetOption#UPSERT}.
+ * @since 1.7
+ */
+ void set(String key, String value, Expiration expiration, SetOption option);
+
Boolean setNX(String key, String value);
void setEx(String key, long seconds, String value);
diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java
index b53c32cf3..11aab7af5 100644
--- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java
+++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java
@@ -28,6 +28,7 @@ import java.util.Map.Entry;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.dao.DataAccessException;
@@ -61,10 +62,11 @@ import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanCursor;
import org.springframework.data.redis.core.ScanIteration;
import org.springframework.data.redis.core.ScanOptions;
+import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
-import org.springframework.util.StringUtils;
+import org.springframework.util.ObjectUtils;
import redis.clients.jedis.BinaryJedisPubSub;
import redis.clients.jedis.Jedis;
@@ -249,7 +251,8 @@ public class JedisClusterConnection implements RedisClusterConnection {
@Override
public byte[] randomKey() {
- List nodes = new ArrayList(topologyProvider.getTopology().getActiveMasterNodes());
+ List nodes = new ArrayList(topologyProvider.getTopology()
+ .getActiveMasterNodes());
Set inspectedNodes = new HashSet(nodes.size());
do {
@@ -594,6 +597,49 @@ public class JedisClusterConnection implements RedisClusterConnection {
}
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.redis.connection.RedisStringCommands#set(byte[], byte[], org.springframework.data.redis.core.types.Expiration, org.springframework.data.redis.connection.RedisStringCommands.SetOptions)
+ */
+ @Override
+ public void set(byte[] key, byte[] value, Expiration expiration, SetOption option) {
+
+ if (expiration == null || expiration.isPersitent()) {
+
+ if (option == null || ObjectUtils.nullSafeEquals(SetOption.UPSERT, option)) {
+ set(key, value);
+ } else {
+
+ // BinaryCluster does not support set with nxxx and binary key/value pairs.
+ if (ObjectUtils.nullSafeEquals(SetOption.SET_IF_PRESENT, option)) {
+ throw new UnsupportedOperationException("Jedis does not support SET XX without PX or EX on BinaryCluster.");
+ }
+
+ setNX(key, value);
+ }
+ } else {
+
+ if (option == null || ObjectUtils.nullSafeEquals(SetOption.UPSERT, option)) {
+
+ if (ObjectUtils.nullSafeEquals(TimeUnit.MILLISECONDS, expiration.getTimeUnit())) {
+ pSetEx(key, expiration.getExpirationTime(), value);
+ } else {
+ setEx(key, expiration.getExpirationTime(), value);
+ }
+ } else {
+
+ byte[] nxxx = JedisConverters.toSetCommandNxXxArgument(option);
+ byte[] expx = JedisConverters.toSetCommandExPxArgument(expiration);
+
+ try {
+ cluster.set(key, value, nxxx, expx, expiration.getExpirationTime());
+ } catch (Exception ex) {
+ throw convertJedisAccessException(ex);
+ }
+ }
+ }
+ }
+
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisStringCommands#setNX(byte[], byte[])
@@ -3432,7 +3478,8 @@ public class JedisClusterConnection implements RedisClusterConnection {
@Override
public void clusterForget(final RedisClusterNode node) {
- Set nodes = new LinkedHashSet(topologyProvider.getTopology().getActiveMasterNodes());
+ Set nodes = new LinkedHashSet(topologyProvider.getTopology()
+ .getActiveMasterNodes());
final RedisClusterNode nodeToRemove = topologyProvider.getTopology().lookup(node);
nodes.remove(nodeToRemove);
@@ -3536,8 +3583,7 @@ public class JedisClusterConnection implements RedisClusterConnection {
Assert.notNull(master, "Master cannot be null!");
- final RedisClusterNode nodeToUse = topologyProvider.getTopology()
- .lookup(master);
+ final RedisClusterNode nodeToUse = topologyProvider.getTopology().lookup(master);
return JedisConverters.toSetOfRedisClusterNodes(clusterCommandExecutor.executeCommandOnSingleNode(
new JedisClusterCommandCallback>() {
diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java
index c199294ae..f9d8f83ea 100644
--- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java
+++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2015 the original author or authors.
+ * Copyright 2011-2016 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.
@@ -54,6 +54,7 @@ import org.springframework.data.redis.core.KeyBoundCursor;
import org.springframework.data.redis.core.ScanCursor;
import org.springframework.data.redis.core.ScanIteration;
import org.springframework.data.redis.core.ScanOptions;
+import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -1139,6 +1140,87 @@ public class JedisConnection extends AbstractRedisConnection {
}
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.redis.connection.RedisStringCommands#set(byte[], byte[], org.springframework.data.redis.core.types.Expiration, org.springframework.data.redis.connection.RedisStringCommands.SetOption)
+ */
+ @Override
+ public void set(byte[] key, byte[] value, Expiration expiration, SetOption option) {
+
+ if (expiration == null || expiration.isPersitent()) {
+
+ if (option == null || ObjectUtils.nullSafeEquals(SetOption.UPSERT, option)) {
+ set(key, value);
+ } else {
+
+ try {
+
+ byte[] nxxx = JedisConverters.toSetCommandNxXxArgument(option);
+
+ if (isPipelined()) {
+
+ pipeline(new JedisStatusResult(pipeline.set(key, value, nxxx)));
+ return;
+ }
+ if (isQueueing()) {
+
+ transaction(new JedisStatusResult(transaction.set(key, value, nxxx)));
+ return;
+ }
+
+ jedis.set(key, value, nxxx);
+ } catch (Exception ex) {
+ throw convertJedisAccessException(ex);
+ }
+ }
+
+ } else {
+
+ if (option == null || ObjectUtils.nullSafeEquals(SetOption.UPSERT, option)) {
+
+ if (ObjectUtils.nullSafeEquals(TimeUnit.MILLISECONDS, expiration.getTimeUnit())) {
+ pSetEx(key, expiration.getExpirationTime(), value);
+ } else {
+ setEx(key, expiration.getExpirationTime(), value);
+ }
+ } else {
+
+ byte[] nxxx = JedisConverters.toSetCommandNxXxArgument(option);
+ byte[] expx = JedisConverters.toSetCommandExPxArgument(expiration);
+
+ try {
+ if (isPipelined()) {
+
+ if (expiration.getExpirationTime() > Integer.MAX_VALUE) {
+
+ throw new IllegalArgumentException(
+ "Expiration.exprirationTime must be less than equals Integer.MAX_VALUE for pipeline in Jedis.");
+ }
+
+ pipeline(new JedisStatusResult(pipeline.set(key, value, nxxx, expx, (int) expiration.getExpirationTime())));
+ return;
+ }
+ if (isQueueing()) {
+
+ if (expiration.getExpirationTime() > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException(
+ "Expiration.exprirationTime must be less than equals Integer.MAX_VALUE for transactions in Jedis.");
+ }
+
+ transaction(new JedisStatusResult(transaction.set(key, value, nxxx, expx,
+ (int) expiration.getExpirationTime())));
+ return;
+ }
+
+ jedis.set(key, value, nxxx, expx, expiration.getExpirationTime());
+
+ } catch (Exception ex) {
+ throw convertJedisAccessException(ex);
+ }
+ }
+ }
+ }
+
public byte[] getSet(byte[] key, byte[] value) {
try {
if (isPipelined()) {
diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java
index e4a6c4101..b83b5da2c 100644
--- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java
+++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2015 the original author or authors.
+ * Copyright 2013-2016 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.
@@ -17,11 +17,11 @@ package org.springframework.data.redis.connection.jedis;
import java.nio.ByteBuffer;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import org.springframework.core.convert.converter.Converter;
import org.springframework.dao.DataAccessException;
@@ -29,7 +29,9 @@ import org.springframework.data.redis.connection.DefaultTuple;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisListCommands.Position;
import org.springframework.data.redis.connection.RedisServer;
+import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.RedisStringCommands.BitOperation;
+import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
import org.springframework.data.redis.connection.RedisZSetCommands.Range.Boundary;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.connection.SortParameters;
@@ -40,9 +42,11 @@ import org.springframework.data.redis.connection.convert.ListConverter;
import org.springframework.data.redis.connection.convert.MapConverter;
import org.springframework.data.redis.connection.convert.SetConverter;
import org.springframework.data.redis.connection.convert.StringToRedisClientInfoConverter;
+import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
+import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import redis.clients.jedis.BinaryClient.LIST_POSITION;
@@ -71,11 +75,17 @@ abstract public class JedisConverters extends Converters {
private static final Converter TUPLE_CONVERTER;
private static final ListConverter TUPLE_LIST_TO_TUPLE_LIST_CONVERTER;
private static final Converter