Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.redisson.spring.data.connection.RedissonStreamCommands Maven / Gradle / Ivy
/**
* Copyright (c) 2013-2024 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.spring.data.connection;
import org.redisson.api.StreamMessageId;
import org.redisson.client.codec.ByteArrayCodec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.handler.State;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.RedisStrictCommand;
import org.redisson.client.protocol.convertor.EmptyMapConvertor;
import org.redisson.client.protocol.convertor.StreamIdConvertor;
import org.redisson.client.protocol.decoder.*;
import org.redisson.command.CommandAsyncExecutor;
import org.springframework.data.domain.Range;
import org.springframework.data.redis.connection.RedisStreamCommands;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.connection.stream.*;
import org.springframework.util.Assert;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
*
* @author Nikita Koksharov
*
*/
public class RedissonStreamCommands implements RedisStreamCommands {
private final RedissonConnection connection;
private final CommandAsyncExecutor executor;
public RedissonStreamCommands(RedissonConnection connection, CommandAsyncExecutor executor) {
this.connection = connection;
this.executor = executor;
}
private static List toStringList(RecordId... recordIds) {
if (recordIds.length == 1) {
return Arrays.asList(recordIds[0].getValue());
}
return Arrays.stream(recordIds).map(RecordId::getValue).collect(Collectors.toList());
}
@Override
public RecordId xAdd(MapRecord record) {
return xAdd(record, XAddOptions.none());
}
private static final RedisStrictCommand XCLAIM_JUSTID = new RedisStrictCommand("XCLAIM", obj -> RecordId.of(obj.toString()));
@Override
public List xClaimJustId(byte[] key, String group, String newOwner, XClaimOptions options) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(group, "Group name must not be null!");
Assert.notNull(newOwner, "NewOwner must not be null!");
Assert.notEmpty(options.getIds(), "Ids collection must not be empty!");
List params = new ArrayList<>();
params.add(key);
params.add(group);
params.add(newOwner);
params.add(Objects.requireNonNull(options.getMinIdleTime()).toMillis());
params.addAll(Arrays.asList(options.getIdsAsStringArray()));
params.add("JUSTID");
return connection.write(key, StringCodec.INSTANCE, XCLAIM_JUSTID, params.toArray());
}
@Override
public List xClaim(byte[] key, String group, String newOwner, XClaimOptions options) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(group, "Group name must not be null!");
Assert.notNull(newOwner, "NewOwner must not be null!");
Assert.notEmpty(options.getIds(), "Ids collection must not be empty!");
List params = new ArrayList<>();
params.add(key);
params.add(group);
params.add(newOwner);
params.add(Objects.requireNonNull(options.getMinIdleTime()).toMillis());
params.addAll(Arrays.asList(options.getIdsAsStringArray()));
return connection.write(key, ByteArrayCodec.INSTANCE, new RedisCommand>("XCLAIM",
new ListMultiDecoder2(
new ByteRecordReplayDecoder(key),
new ObjectDecoder(new StreamIdDecoder()),
new MapEntriesDecoder(new StreamObjectMapReplayDecoder()))), params.toArray());
}
@Override
public String xGroupCreate(byte[] key, String groupName, ReadOffset readOffset, boolean mkStream) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(groupName, "GroupName must not be null!");
Assert.notNull(readOffset, "ReadOffset must not be null!");
List params = new ArrayList<>();
params.add("CREATE");
params.add(key);
params.add(groupName);
params.add(readOffset.getOffset());
if (mkStream) {
params.add("MKSTREAM");
}
return connection.write(key, StringCodec.INSTANCE, XGROUP_STRING, params.toArray());
}
private static class XInfoStreamReplayDecoder implements MultiDecoder {
private final ObjectMapReplayDecoder decoder = new ObjectMapReplayDecoder<>();
@Override
public StreamInfo.XInfoStream decode(List parts, State state) {
Map map = decoder.decode(parts, state);
List> firstEntry = (List>) map.get("first-entry");
if (firstEntry != null) {
StreamMessageId firstId = StreamIdConvertor.INSTANCE.convert(firstEntry.get(0));
Map firstData = (Map) firstEntry.get(1);
map.put("first-entry", firstData);
}
List> lastEntry = (List>) map.get("last-entry");
if (lastEntry != null) {
StreamMessageId lastId = StreamIdConvertor.INSTANCE.convert(lastEntry.get(0));
Map lastData = (Map) lastEntry.get(1);
map.put("last-entry", lastData);
}
List list = map.entrySet().stream()
.flatMap(e -> Stream.of(e.getKey(), e.getValue()))
.collect(Collectors.toList());
return StreamInfo.XInfoStream.fromList(list);
}
}
private static final RedisCommand> XINFO_STREAM = new RedisCommand<>("XINFO", "STREAM",
new ListMultiDecoder2(
new XInfoStreamReplayDecoder(),
new CodecDecoder(),
new ObjectMapDecoder(false)));
@Override
public StreamInfo.XInfoStream xInfo(byte[] key) {
Assert.notNull(key, "Key must not be null!");
return connection.write(key, StringCodec.INSTANCE, XINFO_STREAM, key);
}
private static class XInfoGroupsReplayDecoder implements MultiDecoder {
@Override
public StreamInfo.XInfoGroups decode(List parts, State state) {
List result = new ArrayList<>();
for (List part: (List>) (Object)parts) {
Map res = new HashMap<>();
res.put("name", part.get(1));
res.put("consumers", part.get(3));
res.put("pending", part.get(5));
res.put("last-delivered-id", part.get(7));
List list = res.entrySet().stream()
.flatMap(e -> Stream.of(e.getKey(), e.getValue()))
.collect(Collectors.toList());
result.add(list);
}
return StreamInfo.XInfoGroups.fromList(result);
}
}
RedisCommand XINFO_GROUPS = new RedisCommand<>("XINFO", "GROUPS",
new ListMultiDecoder2(new XInfoGroupsReplayDecoder(),
new ObjectListReplayDecoder(), new ObjectListReplayDecoder())
);
@Override
public StreamInfo.XInfoGroups xInfoGroups(byte[] key) {
return connection.write(key, StringCodec.INSTANCE, XINFO_GROUPS, key);
}
private static class XInfoConsumersReplayDecoder implements MultiDecoder {
private final String groupName;
public XInfoConsumersReplayDecoder(String groupName) {
this.groupName = groupName;
}
@Override
public StreamInfo.XInfoConsumers decode(List parts, State state) {
List result = new ArrayList<>();
for (List part: (List>) (Object)parts) {
Map res = new HashMap<>();
res.put("name", part.get(1));
res.put("pending", part.get(3));
res.put("idle", part.get(5));
List list = res.entrySet().stream()
.flatMap(e -> Stream.of(e.getKey(), e.getValue()))
.collect(Collectors.toList());
result.add(list);
}
return StreamInfo.XInfoConsumers.fromList(groupName, result);
}
}
@Override
public StreamInfo.XInfoConsumers xInfoConsumers(byte[] key, String groupName) {
return connection.write(key, StringCodec.INSTANCE, new RedisCommand("XINFO", "CONSUMERS",
new ListMultiDecoder2(new XInfoConsumersReplayDecoder(groupName),
new ObjectListReplayDecoder(), new ObjectListReplayDecoder())), key, groupName);
}
private static class PendingMessagesSummaryReplayDecoder implements MultiDecoder {
private final String groupName;
public PendingMessagesSummaryReplayDecoder(String groupName) {
this.groupName = groupName;
}
@Override
public PendingMessagesSummary decode(List parts, State state) {
if (parts.isEmpty()) {
return null;
}
List> customerParts = (List>) parts.get(3);
if (customerParts.isEmpty()) {
return new PendingMessagesSummary(groupName, 0, Range.unbounded(), Collections.emptyMap());
}
Map map = customerParts.stream().collect(Collectors.toMap(e -> e.get(0), e -> Long.valueOf(e.get(1)),
(u, v) -> { throw new IllegalStateException("Duplicate key: " + u); },
LinkedHashMap::new));
Range range = Range.open(parts.get(1).toString(), parts.get(2).toString());
return new PendingMessagesSummary(groupName, (Long) parts.get(0), range, map);
}
}
@Override
public PendingMessagesSummary xPending(byte[] key, String groupName) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(groupName, "Group name must not be null!");
return connection.write(key, StringCodec.INSTANCE, new RedisCommand("XPENDING",
new ListMultiDecoder2(new PendingMessagesSummaryReplayDecoder(groupName),
new ObjectListReplayDecoder(), new ObjectListReplayDecoder())), key, groupName);
}
private static class PendingMessageReplayDecoder implements MultiDecoder {
private String groupName;
public PendingMessageReplayDecoder(String groupName) {
this.groupName = groupName;
}
@Override
public PendingMessage decode(List parts, State state) {
PendingMessage pm = new PendingMessage(RecordId.of(parts.get(0).toString()),
Consumer.from(groupName, parts.get(1).toString()),
Duration.of(Long.valueOf(parts.get(2).toString()), ChronoUnit.MILLIS),
Long.valueOf(parts.get(3).toString()));
return pm;
}
}
private static class PendingMessagesReplayDecoder implements MultiDecoder {
private final String groupName;
private final Range> range;
public PendingMessagesReplayDecoder(String groupName, Range> range) {
this.groupName = groupName;
this.range = range;
}
@Override
public PendingMessages decode(List parts, State state) {
List pendingMessages = (List) (Object) parts;
return new PendingMessages(groupName, range, pendingMessages);
}
}
@Override
public PendingMessages xPending(byte[] key, String groupName, XPendingOptions options) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(groupName, "Group name must not be null!");
List params = new ArrayList<>();
params.add(key);
params.add(groupName);
params.add(((Range.Bound)options.getRange().getLowerBound()).getValue().orElse("-"));
params.add(((Range.Bound)options.getRange().getUpperBound()).getValue().orElse("+"));
if (options.getCount() != null) {
params.add(options.getCount());
} else {
params.add(10);
}
if (options.getConsumerName() != null) {
params.add(options.getConsumerName());
}
return connection.write(key, StringCodec.INSTANCE, new RedisCommand<>("XPENDING",
new ListMultiDecoder2(
new PendingMessagesReplayDecoder(groupName, options.getRange()),
new PendingMessageReplayDecoder(groupName))),
params.toArray());
}
@Override
public Long xAck(byte[] key, String group, RecordId... recordIds) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(group, "Group must not be null!");
Assert.notNull(recordIds, "recordIds must not be null!");
List params = new ArrayList<>();
params.add(key);
params.add(group);
params.addAll(toStringList(recordIds));
return connection.write(key, StringCodec.INSTANCE, RedisCommands.XACK, params.toArray());
}
private static final RedisStrictCommand XADD = new RedisStrictCommand("XADD", obj -> RecordId.of(obj.toString()));
@Override
public RecordId xAdd(MapRecord record, XAddOptions options) {
Assert.notNull(record, "record must not be null!");
List params = new LinkedList<>();
params.add(record.getStream());
if (options.getMaxlen() != null) {
params.add("MAXLEN");
params.add(options.getMaxlen());
}
if (!record.getId().shouldBeAutoGenerated()) {
params.add(record.getId().getValue());
} else {
params.add("*");
}
record.getValue().forEach((key, value) -> {
params.add(key);
params.add(value);
});
return connection.write(record.getStream(), StringCodec.INSTANCE, XADD, params.toArray());
}
@Override
public Long xDel(byte[] key, RecordId... recordIds) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(recordIds, "recordIds must not be null!");
List params = new ArrayList<>();
params.add(key);
params.addAll(toStringList(recordIds));
return connection.write(key, StringCodec.INSTANCE, RedisCommands.XDEL, params.toArray());
}
private static final RedisStrictCommand XGROUP_STRING = new RedisStrictCommand<>("XGROUP");
@Override
public String xGroupCreate(byte[] key, String groupName, ReadOffset readOffset) {
return xGroupCreate(key, groupName, readOffset, false);
}
private static final RedisStrictCommand XGROUP_BOOLEAN = new RedisStrictCommand("XGROUP", obj -> ((Long)obj) > 0);
@Override
public Boolean xGroupDelConsumer(byte[] key, Consumer consumer) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(consumer, "Consumer must not be null!");
Assert.notNull(consumer.getName(), "Consumer name must not be null!");
Assert.notNull(consumer.getGroup(), "Consumer group must not be null!");
return connection.write(key, StringCodec.INSTANCE, XGROUP_BOOLEAN, "DELCONSUMER", key, consumer.getGroup(), consumer.getName());
}
@Override
public Boolean xGroupDestroy(byte[] key, String groupName) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(groupName, "GroupName must not be null!");
return connection.write(key, StringCodec.INSTANCE, XGROUP_BOOLEAN, "DESTROY", key, groupName);
}
@Override
public Long xLen(byte[] key) {
Assert.notNull(key, "Key must not be null!");
return connection.write(key, StringCodec.INSTANCE, RedisCommands.XLEN, key);
}
private List range(RedisCommand> rangeCommand, byte[] key, Range range, RedisZSetCommands.Limit limit) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(range, "Range must not be null!");
Assert.notNull(limit, "Limit must not be null!");
List params = new LinkedList<>();
params.add(key);
if (rangeCommand.getName().equals(RedisCommands.XRANGE.getName())) {
params.add(range.getLowerBound().getValue().orElse("-"));
params.add(range.getUpperBound().getValue().orElse("+"));
} else {
params.add(range.getUpperBound().getValue().orElse("+"));
params.add(range.getLowerBound().getValue().orElse("-"));
}
if (limit.getCount() > 0) {
params.add("COUNT");
params.add(limit.getCount());
}
return connection.write(key, ByteArrayCodec.INSTANCE, rangeCommand, params.toArray());
}
private static class ByteRecordReplayDecoder implements MultiDecoder> {
private final byte[] key;
ByteRecordReplayDecoder(byte[] key) {
this.key = key;
}
@Override
public List decode(List parts, State state) {
List> list = (List>) (Object) parts;
List result = new ArrayList<>(parts.size()/2);
for (List entry : list) {
ByteRecord record = StreamRecords.newRecord()
.in(key)
.withId(RecordId.of(entry.get(0).toString()))
.ofBytes((Map) entry.get(1));
result.add(record);
}
return result;
}
}
@Override
public List xRange(byte[] key, Range range, RedisZSetCommands.Limit limit) {
return range(new RedisCommand<>("XRANGE",
new ListMultiDecoder2(
new ByteRecordReplayDecoder(key),
new ObjectDecoder(new StreamIdDecoder()),
new MapEntriesDecoder(new StreamObjectMapReplayDecoder()))),
key, range, limit);
}
private static class ByteRecordReplayDecoder2 implements MultiDecoder> {
@Override
public List decode(List parts, State state) {
List> list = (List>) (Object) parts;
List result = new ArrayList<>(parts.size()/2);
for (List entries : list) {
List> streamEntries = (List>) entries.get(1);
if (streamEntries.isEmpty()) {
continue;
}
String name = (String) entries.get(0);
for (List se : streamEntries) {
ByteRecord record = StreamRecords.newRecord()
.in(name.getBytes())
.withId(RecordId.of(se.get(0).toString()))
.ofBytes((Map) se.get(1));
result.add(record);
}
}
return result;
}
}
private static class ByteRecordReplayDecoder2_V2 implements MultiDecoder> {
@Override
public List decode(List parts, State state) {
List list = parts;
List result = new ArrayList<>(parts.size()/2);
for (int i = 0; i < list.size(); i += 2) {
List> streamEntries = (List>) list.get(i+1);
if (streamEntries.isEmpty()) {
continue;
}
byte[] name = (byte[]) list.get(0);
for (List se : streamEntries) {
ByteRecord record = StreamRecords.newRecord()
.in(name)
.withId(RecordId.of(se.get(0).toString()))
.ofBytes((Map) se.get(1));
result.add(record);
}
}
return result;
}
}
private static final RedisCommand> XREAD = new RedisCommand<>("XREAD",
new ListMultiDecoder2(
new ByteRecordReplayDecoder2(),
new ObjectDecoder(StringCodec.INSTANCE.getValueDecoder()),
new ObjectDecoder(new StreamIdDecoder()),
new ObjectDecoder(new StreamIdDecoder()),
new MapEntriesDecoder(new StreamObjectMapReplayDecoder())));
private static final RedisCommand> XREAD_BLOCKING =
new RedisCommand<>("XREAD", XREAD.getReplayMultiDecoder());
private static final RedisCommand> XREADGROUP =
new RedisCommand<>("XREADGROUP", XREAD.getReplayMultiDecoder());
private static final RedisCommand> XREADGROUP_BLOCKING =
new RedisCommand<>("XREADGROUP", XREADGROUP.getReplayMultiDecoder());
private static final RedisCommand> XREAD_V2 = new RedisCommand<>("XREAD",
new ListMultiDecoder2(
new ByteRecordReplayDecoder2_V2(),
new CodecDecoder(),
new ObjectDecoder(new StreamIdDecoder()),
new StreamObjectMapReplayDecoder()), new EmptyMapConvertor());
private static final RedisCommand> XREAD_BLOCKING_V2 =
new RedisCommand<>("XREAD", XREAD_V2.getReplayMultiDecoder());
private static final RedisCommand> XREADGROUP_V2 =
new RedisCommand<>("XREADGROUP", XREAD_V2.getReplayMultiDecoder());
private static final RedisCommand> XREADGROUP_BLOCKING_V2 =
new RedisCommand<>("XREADGROUP", XREADGROUP_V2.getReplayMultiDecoder());
static {
RedisCommands.BLOCKING_COMMANDS.add(XREAD_BLOCKING);
RedisCommands.BLOCKING_COMMANDS.add(XREADGROUP_BLOCKING);
RedisCommands.BLOCKING_COMMANDS.add(XREAD_BLOCKING_V2);
RedisCommands.BLOCKING_COMMANDS.add(XREADGROUP_BLOCKING_V2);
}
@Override
public List xRead(StreamReadOptions readOptions, StreamOffset... streams) {
Assert.notNull(readOptions, "ReadOptions must not be null!");
Assert.notNull(streams, "StreamOffsets must not be null!");
List params = new ArrayList<>();
if (readOptions.getCount() != null && readOptions.getCount() > 0) {
params.add("COUNT");
params.add(readOptions.getCount());
}
if (readOptions.getBlock() != null && readOptions.getBlock() > 0) {
params.add("BLOCK");
params.add(readOptions.getBlock());
}
params.add("STREAMS");
for (StreamOffset streamOffset : streams) {
params.add(streamOffset.getKey());
}
for (StreamOffset streamOffset : streams) {
params.add(streamOffset.getOffset().getOffset());
}
if (executor.getServiceManager().isResp3()) {
if (readOptions.getBlock() != null && readOptions.getBlock() > 0) {
return connection.read(streams[0].getKey(), ByteArrayCodec.INSTANCE, XREAD_BLOCKING_V2, params.toArray());
}
return connection.read(streams[0].getKey(), ByteArrayCodec.INSTANCE, XREAD_V2, params.toArray());
}
if (readOptions.getBlock() != null && readOptions.getBlock() > 0) {
return connection.read(streams[0].getKey(), ByteArrayCodec.INSTANCE, XREAD_BLOCKING, params.toArray());
}
return connection.read(streams[0].getKey(), ByteArrayCodec.INSTANCE, XREAD, params.toArray());
}
@Override
public List xReadGroup(Consumer consumer, StreamReadOptions readOptions, StreamOffset... streams) {
Assert.notNull(readOptions, "Consumer must not be null!");
Assert.notNull(readOptions, "ReadOptions must not be null!");
Assert.notNull(streams, "StreamOffsets must not be null!");
List params = new ArrayList<>();
params.add("GROUP");
params.add(consumer.getGroup());
params.add(consumer.getName());
if (readOptions.getCount() != null && readOptions.getCount() > 0) {
params.add("COUNT");
params.add(readOptions.getCount());
}
if (readOptions.getBlock() != null && readOptions.getBlock() > 0) {
params.add("BLOCK");
params.add(readOptions.getBlock());
}
if (readOptions.isNoack()) {
params.add("NOACK");
}
params.add("STREAMS");
for (StreamOffset streamOffset : streams) {
params.add(streamOffset.getKey());
}
for (StreamOffset streamOffset : streams) {
params.add(streamOffset.getOffset().getOffset());
}
if (executor.getServiceManager().isResp3()) {
if (readOptions.getBlock() != null && readOptions.getBlock() > 0) {
return connection.write(streams[0].getKey(), ByteArrayCodec.INSTANCE, XREADGROUP_BLOCKING_V2, params.toArray());
}
return connection.write(streams[0].getKey(), ByteArrayCodec.INSTANCE, XREADGROUP_V2, params.toArray());
}
if (readOptions.getBlock() != null && readOptions.getBlock() > 0) {
return connection.write(streams[0].getKey(), ByteArrayCodec.INSTANCE, XREADGROUP_BLOCKING, params.toArray());
}
return connection.write(streams[0].getKey(), ByteArrayCodec.INSTANCE, XREADGROUP, params.toArray());
}
@Override
public List xRevRange(byte[] key, Range range, RedisZSetCommands.Limit limit) {
return range(new RedisCommand<>("XREVRANGE",
new ListMultiDecoder2(
new ByteRecordReplayDecoder(key),
new ObjectDecoder(new StreamIdDecoder()),
new MapEntriesDecoder(new StreamObjectMapReplayDecoder()))),
key, range, limit);
}
@Override
public Long xTrim(byte[] key, long count) {
return xTrim(key, count, false);
}
@Override
public Long xTrim(byte[] key, long count, boolean approximateTrimming) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(count, "Count must not be null!");
List params = new ArrayList<>(4);
params.add(key);
params.add("MAXLEN");
if (approximateTrimming) {
params.add("~");
}
params.add(count);
return connection.write(key, StringCodec.INSTANCE, RedisCommands.XTRIM, params.toArray());
}
}