com.redis.spring.batch.common.KeyComparisonItemReader Maven / Gradle / Ivy
The newest version!
package com.redis.spring.batch.common;
import java.time.Duration;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.batch.item.Chunk;
import org.springframework.util.CollectionUtils;
import com.redis.spring.batch.RedisItemReader;
import com.redis.spring.batch.common.KeyComparison.Status;
import com.redis.spring.batch.reader.AbstractKeyValueItemReader;
import com.redis.spring.batch.util.CodecUtils;
import io.lettuce.core.StreamMessage;
public class KeyComparisonItemReader extends RedisItemReader {
public static final Duration DEFAULT_TTL_TOLERANCE = Duration.ofMillis(100);
private final AbstractKeyValueItemReader source;
private final AbstractKeyValueItemReader target;
private Duration ttlTolerance = DEFAULT_TTL_TOLERANCE;
private ValueReader> sourceValueReader;
private ValueReader> targetValueReader;
private Function, KeyValue> processor = Function.identity();
private boolean compareStreamMessageIds;
public KeyComparisonItemReader(AbstractKeyValueItemReader source,
AbstractKeyValueItemReader target) {
super(source.getClient(), CodecUtils.STRING_CODEC);
this.source = source;
this.target = target;
}
public void setCompareStreamMessageIds(boolean enable) {
this.compareStreamMessageIds = enable;
}
public void setProcessor(Function, KeyValue> processor) {
this.processor = processor;
}
public void setTtlTolerance(Duration ttlTolerance) {
this.ttlTolerance = ttlTolerance;
}
@Override
protected synchronized void doOpen() throws Exception {
if (sourceValueReader == null) {
sourceValueReader = source.valueReader();
sourceValueReader.open();
}
if (targetValueReader == null) {
targetValueReader = target.valueReader();
targetValueReader.open();
}
super.doOpen();
}
@Override
protected synchronized void doClose() throws Exception {
super.doClose();
if (sourceValueReader != null) {
sourceValueReader.close();
sourceValueReader = null;
}
if (targetValueReader != null) {
targetValueReader.close();
targetValueReader = null;
}
}
@SuppressWarnings("unchecked")
@Override
public Chunk values(Chunk extends String> keys) {
Chunk comparisons = new Chunk<>();
Chunk processedKeys = processKeys((Chunk) keys);
Chunk> sourceItems = sourceValueReader.execute(processedKeys);
List> items = processValues(sourceItems).getItems();
List targetKeys = items.stream().map(KeyValue::getKey).collect(Collectors.toList());
List> targetItems = targetValueReader.execute(new Chunk<>(targetKeys)).getItems();
for (int index = 0; index < items.size(); index++) {
KeyComparison comparison = new KeyComparison();
comparison.setSource(items.get(index));
if (index < targetItems.size()) {
comparison.setTarget(targetItems.get(index));
}
comparison.setStatus(status(comparison));
comparisons.add(comparison);
}
return comparisons;
}
private Chunk> processValues(Chunk> values) {
if (processor == null) {
return values;
}
Chunk> processedValues = new Chunk<>();
for (KeyValue value : values) {
KeyValue processedValue = processor.apply(value);
if (processedValue != null) {
processedValues.add(processedValue);
}
}
return processedValues;
}
private Chunk processKeys(Chunk keys) {
if (keyProcessor == null) {
return keys;
}
Chunk processedKeys = new Chunk<>();
for (String key : keys) {
try {
String processedKey = keyProcessor.process(key);
if (processedKey != null) {
processedKeys.add(processedKey);
}
} catch (Exception e) {
// ignore
}
}
return processedKeys;
}
private Status status(KeyComparison comparison) {
KeyValue sourceEntry = comparison.getSource();
KeyValue targetEntry = comparison.getTarget();
if (targetEntry == null) {
if (sourceEntry == null) {
return Status.OK;
}
return Status.MISSING;
}
if (!targetEntry.exists() && sourceEntry.exists()) {
return Status.MISSING;
}
if (targetEntry.getType() != sourceEntry.getType()) {
return Status.TYPE;
}
if (!valueEquals(sourceEntry, targetEntry)) {
return Status.VALUE;
}
if (sourceEntry.getTtl() != targetEntry.getTtl()) {
long delta = Math.abs(sourceEntry.getTtl() - targetEntry.getTtl());
if (delta > ttlTolerance.toMillis()) {
return Status.TTL;
}
}
return Status.OK;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private boolean valueEquals(KeyValue source, KeyValue target) {
if (source.getType() == DataType.STREAM) {
return streamEquals((Collection) source.getValue(),
(Collection) target.getValue());
}
return Objects.deepEquals(source.getValue(), target.getValue());
}
@SuppressWarnings("rawtypes")
private boolean streamEquals(Collection source, Collection target) {
if (CollectionUtils.isEmpty(source)) {
return CollectionUtils.isEmpty(target);
}
if (source.size() != target.size()) {
return false;
}
Iterator sourceIterator = source.iterator();
Iterator targetIterator = target.iterator();
while (sourceIterator.hasNext()) {
if (!targetIterator.hasNext()) {
return false;
}
StreamMessage sourceMessage = sourceIterator.next();
StreamMessage targetMessage = targetIterator.next();
if (!streamMessageEquals(sourceMessage, targetMessage)) {
return false;
}
}
return true;
}
@SuppressWarnings("rawtypes")
private boolean streamMessageEquals(StreamMessage sourceMessage, StreamMessage targetMessage) {
if (!Objects.equals(sourceMessage.getStream(), targetMessage.getStream())) {
return false;
}
if (compareStreamMessageIds && !Objects.equals(sourceMessage.getId(), targetMessage.getId())) {
return false;
}
Map sourceBody = sourceMessage.getBody();
Map targetBody = targetMessage.getBody();
if (CollectionUtils.isEmpty(sourceBody)) {
return CollectionUtils.isEmpty(targetBody);
}
return sourceBody.equals(targetBody);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy