/** * Copyright 2016 Nikita Koksharov * * 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.redisson.reactive; import java.math.BigDecimal; import java.net.InetSocketAddress; import java.util.Collection; import java.util.Collections; import org.reactivestreams.Publisher; import org.redisson.api.RScoredSortedSetReactive; import org.redisson.client.codec.Codec; import org.redisson.client.codec.ScanCodec; import org.redisson.client.codec.StringCodec; import org.redisson.client.protocol.RedisCommand; import org.redisson.client.protocol.RedisCommand.ValueType; import org.redisson.client.protocol.RedisCommands; import org.redisson.client.protocol.ScoredEntry; import org.redisson.client.protocol.convertor.BooleanReplayConvertor; import org.redisson.client.protocol.decoder.ListScanResult; import org.redisson.client.protocol.decoder.ScanObjectEntry; import org.redisson.command.CommandReactiveExecutor; public class RedissonScoredSortedSetReactive<V> extends RedissonExpirableReactive implements RScoredSortedSetReactive<V> { public RedissonScoredSortedSetReactive(CommandReactiveExecutor commandExecutor, String name) { super(commandExecutor, name); } public RedissonScoredSortedSetReactive(Codec codec, CommandReactiveExecutor commandExecutor, String name) { super(codec, commandExecutor, name); } @Override public Publisher<V> pollFirst() { return poll(0); } @Override public Publisher<V> pollLast() { return poll(-1); } private Publisher<V> poll(int index) { return commandExecutor.evalWriteReactive(getName(), codec, RedisCommands.EVAL_OBJECT, "local v = redis.call('zrange', KEYS[1], ARGV[1], ARGV[2]); " + "if v[1] ~= nil then " + "redis.call('zremrangebyrank', KEYS[1], ARGV[1], ARGV[2]); " + "return v[1]; " + "end " + "return nil;", Collections.<Object>singletonList(getName()), index, index); } @Override public Publisher<V> first() { return commandExecutor.readReactive(getName(), codec, RedisCommands.ZRANGE_SINGLE, getName(), 0, 0); } @Override public Publisher<V> last() { return commandExecutor.readReactive(getName(), codec, RedisCommands.ZRANGE_SINGLE, getName(), -1, -1); } @Override public Publisher<Boolean> add(double score, V object) { return commandExecutor.writeReactive(getName(), codec, RedisCommands.ZADD_BOOL, getName(), BigDecimal.valueOf(score).toPlainString(), object); } @Override public Publisher<Integer> removeRangeByRank(int startIndex, int endIndex) { return commandExecutor.writeReactive(getName(), codec, RedisCommands.ZREMRANGEBYRANK, getName(), startIndex, endIndex); } @Override public Publisher<Integer> removeRangeByScore(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive) { String startValue = value(BigDecimal.valueOf(startScore).toPlainString(), startScoreInclusive); String endValue = value(BigDecimal.valueOf(endScore).toPlainString(), endScoreInclusive); return commandExecutor.writeReactive(getName(), codec, RedisCommands.ZREMRANGEBYSCORE, getName(), startValue, endValue); } private String value(String element, boolean inclusive) { if (!inclusive) { element = "(" + element; } return element; } @Override public Publisher<Boolean> remove(Object object) { return commandExecutor.writeReactive(getName(), codec, RedisCommands.ZREM, getName(), object); } @Override public Publisher<Long> size() { return commandExecutor.readReactive(getName(), codec, RedisCommands.ZCARD, getName()); } @Override public Publisher<Boolean> contains(Object o) { return commandExecutor.readReactive(getName(), codec, RedisCommands.ZSCORE_CONTAINS, getName(), o); } @Override public Publisher<Double> getScore(V o) { return commandExecutor.readReactive(getName(), StringCodec.INSTANCE, RedisCommands.ZSCORE, getName(), o); } @Override public Publisher<Long> rank(V o) { return commandExecutor.readReactive(getName(), codec, RedisCommands.ZRANK, getName(), o); } private Publisher<ListScanResult<ScanObjectEntry>> scanIteratorReactive(InetSocketAddress client, long startPos) { return commandExecutor.readReactive(client, getName(), new ScanCodec(codec), RedisCommands.ZSCAN, getName(), startPos); } @Override public Publisher<V> iterator() { return new SetReactiveIterator<V>() { @Override protected Publisher<ListScanResult<ScanObjectEntry>> scanIteratorReactive(InetSocketAddress client, long nextIterPos) { return RedissonScoredSortedSetReactive.this.scanIteratorReactive(client, nextIterPos); } }; } @Override public Publisher<Boolean> containsAll(Collection<?> c) { return commandExecutor.evalReadReactive(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS), "local s = redis.call('zrange', KEYS[1], 0, -1);" + "for i = 1, table.getn(s), 1 do " + "for j = 1, table.getn(ARGV), 1 do " + "if ARGV[j] == s[i] " + "then table.remove(ARGV, j) end " + "end; " + "end;" + "return table.getn(ARGV) == 0 and 1 or 0; ", Collections.<Object>singletonList(getName()), c.toArray()); } @Override public Publisher<Boolean> removeAll(Collection<?> c) { return commandExecutor.evalWriteReactive(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS), "local v = 0 " + "for i = 1, table.getn(ARGV), 1 do " + "if redis.call('zrem', KEYS[1], ARGV[i]) == 1 " + "then v = 1 end " +"end " + "return v ", Collections.<Object>singletonList(getName()), c.toArray()); } @Override public Publisher<Boolean> retainAll(Collection<?> c) { return commandExecutor.evalWriteReactive(getName(), codec, new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(), 4, ValueType.OBJECTS), "local changed = 0 " + "local s = redis.call('zrange', KEYS[1], 0, -1) " + "local i = 1 " + "while i <= table.getn(s) do " + "local element = s[i] " + "local isInAgrs = false " + "for j = 1, table.getn(ARGV), 1 do " + "if ARGV[j] == element then " + "isInAgrs = true " + "break " + "end " + "end " + "if isInAgrs == false then " + "redis.call('zrem', KEYS[1], element) " + "changed = 1 " + "end " + "i = i + 1 " + "end " + "return changed ", Collections.<Object>singletonList(getName()), c.toArray()); } @Override public Publisher<Double> addScore(V object, Number value) { return commandExecutor.writeReactive(getName(), StringCodec.INSTANCE, RedisCommands.ZINCRBY, getName(), new BigDecimal(value.toString()).toPlainString(), object); } @Override public Publisher<Collection<V>> valueRange(int startIndex, int endIndex) { return commandExecutor.readReactive(getName(), codec, RedisCommands.ZRANGE, getName(), startIndex, endIndex); } @Override public Publisher<Collection<ScoredEntry<V>>> entryRange(int startIndex, int endIndex) { return commandExecutor.readReactive(getName(), codec, RedisCommands.ZRANGE_ENTRY, getName(), startIndex, endIndex, "WITHSCORES"); } @Override public Publisher<Collection<V>> valueRange(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive) { String startValue = value(BigDecimal.valueOf(startScore).toPlainString(), startScoreInclusive); String endValue = value(BigDecimal.valueOf(endScore).toPlainString(), endScoreInclusive); return commandExecutor.readReactive(getName(), codec, RedisCommands.ZRANGEBYSCORE_LIST, getName(), startValue, endValue); } @Override public Publisher<Collection<ScoredEntry<V>>> entryRange(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive) { String startValue = value(BigDecimal.valueOf(startScore).toPlainString(), startScoreInclusive); String endValue = value(BigDecimal.valueOf(endScore).toPlainString(), endScoreInclusive); return commandExecutor.readReactive(getName(), codec, RedisCommands.ZRANGEBYSCORE_ENTRY, getName(), startValue, endValue, "WITHSCORES"); } @Override public Publisher<Collection<V>> valueRange(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count) { String startValue = value(BigDecimal.valueOf(startScore).toPlainString(), startScoreInclusive); String endValue = value(BigDecimal.valueOf(endScore).toPlainString(), endScoreInclusive); return commandExecutor.readReactive(getName(), codec, RedisCommands.ZRANGEBYSCORE_LIST, getName(), startValue, endValue, "LIMIT", offset, count); } @Override public Publisher<Collection<ScoredEntry<V>>> entryRange(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive, int offset, int count) { String startValue = value(BigDecimal.valueOf(startScore).toPlainString(), startScoreInclusive); String endValue = value(BigDecimal.valueOf(endScore).toPlainString(), endScoreInclusive); return commandExecutor.readReactive(getName(), codec, RedisCommands.ZRANGEBYSCORE_ENTRY, getName(), startValue, endValue, "WITHSCORES", "LIMIT", offset, count); } }