/*
* Copyright 2017 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.integration.aws.inbound.kinesis;
import java.math.BigInteger;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.metadata.MetadataStore;
import com.amazonaws.services.kinesis.model.Record;
/**
* An internal {@link Checkpointer} implementation based on
* provided {@link MetadataStore} and unikey {@code key} for shard.
* <p>
* The instances of this class is created by the {@link KinesisMessageDrivenChannelAdapter}
* for each {@code ShardConsumer}.
*
* @author Artem Bilan
*
* @since 1.1
*/
class ShardCheckpointer implements Checkpointer {
private static final Log logger = LogFactory.getLog(ShardCheckpointer.class);
private final MetadataStore checkpointStore;
private final String key;
private volatile String lastCheckpointValue;
private volatile boolean active = true;
ShardCheckpointer(MetadataStore checkpointStore, String key) {
this.checkpointStore = checkpointStore;
this.key = key;
}
@Override
public void checkpoint() {
checkpoint(this.lastCheckpointValue);
}
@Override
public void checkpoint(String sequenceNumber) {
if (this.active) {
String existingSequence = this.checkpointStore.get(this.key);
if (existingSequence == null ||
new BigInteger(existingSequence).compareTo(new BigInteger(sequenceNumber)) <= 0) {
this.checkpointStore.put(this.key, sequenceNumber);
}
}
else {
if (logger.isInfoEnabled()) {
logger.info("The [" + this + "] has been closed. Checkpoints aren't accepted anymore.");
}
}
}
List<Record> filterRecords(List<Record> records) {
List<Record> recordsToProcess = new LinkedList<>(records);
this.lastCheckpointValue = getCheckpoint();
if (this.lastCheckpointValue != null) {
for (Iterator<Record> iterator = recordsToProcess.iterator(); iterator.hasNext(); ) {
Record record = iterator.next();
String sequenceNumber = record.getSequenceNumber();
if (new BigInteger(sequenceNumber).compareTo(new BigInteger(this.lastCheckpointValue)) <= 0) {
if (logger.isDebugEnabled()) {
logger.debug("Removing record with sequenceNumber " + sequenceNumber +
" because the sequenceNumber is <= checkpoint(" + this.lastCheckpointValue + ")");
}
iterator.remove();
}
else {
this.lastCheckpointValue = sequenceNumber;
}
}
}
else {
this.lastCheckpointValue = recordsToProcess.get(recordsToProcess.size() - 1).getSequenceNumber();
}
return recordsToProcess;
}
String getCheckpoint() {
return this.checkpointStore.get(this.key);
}
String getLastCheckpointValue() {
return this.lastCheckpointValue;
}
void remove() {
this.checkpointStore.remove(this.key);
}
void close() {
this.active = false;
}
@Override
public String toString() {
return "ShardCheckpointer{" +
"key='" + this.key + '\'' +
", lastCheckpointValue='" + this.lastCheckpointValue + '\'' +
'}';
}
}