/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.camel.component.aws.ddbstream; import java.math.BigInteger; import com.amazonaws.services.dynamodbv2.AmazonDynamoDBStreams; import com.amazonaws.services.dynamodbv2.model.DescribeStreamRequest; import com.amazonaws.services.dynamodbv2.model.DescribeStreamResult; import com.amazonaws.services.dynamodbv2.model.GetShardIteratorRequest; import com.amazonaws.services.dynamodbv2.model.GetShardIteratorResult; import com.amazonaws.services.dynamodbv2.model.ListStreamsRequest; import com.amazonaws.services.dynamodbv2.model.ListStreamsResult; import com.amazonaws.services.dynamodbv2.model.Shard; import com.amazonaws.services.dynamodbv2.model.ShardIteratorType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; class ShardIteratorHandler { private static final Logger LOG = LoggerFactory.getLogger(ShardIteratorHandler.class); private final DdbStreamEndpoint endpoint; private final ShardList shardList = new ShardList(); private String currentShardIterator; private Shard currentShard; ShardIteratorHandler(DdbStreamEndpoint endpoint) { this.endpoint = endpoint; } String getShardIterator(String resumeFromSequenceNumber) { ShardIteratorType iteratorType = getEndpoint().getIteratorType(); String sequenceNumber = getEndpoint().getSequenceNumber(); if (resumeFromSequenceNumber != null) { // Reset things as we're in an error condition. currentShard = null; currentShardIterator = null; iteratorType = ShardIteratorType.AFTER_SEQUENCE_NUMBER; sequenceNumber = resumeFromSequenceNumber; } // either return a cached one or get a new one via a GetShardIterator request. if (currentShardIterator == null) { ListStreamsResult streamsListResult = getClient().listStreams( new ListStreamsRequest().withTableName(getEndpoint().getTableName()) ); final String streamArn = streamsListResult.getStreams().get(0).getStreamArn(); // XXX assumes there is only one stream DescribeStreamResult streamDescriptionResult = getClient().describeStream( new DescribeStreamRequest().withStreamArn(streamArn) ); shardList.addAll(streamDescriptionResult.getStreamDescription().getShards()); LOG.trace("Current shard is: {} (in {})", currentShard, shardList); if (currentShard == null) { currentShard = resolveNewShard(iteratorType, resumeFromSequenceNumber); } else { currentShard = shardList.nextAfter(currentShard); } shardList.removeOlderThan(currentShard); LOG.trace("Next shard is: {} (in {})", currentShard, shardList); GetShardIteratorResult result = getClient().getShardIterator( buildGetShardIteratorRequest(streamArn, iteratorType, sequenceNumber) ); currentShardIterator = result.getShardIterator(); } LOG.trace("Shard Iterator is: {}", currentShardIterator); return currentShardIterator; } private GetShardIteratorRequest buildGetShardIteratorRequest(final String streamArn, ShardIteratorType iteratorType, String sequenceNumber) { GetShardIteratorRequest req = new GetShardIteratorRequest() .withStreamArn(streamArn) .withShardId(currentShard.getShardId()) .withShardIteratorType(iteratorType); switch (iteratorType) { case AFTER_SEQUENCE_NUMBER: case AT_SEQUENCE_NUMBER: // if you request with a sequence number that is LESS than the // start of the shard, you get a HTTP 400 from AWS. // So only add the sequence number if the endpoints // sequence number is less than or equal to the starting // sequence for the shard. // Otherwise change the shart iterator type to trim_horizon // because we get a 400 when we use one of the // {at,after}_sequence_number iterator types and don't supply // a sequence number. if (BigIntComparisons.Conditions.LTEQ.matches( new BigInteger(currentShard.getSequenceNumberRange().getStartingSequenceNumber()), new BigInteger(sequenceNumber) )) { req = req.withSequenceNumber(sequenceNumber); } else { req = req.withShardIteratorType(ShardIteratorType.TRIM_HORIZON); } break; default: } return req; } private Shard resolveNewShard(ShardIteratorType type, String resumeFrom) { switch(type) { case AFTER_SEQUENCE_NUMBER: return shardList.afterSeq(resumeFrom != null ? resumeFrom : getEndpoint().getSequenceNumber()); case AT_SEQUENCE_NUMBER: return shardList.atSeq(getEndpoint().getSequenceNumber()); case TRIM_HORIZON: return shardList.first(); case LATEST: default: return shardList.last(); } } void updateShardIterator(String nextShardIterator) { this.currentShardIterator = nextShardIterator; } DdbStreamEndpoint getEndpoint() { return endpoint; } private AmazonDynamoDBStreams getClient() { return getEndpoint().getClient(); } }