diff --git a/build.gradle b/build.gradle index 902847259..91bd4b249 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,7 @@ dependencies { compile("com.github.spullara.redis:client:$srpVersion", optional) compile("org.jredis:jredis-anthonylauzon:$jredisVersion", optional) compile("org.idevlab:rjc:$rjcVersion", optional) + compile("com.lambdaworks:lettuce:$lettuceVersion", optional) // Mappers compile("org.codehaus.jackson:jackson-mapper-asl:$jacksonVersion", optional) diff --git a/gradle.properties b/gradle.properties index 2f551da90..564ebe375 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,6 +17,7 @@ jedisVersion = 2.1.0 jredisVersion = 03122010 rjcVersion = 0.6.4 srpVersion = 0.2 +lettuceVersion = 2.2.0 # -------------------- # Project wide version diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/BytesRedisCodec.java b/src/main/java/org/springframework/data/redis/connection/lettuce/BytesRedisCodec.java new file mode 100644 index 000000000..67b7d967e --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/BytesRedisCodec.java @@ -0,0 +1,55 @@ +/* + * Copyright 2011-2013 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.springframework.data.redis.connection.lettuce; + +import java.nio.ByteBuffer; + +import com.lambdaworks.redis.codec.RedisCodec; + +/** + * Basic codec that returns the raw data as byte[]. + * + * @author Costin Leau + */ +class BytesRedisCodec extends RedisCodec { + + @Override + public byte[] decodeKey(ByteBuffer bytes) { + return getBytes(bytes); + } + + @Override + public byte[] decodeValue(ByteBuffer bytes) { + return getBytes(bytes); + } + + @Override + public byte[] encodeKey(byte[] key) { + return key; + } + + @Override + public byte[] encodeValue(byte[] value) { + return value; + } + + private static byte[] getBytes(ByteBuffer buffer) { + byte[] b = new byte[buffer.remaining()]; + buffer.get(b); + return b; + } +} diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java new file mode 100644 index 000000000..1169e2c26 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java @@ -0,0 +1,1830 @@ +/* + * Copyright 2011-2013 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.springframework.data.redis.connection.lettuce; + +import static com.lambdaworks.redis.protocol.CommandType.MULTI; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.jboss.netty.channel.ChannelException; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.QueryTimeoutException; +import org.springframework.data.redis.RedisConnectionFailureException; +import org.springframework.data.redis.RedisSystemException; +import org.springframework.data.redis.connection.DataType; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisPipelineException; +import org.springframework.data.redis.connection.RedisSubscribedConnectionException; +import org.springframework.data.redis.connection.SortParameters; +import org.springframework.data.redis.connection.Subscription; +import org.springframework.util.Assert; + +import com.lambdaworks.redis.RedisAsyncConnection; +import com.lambdaworks.redis.RedisClient; +import com.lambdaworks.redis.RedisException; +import com.lambdaworks.redis.SortArgs; +import com.lambdaworks.redis.ZStoreArgs; +import com.lambdaworks.redis.codec.RedisCodec; +import com.lambdaworks.redis.output.ByteArrayOutput; +import com.lambdaworks.redis.protocol.Command; +import com.lambdaworks.redis.protocol.CommandArgs; +import com.lambdaworks.redis.protocol.CommandType; +import com.lambdaworks.redis.pubsub.RedisPubSubConnection; + +/** + * {@code RedisConnection} implementation on top of Lettuce Redis client. + * + * @author Costin Leau + */ +public class LettuceConnection implements RedisConnection { + + private final com.lambdaworks.redis.RedisAsyncConnection asyncConn; + private final com.lambdaworks.redis.RedisConnection con; + private final RedisCodec codec = LettuceUtils.CODEC; + private final long timeout; + + // refers only to main connection as pubsub happens on a different one + private boolean isClosed = false; + private boolean isMulti = false; + private boolean isPipelined = false; + private List> ppline; + private RedisClient client; + private volatile LettuceSubscription subscription; + + /** + * Instantiates a new lettuce connection. + * + * @param connection underlying Lettuce async connection + * @param timeout the connection timeout (in milliseconds) + */ + public LettuceConnection(com.lambdaworks.redis.RedisAsyncConnection connection, long timeout, RedisClient client) { + Assert.notNull(connection, "a valid connection is required"); + + this.asyncConn = connection; + this.timeout = timeout; + this.con = new com.lambdaworks.redis.RedisConnection(asyncConn); + this.client = client; + } + + protected DataAccessException convertLettuceAccessException(Exception ex) { + if (ex instanceof RedisException) { + return LettuceUtils.convertRedisAccessException((RedisException) ex); + } + if (ex instanceof ChannelException) { + return new RedisConnectionFailureException("Redis connection failed", ex); + } + if (ex instanceof TimeoutException) { + return new QueryTimeoutException("Redis command timed out", ex); + } + return new RedisSystemException("Unknown Lettuce exception", ex); + } + + private Object await(Command cmd) { + if (isMulti && cmd.type != MULTI) + return null; + return asyncConn.await(cmd, timeout, TimeUnit.MILLISECONDS); + } + + public Object execute(String command, byte[]... args) { + Assert.hasText(command, "a valid command needs to be specified"); + try { + String name = command.trim().toUpperCase(); + CommandType cmd = CommandType.valueOf(name); + CommandArgs cmdArg = new CommandArgs(codec).addKeys(args); + + if (isPipelined()) { + asyncConn.dispatch(cmd, new ByteArrayOutput(codec), cmdArg); + return null; + } + else { + return await(asyncConn.dispatch(cmd, new ByteArrayOutput(codec), cmdArg)); + } + } catch (RedisException ex) { + throw convertLettuceAccessException(ex); + } + } + + public void close() throws DataAccessException { + isClosed = true; + + try { + asyncConn.close(); + } catch (RuntimeException ex) { + throw convertLettuceAccessException(ex); + } + } + + public boolean isClosed() { + return isClosed && !isSubscribed(); + } + + public RedisAsyncConnection getNativeConnection() { + return asyncConn; + } + + + public boolean isQueueing() { + return isMulti; + } + + public boolean isPipelined() { + return isPipelined; + } + + + public void openPipeline() { + if (!isPipelined) { + isPipelined = true; + ppline = new ArrayList>(); + } + } + + public List closePipeline() { + if (isPipelined) { + boolean done = asyncConn.awaitAll(ppline.toArray(new Command[ppline.size()])); + List results = new ArrayList(ppline.size()); + + if (done) { + for (Command cmd : ppline) { + results.add(cmd.get()); + } + } + ppline.clear(); + + if (done) { + return results; + } + + throw new RedisPipelineException(new QueryTimeoutException("Redis command timed out")); + } + + return Collections.emptyList(); + } + + + public List sort(byte[] key, SortParameters params) { + + SortArgs args = LettuceUtils.sort(params); + + try { + if (isPipelined()) { + pipeline(asyncConn.sort(key, args)); + return null; + } + return con.sort(key, args); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Long sort(byte[] key, SortParameters params, byte[] sortKey) { + + SortArgs args = LettuceUtils.sort(params); + + try { + if (isPipelined()) { + pipeline(asyncConn.sortStore(key, args, sortKey)); + return null; + } + return con.sortStore(key, args, sortKey); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Long dbSize() { + try { + if (isPipelined()) { + pipeline(asyncConn.dbsize()); + return null; + } + return con.dbsize(); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + + public void flushDb() { + try { + if (isPipelined()) { + pipeline(asyncConn.flushdb()); + return; + } + con.flushdb(); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void flushAll() { + try { + if (isPipelined()) { + pipeline(asyncConn.flushall()); + return; + } + con.flushall(); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void bgSave() { + try { + if (isPipelined()) { + pipeline(asyncConn.bgsave()); + return; + } + con.bgsave(); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void bgWriteAof() { + try { + if (isPipelined()) { + pipeline(asyncConn.bgrewriteaof()); + return; + } + con.bgrewriteaof(); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void save() { + try { + if (isPipelined()) { + pipeline(asyncConn.save()); + return; + } + con.save(); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public List getConfig(String param) { + try { + if (isPipelined()) { + pipeline(asyncConn.configGet(param)); + return null; + } + return con.configGet(param); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Properties info() { + try { + if (isPipelined()) { + pipeline(asyncConn.info()); + return null; + } + return LettuceUtils.info(con.info()); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Long lastSave() { + try { + if (isPipelined()) { + pipeline(asyncConn.lastsave()); + return null; + } + return con.lastsave().getTime(); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void setConfig(String param, String value) { + try { + if (isPipelined()) { + pipeline(asyncConn.configSet(param, value)); + return; + } + con.configSet(param, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + + public void resetConfigStats() { + try { + if (isPipelined()) { + pipeline(asyncConn.configResetstat()); + return; + } + con.configResetstat(); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void shutdown() { + try { + if (isPipelined()) { + asyncConn.shutdown(true); + return; + } + con.shutdown(true); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public byte[] echo(byte[] message) { + try { + if (isPipelined()) { + pipeline(asyncConn.echo(message)); + return null; + } + return con.echo(message); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public String ping() { + try { + if (isPipelined()) { + pipeline(asyncConn.ping()); + } + return con.ping(); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Long del(byte[]... keys) { + try { + if (isPipelined()) { + pipeline(asyncConn.del(keys)); + return null; + } + return con.del(keys); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void discard() { + isMulti = false; + try { + if (isPipelined()) { + con.discard(); + } + + con.discard(); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public List exec() { + isMulti = false; + try { + return con.exec(); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Boolean exists(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.exists(key)); + return null; + } + return con.exists(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Boolean expire(byte[] key, long seconds) { + try { + if (isPipelined()) { + pipeline(asyncConn.expire(key, seconds)); + return null; + } + return con.expire(key, seconds); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Boolean expireAt(byte[] key, long unixTime) { + try { + if (isPipelined()) { + pipeline(asyncConn.expireat(key, unixTime)); + return null; + } + return con.expireat(key, unixTime); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Set keys(byte[] pattern) { + try { + if (isPipelined()) { + pipeline(asyncConn.keys(pattern)); + return null; + } + return new LinkedHashSet(con.keys(pattern)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void multi() { + if (isQueueing()) { + return; + } + isMulti = true; + openPipeline(); + try { + if (isPipelined()) { + con.multi(); + return; + } + con.multi(); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Boolean persist(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.persist(key)); + return null; + } + return con.persist(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Boolean move(byte[] key, int dbIndex) { + try { + if (isPipelined()) { + pipeline(asyncConn.move(key, dbIndex)); + return null; + } + return con.move(key, dbIndex); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public byte[] randomKey() { + try { + if (isPipelined()) { + pipeline(asyncConn.randomkey()); + return null; + } + return con.randomkey(); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public void rename(byte[] oldName, byte[] newName) { + try { + if (isPipelined()) { + pipeline(asyncConn.rename(oldName, newName)); + return; + } + con.rename(oldName, newName); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Boolean renameNX(byte[] oldName, byte[] newName) { + try { + if (isPipelined()) { + pipeline(asyncConn.renamenx(oldName, newName)); + return null; + } + return (con.renamenx(oldName, newName)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void select(int dbIndex) { + try { + if (isPipelined()) { + asyncConn.select(dbIndex); + } + con.select(dbIndex); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Long ttl(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.ttl(key)); + return null; + } + return con.ttl(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public DataType type(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.type(key)); + return null; + } + return DataType.fromCode(con.type(key)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void unwatch() { + try { + if (isPipelined()) { + pipeline(asyncConn.unwatch()); + } + + con.unwatch(); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void watch(byte[]... keys) { + try { + if (isPipelined()) { + pipeline(asyncConn.watch(keys)); + } + else { + con.watch(keys); + } + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + // + // String commands + // + + public byte[] get(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.get(key)); + return null; + } + + return con.get(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public void set(byte[] key, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.set(key, value)); + return; + } + con.set(key, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + + public byte[] getSet(byte[] key, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.getset(key, value)); + return null; + } + return con.getset(key, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Long append(byte[] key, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.append(key, value)); + return null; + } + return con.append(key, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public List mGet(byte[]... keys) { + try { + if (isPipelined()) { + pipeline(asyncConn.mget(keys)); + ; + return null; + } + return con.mget(keys); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void mSet(Map tuples) { + try { + if (isPipelined()) { + pipeline(asyncConn.mset(tuples)); + return; + } + con.mset(tuples); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void mSetNX(Map tuples) { + try { + if (isPipelined()) { + pipeline(asyncConn.msetnx(tuples)); + return; + } + con.msetnx(tuples); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void setEx(byte[] key, long time, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.setex(key, time, value)); + return; + } + con.setex(key, time, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Boolean setNX(byte[] key, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.setnx(key, value)); + return null; + } + return con.setnx(key, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public byte[] getRange(byte[] key, long start, long end) { + try { + if (isPipelined()) { + pipeline(asyncConn.getrange(key, start, end)); + return null; + } + return con.getrange(key, start, end); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Long decr(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.decr(key)); + return null; + } + return con.decr(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Long decrBy(byte[] key, long value) { + try { + if (isPipelined()) { + pipeline(asyncConn.decrby(key, value)); + return null; + } + return con.decrby(key, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Long incr(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.incr(key)); + return null; + } + return con.incr(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Long incrBy(byte[] key, long value) { + try { + if (isPipelined()) { + pipeline(asyncConn.incrby(key, value)); + return null; + } + return con.incrby(key, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Boolean getBit(byte[] key, long offset) { + try { + if (isPipelined()) { + asyncConn.getbit(key, offset); + return null; + } + return Long.valueOf(1).equals(con.getbit(key, offset)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void setBit(byte[] key, long offset, boolean value) { + try { + if (isPipelined()) { + asyncConn.setbit(key, offset, LettuceUtils.asBit(value)); + } + con.setbit(key, offset, LettuceUtils.asBit(value)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void setRange(byte[] key, byte[] value, long start) { + try { + if (isPipelined()) { + asyncConn.setrange(key, start, value); + } + con.setrange(key, start, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Long strLen(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.strlen(key)); + return null; + } + return con.strlen(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + // + // List commands + // + + + public Long lPush(byte[] key, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.lpush(key, value)); + return null; + } + return con.lpush(key, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Long rPush(byte[] key, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.rpush(key, value)); + return null; + } + return con.rpush(key, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public List bLPop(int timeout, byte[]... keys) { + try { + if (isPipelined()) { + pipeline(asyncConn.blpop(timeout, keys)); + return null; + } + return LettuceUtils.toList(con.blpop(timeout, keys)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public List bRPop(int timeout, byte[]... keys) { + try { + if (isPipelined()) { + pipeline(asyncConn.brpop(timeout, keys)); + return null; + } + return LettuceUtils.toList(con.brpop(timeout, keys)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public byte[] lIndex(byte[] key, long index) { + try { + if (isPipelined()) { + pipeline(asyncConn.lindex(key, index)); + return null; + } + return con.lindex(key, index); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Long lInsert(byte[] key, Position where, byte[] pivot, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.linsert(key, LettuceUtils.convertPosition(where), pivot, value)); + return null; + } + return con.linsert(key, LettuceUtils.convertPosition(where), pivot, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Long lLen(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.llen(key)); + return null; + } + return con.llen(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public byte[] lPop(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.lpop(key)); + return null; + } + return con.lpop(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public List lRange(byte[] key, long start, long end) { + try { + if (isPipelined()) { + pipeline(asyncConn.lrange(key, start, end)); + return null; + } + return con.lrange(key, start, end); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Long lRem(byte[] key, long count, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.lrem(key, count, value)); + return null; + } + return con.lrem(key, count, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public void lSet(byte[] key, long index, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.lset(key, index, value)); + return; + } + con.lset(key, index, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void lTrim(byte[] key, long start, long end) { + try { + if (isPipelined()) { + pipeline(asyncConn.ltrim(key, start, end)); + return; + } + con.ltrim(key, start, end); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public byte[] rPop(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.rpop(key)); + return null; + } + return con.rpop(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public byte[] rPopLPush(byte[] srcKey, byte[] dstKey) { + try { + if (isPipelined()) { + pipeline(asyncConn.rpoplpush(srcKey, dstKey)); + return null; + } + return con.rpoplpush(srcKey, dstKey); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public byte[] bRPopLPush(int timeout, byte[] srcKey, byte[] dstKey) { + try { + if (isPipelined()) { + pipeline(asyncConn.brpoplpush(timeout, srcKey, dstKey)); + return null; + } + return con.brpoplpush(timeout, srcKey, dstKey); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Long lPushX(byte[] key, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.lpushx(key, value)); + return null; + } + return con.lpushx(key, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Long rPushX(byte[] key, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.rpushx(key, value)); + return null; + } + return con.rpushx(key, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + // + // Set commands + // + + + public Boolean sAdd(byte[] key, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.sadd(key, value)); + return null; + } + return con.sadd(key, value) == 1; + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Long sCard(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.scard(key)); + return null; + } + return con.scard(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Set sDiff(byte[]... keys) { + try { + if (isPipelined()) { + pipeline(asyncConn.sdiff(keys)); + return null; + } + return con.sdiff(keys); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Long sDiffStore(byte[] destKey, byte[]... keys) { + try { + if (isPipelined()) { + pipeline(asyncConn.sdiffstore(destKey, keys)); + return null; + } + return con.sdiffstore(destKey, keys); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Set sInter(byte[]... keys) { + try { + if (isPipelined()) { + pipeline(asyncConn.sinter(keys)); + return null; + } + return con.sinter(keys); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Long sInterStore(byte[] destKey, byte[]... keys) { + try { + if (isPipelined()) { + pipeline(asyncConn.sinterstore(destKey, keys)); + return null; + } + return con.sinterstore(destKey, keys); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Boolean sIsMember(byte[] key, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.sismember(key, value)); + return null; + } + return con.sismember(key, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Set sMembers(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.smembers(key)); + return null; + } + return con.smembers(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Boolean sMove(byte[] srcKey, byte[] destKey, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.smove(srcKey, destKey, value)); + return null; + } + return con.smove(srcKey, destKey, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public byte[] sPop(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.spop(key)); + return null; + } + return con.spop(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public byte[] sRandMember(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.srandmember(key)); + return null; + } + return con.srandmember(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Boolean sRem(byte[] key, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.srem(key, value)); + return null; + } + return Long.valueOf(1).equals(con.srem(key, value)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Set sUnion(byte[]... keys) { + try { + if (isPipelined()) { + pipeline(asyncConn.sunion(keys)); + return null; + } + return con.sunion(keys); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Long sUnionStore(byte[] destKey, byte[]... keys) { + try { + if (isPipelined()) { + pipeline(asyncConn.sunionstore(destKey, keys)); + return null; + } + return con.sunionstore(destKey, keys); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + // + // ZSet commands + // + + + public Boolean zAdd(byte[] key, double score, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.zadd(key, score, value)); + return null; + } + return Long.valueOf(1).equals(con.zadd(key, score, value)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Long zCard(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.zcard(key)); + return null; + } + return con.zcard(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Long zCount(byte[] key, double min, double max) { + try { + if (isQueueing()) { + pipeline(asyncConn.zcount(key, min, max)); + return null; + } + return con.zcount(key, min, max); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Double zIncrBy(byte[] key, double increment, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.zincrby(key, increment, value)); + return null; + } + return con.zincrby(key, increment, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Long zInterStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) { + ZStoreArgs storeArgs = LettuceUtils.zArgs(aggregate, weights); + + try { + if (isQueueing()) { + pipeline(asyncConn.zinterstore(destKey, storeArgs, sets)); + return null; + } + return con.zinterstore(destKey, storeArgs, sets); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Long zInterStore(byte[] destKey, byte[]... sets) { + try { + if (isQueueing()) { + pipeline(asyncConn.zinterstore(destKey, sets)); + return null; + } + return con.zinterstore(destKey, sets); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Set zRange(byte[] key, long start, long end) { + try { + if (isPipelined()) { + pipeline(asyncConn.zrange(key, start, end)); + return null; + } + return new LinkedHashSet(con.zrange(key, start, end)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Set zRangeWithScores(byte[] key, long start, long end) { + try { + if (isPipelined()) { + pipeline(asyncConn.zrangeWithScores(key, start, end)); + return null; + } + return LettuceUtils.convertTuple(con.zrangeWithScores(key, start, end)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Set zRangeByScore(byte[] key, double min, double max) { + try { + if (isPipelined()) { + pipeline(asyncConn.zrangebyscore(key, min, max)); + return null; + } + return new LinkedHashSet(con.zrangebyscore(key, min, max)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Set zRangeByScoreWithScores(byte[] key, double min, double max) { + try { + if (isPipelined()) { + pipeline(asyncConn.zrangebyscoreWithScores(key, min, max)); + return null; + } + return LettuceUtils.convertTuple(con.zrangebyscoreWithScores(key, min, max)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Set zRevRangeWithScores(byte[] key, long start, long end) { + try { + if (isPipelined()) { + pipeline(asyncConn.zrevrangeWithScores(key, start, end)); + return null; + } + return LettuceUtils.convertTuple(con.zrevrangeWithScores(key, start, end)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Set zRangeByScore(byte[] key, double min, double max, long offset, long count) { + try { + if (isPipelined()) { + pipeline(asyncConn.zrangebyscore(key, min, max, offset, count)); + return null; + } + return new LinkedHashSet(con.zrangebyscore(key, min, max, offset, count)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Set zRangeByScoreWithScores(byte[] key, double min, double max, long offset, long count) { + try { + if (isPipelined()) { + pipeline(asyncConn.zrangebyscoreWithScores(key, min, max, offset, count)); + return null; + } + return LettuceUtils.convertTuple(con.zrangebyscoreWithScores(key, min, max, offset, count)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Set zRevRangeByScore(byte[] key, double min, double max, long offset, long count) { + try { + if (isPipelined()) { + pipeline(asyncConn.zrevrangebyscore(key, min, max, offset, count)); + return null; + } + return new LinkedHashSet(con.zrevrangebyscore(key, min, max, offset, count)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Set zRevRangeByScore(byte[] key, double min, double max) { + try { + if (isPipelined()) { + pipeline(asyncConn.zrevrangebyscore(key, min, max)); + return null; + } + return new LinkedHashSet(con.zrevrangebyscore(key, min, max)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Set zRevRangeByScoreWithScores(byte[] key, double min, double max, long offset, long count) { + try { + if (isPipelined()) { + pipeline(asyncConn.zrevrangebyscoreWithScores(key, min, max)); + return null; + } + return LettuceUtils.convertTuple(con.zrevrangebyscoreWithScores(key, min, max)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Set zRevRangeByScoreWithScores(byte[] key, double min, double max) { + try { + if (isPipelined()) { + pipeline(asyncConn.zrevrangebyscoreWithScores(key, min, max)); + return null; + } + return LettuceUtils.convertTuple(con.zrevrangebyscoreWithScores(key, min, max)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Long zRank(byte[] key, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.zrank(key, value)); + return null; + } + return con.zrank(key, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Boolean zRem(byte[] key, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.zrem(key, value)); + return null; + } + return Long.valueOf(1).equals(con.zrem(key, value)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Long zRemRange(byte[] key, long start, long end) { + try { + if (isPipelined()) { + pipeline(asyncConn.zremrangebyrank(key, start, end)); + return null; + } + return con.zremrangebyrank(key, start, end); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Long zRemRangeByScore(byte[] key, double min, double max) { + try { + if (isPipelined()) { + pipeline(asyncConn.zremrangebyscore(key, min, max)); + return null; + } + return con.zremrangebyscore(key, min, max); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Set zRevRange(byte[] key, long start, long end) { + try { + if (isPipelined()) { + pipeline(asyncConn.zrevrange(key, start, end)); + return null; + } + return new LinkedHashSet(con.zrevrange(key, start, end)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Long zRevRank(byte[] key, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.zrevrank(key, value)); + return null; + } + return con.zrevrank(key, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Double zScore(byte[] key, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.zscore(key, value)); + return null; + } + return con.zscore(key, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Long zUnionStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) { + ZStoreArgs storeArgs = LettuceUtils.zArgs(aggregate, weights); + + try { + if (isPipelined()) { + pipeline(asyncConn.zunionstore(destKey, storeArgs, sets)); + return null; + } + return con.zunionstore(destKey, storeArgs, sets); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Long zUnionStore(byte[] destKey, byte[]... sets) { + try { + if (isPipelined()) { + pipeline(asyncConn.zunionstore(destKey, sets)); + return null; + } + return con.zunionstore(destKey, sets); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + // + // Hash commands + // + + public Boolean hSet(byte[] key, byte[] field, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.hset(key, field, value)); + return null; + } + return con.hset(key, field, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Boolean hSetNX(byte[] key, byte[] field, byte[] value) { + try { + if (isPipelined()) { + pipeline(asyncConn.hsetnx(key, field, value)); + return null; + } + return con.hsetnx(key, field, value); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Boolean hDel(byte[] key, byte[] field) { + try { + if (isPipelined()) { + pipeline(asyncConn.hdel(key, field)); + return null; + } + return Long.valueOf(1).equals(con.hdel(key, field)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Boolean hExists(byte[] key, byte[] field) { + try { + if (isPipelined()) { + pipeline(asyncConn.hexists(key, field)); + return null; + } + return con.hexists(key, field); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public byte[] hGet(byte[] key, byte[] field) { + try { + if (isPipelined()) { + pipeline(asyncConn.hget(key, field)); + return null; + } + return con.hget(key, field); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Map hGetAll(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.hgetall(key)); + return null; + } + return con.hgetall(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Long hIncrBy(byte[] key, byte[] field, long delta) { + try { + if (isPipelined()) { + pipeline(asyncConn.hincrby(key, field, delta)); + return null; + } + return con.hincrby(key, field, delta); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Set hKeys(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.hkeys(key)); + return null; + } + return new LinkedHashSet(con.hkeys(key)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public Long hLen(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.hlen(key)); + return null; + } + return con.hlen(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public List hMGet(byte[] key, byte[]... fields) { + try { + if (isPipelined()) { + pipeline(asyncConn.hmget(key, fields)); + return null; + } + return con.hmget(key, fields); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void hMSet(byte[] key, Map tuple) { + try { + if (isPipelined()) { + pipeline(asyncConn.hmset(key, tuple)); + return; + } + con.hmset(key, tuple); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public List hVals(byte[] key) { + try { + if (isPipelined()) { + pipeline(asyncConn.hvals(key)); + return null; + } + return con.hvals(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + // + // Pub/Sub functionality + // + + public Long publish(byte[] channel, byte[] message) { + try { + if (isPipelined()) { + pipeline(asyncConn.publish(channel, message)); + return null; + } + return con.publish(channel, message); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + public Subscription getSubscription() { + return subscription; + } + + + public boolean isSubscribed() { + return (subscription != null && subscription.isAlive()); + } + + + public void pSubscribe(MessageListener listener, byte[]... patterns) { + checkSubscription(); + + try { + if (isQueueing()) { + throw new UnsupportedOperationException(); + } + if (isPipelined()) { + throw new UnsupportedOperationException(); + } + + subscription = new LettuceSubscription(listener, switchToPubSub()); + subscription.pSubscribe(patterns); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + + public void subscribe(MessageListener listener, byte[]... channels) { + checkSubscription(); + + try { + if (isPipelined()) { + throw new UnsupportedOperationException(); + } + subscription = new LettuceSubscription(listener, switchToPubSub()); + subscription.subscribe(channels); + + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + private void checkSubscription() { + if (isSubscribed()) { + throw new RedisSubscribedConnectionException( + "Connection already subscribed; use the connection Subscription to cancel or add new channels"); + } + } + + private RedisPubSubConnection switchToPubSub() { + close(); + // open a pubsub one + return client.connectPubSub(LettuceUtils.CODEC); + } + + private void pipeline(Future command) { + // the future will always be a command plus it throws no exception on #get + ppline.add((Command) command); + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java new file mode 100644 index 000000000..1b8d78aab --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java @@ -0,0 +1,130 @@ +/* + * Copyright 2011-2013 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.springframework.data.redis.connection.lettuce; + +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.dao.DataAccessException; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; + +import com.lambdaworks.redis.RedisClient; + +/** + * Connection factory creating Lettuce-based connections. + * + * @author Costin Leau + */ +/** + * @author Costin Leau + */ +public class LettuceConnectionFactory implements InitializingBean, DisposableBean, RedisConnectionFactory { + + private String hostName = "localhost"; + private int port = 6379; + private RedisClient client; + private long timeout = TimeUnit.MILLISECONDS.convert(60, TimeUnit.SECONDS); + + /** + * Constructs a new LettuceConnectionFactory instance + * with default settings. + */ + public LettuceConnectionFactory() { + } + + /** + * Constructs a new LettuceConnectionFactory instance + * with default settings. + */ + public LettuceConnectionFactory(String host, int port) { + this.hostName = host; + this.port = port; + } + + public void afterPropertiesSet() { + client = new RedisClient(hostName, port); + client.setDefaultTimeout(timeout, TimeUnit.MILLISECONDS); + } + + public void destroy() { + client.shutdown(); + } + + public RedisConnection getConnection() { + return new LettuceConnection(client.connectAsync(LettuceUtils.CODEC), timeout, client); + } + + public DataAccessException translateExceptionIfPossible(RuntimeException ex) { + return LettuceUtils.convertRedisAccessException(ex); + } + + /** + * Returns the current host. + * + * @return the host + */ + public String getHostName() { + return hostName; + } + + /** + * Sets the host. + * + * @param host the host to set + */ + public void setHostName(String host) { + this.hostName = host; + } + + /** + * Returns the current port. + * + * @return the port + */ + public int getPort() { + return port; + } + + /** + * Sets the port. + * + * @param port the port to set + */ + public void setPort(int port) { + this.port = port; + } + + /** + * Returns the connection timeout (in milliseconds). + * + * @return connection timeout + */ + public long getTimeout() { + return timeout; + } + + /** + * Sets the connection timeout (in milliseconds). + * + * @param timeout connection timeout + */ + public void setTimeout(long timeout) { + this.timeout = timeout; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceMessageListener.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceMessageListener.java new file mode 100644 index 000000000..a0de169f0 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceMessageListener.java @@ -0,0 +1,58 @@ +/* + * Copyright 2011-2013 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.springframework.data.redis.connection.lettuce; + +import org.springframework.data.redis.connection.DefaultMessage; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.util.Assert; + +import com.lambdaworks.redis.pubsub.RedisPubSubListener; + +/** + * MessageListener wrapper around Lettuce {@link RedisPubSubListener}. + * + * @author Costin Leau + */ +class LettuceMessageListener implements RedisPubSubListener { + + private final MessageListener listener; + + LettuceMessageListener(MessageListener listener) { + Assert.notNull(listener, "message listener is required"); + this.listener = listener; + } + + public void message(byte[] channel, byte[] message) { + listener.onMessage(new DefaultMessage(channel, message), null); + } + + public void message(byte[] pattern, byte[] channel, byte[] message) { + listener.onMessage(new DefaultMessage(channel, message), pattern); + } + + public void subscribed(byte[] channel, long count) { + } + + public void psubscribed(byte[] pattern, long count) { + } + + public void unsubscribed(byte[] channel, long count) { + } + + public void punsubscribed(byte[] pattern, long count) { + } +} diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceSubscription.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceSubscription.java new file mode 100644 index 000000000..60907051f --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceSubscription.java @@ -0,0 +1,74 @@ +/* + * Copyright 2011-2013 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.springframework.data.redis.connection.lettuce; + +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.data.redis.connection.util.AbstractSubscription; + +import com.lambdaworks.redis.pubsub.RedisPubSubConnection; + +/** + * Message subscription on top of Lettuce. + * + * @author Costin Leau + */ +class LettuceSubscription extends AbstractSubscription { + + private final RedisPubSubConnection pubsub; + private LettuceMessageListener listener; + + LettuceSubscription(MessageListener listener, RedisPubSubConnection pubsubConnection) { + super(listener); + this.pubsub = pubsubConnection; + this.listener = new LettuceMessageListener(listener); + + pubsub.addListener(this.listener); + } + + protected void doClose() { + pubsub.unsubscribe(new byte[0]); + pubsub.punsubscribe(new byte[0]); + pubsub.removeListener(this.listener); + } + + + protected void doPsubscribe(byte[]... patterns) { + pubsub.psubscribe(patterns); + } + + protected void doPUnsubscribe(boolean all, byte[]... patterns) { + if (all) { + pubsub.punsubscribe(new byte[0]); + } + else { + pubsub.punsubscribe(patterns); + } + } + + protected void doSubscribe(byte[]... channels) { + pubsub.subscribe(channels); + } + + protected void doUnsubscribe(boolean all, byte[]... channels) { + if (all) { + pubsub.unsubscribe(new byte[0]); + } + else { + pubsub.unsubscribe(channels); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceUtils.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceUtils.java new file mode 100644 index 000000000..325a4be11 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceUtils.java @@ -0,0 +1,159 @@ +/* + * Copyright 2011-2013 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.springframework.data.redis.connection.lettuce; + +import java.io.StringReader; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; + +import org.springframework.dao.DataAccessException; +import org.springframework.data.redis.RedisSystemException; +import org.springframework.data.redis.connection.DefaultTuple; +import org.springframework.data.redis.connection.RedisListCommands.Position; +import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate; +import org.springframework.data.redis.connection.RedisZSetCommands.Tuple; +import org.springframework.data.redis.connection.SortParameters; +import org.springframework.data.redis.connection.SortParameters.Order; +import org.springframework.util.Assert; + +import com.lambdaworks.redis.KeyValue; +import com.lambdaworks.redis.RedisCommandInterruptedException; +import com.lambdaworks.redis.RedisException; +import com.lambdaworks.redis.ScoredValue; +import com.lambdaworks.redis.SortArgs; +import com.lambdaworks.redis.ZStoreArgs; +import com.lambdaworks.redis.codec.RedisCodec; +import com.lambdaworks.redis.protocol.Charsets; + +/** + * Helper class featuring methods for Lettuce connection handling, providing support for exception translation. + * + * @author Costin Leau + */ +abstract class LettuceUtils { + + static final RedisCodec CODEC = new BytesRedisCodec(); + + static DataAccessException convertRedisAccessException(RuntimeException ex) { + if (ex instanceof RedisCommandInterruptedException) { + return new RedisSystemException("Redis command interrupted", ex); + } + if (ex instanceof RedisException) { + return new RedisSystemException("Redis exception", ex); + } + return null; + } + + static Properties info(String reply) { + Properties info = new Properties(); + StringReader stringReader = new StringReader(reply); + try { + info.load(stringReader); + } catch (Exception ex) { + throw new RedisSystemException("Cannot read Redis info", ex); + } finally { + stringReader.close(); + } + return info; + } + + static int asBit(boolean value) { + return (value ? 1 : 0); + } + + static boolean convertPosition(Position where) { + Assert.notNull("list positions are mandatory"); + return (Position.AFTER.equals(where) ? false : true); + } + + static Set convertTuple(List> zrange) { + Set tuples = new LinkedHashSet(zrange.size()); + + for (int i = 0; i < zrange.size(); i++) { + tuples.add(new DefaultTuple(zrange.get(i).value, Double.valueOf(zrange.get(i).score))); + } + return tuples; + } + + static SortArgs sort(SortParameters params) { + SortArgs args = new SortArgs(); + + if (params.getByPattern() != null) { + args.by(new String(params.getByPattern(), Charsets.ASCII)); + } + + if (params.getLimit() != null) { + args.limit(params.getLimit().getStart(), params.getLimit().getCount()); + } + + if (params.getGetPattern() != null) { + byte[][] pattern = params.getGetPattern(); + for (byte[] bs : pattern) { + args.get(new String(bs, Charsets.ASCII)); + } + } + + if (params.getOrder() != null) { + if (params.getOrder() == Order.ASC) { + args.asc(); + } + else { + args.desc(); + } + } + + if (params.isAlphabetic()) { + args.alpha(); + } + return args; + } + + static ZStoreArgs zArgs(Aggregate aggregate, int[] weights) { + ZStoreArgs args = new ZStoreArgs(); + + if (aggregate != null) { + switch (aggregate) { + case MIN: + args.min(); + break; + case MAX: + args.max(); + break; + default: + args.sum(); + break; + } + } + + long[] lg = new long[weights.length]; + for (int i = 0; i < lg.length; i++) { + lg[i] = (long) weights[i]; + } + args.weights(lg); + return args; + } + + static List toList(KeyValue blpop) { + List list = new ArrayList(2); + list.add(blpop.key); + list.add(blpop.value); + return list; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/package-info.java b/src/main/java/org/springframework/data/redis/connection/lettuce/package-info.java new file mode 100644 index 000000000..c54723fa1 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/package-info.java @@ -0,0 +1,5 @@ +/** + * Connection package for Lettuce Redis client. + */ +package org.springframework.data.redis.connection.lettuce; +