Start version 2.3
This commit is contained in:
@@ -37,8 +37,8 @@ ext {
|
||||
jacksonVersion = '2.9.9'
|
||||
servletApiVersion = '4.0.1'
|
||||
log4jVersion = '2.11.2'
|
||||
springCloudAwsVersion = '2.1.1.RELEASE'
|
||||
springIntegrationVersion = '5.1.6.RELEASE'
|
||||
springCloudAwsVersion = '2.2.0.BUILD-SNAPSHOT'
|
||||
springIntegrationVersion = '5.2.0.BUILD-SNAPSHOT'
|
||||
kinesisClientVersion = '1.10.0'
|
||||
kinesisProducerVersion = '0.12.11'
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
version=2.2.1.BUILD-SNAPSHOT
|
||||
version=2.3.0.BUILD-SNAPSHOT
|
||||
|
||||
@@ -23,17 +23,16 @@ import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHa
|
||||
*
|
||||
* @author Amol Nayak
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 0.5
|
||||
*/
|
||||
public class AwsNamespaceHandler extends AbstractIntegrationNamespaceHandler {
|
||||
|
||||
|
||||
public void init() {
|
||||
registerBeanDefinitionParser("s3-outbound-channel-adapter", new S3OutboundChannelAdapterParser());
|
||||
registerBeanDefinitionParser("s3-outbound-gateway", new S3OutboundGatewayParser());
|
||||
registerBeanDefinitionParser("s3-inbound-channel-adapter", new S3InboundChannelAdapterParser());
|
||||
registerBeanDefinitionParser("s3-inbound-streaming-channel-adapter", new S3StreamingInboundChannelAdapterParser());
|
||||
registerBeanDefinitionParser("s3-inbound-streaming-channel-adapter",
|
||||
new S3StreamingInboundChannelAdapterParser());
|
||||
registerBeanDefinitionParser("sqs-outbound-channel-adapter", new SqsOutboundChannelAdapterParser());
|
||||
registerBeanDefinitionParser("sqs-message-driven-channel-adapter", new SqsMessageDrivenChannelAdapterParser());
|
||||
registerBeanDefinitionParser("sns-inbound-channel-adapter", new SnsInboundChannelAdapterParser());
|
||||
|
||||
@@ -58,12 +58,11 @@ public final class AwsParserUtils {
|
||||
super();
|
||||
}
|
||||
|
||||
static void populateExpressionAttribute(String attributeName, BeanDefinitionBuilder builder,
|
||||
Element element, ParserContext parserContext) {
|
||||
static void populateExpressionAttribute(String attributeName, BeanDefinitionBuilder builder, Element element,
|
||||
ParserContext parserContext) {
|
||||
|
||||
BeanDefinition beanDefinition =
|
||||
IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression(attributeName,
|
||||
attributeName + "-expression", parserContext, element, false);
|
||||
BeanDefinition beanDefinition = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression(
|
||||
attributeName, attributeName + "-expression", parserContext, element, false);
|
||||
if (beanDefinition != null) {
|
||||
builder.addPropertyValue(Conventions.attributeNameToPropertyName(attributeName) + "Expression",
|
||||
beanDefinition);
|
||||
|
||||
@@ -31,8 +31,7 @@ public class S3OutboundChannelAdapterParser extends AbstractOutboundChannelAdapt
|
||||
|
||||
@Override
|
||||
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
|
||||
AbstractBeanDefinition beanDefinition = new S3OutboundGatewayParser()
|
||||
.parseHandler(element, parserContext)
|
||||
AbstractBeanDefinition beanDefinition = new S3OutboundGatewayParser().parseHandler(element, parserContext)
|
||||
.getBeanDefinition();
|
||||
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(2, false);
|
||||
return beanDefinition;
|
||||
|
||||
@@ -50,18 +50,15 @@ public class S3OutboundGatewayParser extends AbstractConsumerEndpointParser {
|
||||
.error("One and only of 's3' and 'transfer-manager' attributes must be provided", element);
|
||||
}
|
||||
|
||||
BeanDefinition bucketExpression =
|
||||
IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("bucket", "bucket-expression",
|
||||
parserContext, element, true);
|
||||
BeanDefinition bucketExpression = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression(
|
||||
"bucket", "bucket-expression", parserContext, element, true);
|
||||
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(S3MessageHandler.class)
|
||||
.addConstructorArgReference(hasS3 ? s3 : transferManager)
|
||||
.addConstructorArgValue(bucketExpression)
|
||||
.addConstructorArgReference(hasS3 ? s3 : transferManager).addConstructorArgValue(bucketExpression)
|
||||
.addConstructorArgValue(true);
|
||||
|
||||
BeanDefinition commandExpression =
|
||||
IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("command",
|
||||
"command-expression", parserContext, element, false);
|
||||
BeanDefinition commandExpression = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression(
|
||||
"command", "command-expression", parserContext, element, false);
|
||||
|
||||
if (commandExpression != null) {
|
||||
builder.addPropertyValue("commandExpression", commandExpression);
|
||||
@@ -70,27 +67,26 @@ public class S3OutboundGatewayParser extends AbstractConsumerEndpointParser {
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "progress-listener");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "upload-metadata-provider");
|
||||
|
||||
BeanDefinition keyExpression =
|
||||
IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("key-expression", element);
|
||||
BeanDefinition keyExpression = IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("key-expression",
|
||||
element);
|
||||
if (keyExpression != null) {
|
||||
builder.addPropertyValue("keyExpression", keyExpression);
|
||||
}
|
||||
|
||||
BeanDefinition objectAclExpression =
|
||||
IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("object-acl-expression", element);
|
||||
BeanDefinition objectAclExpression = IntegrationNamespaceUtils
|
||||
.createExpressionDefIfAttributeDefined("object-acl-expression", element);
|
||||
if (objectAclExpression != null) {
|
||||
builder.addPropertyValue("objectAclExpression", objectAclExpression);
|
||||
}
|
||||
|
||||
BeanDefinition destinationBucketExpression =
|
||||
IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("destination-bucket-expression",
|
||||
element);
|
||||
BeanDefinition destinationBucketExpression = IntegrationNamespaceUtils
|
||||
.createExpressionDefIfAttributeDefined("destination-bucket-expression", element);
|
||||
if (destinationBucketExpression != null) {
|
||||
builder.addPropertyValue("destinationBucketExpression", destinationBucketExpression);
|
||||
}
|
||||
|
||||
BeanDefinition destinationKeyExpression =
|
||||
IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("destination-key-expression", element);
|
||||
BeanDefinition destinationKeyExpression = IntegrationNamespaceUtils
|
||||
.createExpressionDefIfAttributeDefined("destination-key-expression", element);
|
||||
if (destinationKeyExpression != null) {
|
||||
builder.addPropertyValue("destinationKeyExpression", destinationKeyExpression);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.springframework.integration.file.remote.RemoteFileOperations;
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public class S3StreamingInboundChannelAdapterParser extends AbstractRemoteFileStreamingInboundChannelAdapterParser {
|
||||
|
||||
@@ -47,7 +47,8 @@ public class SnsInboundChannelAdapterParser extends AbstractSingleBeanDefinition
|
||||
String id = super.resolveId(element, definition, parserContext);
|
||||
|
||||
if (!element.hasAttribute("channel")) {
|
||||
// the created channel will get the 'id', so the adapter's bean name includes a suffix
|
||||
// the created channel will get the 'id', so the adapter's bean name includes
|
||||
// a suffix
|
||||
id = id + ".adapter";
|
||||
}
|
||||
if (!StringUtils.hasText(id)) {
|
||||
@@ -71,8 +72,8 @@ public class SnsInboundChannelAdapterParser extends AbstractSingleBeanDefinition
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout", "requestTimeout");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.AUTO_STARTUP);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.PHASE);
|
||||
BeanDefinition payloadExpressionDef =
|
||||
IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("payload-expression", element);
|
||||
BeanDefinition payloadExpressionDef = IntegrationNamespaceUtils
|
||||
.createExpressionDefIfAttributeDefined("payload-expression", element);
|
||||
if (payloadExpressionDef != null) {
|
||||
builder.addPropertyValue("payloadExpression", payloadExpressionDef);
|
||||
}
|
||||
|
||||
@@ -37,15 +37,14 @@ public class SnsOutboundChannelAdapterParser extends AbstractOutboundChannelAdap
|
||||
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
|
||||
String sns = element.getAttribute(AwsParserUtils.SNS_REF);
|
||||
|
||||
BeanDefinitionBuilder builder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(SnsMessageHandler.class)
|
||||
.addConstructorArgReference(sns);
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SnsMessageHandler.class)
|
||||
.addConstructorArgReference(sns);
|
||||
|
||||
AwsParserUtils.populateExpressionAttribute("topic-arn", builder, element, parserContext);
|
||||
AwsParserUtils.populateExpressionAttribute("subject", builder, element, parserContext);
|
||||
|
||||
BeanDefinition message =
|
||||
IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("body-expression", element);
|
||||
BeanDefinition message = IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("body-expression",
|
||||
element);
|
||||
if (message != null) {
|
||||
builder.addPropertyValue("bodyExpression", message);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@ public class SqsMessageDrivenChannelAdapterParser extends AbstractSingleBeanDefi
|
||||
String id = super.resolveId(element, definition, parserContext);
|
||||
|
||||
if (!element.hasAttribute("channel")) {
|
||||
// the created channel will get the 'id', so the adapter's bean name includes a suffix
|
||||
// the created channel will get the 'id', so the adapter's bean name includes
|
||||
// a suffix
|
||||
id = id + ".adapter";
|
||||
}
|
||||
if (!StringUtils.hasText(id)) {
|
||||
|
||||
@@ -47,7 +47,8 @@ public class S3InboundFileSynchronizer extends AbstractInboundFileSynchronizer<S
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a synchronizer with the {@link SessionFactory} used to acquire {@link Session} instances.
|
||||
* Create a synchronizer with the {@link SessionFactory} used to acquire
|
||||
* {@link Session} instances.
|
||||
* @param sessionFactory The session factory.
|
||||
*/
|
||||
public S3InboundFileSynchronizer(SessionFactory<S3ObjectSummary> sessionFactory) {
|
||||
|
||||
@@ -25,7 +25,8 @@ import org.springframework.integration.file.remote.synchronizer.AbstractInboundF
|
||||
import com.amazonaws.services.s3.model.S3ObjectSummary;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.integration.core.MessageSource} implementation for the Amazon S3.
|
||||
* A {@link org.springframework.integration.core.MessageSource} implementation for the
|
||||
* Amazon S3.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@@ -41,7 +42,6 @@ public class S3InboundFileSynchronizingMessageSource
|
||||
super(synchronizer, comparator);
|
||||
}
|
||||
|
||||
|
||||
public String getComponentType() {
|
||||
return "aws:s3-inbound-channel-adapter";
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ import com.amazonaws.services.s3.model.S3ObjectSummary;
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public class S3StreamingMessageSource extends AbstractRemoteFileStreamingMessageSource<S3ObjectSummary> {
|
||||
@@ -55,9 +54,7 @@ public class S3StreamingMessageSource extends AbstractRemoteFileStreamingMessage
|
||||
|
||||
@Override
|
||||
protected List<AbstractFileInfo<S3ObjectSummary>> asFileInfoList(Collection<S3ObjectSummary> collection) {
|
||||
return collection.stream()
|
||||
.map(S3FileInfo::new)
|
||||
.collect(Collectors.toList());
|
||||
return collection.stream().map(S3FileInfo::new).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -46,27 +46,29 @@ import com.amazonaws.services.sns.AmazonSNS;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
/**
|
||||
* The {@link HttpRequestHandlingMessagingGateway} extension for the Amazon WS SNS HTTP(S) endpoints.
|
||||
* Accepts all {@code x-amz-sns-message-type}s, converts the received Topic JSON message to the
|
||||
* {@link Map} using {@link MappingJackson2HttpMessageConverter} and send it to the provided
|
||||
* {@link #getRequestChannel()} as {@link Message} {@code payload}.
|
||||
* The {@link HttpRequestHandlingMessagingGateway} extension for the Amazon WS SNS HTTP(S)
|
||||
* endpoints. Accepts all {@code x-amz-sns-message-type}s, converts the received Topic
|
||||
* JSON message to the {@link Map} using {@link MappingJackson2HttpMessageConverter} and
|
||||
* send it to the provided {@link #getRequestChannel()} as {@link Message}
|
||||
* {@code payload}.
|
||||
* <p>
|
||||
* The mapped url must be configured inside the Amazon Web Service platform as a subscription.
|
||||
* Before receiving any notification itself this HTTP endpoint must confirm the subscription.
|
||||
* The mapped url must be configured inside the Amazon Web Service platform as a
|
||||
* subscription. Before receiving any notification itself this HTTP endpoint must confirm
|
||||
* the subscription.
|
||||
* <p>
|
||||
* The {@link #handleNotificationStatus} flag (defaults to {@code false}) indicates that
|
||||
* this endpoint should send the {@code SubscriptionConfirmation/UnsubscribeConfirmation}
|
||||
* messages to the the provided {@link #getRequestChannel()}.
|
||||
* If that, the {@link AwsHeaders#NOTIFICATION_STATUS} header is populated
|
||||
* with the {@link NotificationStatus} value. In that case it is a responsibility of
|
||||
* the application to {@link NotificationStatus#confirmSubscription()} or not.
|
||||
* messages to the the provided {@link #getRequestChannel()}. If that, the
|
||||
* {@link AwsHeaders#NOTIFICATION_STATUS} header is populated with the
|
||||
* {@link NotificationStatus} value. In that case it is a responsibility of the
|
||||
* application to {@link NotificationStatus#confirmSubscription()} or not.
|
||||
* <p>
|
||||
* By default this endpoint just does {@link NotificationStatus#confirmSubscription()}
|
||||
* for the {@code SubscriptionConfirmation} message type.
|
||||
* And does nothing for the {@code UnsubscribeConfirmation}.
|
||||
* By default this endpoint just does {@link NotificationStatus#confirmSubscription()} for
|
||||
* the {@code SubscriptionConfirmation} message type. And does nothing for the
|
||||
* {@code UnsubscribeConfirmation}.
|
||||
* <p>
|
||||
* For the convenience on the underlying message flow routing a {@link AwsHeaders#SNS_MESSAGE_TYPE}
|
||||
* header is present.
|
||||
* For the convenience on the underlying message flow routing a
|
||||
* {@link AwsHeaders#SNS_MESSAGE_TYPE} header is present.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @author Kamil Przerwa
|
||||
@@ -75,8 +77,7 @@ public class SnsInboundChannelAdapter extends HttpRequestHandlingMessagingGatewa
|
||||
|
||||
private final NotificationStatusResolver notificationStatusResolver;
|
||||
|
||||
private final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter =
|
||||
new MappingJackson2HttpMessageConverter();
|
||||
private final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
|
||||
|
||||
private volatile boolean handleNotificationStatus;
|
||||
|
||||
@@ -94,8 +95,8 @@ public class SnsInboundChannelAdapter extends HttpRequestHandlingMessagingGatewa
|
||||
requestMapping.setMethods(HttpMethod.POST);
|
||||
requestMapping.setHeaders("x-amz-sns-message-type");
|
||||
requestMapping.setPathPatterns(path);
|
||||
this.jackson2HttpMessageConverter.setSupportedMediaTypes(
|
||||
Arrays.asList(MediaType.APPLICATION_JSON_UTF8, MediaType.TEXT_PLAIN));
|
||||
this.jackson2HttpMessageConverter
|
||||
.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN));
|
||||
super.setRequestMapping(requestMapping);
|
||||
super.setStatusCodeExpression(new ValueExpression<>(HttpStatus.NO_CONTENT));
|
||||
super.setMessageConverters(Collections.singletonList(this.jackson2HttpMessageConverter));
|
||||
@@ -148,8 +149,9 @@ public class SnsInboundChannelAdapter extends HttpRequestHandlingMessagingGatewa
|
||||
return;
|
||||
}
|
||||
}
|
||||
messageToSendBuilder.setHeader(AwsHeaders.SNS_MESSAGE_TYPE, type)
|
||||
.setHeader(AwsHeaders.MESSAGE_ID, payload.get("MessageId"));
|
||||
messageToSendBuilder.setHeader(AwsHeaders.SNS_MESSAGE_TYPE, type).setHeader(AwsHeaders.MESSAGE_ID,
|
||||
payload.get("MessageId"));
|
||||
|
||||
super.send(messageToSendBuilder.build());
|
||||
}
|
||||
|
||||
@@ -203,7 +205,6 @@ public class SnsInboundChannelAdapter extends HttpRequestHandlingMessagingGatewa
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
private static class NotificationStatusResolver extends NotificationStatusHandlerMethodArgumentResolver {
|
||||
|
||||
NotificationStatusResolver(AmazonSNS amazonSns) {
|
||||
|
||||
@@ -45,12 +45,12 @@ import org.springframework.util.Assert;
|
||||
import com.amazonaws.services.sqs.AmazonSQSAsync;
|
||||
|
||||
/**
|
||||
* The {@link MessageProducerSupport} implementation for the Amazon SQS {@code receiveMessage}.
|
||||
* Works in 'listener' manner and delegates hard to the {@link SimpleMessageListenerContainer}.
|
||||
* The {@link MessageProducerSupport} implementation for the Amazon SQS
|
||||
* {@code receiveMessage}. Works in 'listener' manner and delegates hard to the
|
||||
* {@link SimpleMessageListenerContainer}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @author Patrick Fitzsimons
|
||||
*
|
||||
* @see SimpleMessageListenerContainerFactory
|
||||
* @see SimpleMessageListenerContainer
|
||||
* @see QueueMessageHandler
|
||||
@@ -59,8 +59,7 @@ import com.amazonaws.services.sqs.AmazonSQSAsync;
|
||||
@IntegrationManagedResource
|
||||
public class SqsMessageDrivenChannelAdapter extends MessageProducerSupport implements DisposableBean {
|
||||
|
||||
private final SimpleMessageListenerContainerFactory simpleMessageListenerContainerFactory =
|
||||
new SimpleMessageListenerContainerFactory();
|
||||
private final SimpleMessageListenerContainerFactory simpleMessageListenerContainerFactory = new SimpleMessageListenerContainerFactory();
|
||||
|
||||
private final String[] queues;
|
||||
|
||||
@@ -163,7 +162,7 @@ public class SqsMessageDrivenChannelAdapter extends MessageProducerSupport imple
|
||||
|
||||
@ManagedAttribute
|
||||
public String[] getQueues() {
|
||||
return Arrays.copyOf(this.queues, this.queues.length);
|
||||
return Arrays.copyOf(this.queues, this.queues.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -185,17 +184,12 @@ public class SqsMessageDrivenChannelAdapter extends MessageProducerSupport imple
|
||||
protected void handleMessageInternal(Message<?> message, String lookupDestination) {
|
||||
MessageHeaders headers = message.getHeaders();
|
||||
|
||||
Message<?> messageToSend = getMessageBuilderFactory()
|
||||
.fromMessage(message)
|
||||
.removeHeaders("LogicalResourceId",
|
||||
"MessageId",
|
||||
"ReceiptHandle",
|
||||
"Acknowledgment")
|
||||
Message<?> messageToSend = getMessageBuilderFactory().fromMessage(message)
|
||||
.removeHeaders("LogicalResourceId", "MessageId", "ReceiptHandle", "Acknowledgment")
|
||||
.setHeader(AwsHeaders.MESSAGE_ID, headers.get("MessageId"))
|
||||
.setHeader(AwsHeaders.RECEIPT_HANDLE, headers.get("ReceiptHandle"))
|
||||
.setHeader(AwsHeaders.RECEIVED_QUEUE, headers.get("LogicalResourceId"))
|
||||
.setHeader(AwsHeaders.ACKNOWLEDGMENT, headers.get("Acknowledgment"))
|
||||
.build();
|
||||
.setHeader(AwsHeaders.ACKNOWLEDGMENT, headers.get("Acknowledgment")).build();
|
||||
|
||||
sendMessage(messageToSend);
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ package org.springframework.integration.aws.inbound.kinesis;
|
||||
public enum CheckpointMode {
|
||||
|
||||
/**
|
||||
* Checkpoint after each processed record.
|
||||
* Makes sense only if {@link ListenerMode#record} is used.
|
||||
* Checkpoint after each processed record. Makes sense only if
|
||||
* {@link ListenerMode#record} is used.
|
||||
*/
|
||||
record,
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ package org.springframework.integration.aws.inbound.kinesis;
|
||||
* A callback for target record process to perform checkpoint on the related shard.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public interface Checkpointer {
|
||||
|
||||
@@ -110,9 +110,8 @@ public class KclMessageDrivenChannelAdapter extends MessageProducerSupport {
|
||||
private boolean bindSourceRecord;
|
||||
|
||||
public KclMessageDrivenChannelAdapter(String streams) {
|
||||
this(streams, AmazonKinesisClientBuilder.defaultClient(),
|
||||
AmazonCloudWatchClientBuilder.defaultClient(), AmazonDynamoDBClientBuilder.defaultClient(),
|
||||
new DefaultAWSCredentialsProviderChain());
|
||||
this(streams, AmazonKinesisClientBuilder.defaultClient(), AmazonCloudWatchClientBuilder.defaultClient(),
|
||||
AmazonDynamoDBClientBuilder.defaultClient(), new DefaultAWSCredentialsProviderChain());
|
||||
}
|
||||
|
||||
public KclMessageDrivenChannelAdapter(String streams, Regions region) {
|
||||
@@ -121,8 +120,7 @@ public class KclMessageDrivenChannelAdapter extends MessageProducerSupport {
|
||||
AmazonDynamoDBClient.builder().withRegion(region).build(), new DefaultAWSCredentialsProviderChain());
|
||||
}
|
||||
|
||||
public KclMessageDrivenChannelAdapter(String stream,
|
||||
AmazonKinesis kinesisClient, AmazonCloudWatch cloudWatchClient,
|
||||
public KclMessageDrivenChannelAdapter(String stream, AmazonKinesis kinesisClient, AmazonCloudWatch cloudWatchClient,
|
||||
AmazonDynamoDB dynamoDBClient, AWSCredentialsProvider kinesisProxyCredentialsProvider) {
|
||||
|
||||
Assert.notNull(stream, "'stream' must not be null.");
|
||||
@@ -148,8 +146,8 @@ public class KclMessageDrivenChannelAdapter extends MessageProducerSupport {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify an {@link InboundMessageMapper} to extract message headers embedded
|
||||
* into the record data.
|
||||
* Specify an {@link InboundMessageMapper} to extract message headers embedded into
|
||||
* the record data.
|
||||
* @param embeddedHeadersMapper the {@link InboundMessageMapper} to use.
|
||||
*/
|
||||
public void setEmbeddedHeadersMapper(InboundMessageMapper<byte[]> embeddedHeadersMapper) {
|
||||
@@ -183,8 +181,8 @@ public class KclMessageDrivenChannelAdapter extends MessageProducerSupport {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the worker identifier used to distinguish different
|
||||
* workers/processes of a Kinesis application.
|
||||
* Sets the worker identifier used to distinguish different workers/processes of a
|
||||
* Kinesis application.
|
||||
* @param workerId the worker identifier to use
|
||||
*/
|
||||
public void setWorkerId(String workerId) {
|
||||
@@ -194,8 +192,8 @@ public class KclMessageDrivenChannelAdapter extends MessageProducerSupport {
|
||||
|
||||
/**
|
||||
* Set to true to bind the source consumer record in the header named
|
||||
* {@link IntegrationMessageHeaderAccessor#SOURCE_DATA}.
|
||||
* Does not apply to batch listeners.
|
||||
* {@link IntegrationMessageHeaderAccessor#SOURCE_DATA}. Does not apply to batch
|
||||
* listeners.
|
||||
* @param bindSourceRecord true to bind.
|
||||
* @since 2.2
|
||||
*/
|
||||
@@ -207,40 +205,22 @@ public class KclMessageDrivenChannelAdapter extends MessageProducerSupport {
|
||||
protected void onInit() {
|
||||
super.onInit();
|
||||
|
||||
KinesisClientLibConfiguration config =
|
||||
new KinesisClientLibConfiguration(
|
||||
this.consumerGroup,
|
||||
this.stream,
|
||||
null,
|
||||
this.streamInitialSequence,
|
||||
this.kinesisProxyCredentialsProvider,
|
||||
null, null,
|
||||
KinesisClientLibConfiguration.DEFAULT_FAILOVER_TIME_MILLIS,
|
||||
this.workerId,
|
||||
KinesisClientLibConfiguration.DEFAULT_MAX_RECORDS,
|
||||
this.idleBetweenPolls,
|
||||
false,
|
||||
KinesisClientLibConfiguration.DEFAULT_PARENT_SHARD_POLL_INTERVAL_MILLIS,
|
||||
KinesisClientLibConfiguration.DEFAULT_SHARD_SYNC_INTERVAL_MILLIS,
|
||||
KinesisClientLibConfiguration.DEFAULT_CLEANUP_LEASES_UPON_SHARDS_COMPLETION,
|
||||
new ClientConfiguration(),
|
||||
new ClientConfiguration(),
|
||||
new ClientConfiguration(),
|
||||
this.consumerBackoff,
|
||||
KinesisClientLibConfiguration.DEFAULT_METRICS_BUFFER_TIME_MILLIS,
|
||||
KinesisClientLibConfiguration.DEFAULT_METRICS_MAX_QUEUE_SIZE,
|
||||
KinesisClientLibConfiguration.DEFAULT_VALIDATE_SEQUENCE_NUMBER_BEFORE_CHECKPOINTING,
|
||||
null,
|
||||
KinesisClientLibConfiguration.DEFAULT_SHUTDOWN_GRACE_MILLIS);
|
||||
KinesisClientLibConfiguration config = new KinesisClientLibConfiguration(this.consumerGroup, this.stream, null,
|
||||
this.streamInitialSequence, this.kinesisProxyCredentialsProvider, null, null,
|
||||
KinesisClientLibConfiguration.DEFAULT_FAILOVER_TIME_MILLIS, this.workerId,
|
||||
KinesisClientLibConfiguration.DEFAULT_MAX_RECORDS, this.idleBetweenPolls, false,
|
||||
KinesisClientLibConfiguration.DEFAULT_PARENT_SHARD_POLL_INTERVAL_MILLIS,
|
||||
KinesisClientLibConfiguration.DEFAULT_SHARD_SYNC_INTERVAL_MILLIS,
|
||||
KinesisClientLibConfiguration.DEFAULT_CLEANUP_LEASES_UPON_SHARDS_COMPLETION, new ClientConfiguration(),
|
||||
new ClientConfiguration(), new ClientConfiguration(), this.consumerBackoff,
|
||||
KinesisClientLibConfiguration.DEFAULT_METRICS_BUFFER_TIME_MILLIS,
|
||||
KinesisClientLibConfiguration.DEFAULT_METRICS_MAX_QUEUE_SIZE,
|
||||
KinesisClientLibConfiguration.DEFAULT_VALIDATE_SEQUENCE_NUMBER_BEFORE_CHECKPOINTING, null,
|
||||
KinesisClientLibConfiguration.DEFAULT_SHUTDOWN_GRACE_MILLIS);
|
||||
|
||||
this.scheduler = new Worker.Builder()
|
||||
.kinesisClient(this.kinesisClient)
|
||||
.dynamoDBClient(this.dynamoDBClient)
|
||||
.cloudWatchClient(this.cloudWatchClient)
|
||||
.recordProcessorFactory(new RecordProcessorFactory())
|
||||
.execService(new ExecutorServiceAdapter(this.executor))
|
||||
.config(config)
|
||||
.build();
|
||||
this.scheduler = new Worker.Builder().kinesisClient(this.kinesisClient).dynamoDBClient(this.dynamoDBClient)
|
||||
.cloudWatchClient(this.cloudWatchClient).recordProcessorFactory(new RecordProcessorFactory())
|
||||
.execService(new ExecutorServiceAdapter(this.executor)).config(config).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -273,8 +253,8 @@ public class KclMessageDrivenChannelAdapter extends MessageProducerSupport {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KclMessageDrivenChannelAdapter{consumerGroup='" + this.consumerGroup + '\'' +
|
||||
", stream='" + this.stream + "'}";
|
||||
return "KclMessageDrivenChannelAdapter{consumerGroup='" + this.consumerGroup + '\'' + ", stream='" + this.stream
|
||||
+ "'}";
|
||||
}
|
||||
|
||||
private class RecordProcessorFactory implements IRecordProcessorFactory {
|
||||
@@ -354,11 +334,11 @@ public class KclMessageDrivenChannelAdapter extends MessageProducerSupport {
|
||||
|
||||
if (KclMessageDrivenChannelAdapter.this.embeddedHeadersMapper != null) {
|
||||
try {
|
||||
messageToUse =
|
||||
KclMessageDrivenChannelAdapter.this.embeddedHeadersMapper.toMessage((byte[]) payload);
|
||||
messageToUse = KclMessageDrivenChannelAdapter.this.embeddedHeadersMapper
|
||||
.toMessage((byte[]) payload);
|
||||
if (messageToUse == null) {
|
||||
throw new IllegalStateException("The 'embeddedHeadersMapper' returned null for payload: " +
|
||||
Arrays.toString((byte[]) payload));
|
||||
throw new IllegalStateException("The 'embeddedHeadersMapper' returned null for payload: "
|
||||
+ Arrays.toString((byte[]) payload));
|
||||
}
|
||||
payload = messageToUse.getPayload();
|
||||
}
|
||||
@@ -401,8 +381,8 @@ public class KclMessageDrivenChannelAdapter extends MessageProducerSupport {
|
||||
}
|
||||
|
||||
/**
|
||||
* If there's an error channel, we create a new attributes holder here.
|
||||
* Then set the attributes for use by the {@link ErrorMessageStrategy}.
|
||||
* If there's an error channel, we create a new attributes holder here. Then set
|
||||
* the attributes for use by the {@link ErrorMessageStrategy}.
|
||||
* @param record the Kinesis record to use.
|
||||
* @param message the Spring Messaging message to use.
|
||||
*/
|
||||
@@ -426,7 +406,8 @@ public class KclMessageDrivenChannelAdapter extends MessageProducerSupport {
|
||||
checkpointer.checkpoint();
|
||||
}
|
||||
catch (ShutdownException se) {
|
||||
// Ignore checkpoint if the processor instance has been shutdown (fail over).
|
||||
// Ignore checkpoint if the processor instance has been shutdown (fail
|
||||
// over).
|
||||
logger.info("Caught shutdown exception, skipping checkpoint.", se);
|
||||
}
|
||||
catch (ThrottlingException e) {
|
||||
@@ -435,7 +416,8 @@ public class KclMessageDrivenChannelAdapter extends MessageProducerSupport {
|
||||
}
|
||||
}
|
||||
catch (InvalidStateException e) {
|
||||
// This indicates an issue with the DynamoDB table (check for table, provisioned
|
||||
// This indicates an issue with the DynamoDB table (check for table,
|
||||
// provisioned
|
||||
// IOPS).
|
||||
logger.error("Cannot save checkpoint to the DynamoDB table used by the Amazon Kinesis Client Library.",
|
||||
e);
|
||||
|
||||
@@ -80,12 +80,12 @@ import com.amazonaws.services.kinesis.model.ShardIteratorType;
|
||||
import com.amazonaws.services.kinesis.model.StreamStatus;
|
||||
|
||||
/**
|
||||
* The {@link MessageProducerSupport} implementation for receiving data from Amazon Kinesis stream(s).
|
||||
* The {@link MessageProducerSupport} implementation for receiving data from Amazon
|
||||
* Kinesis stream(s).
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @author Krzysztof Witkowski
|
||||
* @author Hervé Fortin
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
@ManagedResource
|
||||
@@ -108,13 +108,8 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
|
||||
private final ShardConsumerManager shardConsumerManager = new ShardConsumerManager();
|
||||
|
||||
private final ExecutorService shardLocksExecutor =
|
||||
Executors.newSingleThreadExecutor(
|
||||
new CustomizableThreadFactory(
|
||||
(getComponentName() == null
|
||||
? ""
|
||||
: getComponentName())
|
||||
+ "-kinesis-shard-locks-"));
|
||||
private final ExecutorService shardLocksExecutor = Executors.newSingleThreadExecutor(new CustomizableThreadFactory(
|
||||
(getComponentName() == null ? "" : getComponentName()) + "-kinesis-shard-locks-"));
|
||||
|
||||
private String consumerGroup = "SpringIntegration";
|
||||
|
||||
@@ -175,8 +170,7 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
this.streams = Arrays.copyOf(streams, streams.length);
|
||||
}
|
||||
|
||||
public KinesisMessageDrivenChannelAdapter(AmazonKinesis amazonKinesis,
|
||||
KinesisShardOffset... shardOffsets) {
|
||||
public KinesisMessageDrivenChannelAdapter(AmazonKinesis amazonKinesis, KinesisShardOffset... shardOffsets) {
|
||||
|
||||
Assert.notNull(amazonKinesis, "'amazonKinesis' must not be null.");
|
||||
Assert.notEmpty(shardOffsets, "'shardOffsets' must not be null.");
|
||||
@@ -245,8 +239,8 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum record to poll per on get-records request.
|
||||
* Not greater then {@code 10000}.
|
||||
* The maximum record to poll per on get-records request. Not greater then
|
||||
* {@code 10000}.
|
||||
* @param recordsLimit the number of records to for per on get-records request.
|
||||
* @see GetRecordsRequest#setLimit
|
||||
*/
|
||||
@@ -274,12 +268,11 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of concurrent {@link ConsumerInvoker}s running.
|
||||
* The {@link ShardConsumer}s are evenly distributed between {@link ConsumerInvoker}s.
|
||||
* Messages from within the same shard will be processed sequentially.
|
||||
* In other words each shard is tied with the particular thread.
|
||||
* By default the concurrency is unlimited and shard
|
||||
* is processed in the {@link #consumerExecutor} directly.
|
||||
* The maximum number of concurrent {@link ConsumerInvoker}s running. The
|
||||
* {@link ShardConsumer}s are evenly distributed between {@link ConsumerInvoker}s.
|
||||
* Messages from within the same shard will be processed sequentially. In other words
|
||||
* each shard is tied with the particular thread. By default the concurrency is
|
||||
* unlimited and shard is processed in the {@link #consumerExecutor} directly.
|
||||
* @param concurrency the concurrency maximum number
|
||||
*/
|
||||
public void setConcurrency(int concurrency) {
|
||||
@@ -287,8 +280,8 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
}
|
||||
|
||||
/**
|
||||
* The sleep interval in milliseconds used in the main loop between shards polling cycles.
|
||||
* Defaults to {@code 1000}l minimum {@code 250}.
|
||||
* The sleep interval in milliseconds used in the main loop between shards polling
|
||||
* cycles. Defaults to {@code 1000}l minimum {@code 250}.
|
||||
* @param idleBetweenPolls the interval to sleep between shards polling cycles.
|
||||
*/
|
||||
public void setIdleBetweenPolls(int idleBetweenPolls) {
|
||||
@@ -296,7 +289,8 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify an {@link InboundMessageMapper} to extract message headers embedded into the record data.
|
||||
* Specify an {@link InboundMessageMapper} to extract message headers embedded into
|
||||
* the record data.
|
||||
* @param embeddedHeadersMapper the {@link InboundMessageMapper} to use.
|
||||
* @since 2.0
|
||||
*/
|
||||
@@ -305,8 +299,8 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a {@link LockRegistry} for an exclusive access to provided streams.
|
||||
* This is not used when shards-based configuration is provided.
|
||||
* Specify a {@link LockRegistry} for an exclusive access to provided streams. This is
|
||||
* not used when shards-based configuration is provided.
|
||||
* @param lockRegistry the {@link LockRegistry} to use.
|
||||
* @since 2.0
|
||||
*/
|
||||
@@ -316,8 +310,8 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
|
||||
/**
|
||||
* Set to true to bind the source consumer record in the header named
|
||||
* {@link IntegrationMessageHeaderAccessor#SOURCE_DATA}.
|
||||
* Does not apply to batch listeners.
|
||||
* {@link IntegrationMessageHeaderAccessor#SOURCE_DATA}. Does not apply to batch
|
||||
* listeners.
|
||||
* @param bindSourceRecord true to bind.
|
||||
* @since 2.2
|
||||
*/
|
||||
@@ -330,19 +324,12 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
super.onInit();
|
||||
|
||||
if (this.consumerExecutor == null) {
|
||||
this.consumerExecutor = Executors.newCachedThreadPool(
|
||||
new CustomizableThreadFactory((getComponentName() == null
|
||||
? ""
|
||||
: getComponentName())
|
||||
+ "-kinesis-consumer-"));
|
||||
this.consumerExecutor = Executors.newCachedThreadPool(new CustomizableThreadFactory(
|
||||
(getComponentName() == null ? "" : getComponentName()) + "-kinesis-consumer-"));
|
||||
}
|
||||
if (this.dispatcherExecutor == null) {
|
||||
this.dispatcherExecutor =
|
||||
Executors.newCachedThreadPool(
|
||||
new CustomizableThreadFactory((getComponentName() == null
|
||||
? ""
|
||||
: getComponentName())
|
||||
+ "-kinesis-dispatcher-"));
|
||||
this.dispatcherExecutor = Executors.newCachedThreadPool(new CustomizableThreadFactory(
|
||||
(getComponentName() == null ? "" : getComponentName()) + "-kinesis-dispatcher-"));
|
||||
}
|
||||
|
||||
if (this.streams == null) {
|
||||
@@ -371,8 +358,8 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
}
|
||||
else {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("There is no ShardConsumer for shard [" + shard + "] in stream [" + shard
|
||||
+ "] to stop.");
|
||||
this.logger.debug(
|
||||
"There is no ShardConsumer for shard [" + shard + "] in stream [" + shard + "] to stop.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -420,9 +407,8 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
|
||||
private void restartShardConsumerForOffset(KinesisShardOffset shardOffset) {
|
||||
Assert.isTrue(this.shardOffsets.contains(shardOffset),
|
||||
"The [" + KinesisMessageDrivenChannelAdapter.this +
|
||||
"] doesn't operate shard [" + shardOffset.getShard() +
|
||||
"] for stream [" + shardOffset.getStream() + "]");
|
||||
"The [" + KinesisMessageDrivenChannelAdapter.this + "] doesn't operate shard [" + shardOffset.getShard()
|
||||
+ "] for stream [" + shardOffset.getStream() + "]");
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Resetting consumer for [" + shardOffset + "]...");
|
||||
@@ -456,8 +442,8 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
super.doStart();
|
||||
if (ListenerMode.batch.equals(this.listenerMode) && CheckpointMode.record.equals(this.checkpointMode)) {
|
||||
this.checkpointMode = CheckpointMode.batch;
|
||||
logger.warn("The 'checkpointMode' is overridden from [CheckpointMode.record] to [CheckpointMode.batch] " +
|
||||
"because it does not make sense in case of [ListenerMode.batch].");
|
||||
logger.warn("The 'checkpointMode' is overridden from [CheckpointMode.record] to [CheckpointMode.batch] "
|
||||
+ "because it does not make sense in case of [ListenerMode.batch].");
|
||||
}
|
||||
|
||||
if (this.streams != null) {
|
||||
@@ -507,14 +493,13 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
}
|
||||
try {
|
||||
if (!shardsGatherLatch.await(this.startTimeout, TimeUnit.MILLISECONDS)) {
|
||||
throw new IllegalStateException("The [ "
|
||||
+ KinesisMessageDrivenChannelAdapter.this +
|
||||
"] could not start during timeout: " + this.startTimeout);
|
||||
throw new IllegalStateException("The [ " + KinesisMessageDrivenChannelAdapter.this
|
||||
+ "] could not start during timeout: " + this.startTimeout);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new IllegalStateException("The [ " + KinesisMessageDrivenChannelAdapter.this +
|
||||
"] has been interrupted from start.");
|
||||
throw new IllegalStateException(
|
||||
"The [ " + KinesisMessageDrivenChannelAdapter.this + "] has been interrupted from start.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,28 +511,26 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
|
||||
String exclusiveStartShardId = null;
|
||||
while (true) {
|
||||
DescribeStreamRequest describeStreamRequest = new DescribeStreamRequest()
|
||||
.withStreamName(stream)
|
||||
DescribeStreamRequest describeStreamRequest = new DescribeStreamRequest().withStreamName(stream)
|
||||
.withExclusiveStartShardId(exclusiveStartShardId);
|
||||
|
||||
DescribeStreamResult describeStreamResult = null;
|
||||
// Call DescribeStream, with backoff and retries (if we get LimitExceededException).
|
||||
// Call DescribeStream, with backoff and retries (if we get
|
||||
// LimitExceededException).
|
||||
try {
|
||||
describeStreamResult = this.amazonKinesis.describeStream(describeStreamRequest);
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.info("Got an exception when describing stream [" + stream + "]. " +
|
||||
"Backing off for [" + this.describeStreamBackoff + "] millis.", e);
|
||||
logger.info("Got an exception when describing stream [" + stream + "]. " + "Backing off for ["
|
||||
+ this.describeStreamBackoff + "] millis.", e);
|
||||
}
|
||||
|
||||
if (describeStreamResult == null ||
|
||||
!StreamStatus.ACTIVE.toString().equals(
|
||||
describeStreamResult.getStreamDescription().getStreamStatus())) {
|
||||
if (describeStreamResult == null || !StreamStatus.ACTIVE.toString()
|
||||
.equals(describeStreamResult.getStreamDescription().getStreamStatus())) {
|
||||
|
||||
if (describeStreamRetries++ > this.describeStreamRetries) {
|
||||
ResourceNotFoundException resourceNotFoundException =
|
||||
new ResourceNotFoundException("The stream [" + stream +
|
||||
"] isn't ACTIVE or doesn't exist.");
|
||||
ResourceNotFoundException resourceNotFoundException = new ResourceNotFoundException(
|
||||
"The stream [" + stream + "] isn't ACTIVE or doesn't exist.");
|
||||
resourceNotFoundException.setServiceName("Kinesis");
|
||||
throw resourceNotFoundException;
|
||||
}
|
||||
@@ -557,8 +540,9 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException("The [describeStream] thread for the stream ["
|
||||
+ stream + "] has been interrupted.", e);
|
||||
throw new IllegalStateException(
|
||||
"The [describeStream] thread for the stream [" + stream + "] has been interrupted.",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,19 +555,19 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
if (endingSequenceNumber != null) {
|
||||
String checkpoint = this.checkpointStore.get(key);
|
||||
|
||||
boolean skipClosedShard = checkpoint != null &&
|
||||
new BigInteger(endingSequenceNumber)
|
||||
.compareTo(new BigInteger(checkpoint)) <= 0;
|
||||
boolean skipClosedShard = checkpoint != null && new BigInteger(endingSequenceNumber)
|
||||
.compareTo(new BigInteger(checkpoint)) <= 0;
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("The shard [" + shard + "] in stream [" + stream +
|
||||
"] is closed CLOSED with endingSequenceNumber [" + endingSequenceNumber +
|
||||
"].\nThe last processed checkpoint is [" + checkpoint + "]." +
|
||||
(skipClosedShard ? "\nThe shard will be skipped." : ""));
|
||||
logger.trace("The shard [" + shard + "] in stream [" + stream
|
||||
+ "] is closed CLOSED with endingSequenceNumber [" + endingSequenceNumber
|
||||
+ "].\nThe last processed checkpoint is [" + checkpoint + "]."
|
||||
+ (skipClosedShard ? "\nThe shard will be skipped." : ""));
|
||||
}
|
||||
|
||||
if (skipClosedShard) {
|
||||
// Skip CLOSED shard which has been read before according a checkpoint
|
||||
// Skip CLOSED shard which has been read before
|
||||
// according a checkpoint
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -592,8 +576,9 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.info("Got an exception when processing shards in stream [" + stream + "].\n" +
|
||||
"Retrying...", e);
|
||||
logger.info(
|
||||
"Got an exception when processing shards in stream [" + stream + "].\n" + "Retrying...",
|
||||
e);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -693,8 +678,8 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
}
|
||||
|
||||
/**
|
||||
* If there's an error channel, we create a new attributes holder here.
|
||||
* Then set the attributes for use by the {@link ErrorMessageStrategy}.
|
||||
* If there's an error channel, we create a new attributes holder here. Then set the
|
||||
* attributes for use by the {@link ErrorMessageStrategy}.
|
||||
* @param record the Kinesis record to use.
|
||||
* @param message the Spring Messaging message to use.
|
||||
*/
|
||||
@@ -719,10 +704,8 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KinesisMessageDrivenChannelAdapter{" +
|
||||
"shardOffsets=" + this.shardOffsets +
|
||||
", consumerGroup='" + this.consumerGroup + '\'' +
|
||||
'}';
|
||||
return "KinesisMessageDrivenChannelAdapter{" + "shardOffsets=" + this.shardOffsets + ", consumerGroup='"
|
||||
+ this.consumerGroup + '\'' + '}';
|
||||
}
|
||||
|
||||
private final class ConsumerDispatcher implements SchedulingAwareRunnable {
|
||||
@@ -744,8 +727,8 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
}
|
||||
}
|
||||
|
||||
Iterator<ShardConsumer> iterator =
|
||||
KinesisMessageDrivenChannelAdapter.this.shardConsumers.values().iterator();
|
||||
Iterator<ShardConsumer> iterator = KinesisMessageDrivenChannelAdapter.this.shardConsumers.values()
|
||||
.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
ShardConsumer shardConsumer = iterator.next();
|
||||
shardConsumer.execute();
|
||||
@@ -836,65 +819,62 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
if (this.task == null) {
|
||||
switch (this.state) {
|
||||
|
||||
case NEW:
|
||||
case EXPIRED:
|
||||
this.task = () -> {
|
||||
try {
|
||||
if (this.shardOffset.isReset()) {
|
||||
this.checkpointer.remove();
|
||||
}
|
||||
else {
|
||||
String checkpoint = this.checkpointer.getCheckpoint();
|
||||
if (checkpoint != null) {
|
||||
this.shardOffset.setSequenceNumber(checkpoint);
|
||||
this.shardOffset.setIteratorType(ShardIteratorType.AFTER_SEQUENCE_NUMBER);
|
||||
}
|
||||
}
|
||||
if (logger.isInfoEnabled() && this.state == ConsumerState.NEW) {
|
||||
logger.info("The [" + this + "] has been started.");
|
||||
}
|
||||
GetShardIteratorRequest shardIteratorRequest =
|
||||
this.shardOffset.toShardIteratorRequest();
|
||||
this.shardIterator =
|
||||
KinesisMessageDrivenChannelAdapter.this.amazonKinesis
|
||||
.getShardIterator(shardIteratorRequest)
|
||||
.getShardIterator();
|
||||
if (ConsumerState.STOP != this.state) {
|
||||
this.state = ConsumerState.CONSUME;
|
||||
case NEW:
|
||||
case EXPIRED:
|
||||
this.task = () -> {
|
||||
try {
|
||||
if (this.shardOffset.isReset()) {
|
||||
this.checkpointer.remove();
|
||||
}
|
||||
else {
|
||||
String checkpoint = this.checkpointer.getCheckpoint();
|
||||
if (checkpoint != null) {
|
||||
this.shardOffset.setSequenceNumber(checkpoint);
|
||||
this.shardOffset.setIteratorType(ShardIteratorType.AFTER_SEQUENCE_NUMBER);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.task = null;
|
||||
if (logger.isInfoEnabled() && this.state == ConsumerState.NEW) {
|
||||
logger.info("The [" + this + "] has been started.");
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
case CONSUME:
|
||||
this.task = this.processTask;
|
||||
break;
|
||||
|
||||
case SLEEP:
|
||||
if (System.currentTimeMillis() >= this.sleepUntil) {
|
||||
this.state = ConsumerState.CONSUME;
|
||||
}
|
||||
this.task = null;
|
||||
break;
|
||||
|
||||
case STOP:
|
||||
if (this.shardIterator == null) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Stopping the [" + this +
|
||||
"] on the checkpoint [" + this.checkpointer.getCheckpoint() +
|
||||
"] because the shard has been CLOSED and exhausted.");
|
||||
GetShardIteratorRequest shardIteratorRequest = this.shardOffset.toShardIteratorRequest();
|
||||
this.shardIterator = KinesisMessageDrivenChannelAdapter.this.amazonKinesis
|
||||
.getShardIterator(shardIteratorRequest).getShardIterator();
|
||||
if (ConsumerState.STOP != this.state) {
|
||||
this.state = ConsumerState.CONSUME;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Stopping the [" + this + "].");
|
||||
}
|
||||
finally {
|
||||
this.task = null;
|
||||
}
|
||||
this.task = null;
|
||||
break;
|
||||
};
|
||||
break;
|
||||
|
||||
case CONSUME:
|
||||
this.task = this.processTask;
|
||||
break;
|
||||
|
||||
case SLEEP:
|
||||
if (System.currentTimeMillis() >= this.sleepUntil) {
|
||||
this.state = ConsumerState.CONSUME;
|
||||
}
|
||||
this.task = null;
|
||||
break;
|
||||
|
||||
case STOP:
|
||||
if (this.shardIterator == null) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Stopping the [" + this + "] on the checkpoint ["
|
||||
+ this.checkpointer.getCheckpoint()
|
||||
+ "] because the shard has been CLOSED and exhausted.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Stopping the [" + this + "].");
|
||||
}
|
||||
}
|
||||
this.task = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.task != null) {
|
||||
@@ -939,11 +919,9 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
|
||||
if (ConsumerState.STOP != this.state && result.getRecords().isEmpty()) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("No records for [" + this +
|
||||
"] on sequenceNumber [" +
|
||||
this.checkpointer.getLastCheckpointValue() +
|
||||
"]. Suspend consuming for [" +
|
||||
KinesisMessageDrivenChannelAdapter.this.consumerBackoff + "] milliseconds.");
|
||||
logger.debug("No records for [" + this + "] on sequenceNumber ["
|
||||
+ this.checkpointer.getLastCheckpointValue() + "]. Suspend consuming for ["
|
||||
+ KinesisMessageDrivenChannelAdapter.this.consumerBackoff + "] milliseconds.");
|
||||
}
|
||||
prepareSleepState();
|
||||
}
|
||||
@@ -959,18 +937,20 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
return KinesisMessageDrivenChannelAdapter.this.amazonKinesis.getRecords(getRecordsRequest);
|
||||
}
|
||||
catch (ExpiredIteratorException e) {
|
||||
// Iterator expired, but this does not mean that shard no longer contains records.
|
||||
// Lets acquire iterator again (using checkpointer for iterator start sequence number).
|
||||
// Iterator expired, but this does not mean that shard no longer contains
|
||||
// records.
|
||||
// Lets acquire iterator again (using checkpointer for iterator start
|
||||
// sequence number).
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Shard iterator for [" + ShardConsumer.this + "] expired.\n" +
|
||||
"A new one will be started from the check pointed sequence number.");
|
||||
logger.info("Shard iterator for [" + ShardConsumer.this + "] expired.\n"
|
||||
+ "A new one will be started from the check pointed sequence number.");
|
||||
}
|
||||
this.state = ConsumerState.EXPIRED;
|
||||
}
|
||||
catch (ProvisionedThroughputExceededException e) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("GetRecords request throttled for [" + ShardConsumer.this +
|
||||
"] with the reason: " + e.getErrorMessage());
|
||||
logger.warn("GetRecords request throttled for [" + ShardConsumer.this + "] with the reason: "
|
||||
+ e.getErrorMessage());
|
||||
}
|
||||
// We are throttled, so let's sleep
|
||||
prepareSleepState();
|
||||
@@ -980,8 +960,8 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
}
|
||||
|
||||
private void prepareSleepState() {
|
||||
ShardConsumer.this.sleepUntil = System.currentTimeMillis() +
|
||||
KinesisMessageDrivenChannelAdapter.this.consumerBackoff;
|
||||
ShardConsumer.this.sleepUntil = System.currentTimeMillis()
|
||||
+ KinesisMessageDrivenChannelAdapter.this.consumerBackoff;
|
||||
ShardConsumer.this.state = ConsumerState.SLEEP;
|
||||
}
|
||||
|
||||
@@ -993,63 +973,56 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
this.checkpointer.setHighestSequence(records.get(records.size() - 1).getSequenceNumber());
|
||||
|
||||
switch (KinesisMessageDrivenChannelAdapter.this.listenerMode) {
|
||||
case record:
|
||||
for (Record record : records) {
|
||||
performSend(prepareMessageForRecord(record), record);
|
||||
case record:
|
||||
for (Record record : records) {
|
||||
performSend(prepareMessageForRecord(record), record);
|
||||
|
||||
if (CheckpointMode.record.equals(KinesisMessageDrivenChannelAdapter.this.checkpointMode)) {
|
||||
this.checkpointer.checkpoint(record.getSequenceNumber());
|
||||
}
|
||||
if (CheckpointMode.record.equals(KinesisMessageDrivenChannelAdapter.this.checkpointMode)) {
|
||||
this.checkpointer.checkpoint(record.getSequenceNumber());
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
break;
|
||||
|
||||
case batch:
|
||||
Object payload = records;
|
||||
case batch:
|
||||
Object payload = records;
|
||||
|
||||
if (KinesisMessageDrivenChannelAdapter.this.embeddedHeadersMapper != null) {
|
||||
payload = records.stream()
|
||||
.map(this::prepareMessageForRecord)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
if (KinesisMessageDrivenChannelAdapter.this.embeddedHeadersMapper != null) {
|
||||
payload = records.stream().map(this::prepareMessageForRecord).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
final List<String> partitionKeys;
|
||||
final List<String> sequenceNumbers;
|
||||
if (KinesisMessageDrivenChannelAdapter.this.converter != null) {
|
||||
partitionKeys = new ArrayList<>();
|
||||
sequenceNumbers = new ArrayList<>();
|
||||
final List<String> partitionKeys;
|
||||
final List<String> sequenceNumbers;
|
||||
if (KinesisMessageDrivenChannelAdapter.this.converter != null) {
|
||||
partitionKeys = new ArrayList<>();
|
||||
sequenceNumbers = new ArrayList<>();
|
||||
|
||||
payload = records.stream()
|
||||
.map(r -> {
|
||||
partitionKeys.add(r.getPartitionKey());
|
||||
sequenceNumbers.add(r.getSequenceNumber());
|
||||
payload = records.stream().map(r -> {
|
||||
partitionKeys.add(r.getPartitionKey());
|
||||
sequenceNumbers.add(r.getSequenceNumber());
|
||||
|
||||
return KinesisMessageDrivenChannelAdapter.this.converter
|
||||
.convert(r.getData().array());
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
else {
|
||||
partitionKeys = null;
|
||||
sequenceNumbers = null;
|
||||
}
|
||||
return KinesisMessageDrivenChannelAdapter.this.converter.convert(r.getData().array());
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
else {
|
||||
partitionKeys = null;
|
||||
sequenceNumbers = null;
|
||||
}
|
||||
|
||||
AbstractIntegrationMessageBuilder<?> messageBuilder =
|
||||
getMessageBuilderFactory()
|
||||
.withPayload(payload)
|
||||
.setHeader(AwsHeaders.RECEIVED_PARTITION_KEY, partitionKeys)
|
||||
.setHeader(AwsHeaders.RECEIVED_SEQUENCE_NUMBER, sequenceNumbers);
|
||||
AbstractIntegrationMessageBuilder<?> messageBuilder = getMessageBuilderFactory().withPayload(payload)
|
||||
.setHeader(AwsHeaders.RECEIVED_PARTITION_KEY, partitionKeys)
|
||||
.setHeader(AwsHeaders.RECEIVED_SEQUENCE_NUMBER, sequenceNumbers);
|
||||
|
||||
performSend(messageBuilder, records);
|
||||
performSend(messageBuilder, records);
|
||||
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
if (CheckpointMode.batch.equals(KinesisMessageDrivenChannelAdapter.this.checkpointMode)) {
|
||||
this.checkpointer.checkpoint();
|
||||
}
|
||||
else if (CheckpointMode.periodic.equals(KinesisMessageDrivenChannelAdapter.this.checkpointMode) &&
|
||||
System.currentTimeMillis() > nextCheckpointTimeInMillis) {
|
||||
else if (CheckpointMode.periodic.equals(KinesisMessageDrivenChannelAdapter.this.checkpointMode)
|
||||
&& System.currentTimeMillis() > nextCheckpointTimeInMillis) {
|
||||
this.checkpointer.checkpoint();
|
||||
this.nextCheckpointTimeInMillis = System.currentTimeMillis() + checkpointsInterval;
|
||||
}
|
||||
@@ -1061,9 +1034,8 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
|
||||
if (KinesisMessageDrivenChannelAdapter.this.embeddedHeadersMapper != null) {
|
||||
try {
|
||||
messageToUse =
|
||||
KinesisMessageDrivenChannelAdapter.this.embeddedHeadersMapper
|
||||
.toMessage((byte[]) payload);
|
||||
messageToUse = KinesisMessageDrivenChannelAdapter.this.embeddedHeadersMapper
|
||||
.toMessage((byte[]) payload);
|
||||
|
||||
payload = messageToUse.getPayload();
|
||||
}
|
||||
@@ -1072,17 +1044,14 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
}
|
||||
}
|
||||
|
||||
if (payload instanceof byte[] &&
|
||||
KinesisMessageDrivenChannelAdapter.this.converter != null) {
|
||||
if (payload instanceof byte[] && KinesisMessageDrivenChannelAdapter.this.converter != null) {
|
||||
|
||||
payload = KinesisMessageDrivenChannelAdapter.this.converter.convert((byte[]) payload);
|
||||
}
|
||||
|
||||
AbstractIntegrationMessageBuilder<Object> messageBuilder =
|
||||
getMessageBuilderFactory()
|
||||
.withPayload(payload)
|
||||
.setHeader(AwsHeaders.RECEIVED_PARTITION_KEY, record.getPartitionKey())
|
||||
.setHeader(AwsHeaders.RECEIVED_SEQUENCE_NUMBER, record.getSequenceNumber());
|
||||
AbstractIntegrationMessageBuilder<Object> messageBuilder = getMessageBuilderFactory().withPayload(payload)
|
||||
.setHeader(AwsHeaders.RECEIVED_PARTITION_KEY, record.getPartitionKey())
|
||||
.setHeader(AwsHeaders.RECEIVED_SEQUENCE_NUMBER, record.getSequenceNumber());
|
||||
|
||||
if (KinesisMessageDrivenChannelAdapter.this.bindSourceRecord) {
|
||||
messageBuilder.setHeader(IntegrationMessageHeaderAccessor.SOURCE_DATA, record);
|
||||
@@ -1109,18 +1078,14 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
sendMessage(messageToSend);
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.info("Got an exception during sending a '" + messageToSend + "'" +
|
||||
"\nfor the '" + rawRecord + "'.\n" +
|
||||
"Consider to use 'errorChannel' flow for the compensation logic.", e);
|
||||
logger.info("Got an exception during sending a '" + messageToSend + "'" + "\nfor the '" + rawRecord
|
||||
+ "'.\n" + "Consider to use 'errorChannel' flow for the compensation logic.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ShardConsumer{" +
|
||||
"shardOffset=" + this.shardOffset +
|
||||
", state=" + this.state +
|
||||
'}';
|
||||
return "ShardConsumer{" + "shardOffset=" + this.shardOffset + ", state=" + this.state + '}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1166,7 +1131,7 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
throw new IllegalStateException("ConsumerInvoker thread [" + this + "] has been interrupted", e);
|
||||
}
|
||||
|
||||
for (Iterator<ShardConsumer> iterator = this.consumers.iterator(); iterator.hasNext(); ) {
|
||||
for (Iterator<ShardConsumer> iterator = this.consumers.iterator(); iterator.hasNext();) {
|
||||
ShardConsumer shardConsumer = iterator.next();
|
||||
if (ConsumerState.STOP == shardConsumer.state) {
|
||||
iterator.remove();
|
||||
@@ -1184,7 +1149,8 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
}
|
||||
}
|
||||
synchronized (KinesisMessageDrivenChannelAdapter.this.consumerInvokers) {
|
||||
// The attempt to survive if ShardConsumer has been added during synchronization
|
||||
// The attempt to survive if ShardConsumer has been added during
|
||||
// synchronization
|
||||
if (this.consumers.isEmpty()) {
|
||||
KinesisMessageDrivenChannelAdapter.this.consumerInvokers.remove(this);
|
||||
break;
|
||||
@@ -1225,33 +1191,31 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
|
||||
this.shardOffsetsToConsumer.entrySet()
|
||||
.removeIf(entry -> {
|
||||
boolean remove = true;
|
||||
if (KinesisMessageDrivenChannelAdapter.this.lockRegistry != null) {
|
||||
String key = entry.getKey();
|
||||
Lock lock =
|
||||
KinesisMessageDrivenChannelAdapter.this.lockRegistry.obtain(key);
|
||||
try {
|
||||
if (lock.tryLock()) {
|
||||
this.locks.put(key, lock);
|
||||
}
|
||||
else {
|
||||
remove = false;
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("Error during locking: " + lock, e);
|
||||
}
|
||||
this.shardOffsetsToConsumer.entrySet().removeIf(entry -> {
|
||||
boolean remove = true;
|
||||
if (KinesisMessageDrivenChannelAdapter.this.lockRegistry != null) {
|
||||
String key = entry.getKey();
|
||||
Lock lock = KinesisMessageDrivenChannelAdapter.this.lockRegistry.obtain(key);
|
||||
try {
|
||||
if (lock.tryLock()) {
|
||||
this.locks.put(key, lock);
|
||||
}
|
||||
else {
|
||||
remove = false;
|
||||
}
|
||||
|
||||
if (remove) {
|
||||
populateConsumer(entry.getValue());
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("Error during locking: " + lock, e);
|
||||
}
|
||||
}
|
||||
|
||||
return remove;
|
||||
});
|
||||
if (remove) {
|
||||
populateConsumer(entry.getValue());
|
||||
}
|
||||
|
||||
return remove;
|
||||
});
|
||||
|
||||
while (KinesisMessageDrivenChannelAdapter.this.lockRegistry != null) {
|
||||
String lockKey = this.forUnlocking.poll();
|
||||
@@ -1276,13 +1240,13 @@ public class KinesisMessageDrivenChannelAdapter extends MessageProducerSupport i
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException("ShardConsumerManager Thread [" +
|
||||
this + "] has been interrupted", e);
|
||||
throw new IllegalStateException(
|
||||
"ShardConsumerManager Thread [" + this + "] has been interrupted", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
for (Iterator<Lock> iterator = this.locks.values().iterator(); iterator.hasNext(); ) {
|
||||
for (Iterator<Lock> iterator = this.locks.values().iterator(); iterator.hasNext();) {
|
||||
Lock lock = iterator.next();
|
||||
try {
|
||||
lock.unlock();
|
||||
|
||||
@@ -28,25 +28,22 @@ import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.ErrorMessage;
|
||||
|
||||
/**
|
||||
* The {@link ErrorMessageStrategy} implementation to build an {@link ErrorMessage}
|
||||
* with the {@link AwsHeaders#RAW_RECORD} header by the value from
|
||||
* the the provided {@link AttributeAccessor}.
|
||||
* The {@link ErrorMessageStrategy} implementation to build an {@link ErrorMessage} with
|
||||
* the {@link AwsHeaders#RAW_RECORD} header by the value from the the provided
|
||||
* {@link AttributeAccessor}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class KinesisMessageHeaderErrorMessageStrategy implements ErrorMessageStrategy {
|
||||
|
||||
@Override
|
||||
public ErrorMessage buildErrorMessage(Throwable throwable, AttributeAccessor context) {
|
||||
Object inputMessage =
|
||||
context == null ? null : context.getAttribute(ErrorMessageUtils.INPUT_MESSAGE_CONTEXT_KEY);
|
||||
Object inputMessage = context == null ? null
|
||||
: context.getAttribute(ErrorMessageUtils.INPUT_MESSAGE_CONTEXT_KEY);
|
||||
|
||||
Map<String, Object> headers =
|
||||
context == null
|
||||
? new HashMap<>()
|
||||
: Collections.singletonMap(AwsHeaders.RAW_RECORD, context.getAttribute(AwsHeaders.RAW_RECORD));
|
||||
Map<String, Object> headers = context == null ? new HashMap<>()
|
||||
: Collections.singletonMap(AwsHeaders.RAW_RECORD, context.getAttribute(AwsHeaders.RAW_RECORD));
|
||||
|
||||
return new ErrorMessage(throwable, headers, inputMessage instanceof Message ? (Message<?>) inputMessage : null);
|
||||
}
|
||||
|
||||
@@ -114,11 +114,8 @@ public class KinesisShardOffset {
|
||||
public GetShardIteratorRequest toShardIteratorRequest() {
|
||||
Assert.state(this.stream != null && this.shard != null,
|
||||
"'stream' and 'shard' must not be null for conversion to the GetShardIteratorRequest.");
|
||||
return new GetShardIteratorRequest()
|
||||
.withStreamName(this.stream)
|
||||
.withShardId(this.shard)
|
||||
.withShardIteratorType(this.iteratorType)
|
||||
.withStartingSequenceNumber(this.sequenceNumber)
|
||||
return new GetShardIteratorRequest().withStreamName(this.stream).withShardId(this.shard)
|
||||
.withShardIteratorType(this.iteratorType).withStartingSequenceNumber(this.sequenceNumber)
|
||||
.withTimestamp(this.timestamp);
|
||||
}
|
||||
|
||||
@@ -131,8 +128,7 @@ public class KinesisShardOffset {
|
||||
return false;
|
||||
}
|
||||
KinesisShardOffset that = (KinesisShardOffset) o;
|
||||
return Objects.equals(this.stream, that.stream) &&
|
||||
Objects.equals(this.shard, that.shard);
|
||||
return Objects.equals(this.stream, that.stream) && Objects.equals(this.shard, that.shard);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -140,17 +136,11 @@ public class KinesisShardOffset {
|
||||
return Objects.hash(this.stream, this.shard);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KinesisShardOffset{" +
|
||||
"iteratorType=" + this.iteratorType +
|
||||
", sequenceNumber='" + this.sequenceNumber + '\'' +
|
||||
", timestamp=" + this.timestamp +
|
||||
", stream='" + this.stream + '\'' +
|
||||
", shard='" + this.shard + '\'' +
|
||||
", reset=" + this.reset +
|
||||
'}';
|
||||
return "KinesisShardOffset{" + "iteratorType=" + this.iteratorType + ", sequenceNumber='" + this.sequenceNumber
|
||||
+ '\'' + ", timestamp=" + this.timestamp + ", stream='" + this.stream + '\'' + ", shard='" + this.shard
|
||||
+ '\'' + ", reset=" + this.reset + '}';
|
||||
}
|
||||
|
||||
public static KinesisShardOffset latest() {
|
||||
@@ -211,5 +201,4 @@ public class KinesisShardOffset {
|
||||
return kinesisShardOffset;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ public enum ListenerMode {
|
||||
record,
|
||||
|
||||
/**
|
||||
* Each {@link Message} will contain {@code List} ( if not empty)
|
||||
* of converted or raw {@code Record}s.
|
||||
* Each {@link Message} will contain {@code List} ( if not empty) of converted or raw
|
||||
* {@code Record}s.
|
||||
*/
|
||||
batch
|
||||
|
||||
|
||||
@@ -25,14 +25,13 @@ import org.springframework.integration.metadata.ConcurrentMetadataStore;
|
||||
import org.springframework.integration.metadata.MetadataStore;
|
||||
|
||||
/**
|
||||
* An internal {@link Checkpointer} implementation based on
|
||||
* provided {@link MetadataStore} and {@code key} for shard.
|
||||
* An internal {@link Checkpointer} implementation based on provided {@link MetadataStore}
|
||||
* and {@code key} for shard.
|
||||
* <p>
|
||||
* The instances of this class is created by the {@link KinesisMessageDrivenChannelAdapter}
|
||||
* for each {@code ShardConsumer}.
|
||||
* 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 {
|
||||
@@ -61,8 +60,8 @@ class ShardCheckpointer implements Checkpointer {
|
||||
public boolean checkpoint(String sequenceNumber) {
|
||||
if (this.active) {
|
||||
String existingSequence = getCheckpoint();
|
||||
if (existingSequence == null ||
|
||||
new BigInteger(existingSequence).compareTo(new BigInteger(sequenceNumber)) < 0) {
|
||||
if (existingSequence == null
|
||||
|| new BigInteger(existingSequence).compareTo(new BigInteger(sequenceNumber)) < 0) {
|
||||
if (existingSequence != null) {
|
||||
return this.checkpointStore.replace(this.key, existingSequence, sequenceNumber);
|
||||
}
|
||||
@@ -102,10 +101,8 @@ class ShardCheckpointer implements Checkpointer {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ShardCheckpointer{" +
|
||||
"key='" + this.key + '\'' +
|
||||
", lastCheckpointValue='" + this.lastCheckpointValue + '\'' +
|
||||
'}';
|
||||
return "ShardCheckpointer{" + "key='" + this.key + '\'' + ", lastCheckpointValue='" + this.lastCheckpointValue
|
||||
+ '\'' + '}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -54,14 +54,14 @@ import com.amazonaws.services.dynamodbv2.model.LockTableDoesNotExistException;
|
||||
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;
|
||||
|
||||
/**
|
||||
* An {@link ExpirableLockRegistry} implementation for the AWS DynamoDB.
|
||||
* The algorithm is based on the {@link AmazonDynamoDBLockClient}.
|
||||
* An {@link ExpirableLockRegistry} implementation for the AWS DynamoDB. The algorithm is
|
||||
* based on the {@link AmazonDynamoDBLockClient}.
|
||||
* <p>
|
||||
* Can create table in DynamoDB if an external {@link AmazonDynamoDBLockClient} is not provided.
|
||||
* Can create table in DynamoDB if an external {@link AmazonDynamoDBLockClient} is not
|
||||
* provided.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @author Karl Lessard
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class DynamoDbLockRegistry implements ExpirableLockRegistry, InitializingBean, DisposableBean {
|
||||
@@ -72,7 +72,8 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
public static final String DEFAULT_TABLE_NAME = "SpringIntegrationLockRegistry";
|
||||
|
||||
/**
|
||||
* The {@value DEFAULT_PARTITION_KEY_NAME} default name for the partition key in the table.
|
||||
* The {@value DEFAULT_PARTITION_KEY_NAME} default name for the partition key in the
|
||||
* table.
|
||||
*/
|
||||
public static final String DEFAULT_PARTITION_KEY_NAME = "lockKey";
|
||||
|
||||
@@ -87,7 +88,8 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
public static final String DEFAULT_SORT_KEY = "SpringIntegrationLocks";
|
||||
|
||||
/**
|
||||
* The {@value DEFAULT_REFRESH_PERIOD_MS} default period in milliseconds between DB polling requests.
|
||||
* The {@value DEFAULT_REFRESH_PERIOD_MS} default period in milliseconds between DB
|
||||
* polling requests.
|
||||
*/
|
||||
public static final long DEFAULT_REFRESH_PERIOD_MS = 1000L;
|
||||
|
||||
@@ -124,11 +126,11 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
private long heartbeatPeriod = 5L;
|
||||
|
||||
/**
|
||||
* An {@link ExecutorService} to call {@link AmazonDynamoDBLockClient#releaseLock(LockItem)}
|
||||
* in the separate thread when the current one is interrupted.
|
||||
* An {@link ExecutorService} to call
|
||||
* {@link AmazonDynamoDBLockClient#releaseLock(LockItem)} in the separate thread when
|
||||
* the current one is interrupted.
|
||||
*/
|
||||
private Executor executor =
|
||||
Executors.newCachedThreadPool(new CustomizableThreadFactory("dynamodb-lock-registry-"));
|
||||
private Executor executor = Executors.newCachedThreadPool(new CustomizableThreadFactory("dynamodb-lock-registry-"));
|
||||
|
||||
/**
|
||||
* Flag to denote whether the {@link ExecutorService} was provided via the setter and
|
||||
@@ -138,7 +140,6 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
|
||||
private volatile boolean initialized;
|
||||
|
||||
|
||||
public DynamoDbLockRegistry(AmazonDynamoDB dynamoDB) {
|
||||
this(dynamoDB, DEFAULT_TABLE_NAME);
|
||||
}
|
||||
@@ -202,8 +203,8 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link Executor}, where is not provided then a default of
|
||||
* cached thread pool Executor will be used.
|
||||
* Set the {@link Executor}, where is not provided then a default of cached thread
|
||||
* pool Executor will be used.
|
||||
* @param executor the executor service
|
||||
*/
|
||||
public void setExecutor(Executor executor) {
|
||||
@@ -214,22 +215,16 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (!this.dynamoDBLockClientExplicitlySet) {
|
||||
AmazonDynamoDBLockClientOptions dynamoDBLockClientOptions =
|
||||
AmazonDynamoDBLockClientOptions
|
||||
.builder(this.dynamoDB, this.tableName)
|
||||
.withPartitionKeyName(this.partitionKey)
|
||||
.withSortKeyName(this.sortKeyName)
|
||||
.withHeartbeatPeriod(this.heartbeatPeriod)
|
||||
.withLeaseDuration(this.leaseDuration)
|
||||
.build();
|
||||
AmazonDynamoDBLockClientOptions dynamoDBLockClientOptions = AmazonDynamoDBLockClientOptions
|
||||
.builder(this.dynamoDB, this.tableName).withPartitionKeyName(this.partitionKey)
|
||||
.withSortKeyName(this.sortKeyName).withHeartbeatPeriod(this.heartbeatPeriod)
|
||||
.withLeaseDuration(this.leaseDuration).build();
|
||||
|
||||
this.dynamoDBLockClient = new AmazonDynamoDBLockClient(dynamoDBLockClientOptions);
|
||||
}
|
||||
|
||||
this.leaseDuration =
|
||||
(long) new DirectFieldAccessor(this.dynamoDBLockClient)
|
||||
.getPropertyValue("leaseDurationInMilliseconds");
|
||||
|
||||
this.leaseDuration = (long) new DirectFieldAccessor(this.dynamoDBLockClient)
|
||||
.getPropertyValue("leaseDurationInMilliseconds");
|
||||
|
||||
this.executor.execute(() -> {
|
||||
try {
|
||||
@@ -244,14 +239,10 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
}
|
||||
}
|
||||
|
||||
CreateDynamoDBTableOptions createDynamoDBTableOptions =
|
||||
CreateDynamoDBTableOptions
|
||||
.builder(this.dynamoDB,
|
||||
new ProvisionedThroughput(this.readCapacity, this.writeCapacity),
|
||||
this.tableName)
|
||||
.withPartitionKeyName(this.partitionKey)
|
||||
.withSortKeyName(this.sortKeyName)
|
||||
.build();
|
||||
CreateDynamoDBTableOptions createDynamoDBTableOptions = CreateDynamoDBTableOptions
|
||||
.builder(this.dynamoDB, new ProvisionedThroughput(this.readCapacity, this.writeCapacity),
|
||||
this.tableName)
|
||||
.withPartitionKeyName(this.partitionKey).withSortKeyName(this.sortKeyName).build();
|
||||
|
||||
AmazonDynamoDBLockClient.createLockTableInDynamoDB(createDynamoDBTableOptions);
|
||||
}
|
||||
@@ -278,7 +269,8 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
finally {
|
||||
// Release create table barrier either way.
|
||||
// If there is an error during creation/description,
|
||||
// we deffer the actual ResourceNotFoundException to the end-user active calls.
|
||||
// we deffer the actual ResourceNotFoundException to the end-user active
|
||||
// calls.
|
||||
this.createTableLatch.countDown();
|
||||
}
|
||||
});
|
||||
@@ -287,12 +279,11 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
}
|
||||
|
||||
private void awaitForActive() {
|
||||
Assert.state(this.initialized, () -> "The component has not been initialized: " + this +
|
||||
".\n Is it declared as a bean?");
|
||||
Assert.state(this.initialized,
|
||||
() -> "The component has not been initialized: " + this + ".\n Is it declared as a bean?");
|
||||
|
||||
IllegalStateException illegalStateException =
|
||||
new IllegalStateException(
|
||||
"The DynamoDb table " + this.tableName + " has not been created during " + 60 + " seconds");
|
||||
IllegalStateException illegalStateException = new IllegalStateException(
|
||||
"The DynamoDb table " + this.tableName + " has not been created during " + 60 + " seconds");
|
||||
try {
|
||||
if (!this.createTableLatch.await(60, TimeUnit.SECONDS)) {
|
||||
throw illegalStateException;
|
||||
@@ -336,16 +327,11 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DynamoDbLockRegistry{" + "tableName='" + this.tableName + '\'' +
|
||||
", readCapacity=" + this.readCapacity +
|
||||
", writeCapacity=" + this.writeCapacity +
|
||||
", partitionKey='" + this.partitionKey + '\'' +
|
||||
", sortKeyName='" + this.sortKeyName + '\'' +
|
||||
", sortKey='" + this.sortKey + '\'' +
|
||||
", refreshPeriod=" + this.refreshPeriod +
|
||||
", leaseDuration=" + this.leaseDuration +
|
||||
", heartbeatPeriod=" + this.heartbeatPeriod +
|
||||
'}';
|
||||
return "DynamoDbLockRegistry{" + "tableName='" + this.tableName + '\'' + ", readCapacity=" + this.readCapacity
|
||||
+ ", writeCapacity=" + this.writeCapacity + ", partitionKey='" + this.partitionKey + '\''
|
||||
+ ", sortKeyName='" + this.sortKeyName + '\'' + ", sortKey='" + this.sortKey + '\'' + ", refreshPeriod="
|
||||
+ this.refreshPeriod + ", leaseDuration=" + this.leaseDuration + ", heartbeatPeriod="
|
||||
+ this.heartbeatPeriod + '}';
|
||||
}
|
||||
|
||||
private final class DynamoDbLock implements Lock {
|
||||
@@ -354,7 +340,8 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
|
||||
private final String key;
|
||||
|
||||
// It is safe to use a shared instance - access is guaranteed by the delegate lock.
|
||||
// It is safe to use a shared instance - access is guaranteed by the delegate
|
||||
// lock.
|
||||
private final AcquireLockOptions.AcquireLockOptionsBuilder acquireLockOptionsBuilder;
|
||||
|
||||
private LockItem lockItem;
|
||||
@@ -363,11 +350,8 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
|
||||
private DynamoDbLock(String key) {
|
||||
this.key = key;
|
||||
this.acquireLockOptionsBuilder =
|
||||
AcquireLockOptions.builder(this.key)
|
||||
.withReplaceData(false)
|
||||
.withSortKey(DynamoDbLockRegistry.this.sortKey)
|
||||
.withTimeUnit(TimeUnit.MILLISECONDS);
|
||||
this.acquireLockOptionsBuilder = AcquireLockOptions.builder(this.key).withReplaceData(false)
|
||||
.withSortKey(DynamoDbLockRegistry.this.sortKey).withTimeUnit(TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void rethrowAsLockException(Exception e) {
|
||||
@@ -388,15 +372,15 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
while (true) {
|
||||
try {
|
||||
while (!doLock()) {
|
||||
Thread.sleep(100); //NOSONAR
|
||||
Thread.sleep(100); // NOSONAR
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
/*
|
||||
* This method must be uninterruptible so catch and ignore
|
||||
* interrupts and only break out of the while loop when
|
||||
* we get the lock.
|
||||
* interrupts and only break out of the while loop when we get the
|
||||
* lock.
|
||||
*/
|
||||
wasInterruptedWhileUninterruptible = true;
|
||||
}
|
||||
@@ -430,7 +414,7 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
|
||||
try {
|
||||
while (!doLock()) {
|
||||
Thread.sleep(100); //NOSONAR
|
||||
Thread.sleep(100); // NOSONAR
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
@@ -468,10 +452,10 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
return false;
|
||||
}
|
||||
|
||||
long additionalTimeToWait = Math.max(TimeUnit.MILLISECONDS.convert(time, unit) - System.currentTimeMillis() + start, 0L);
|
||||
long additionalTimeToWait = Math
|
||||
.max(TimeUnit.MILLISECONDS.convert(time, unit) - System.currentTimeMillis() + start, 0L);
|
||||
|
||||
this.acquireLockOptionsBuilder
|
||||
.withAdditionalTimeToWaitForLock(additionalTimeToWait)
|
||||
this.acquireLockOptionsBuilder.withAdditionalTimeToWaitForLock(additionalTimeToWait)
|
||||
.withRefreshPeriod(DynamoDbLockRegistry.this.refreshPeriod);
|
||||
|
||||
boolean acquired = false;
|
||||
@@ -500,10 +484,8 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
acquired = true;
|
||||
}
|
||||
else {
|
||||
this.lockItem =
|
||||
DynamoDbLockRegistry.this.dynamoDBLockClient
|
||||
.tryAcquireLock(this.acquireLockOptionsBuilder.build())
|
||||
.orElse(null);
|
||||
this.lockItem = DynamoDbLockRegistry.this.dynamoDBLockClient
|
||||
.tryAcquireLock(this.acquireLockOptionsBuilder.build()).orElse(null);
|
||||
|
||||
acquired = this.lockItem != null;
|
||||
}
|
||||
@@ -527,9 +509,8 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
try {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
LockItem lockItemToRelease = this.lockItem;
|
||||
DynamoDbLockRegistry.this.executor.execute(() ->
|
||||
DynamoDbLockRegistry.this.dynamoDBLockClient.releaseLock(lockItemToRelease)
|
||||
);
|
||||
DynamoDbLockRegistry.this.executor
|
||||
.execute(() -> DynamoDbLockRegistry.this.dynamoDBLockClient.releaseLock(lockItemToRelease));
|
||||
}
|
||||
else {
|
||||
DynamoDbLockRegistry.this.dynamoDBLockClient.releaseLock(this.lockItem);
|
||||
@@ -552,10 +533,8 @@ public class DynamoDbLockRegistry implements ExpirableLockRegistry, Initializing
|
||||
@Override
|
||||
public String toString() {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("YYYY-MM-dd@HH:mm:ss.SSS");
|
||||
return "DynamoDbLock [lockKey=" + this.key
|
||||
+ ",lockedAt=" + dateFormat.format(new Date(this.lastUsed))
|
||||
+ ", lockItem=" + this.lockItem
|
||||
+ "]";
|
||||
return "DynamoDbLock [lockKey=" + this.key + ",lockedAt=" + dateFormat.format(new Date(this.lastUsed))
|
||||
+ ", lockItem=" + this.lockItem + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,13 +61,13 @@ import com.amazonaws.waiters.WaiterParameters;
|
||||
* The {@link ConcurrentMetadataStore} for the {@link AmazonDynamoDB}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public class DynamoDbMetadataStore implements ConcurrentMetadataStore, InitializingBean {
|
||||
|
||||
/**
|
||||
* The {@value DEFAULT_TABLE_NAME} default name for the metadata table in the DynamoDB.
|
||||
* The {@value DEFAULT_TABLE_NAME} default name for the metadata table in the
|
||||
* DynamoDB.
|
||||
*/
|
||||
public static final String DEFAULT_TABLE_NAME = "SpringIntegrationMetadataStore";
|
||||
|
||||
@@ -105,9 +105,7 @@ public class DynamoDbMetadataStore implements ConcurrentMetadataStore, Initializ
|
||||
Assert.notNull(dynamoDB, "'dynamoDB' must not be null.");
|
||||
Assert.hasText(tableName, "'tableName' must not be empty.");
|
||||
this.dynamoDB = dynamoDB;
|
||||
this.table =
|
||||
new DynamoDB(this.dynamoDB)
|
||||
.getTable(tableName);
|
||||
this.table = new DynamoDB(this.dynamoDB).getTable(tableName);
|
||||
|
||||
}
|
||||
|
||||
@@ -128,11 +126,13 @@ public class DynamoDbMetadataStore implements ConcurrentMetadataStore, Initializ
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a period in seconds for items expiration.
|
||||
* If it is configured to non-positive value ({@code <= 0}), the TTL is disabled on the table.
|
||||
* Configure a period in seconds for items expiration. If it is configured to
|
||||
* non-positive value ({@code <= 0}), the TTL is disabled on the table.
|
||||
* @param timeToLive period in seconds for items expiration.
|
||||
* @since 2.0
|
||||
* @see <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html">DynamoDB TTL</a>
|
||||
* @see <a href=
|
||||
* "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html">DynamoDB
|
||||
* TTL</a>
|
||||
*/
|
||||
public void setTimeToLive(int timeToLive) {
|
||||
this.timeToLive = timeToLive;
|
||||
@@ -153,37 +153,34 @@ public class DynamoDbMetadataStore implements ConcurrentMetadataStore, Initializ
|
||||
}
|
||||
}
|
||||
|
||||
CreateTableRequest createTableRequest =
|
||||
new CreateTableRequest()
|
||||
.withTableName(this.table.getTableName())
|
||||
.withKeySchema(new KeySchemaElement(KEY, KeyType.HASH))
|
||||
.withAttributeDefinitions(new AttributeDefinition(KEY, ScalarAttributeType.S))
|
||||
.withProvisionedThroughput(new ProvisionedThroughput(this.readCapacity, this.writeCapacity));
|
||||
|
||||
CreateTableRequest createTableRequest = new CreateTableRequest().withTableName(this.table.getTableName())
|
||||
.withKeySchema(new KeySchemaElement(KEY, KeyType.HASH))
|
||||
.withAttributeDefinitions(new AttributeDefinition(KEY, ScalarAttributeType.S))
|
||||
.withProvisionedThroughput(new ProvisionedThroughput(this.readCapacity, this.writeCapacity));
|
||||
|
||||
this.dynamoDB.createTableAsync(createTableRequest,
|
||||
new AsyncHandler<CreateTableRequest, CreateTableResult>() {
|
||||
|
||||
@Override
|
||||
public void onError(Exception e) {
|
||||
logger.error("Cannot create DynamoDb table: " +
|
||||
DynamoDbMetadataStore.this.table.getTableName(), e);
|
||||
logger.error(
|
||||
"Cannot create DynamoDb table: " + DynamoDbMetadataStore.this.table.getTableName(),
|
||||
e);
|
||||
DynamoDbMetadataStore.this.createTableLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(CreateTableRequest request, CreateTableResult createTableResult) {
|
||||
Waiter<DescribeTableRequest> waiter =
|
||||
DynamoDbMetadataStore.this.dynamoDB.waiters()
|
||||
.tableExists();
|
||||
Waiter<DescribeTableRequest> waiter = DynamoDbMetadataStore.this.dynamoDB.waiters()
|
||||
.tableExists();
|
||||
|
||||
WaiterParameters<DescribeTableRequest> waiterParameters =
|
||||
new WaiterParameters<>(
|
||||
new DescribeTableRequest(DynamoDbMetadataStore.this.table.getTableName()))
|
||||
.withPollingStrategy(
|
||||
new PollingStrategy(
|
||||
new MaxAttemptsRetryStrategy(DynamoDbMetadataStore.this.createTableRetries),
|
||||
new FixedDelayStrategy(DynamoDbMetadataStore.this.createTableDelay)));
|
||||
WaiterParameters<DescribeTableRequest> waiterParameters = new WaiterParameters<>(
|
||||
new DescribeTableRequest(DynamoDbMetadataStore.this.table.getTableName()))
|
||||
.withPollingStrategy(new PollingStrategy(
|
||||
new MaxAttemptsRetryStrategy(
|
||||
DynamoDbMetadataStore.this.createTableRetries),
|
||||
new FixedDelayStrategy(
|
||||
DynamoDbMetadataStore.this.createTableDelay)));
|
||||
|
||||
waiter.runAsync(waiterParameters, new WaiterHandler<DescribeTableRequest>() {
|
||||
|
||||
@@ -196,8 +193,8 @@ public class DynamoDbMetadataStore implements ConcurrentMetadataStore, Initializ
|
||||
|
||||
@Override
|
||||
public void onWaitFailure(Exception e) {
|
||||
logger.error("Cannot describe DynamoDb table: " +
|
||||
DynamoDbMetadataStore.this.table.getTableName(), e);
|
||||
logger.error("Cannot describe DynamoDb table: "
|
||||
+ DynamoDbMetadataStore.this.table.getTableName(), e);
|
||||
DynamoDbMetadataStore.this.createTableLatch.countDown();
|
||||
}
|
||||
|
||||
@@ -213,13 +210,9 @@ public class DynamoDbMetadataStore implements ConcurrentMetadataStore, Initializ
|
||||
|
||||
private void updateTimeToLiveIfAny() {
|
||||
if (this.timeToLive != null) {
|
||||
UpdateTimeToLiveRequest updateTimeToLiveRequest =
|
||||
new UpdateTimeToLiveRequest()
|
||||
.withTableName(this.table.getTableName())
|
||||
.withTimeToLiveSpecification(
|
||||
new TimeToLiveSpecification()
|
||||
.withAttributeName(TTL)
|
||||
.withEnabled(this.timeToLive > 0));
|
||||
UpdateTimeToLiveRequest updateTimeToLiveRequest = new UpdateTimeToLiveRequest()
|
||||
.withTableName(this.table.getTableName()).withTimeToLiveSpecification(
|
||||
new TimeToLiveSpecification().withAttributeName(TTL).withEnabled(this.timeToLive > 0));
|
||||
|
||||
try {
|
||||
this.dynamoDB.updateTimeToLive(updateTimeToLiveRequest);
|
||||
@@ -233,15 +226,15 @@ public class DynamoDbMetadataStore implements ConcurrentMetadataStore, Initializ
|
||||
}
|
||||
|
||||
private void awaitForActive() {
|
||||
Assert.state(this.initialized, () -> "The component has not been initialized: " + this +
|
||||
".\n Is it declared as a bean?");
|
||||
Assert.state(this.initialized,
|
||||
() -> "The component has not been initialized: " + this + ".\n Is it declared as a bean?");
|
||||
try {
|
||||
this.createTableLatch.await(this.createTableRetries * this.createTableDelay, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException("The DynamoDb table " + this.table.getTableName() +
|
||||
" has not been created during " + this.createTableRetries * this.createTableDelay + " seconds");
|
||||
throw new IllegalStateException("The DynamoDb table " + this.table.getTableName()
|
||||
+ " has not been created during " + this.createTableRetries * this.createTableDelay + " seconds");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,10 +245,7 @@ public class DynamoDbMetadataStore implements ConcurrentMetadataStore, Initializ
|
||||
|
||||
awaitForActive();
|
||||
|
||||
Item item =
|
||||
new Item()
|
||||
.withPrimaryKey(KEY, key)
|
||||
.withString(VALUE, value);
|
||||
Item item = new Item().withPrimaryKey(KEY, key).withString(VALUE, value);
|
||||
|
||||
if (this.timeToLive != null && this.timeToLive > 0) {
|
||||
item = item.withLong(TTL, (System.currentTimeMillis() + this.timeToLive) / 1000);
|
||||
@@ -282,21 +272,12 @@ public class DynamoDbMetadataStore implements ConcurrentMetadataStore, Initializ
|
||||
|
||||
awaitForActive();
|
||||
|
||||
UpdateItemSpec updateItemSpec =
|
||||
new UpdateItemSpec()
|
||||
.withPrimaryKey(KEY, key)
|
||||
.withAttributeUpdate(
|
||||
new AttributeUpdate(VALUE)
|
||||
.put(value))
|
||||
.withExpected(
|
||||
new Expected(KEY)
|
||||
.notExist());
|
||||
UpdateItemSpec updateItemSpec = new UpdateItemSpec().withPrimaryKey(KEY, key)
|
||||
.withAttributeUpdate(new AttributeUpdate(VALUE).put(value)).withExpected(new Expected(KEY).notExist());
|
||||
|
||||
if (this.timeToLive != null && this.timeToLive > 0) {
|
||||
updateItemSpec =
|
||||
updateItemSpec.addAttributeUpdate(
|
||||
new AttributeUpdate(TTL)
|
||||
.put((System.currentTimeMillis() + this.timeToLive) / 1000));
|
||||
updateItemSpec = updateItemSpec.addAttributeUpdate(
|
||||
new AttributeUpdate(TTL).put((System.currentTimeMillis() + this.timeToLive) / 1000));
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -316,27 +297,17 @@ public class DynamoDbMetadataStore implements ConcurrentMetadataStore, Initializ
|
||||
|
||||
awaitForActive();
|
||||
|
||||
UpdateItemSpec updateItemSpec =
|
||||
new UpdateItemSpec()
|
||||
.withPrimaryKey(KEY, key)
|
||||
.withAttributeUpdate(
|
||||
new AttributeUpdate(VALUE)
|
||||
.put(newValue))
|
||||
.withExpected(
|
||||
new Expected(VALUE)
|
||||
.eq(oldValue))
|
||||
.withReturnValues(ReturnValue.UPDATED_NEW);
|
||||
UpdateItemSpec updateItemSpec = new UpdateItemSpec().withPrimaryKey(KEY, key)
|
||||
.withAttributeUpdate(new AttributeUpdate(VALUE).put(newValue))
|
||||
.withExpected(new Expected(VALUE).eq(oldValue)).withReturnValues(ReturnValue.UPDATED_NEW);
|
||||
|
||||
if (this.timeToLive != null && this.timeToLive > 0) {
|
||||
updateItemSpec =
|
||||
updateItemSpec.addAttributeUpdate(
|
||||
new AttributeUpdate(TTL)
|
||||
.put((System.currentTimeMillis() + this.timeToLive) / 1000));
|
||||
updateItemSpec = updateItemSpec.addAttributeUpdate(
|
||||
new AttributeUpdate(TTL).put((System.currentTimeMillis() + this.timeToLive) / 1000));
|
||||
}
|
||||
|
||||
try {
|
||||
return this.table.updateItem(updateItemSpec)
|
||||
.getItem() != null;
|
||||
return this.table.updateItem(updateItemSpec).getItem() != null;
|
||||
}
|
||||
catch (ConditionalCheckFailedException e) {
|
||||
return false;
|
||||
@@ -349,12 +320,9 @@ public class DynamoDbMetadataStore implements ConcurrentMetadataStore, Initializ
|
||||
|
||||
awaitForActive();
|
||||
|
||||
Item item =
|
||||
this.table.deleteItem(
|
||||
new DeleteItemSpec()
|
||||
.withPrimaryKey(KEY, key)
|
||||
.withReturnValues(ReturnValue.ALL_OLD))
|
||||
.getItem();
|
||||
Item item = this.table
|
||||
.deleteItem(new DeleteItemSpec().withPrimaryKey(KEY, key).withReturnValues(ReturnValue.ALL_OLD))
|
||||
.getItem();
|
||||
|
||||
return getValueIfAny(item);
|
||||
}
|
||||
@@ -370,12 +338,9 @@ public class DynamoDbMetadataStore implements ConcurrentMetadataStore, Initializ
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DynamoDbMetadataStore{" + "table=" + this.table +
|
||||
", createTableRetries=" + this.createTableRetries +
|
||||
", createTableDelay=" + this.createTableDelay +
|
||||
", readCapacity=" + this.readCapacity +
|
||||
", writeCapacity=" + this.writeCapacity +
|
||||
", timeToLive=" + this.timeToLive +
|
||||
'}';
|
||||
return "DynamoDbMetadataStore{" + "table=" + this.table + ", createTableRetries=" + this.createTableRetries
|
||||
+ ", createTableDelay=" + this.createTableDelay + ", readCapacity=" + this.readCapacity
|
||||
+ ", writeCapacity=" + this.writeCapacity + ", timeToLive=" + this.timeToLive + '}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -42,14 +42,12 @@ import com.amazonaws.AmazonWebServiceRequest;
|
||||
import com.amazonaws.handlers.AsyncHandler;
|
||||
|
||||
/**
|
||||
* The base {@link AbstractMessageProducingHandler} for AWS services.
|
||||
* Utilizes common logic ({@link AsyncHandler}, {@link ErrorMessageStrategy},
|
||||
* {@code failureChannel} etc.) and message pre- and post-processing,
|
||||
* The base {@link AbstractMessageProducingHandler} for AWS services. Utilizes common
|
||||
* logic ({@link AsyncHandler}, {@link ErrorMessageStrategy}, {@code failureChannel} etc.)
|
||||
* and message pre- and post-processing,
|
||||
*
|
||||
* @param <H> the headers container type.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public abstract class AbstractAwsMessageHandler<H> extends AbstractMessageProducingHandler {
|
||||
@@ -106,9 +104,9 @@ public abstract class AbstractAwsMessageHandler<H> extends AbstractMessageProduc
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the failure channel. After a failure on put, an {@link ErrorMessage} will be sent
|
||||
* to this channel with a payload of a {@link AwsRequestFailureException} with the
|
||||
* failed message and cause.
|
||||
* Set the failure channel. After a failure on put, an {@link ErrorMessage} will be
|
||||
* sent to this channel with a payload of a {@link AwsRequestFailureException} with
|
||||
* the failed message and cause.
|
||||
* @param failureChannel the failure channel.
|
||||
*/
|
||||
public void setFailureChannel(MessageChannel failureChannel) {
|
||||
@@ -116,8 +114,8 @@ public abstract class AbstractAwsMessageHandler<H> extends AbstractMessageProduc
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the failure channel name. After a failure on put, an {@link ErrorMessage} will be
|
||||
* sent to this channel name with a payload of a {@link AwsRequestFailureException}
|
||||
* Set the failure channel name. After a failure on put, an {@link ErrorMessage} will
|
||||
* be sent to this channel name with a payload of a {@link AwsRequestFailureException}
|
||||
* with the failed message and cause.
|
||||
* @param failureChannelName the failure channel name.
|
||||
*/
|
||||
@@ -213,9 +211,8 @@ public abstract class AbstractAwsMessageHandler<H> extends AbstractMessageProduc
|
||||
}
|
||||
|
||||
if (getFailureChannel() != null) {
|
||||
AbstractAwsMessageHandler.this.messagingTemplate.send(getFailureChannel(),
|
||||
getErrorMessageStrategy()
|
||||
.buildErrorMessage(new AwsRequestFailureException(message, request, ex), null));
|
||||
AbstractAwsMessageHandler.this.messagingTemplate.send(getFailureChannel(), getErrorMessageStrategy()
|
||||
.buildErrorMessage(new AwsRequestFailureException(message, request, ex), null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,15 +224,13 @@ public abstract class AbstractAwsMessageHandler<H> extends AbstractMessageProduc
|
||||
}
|
||||
|
||||
if (getOutputChannel() != null) {
|
||||
AbstractIntegrationMessageBuilder<?> messageBuilder =
|
||||
getMessageBuilderFactory()
|
||||
.fromMessage(message);
|
||||
AbstractIntegrationMessageBuilder<?> messageBuilder = getMessageBuilderFactory()
|
||||
.fromMessage(message);
|
||||
|
||||
additionalOnSuccessHeaders(messageBuilder, request, result);
|
||||
|
||||
messageBuilder.setHeaderIfAbsent(AwsHeaders.SERVICE_RESULT, result);
|
||||
|
||||
|
||||
AbstractAwsMessageHandler.this.messagingTemplate.send(getOutputChannel(), messageBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,13 +44,12 @@ import com.amazonaws.services.kinesis.model.PutRecordsRequest;
|
||||
import com.amazonaws.services.kinesis.model.PutRecordsResult;
|
||||
|
||||
/**
|
||||
* The {@link AbstractMessageHandler} implementation for the Amazon Kinesis {@code putRecord(s)}.
|
||||
* The {@link AbstractMessageHandler} implementation for the Amazon Kinesis
|
||||
* {@code putRecord(s)}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @author Jacob Severson
|
||||
*
|
||||
* @since 1.1
|
||||
*
|
||||
* @see AmazonKinesisAsync#putRecord(PutRecordRequest)
|
||||
* @see AmazonKinesisAsync#putRecords(PutRecordsRequest)
|
||||
* @see com.amazonaws.handlers.AsyncHandler
|
||||
@@ -77,8 +76,8 @@ public class KinesisMessageHandler extends AbstractAwsMessageHandler<Void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a {@link Converter} to serialize {@code payload} to the {@code byte[]}
|
||||
* if that isn't {@code byte[]} already.
|
||||
* Specify a {@link Converter} to serialize {@code payload} to the {@code byte[]} if
|
||||
* that isn't {@code byte[]} already.
|
||||
* @param converter the {@link Converter} to use; cannot be null.
|
||||
*/
|
||||
public void setConverter(Converter<Object, byte[]> converter) {
|
||||
@@ -131,9 +130,10 @@ public class KinesisMessageHandler extends AbstractAwsMessageHandler<Void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a {@link OutboundMessageMapper} for embedding message headers into the record data
|
||||
* together with payload.
|
||||
* @param embeddedHeadersMapper the {@link OutboundMessageMapper} to embed headers into the record data.
|
||||
* Specify a {@link OutboundMessageMapper} for embedding message headers into the
|
||||
* record data together with payload.
|
||||
* @param embeddedHeadersMapper the {@link OutboundMessageMapper} to embed headers
|
||||
* into the record data.
|
||||
* @since 2.0
|
||||
* @see org.springframework.integration.support.json.EmbeddedJsonHeadersMessageMapper
|
||||
*/
|
||||
@@ -148,26 +148,24 @@ public class KinesisMessageHandler extends AbstractAwsMessageHandler<Void> {
|
||||
*/
|
||||
@Override
|
||||
public void setHeaderMapper(HeaderMapper<Void> headerMapper) {
|
||||
throw new UnsupportedOperationException("Kinesis doesn't support headers.\n" +
|
||||
"Consider to use 'OutboundMessageMapper<byte[]>' for embedding headers into the record data.");
|
||||
throw new UnsupportedOperationException("Kinesis doesn't support headers.\n"
|
||||
+ "Consider to use 'OutboundMessageMapper<byte[]>' for embedding headers into the record data.");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Future<?> handleMessageToAws(Message<?> message) {
|
||||
if (message.getPayload() instanceof PutRecordsRequest) {
|
||||
AsyncHandler<PutRecordsRequest, PutRecordsResult> asyncHandler =
|
||||
obtainAsyncHandler(message, (PutRecordsRequest) message.getPayload());
|
||||
AsyncHandler<PutRecordsRequest, PutRecordsResult> asyncHandler = obtainAsyncHandler(message,
|
||||
(PutRecordsRequest) message.getPayload());
|
||||
|
||||
return this.amazonKinesis.putRecordsAsync((PutRecordsRequest) message.getPayload(), asyncHandler);
|
||||
}
|
||||
else {
|
||||
final PutRecordRequest putRecordRequest =
|
||||
(message.getPayload() instanceof PutRecordRequest)
|
||||
? (PutRecordRequest) message.getPayload()
|
||||
: buildPutRecordRequest(message);
|
||||
final PutRecordRequest putRecordRequest = (message.getPayload() instanceof PutRecordRequest)
|
||||
? (PutRecordRequest) message.getPayload() : buildPutRecordRequest(message);
|
||||
|
||||
AsyncHandler<PutRecordRequest, PutRecordResult> asyncHandler =
|
||||
obtainAsyncHandler(message, putRecordRequest);
|
||||
AsyncHandler<PutRecordRequest, PutRecordResult> asyncHandler = obtainAsyncHandler(message,
|
||||
putRecordRequest);
|
||||
|
||||
return this.amazonKinesis.putRecordAsync(putRecordRequest, asyncHandler);
|
||||
}
|
||||
@@ -179,22 +177,21 @@ public class KinesisMessageHandler extends AbstractAwsMessageHandler<Void> {
|
||||
if (!StringUtils.hasText(stream) && this.streamExpression != null) {
|
||||
stream = this.streamExpression.getValue(getEvaluationContext(), message, String.class);
|
||||
}
|
||||
Assert.state(stream != null, "'stream' must not be null for sending a Kinesis record. " +
|
||||
"Consider configuring this handler with a 'stream'( or 'streamExpression') or supply an " +
|
||||
"'aws_stream' message header.");
|
||||
Assert.state(stream != null,
|
||||
"'stream' must not be null for sending a Kinesis record. "
|
||||
+ "Consider configuring this handler with a 'stream'( or 'streamExpression') or supply an "
|
||||
+ "'aws_stream' message header.");
|
||||
|
||||
String partitionKey = messageHeaders.get(AwsHeaders.PARTITION_KEY, String.class);
|
||||
if (!StringUtils.hasText(partitionKey) && this.partitionKeyExpression != null) {
|
||||
partitionKey = this.partitionKeyExpression.getValue(getEvaluationContext(), message, String.class);
|
||||
}
|
||||
Assert.state(partitionKey != null, "'partitionKey' must not be null for sending a Kinesis record. " +
|
||||
"Consider configuring this handler with a 'partitionKey'( or 'partitionKeyExpression') or supply an " +
|
||||
"'aws_partitionKey' message header.");
|
||||
Assert.state(partitionKey != null, "'partitionKey' must not be null for sending a Kinesis record. "
|
||||
+ "Consider configuring this handler with a 'partitionKey'( or 'partitionKeyExpression') or supply an "
|
||||
+ "'aws_partitionKey' message header.");
|
||||
|
||||
String explicitHashKey =
|
||||
(this.explicitHashKeyExpression != null
|
||||
? this.explicitHashKeyExpression.getValue(getEvaluationContext(), message, String.class)
|
||||
: null);
|
||||
String explicitHashKey = (this.explicitHashKeyExpression != null
|
||||
? this.explicitHashKeyExpression.getValue(getEvaluationContext(), message, String.class) : null);
|
||||
|
||||
String sequenceNumber = messageHeaders.get(AwsHeaders.SEQUENCE_NUMBER, String.class);
|
||||
if (!StringUtils.hasText(sequenceNumber) && this.sequenceNumberExpression != null) {
|
||||
@@ -214,10 +211,7 @@ public class KinesisMessageHandler extends AbstractAwsMessageHandler<Void> {
|
||||
}
|
||||
}
|
||||
else {
|
||||
byte[] bytes =
|
||||
payload instanceof byte[]
|
||||
? (byte[]) payload
|
||||
: this.converter.convert(payload);
|
||||
byte[] bytes = payload instanceof byte[] ? (byte[]) payload : this.converter.convert(payload);
|
||||
Assert.notNull(bytes, "payload cannot be null");
|
||||
if (this.embeddedHeadersMapper != null) {
|
||||
messageToEmbed = new MutableMessage<>(bytes, messageHeaders);
|
||||
@@ -238,12 +232,8 @@ public class KinesisMessageHandler extends AbstractAwsMessageHandler<Void> {
|
||||
}
|
||||
}
|
||||
|
||||
return new PutRecordRequest()
|
||||
.withStreamName(stream)
|
||||
.withPartitionKey(partitionKey)
|
||||
.withExplicitHashKey(explicitHashKey)
|
||||
.withSequenceNumberForOrdering(sequenceNumber)
|
||||
.withData(data);
|
||||
return new PutRecordRequest().withStreamName(stream).withPartitionKey(partitionKey)
|
||||
.withExplicitHashKey(explicitHashKey).withSequenceNumberForOrdering(sequenceNumber).withData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -251,11 +241,9 @@ public class KinesisMessageHandler extends AbstractAwsMessageHandler<Void> {
|
||||
AmazonWebServiceRequest request, Object result) {
|
||||
|
||||
if (result instanceof PutRecordResult) {
|
||||
messageBuilder
|
||||
.setHeader(AwsHeaders.SHARD, ((PutRecordResult) result).getShardId())
|
||||
messageBuilder.setHeader(AwsHeaders.SHARD, ((PutRecordResult) result).getShardId())
|
||||
.setHeader(AwsHeaders.SEQUENCE_NUMBER, ((PutRecordResult) result).getSequenceNumber());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -51,13 +51,12 @@ import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
/**
|
||||
* The {@link AbstractMessageHandler} implementation for the Amazon Kinesis Producer Library {@code putRecord(s)}.
|
||||
* The {@link AbstractMessageHandler} implementation for the Amazon Kinesis Producer
|
||||
* Library {@code putRecord(s)}.
|
||||
*
|
||||
* @author Arnaud Lecollaire
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.2.0
|
||||
*
|
||||
* @see AmazonKinesisAsync#putRecord(PutRecordRequest)
|
||||
* @see AmazonKinesisAsync#putRecords(PutRecordsRequest)
|
||||
* @see com.amazonaws.handlers.AsyncHandler
|
||||
@@ -84,8 +83,8 @@ public class KplMessageHandler extends AbstractAwsMessageHandler<Void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a {@link Converter} to serialize {@code payload} to the {@code byte[]}
|
||||
* if that isn't {@code byte[]} already.
|
||||
* Specify a {@link Converter} to serialize {@code payload} to the {@code byte[]} if
|
||||
* that isn't {@code byte[]} already.
|
||||
* @param converter the {@link Converter} to use; cannot be null.
|
||||
*/
|
||||
public void setConverter(Converter<Object, byte[]> converter) {
|
||||
@@ -138,9 +137,10 @@ public class KplMessageHandler extends AbstractAwsMessageHandler<Void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a {@link OutboundMessageMapper} for embedding message headers into the record data
|
||||
* together with payload.
|
||||
* @param embeddedHeadersMapper the {@link OutboundMessageMapper} to embed headers into the record data.
|
||||
* Specify a {@link OutboundMessageMapper} for embedding message headers into the
|
||||
* record data together with payload.
|
||||
* @param embeddedHeadersMapper the {@link OutboundMessageMapper} to embed headers
|
||||
* into the record data.
|
||||
* @since 2.0
|
||||
* @see org.springframework.integration.support.json.EmbeddedJsonHeadersMessageMapper
|
||||
*/
|
||||
@@ -155,8 +155,8 @@ public class KplMessageHandler extends AbstractAwsMessageHandler<Void> {
|
||||
*/
|
||||
@Override
|
||||
public void setHeaderMapper(HeaderMapper<Void> headerMapper) {
|
||||
throw new UnsupportedOperationException("Kinesis doesn't support headers.\n" +
|
||||
"Consider to use 'OutboundMessageMapper<byte[]>' for embedding headers into the record data.");
|
||||
throw new UnsupportedOperationException("Kinesis doesn't support headers.\n"
|
||||
+ "Consider to use 'OutboundMessageMapper<byte[]>' for embedding headers into the record data.");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -168,10 +168,8 @@ public class KplMessageHandler extends AbstractAwsMessageHandler<Void> {
|
||||
return handleUserRecord(message, buildPutRecordRequest(message), (UserRecord) message.getPayload());
|
||||
}
|
||||
else {
|
||||
final PutRecordRequest putRecordRequest =
|
||||
(message.getPayload() instanceof PutRecordRequest)
|
||||
? (PutRecordRequest) message.getPayload()
|
||||
: buildPutRecordRequest(message);
|
||||
final PutRecordRequest putRecordRequest = (message.getPayload() instanceof PutRecordRequest)
|
||||
? (PutRecordRequest) message.getPayload() : buildPutRecordRequest(message);
|
||||
|
||||
// convert the PutRecordRequest to a UserRecord
|
||||
UserRecord userRecord = new UserRecord();
|
||||
@@ -187,16 +185,14 @@ public class KplMessageHandler extends AbstractAwsMessageHandler<Void> {
|
||||
UserRecord userRecord) {
|
||||
ListenableFuture<UserRecordResult> recordResult = this.kinesisProducer.addUserRecord(userRecord);
|
||||
|
||||
final AsyncHandler<PutRecordRequest, UserRecordResult> asyncHandler =
|
||||
obtainAsyncHandler(message, putRecordRequest);
|
||||
final AsyncHandler<PutRecordRequest, UserRecordResult> asyncHandler = obtainAsyncHandler(message,
|
||||
putRecordRequest);
|
||||
final FutureCallback<UserRecordResult> callback = new FutureCallback<UserRecordResult>() {
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable ex) {
|
||||
asyncHandler
|
||||
.onError(ex instanceof Exception ?
|
||||
(Exception) ex :
|
||||
new AwsRequestFailureException(message, putRecordRequest, ex));
|
||||
asyncHandler.onError(ex instanceof Exception ? (Exception) ex
|
||||
: new AwsRequestFailureException(message, putRecordRequest, ex));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -215,22 +211,21 @@ public class KplMessageHandler extends AbstractAwsMessageHandler<Void> {
|
||||
if (!StringUtils.hasText(stream) && this.streamExpression != null) {
|
||||
stream = this.streamExpression.getValue(getEvaluationContext(), message, String.class);
|
||||
}
|
||||
Assert.state(stream != null, "'stream' must not be null for sending a Kinesis record. " +
|
||||
"Consider configuring this handler with a 'stream'( or 'streamExpression') or supply an " +
|
||||
"'aws_stream' message header.");
|
||||
Assert.state(stream != null,
|
||||
"'stream' must not be null for sending a Kinesis record. "
|
||||
+ "Consider configuring this handler with a 'stream'( or 'streamExpression') or supply an "
|
||||
+ "'aws_stream' message header.");
|
||||
|
||||
String partitionKey = messageHeaders.get(AwsHeaders.PARTITION_KEY, String.class);
|
||||
if (!StringUtils.hasText(partitionKey) && this.partitionKeyExpression != null) {
|
||||
partitionKey = this.partitionKeyExpression.getValue(getEvaluationContext(), message, String.class);
|
||||
}
|
||||
Assert.state(partitionKey != null, "'partitionKey' must not be null for sending a Kinesis record. " +
|
||||
"Consider configuring this handler with a 'partitionKey'( or 'partitionKeyExpression') or supply an " +
|
||||
"'aws_partitionKey' message header.");
|
||||
Assert.state(partitionKey != null, "'partitionKey' must not be null for sending a Kinesis record. "
|
||||
+ "Consider configuring this handler with a 'partitionKey'( or 'partitionKeyExpression') or supply an "
|
||||
+ "'aws_partitionKey' message header.");
|
||||
|
||||
String explicitHashKey =
|
||||
(this.explicitHashKeyExpression != null
|
||||
? this.explicitHashKeyExpression.getValue(getEvaluationContext(), message, String.class)
|
||||
: null);
|
||||
String explicitHashKey = (this.explicitHashKeyExpression != null
|
||||
? this.explicitHashKeyExpression.getValue(getEvaluationContext(), message, String.class) : null);
|
||||
|
||||
String sequenceNumber = messageHeaders.get(AwsHeaders.SEQUENCE_NUMBER, String.class);
|
||||
if (!StringUtils.hasText(sequenceNumber) && this.sequenceNumberExpression != null) {
|
||||
@@ -250,10 +245,7 @@ public class KplMessageHandler extends AbstractAwsMessageHandler<Void> {
|
||||
}
|
||||
}
|
||||
else {
|
||||
byte[] bytes =
|
||||
payload instanceof byte[]
|
||||
? (byte[]) payload
|
||||
: this.converter.convert(payload);
|
||||
byte[] bytes = payload instanceof byte[] ? (byte[]) payload : this.converter.convert(payload);
|
||||
Assert.notNull(bytes, "payload cannot be null");
|
||||
if (this.embeddedHeadersMapper != null) {
|
||||
messageToEmbed = new MutableMessage<>(bytes, messageHeaders);
|
||||
@@ -274,12 +266,8 @@ public class KplMessageHandler extends AbstractAwsMessageHandler<Void> {
|
||||
}
|
||||
}
|
||||
|
||||
return new PutRecordRequest()
|
||||
.withStreamName(stream)
|
||||
.withPartitionKey(partitionKey)
|
||||
.withExplicitHashKey(explicitHashKey)
|
||||
.withSequenceNumberForOrdering(sequenceNumber)
|
||||
.withData(data);
|
||||
return new PutRecordRequest().withStreamName(stream).withPartitionKey(partitionKey)
|
||||
.withExplicitHashKey(explicitHashKey).withSequenceNumberForOrdering(sequenceNumber).withData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -287,8 +275,7 @@ public class KplMessageHandler extends AbstractAwsMessageHandler<Void> {
|
||||
AmazonWebServiceRequest request, Object result) {
|
||||
|
||||
if (result instanceof PutRecordResult) {
|
||||
messageBuilder
|
||||
.setHeader(AwsHeaders.SHARD, ((PutRecordResult) result).getShardId())
|
||||
messageBuilder.setHeader(AwsHeaders.SHARD, ((PutRecordResult) result).getShardId())
|
||||
.setHeader(AwsHeaders.SEQUENCE_NUMBER, ((PutRecordResult) result).getSequenceNumber());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,46 +56,53 @@ import com.amazonaws.services.s3.transfer.internal.S3ProgressListenerChain;
|
||||
import com.amazonaws.util.Md5Utils;
|
||||
|
||||
/**
|
||||
* The {@link AbstractReplyProducingMessageHandler} implementation for the Amazon S3 services.
|
||||
* The {@link AbstractReplyProducingMessageHandler} implementation for the Amazon S3
|
||||
* services.
|
||||
* <p>
|
||||
* The implementation is fully based on the {@link TransferManager} and support its {@code upload},
|
||||
* {@code download} and {@code copy} operations which can be determined by the provided
|
||||
* or evaluated via SpEL expression at runtime {@link S3MessageHandler.Command}.
|
||||
* The implementation is fully based on the {@link TransferManager} and support its
|
||||
* {@code upload}, {@code download} and {@code copy} operations which can be determined by
|
||||
* the provided or evaluated via SpEL expression at runtime
|
||||
* {@link S3MessageHandler.Command}.
|
||||
* <p>
|
||||
* This {@link AbstractReplyProducingMessageHandler} can behave as a "one-way" (by default) or
|
||||
* "request-reply" component according to the {@link #produceReply} constructor argument.
|
||||
* This {@link AbstractReplyProducingMessageHandler} can behave as a "one-way" (by
|
||||
* default) or "request-reply" component according to the {@link #produceReply}
|
||||
* constructor argument.
|
||||
* <p>
|
||||
* The "one-way" behavior is also blocking, which is achieved with the {@link Transfer#waitForException()}
|
||||
* invocation. Consider to use an async upstream hand off if this blocking behavior isn't appropriate.
|
||||
* The "one-way" behavior is also blocking, which is achieved with the
|
||||
* {@link Transfer#waitForException()} invocation. Consider to use an async upstream hand
|
||||
* off if this blocking behavior isn't appropriate.
|
||||
* <p>
|
||||
* The "request-reply" behavior is async and the {@link Transfer} result from the {@link TransferManager}
|
||||
* operation is sent to the {@link #getOutputChannel()}, assuming the transfer progress observation in the
|
||||
* downstream flow.
|
||||
* The "request-reply" behavior is async and the {@link Transfer} result from the
|
||||
* {@link TransferManager} operation is sent to the {@link #getOutputChannel()}, assuming
|
||||
* the transfer progress observation in the downstream flow.
|
||||
* <p>
|
||||
* The {@link S3ProgressListener} can be supplied to track the transfer progress.
|
||||
* Also the listener can be populated into the returned {@link Transfer} afterwards in the downstream flow.
|
||||
* If the context of the {@code requestMessage} is important in the {@code progressChanged} event, it is
|
||||
* recommended to use a {@link MessageS3ProgressListener} implementation instead.
|
||||
* * <p>
|
||||
* For the upload operation the {@link UploadMetadataProvider} callback can be supplied to populate required
|
||||
* {@link ObjectMetadata} options, as for a single entry, as well as for each file in directory to upload.
|
||||
* The {@link S3ProgressListener} can be supplied to track the transfer progress. Also the
|
||||
* listener can be populated into the returned {@link Transfer} afterwards in the
|
||||
* downstream flow. If the context of the {@code requestMessage} is important in the
|
||||
* {@code progressChanged} event, it is recommended to use a
|
||||
* {@link MessageS3ProgressListener} implementation instead. *
|
||||
* <p>
|
||||
* For the upload operation the {@link #objectAclExpression} can be provided to {@link AmazonS3#setObjectAcl}
|
||||
* after the successful transfer.
|
||||
* The supported SpEL result types are: {@link AccessControlList} or {@link CannedAccessControlList}.
|
||||
* For the upload operation the {@link UploadMetadataProvider} callback can be supplied to
|
||||
* populate required {@link ObjectMetadata} options, as for a single entry, as well as for
|
||||
* each file in directory to upload.
|
||||
* <p>
|
||||
* For download operation the {@code payload} must be a {@link File} instance, representing a single file
|
||||
* for downloaded content or directory to download all files from the S3 virtual directory.
|
||||
* For the upload operation the {@link #objectAclExpression} can be provided to
|
||||
* {@link AmazonS3#setObjectAcl} after the successful transfer. The supported SpEL result
|
||||
* types are: {@link AccessControlList} or {@link CannedAccessControlList}.
|
||||
* <p>
|
||||
* For download operation the {@code payload} must be a {@link File} instance,
|
||||
* representing a single file for downloaded content or directory to download all files
|
||||
* from the S3 virtual directory.
|
||||
* <p>
|
||||
* An S3 Object {@code key} for upload and download can be determined by the provided
|
||||
* {@link #keyExpression} or the {@link File#getName()} is used directly. The former has precedence.
|
||||
* {@link #keyExpression} or the {@link File#getName()} is used directly. The former has
|
||||
* precedence.
|
||||
* <p>
|
||||
* For copy operation all {@link #keyExpression}, {@link #destinationBucketExpression} and
|
||||
* {@link #destinationKeyExpression} are required and must not evaluate to {@code null}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @author John Logan
|
||||
*
|
||||
* @see TransferManager
|
||||
*/
|
||||
public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
@@ -138,11 +145,7 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
}
|
||||
|
||||
public S3MessageHandler(AmazonS3 amazonS3, Expression bucketExpression, boolean produceReply) {
|
||||
this(TransferManagerBuilder.standard()
|
||||
.withS3Client(amazonS3)
|
||||
.build(),
|
||||
bucketExpression,
|
||||
produceReply);
|
||||
this(TransferManagerBuilder.standard().withS3Client(amazonS3).build(), bucketExpression, produceReply);
|
||||
Assert.notNull(amazonS3, "'amazonS3' must not be null");
|
||||
}
|
||||
|
||||
@@ -168,7 +171,8 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* The SpEL expression to evaluate S3 object key at runtime against {@code requestMessage}.
|
||||
* The SpEL expression to evaluate S3 object key at runtime against
|
||||
* {@code requestMessage}.
|
||||
* @param keyExpression the SpEL expression for S3 key.
|
||||
*/
|
||||
public void setKeyExpression(Expression keyExpression) {
|
||||
@@ -176,8 +180,8 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* The SpEL expression to evaluate S3 object ACL at runtime against {@code requestMessage}
|
||||
* for the {@code upload} operation.
|
||||
* The SpEL expression to evaluate S3 object ACL at runtime against
|
||||
* {@code requestMessage} for the {@code upload} operation.
|
||||
* @param objectAclExpression the SpEL expression for S3 object ACL.
|
||||
*/
|
||||
public void setObjectAclExpression(Expression objectAclExpression) {
|
||||
@@ -185,7 +189,8 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a {@link S3MessageHandler.Command} to perform against {@link TransferManager}.
|
||||
* Specify a {@link S3MessageHandler.Command} to perform against
|
||||
* {@link TransferManager}.
|
||||
* @param command The {@link S3MessageHandler.Command} to use.
|
||||
* @see S3MessageHandler.Command
|
||||
*/
|
||||
@@ -195,9 +200,10 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* The SpEL expression to evaluate the command to perform on {@link TransferManager}: {@code upload},
|
||||
* {@code download} or {@code copy}.
|
||||
* @param commandExpression the SpEL expression to evaluate the {@link TransferManager} operation.
|
||||
* The SpEL expression to evaluate the command to perform on {@link TransferManager}:
|
||||
* {@code upload}, {@code download} or {@code copy}.
|
||||
* @param commandExpression the SpEL expression to evaluate the
|
||||
* {@link TransferManager} operation.
|
||||
* @see Command
|
||||
*/
|
||||
public void setCommandExpression(Expression commandExpression) {
|
||||
@@ -233,8 +239,9 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify an {@link ObjectMetadata} callback to populate the metadata for upload operation,
|
||||
* e.g. {@code Content-MD5}, {@code Content-Type} or any other required options.
|
||||
* Specify an {@link ObjectMetadata} callback to populate the metadata for upload
|
||||
* operation, e.g. {@code Content-MD5}, {@code Content-Type} or any other required
|
||||
* options.
|
||||
* @param uploadMetadataProvider the {@link UploadMetadataProvider} to use for upload.
|
||||
*/
|
||||
public void setUploadMetadataProvider(UploadMetadataProvider uploadMetadataProvider) {
|
||||
@@ -242,7 +249,8 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a {@link ResourceIdResolver} to resolve logical bucket names to physical resource ids.
|
||||
* Specify a {@link ResourceIdResolver} to resolve logical bucket names to physical
|
||||
* resource ids.
|
||||
* @param resourceIdResolver the {@link ResourceIdResolver} to use.
|
||||
*/
|
||||
public void setResourceIdResolver(ResourceIdResolver resourceIdResolver) {
|
||||
@@ -259,23 +267,23 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
@Override
|
||||
protected Object handleRequestMessage(Message<?> requestMessage) {
|
||||
Command command = this.commandExpression.getValue(this.evaluationContext, requestMessage, Command.class);
|
||||
Assert.state(command != null, () ->
|
||||
"'commandExpression' [" + this.commandExpression.getExpressionString() + "] cannot evaluate to null.");
|
||||
Assert.state(command != null, () -> "'commandExpression' [" + this.commandExpression.getExpressionString()
|
||||
+ "] cannot evaluate to null.");
|
||||
|
||||
Transfer transfer = null;
|
||||
|
||||
switch (command) {
|
||||
case UPLOAD:
|
||||
transfer = upload(requestMessage);
|
||||
break;
|
||||
case UPLOAD:
|
||||
transfer = upload(requestMessage);
|
||||
break;
|
||||
|
||||
case DOWNLOAD:
|
||||
transfer = download(requestMessage);
|
||||
break;
|
||||
case DOWNLOAD:
|
||||
transfer = download(requestMessage);
|
||||
break;
|
||||
|
||||
case COPY:
|
||||
transfer = copy(requestMessage);
|
||||
break;
|
||||
case COPY:
|
||||
transfer = copy(requestMessage);
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.produceReply) {
|
||||
@@ -325,8 +333,8 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
InputStream inputStream = (InputStream) payload;
|
||||
if (metadata.getContentMD5() == null) {
|
||||
Assert.state(inputStream.markSupported(),
|
||||
"For an upload InputStream with no MD5 digest metadata, " +
|
||||
"the markSupported() method must evaluate to true.");
|
||||
"For an upload InputStream with no MD5 digest metadata, "
|
||||
+ "the markSupported() method must evaluate to true.");
|
||||
String contentMd5 = Md5Utils.md5AsBase64(inputStream);
|
||||
metadata.setContentMD5(contentMd5);
|
||||
inputStream.reset();
|
||||
@@ -348,9 +356,7 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
if (metadata.getContentType() == null) {
|
||||
metadata.setContentType(Mimetypes.getInstance().getMimetype(fileToUpload));
|
||||
}
|
||||
putObjectRequest =
|
||||
new PutObjectRequest(bucketName, key, fileToUpload)
|
||||
.withMetadata(metadata);
|
||||
putObjectRequest = new PutObjectRequest(bucketName, key, fileToUpload).withMetadata(metadata);
|
||||
}
|
||||
else if (payload instanceof byte[]) {
|
||||
byte[] payloadBytes = (byte[]) payload;
|
||||
@@ -366,10 +372,9 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
putObjectRequest = new PutObjectRequest(bucketName, key, inputStream, metadata);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported payload type: ["
|
||||
+ payload.getClass()
|
||||
+ "]. The only supported payloads for the upload request are " +
|
||||
"java.io.File, java.io.InputStream, byte[] and PutObjectRequest.");
|
||||
throw new IllegalArgumentException("Unsupported payload type: [" + payload.getClass()
|
||||
+ "]. The only supported payloads for the upload request are "
|
||||
+ "java.io.File, java.io.InputStream, byte[] and PutObjectRequest.");
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
@@ -378,8 +383,7 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
|
||||
if (key == null) {
|
||||
if (this.keyExpression != null) {
|
||||
throw new IllegalStateException("The 'keyExpression' ["
|
||||
+ this.keyExpression.getExpressionString()
|
||||
throw new IllegalStateException("The 'keyExpression' [" + this.keyExpression.getExpressionString()
|
||||
+ "] must not evaluate to null. Root object is: " + requestMessage);
|
||||
}
|
||||
else {
|
||||
@@ -410,11 +414,10 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
if (this.objectAclExpression != null) {
|
||||
Object acl = this.objectAclExpression.getValue(this.evaluationContext, requestMessage);
|
||||
Assert.state(acl == null || acl instanceof AccessControlList || acl instanceof CannedAccessControlList,
|
||||
() -> "The 'objectAclExpression' ["
|
||||
+ this.objectAclExpression.getExpressionString()
|
||||
+ "] must evaluate to com.amazonaws.services.s3.model.AccessControlList " +
|
||||
"or must evaluate to com.amazonaws.services.s3.model.CannedAccessControlList. " +
|
||||
"Gotten: [" + acl + "]");
|
||||
() -> "The 'objectAclExpression' [" + this.objectAclExpression.getExpressionString()
|
||||
+ "] must evaluate to com.amazonaws.services.s3.model.AccessControlList "
|
||||
+ "or must evaluate to com.amazonaws.services.s3.model.CannedAccessControlList. "
|
||||
+ "Gotten: [" + acl + "]");
|
||||
|
||||
SetObjectAclRequest aclRequest;
|
||||
|
||||
@@ -459,9 +462,8 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
|
||||
private Transfer download(Message<?> requestMessage) {
|
||||
Object payload = requestMessage.getPayload();
|
||||
Assert.state(payload instanceof File,
|
||||
() -> "For the 'DOWNLOAD' operation the 'payload' must be of " +
|
||||
"'java.io.File' type, but gotten: [" + payload.getClass() + ']');
|
||||
Assert.state(payload instanceof File, () -> "For the 'DOWNLOAD' operation the 'payload' must be of "
|
||||
+ "'java.io.File' type, but gotten: [" + payload.getClass() + ']');
|
||||
|
||||
File targetFile = (File) payload;
|
||||
|
||||
@@ -476,8 +478,8 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
}
|
||||
|
||||
Assert.state(key != null,
|
||||
() -> "The 'keyExpression' must not be null for non-File payloads and can't evaluate to null. " +
|
||||
"Root object is: " + requestMessage);
|
||||
() -> "The 'keyExpression' must not be null for non-File payloads and can't evaluate to null. "
|
||||
+ "Root object is: " + requestMessage);
|
||||
|
||||
if (targetFile.isDirectory()) {
|
||||
return this.transferManager.downloadDirectory(bucket, key, targetFile);
|
||||
@@ -501,10 +503,8 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
sourceKey = this.keyExpression.getValue(this.evaluationContext, requestMessage, String.class);
|
||||
}
|
||||
|
||||
Assert.state(sourceKey != null,
|
||||
() -> "The 'keyExpression' must not be null for 'copy' operation " +
|
||||
"and 'keyExpression' can't evaluate to null. " +
|
||||
"Root object is: " + requestMessage);
|
||||
Assert.state(sourceKey != null, () -> "The 'keyExpression' must not be null for 'copy' operation "
|
||||
+ "and 'keyExpression' can't evaluate to null. " + "Root object is: " + requestMessage);
|
||||
|
||||
String destinationBucketName = null;
|
||||
if (this.destinationBucketExpression != null) {
|
||||
@@ -517,8 +517,8 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
}
|
||||
|
||||
Assert.state(destinationBucketName != null,
|
||||
() -> "The 'destinationBucketExpression' must not be null for 'copy' operation " +
|
||||
"and can't evaluate to null. Root object is: " + requestMessage);
|
||||
() -> "The 'destinationBucketExpression' must not be null for 'copy' operation "
|
||||
+ "and can't evaluate to null. Root object is: " + requestMessage);
|
||||
|
||||
String destinationKey = null;
|
||||
if (this.destinationKeyExpression != null) {
|
||||
@@ -527,12 +527,11 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
}
|
||||
|
||||
Assert.state(destinationKey != null,
|
||||
() -> "The 'destinationKeyExpression' must not be null for 'copy' operation " +
|
||||
"and can't evaluate to null. Root object is: " + requestMessage);
|
||||
() -> "The 'destinationKeyExpression' must not be null for 'copy' operation "
|
||||
+ "and can't evaluate to null. Root object is: " + requestMessage);
|
||||
|
||||
|
||||
CopyObjectRequest copyObjectRequest =
|
||||
new CopyObjectRequest(sourceBucketName, sourceKey, destinationBucketName, destinationKey);
|
||||
CopyObjectRequest copyObjectRequest = new CopyObjectRequest(sourceBucketName, sourceKey, destinationBucketName,
|
||||
destinationKey);
|
||||
return this.transferManager.copy(copyObjectRequest);
|
||||
}
|
||||
|
||||
@@ -544,10 +543,8 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
else {
|
||||
bucketName = this.bucketExpression.getValue(this.evaluationContext, requestMessage, String.class);
|
||||
}
|
||||
Assert.state(bucketName != null,
|
||||
() -> "The 'bucketExpression' ["
|
||||
+ this.bucketExpression.getExpressionString()
|
||||
+ "] must not evaluate to null. Root object is: " + requestMessage);
|
||||
Assert.state(bucketName != null, () -> "The 'bucketExpression' [" + this.bucketExpression.getExpressionString()
|
||||
+ "] must not evaluate to null. Root object is: " + requestMessage);
|
||||
|
||||
if (this.resourceIdResolver != null) {
|
||||
bucketName = this.resourceIdResolver.resolveToPhysicalResourceId(bucketName);
|
||||
@@ -581,8 +578,8 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link S3ProgressListener} extension to provide a {@code requestMessage}
|
||||
* context for the {@code progressChanged} event.
|
||||
* An {@link S3ProgressListener} extension to provide a {@code requestMessage} context
|
||||
* for the {@code progressChanged} event.
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
@@ -598,8 +595,8 @@ public class S3MessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* The callback to populate an {@link ObjectMetadata} for upload operation.
|
||||
* The message can be used as a metadata source.
|
||||
* The callback to populate an {@link ObjectMetadata} for upload operation. The
|
||||
* message can be used as a metadata source.
|
||||
*/
|
||||
public interface UploadMetadataProvider {
|
||||
|
||||
|
||||
@@ -49,36 +49,28 @@ import com.amazonaws.services.sns.model.PublishResult;
|
||||
* <p>
|
||||
* The algorithm to populate SNS Message body is like:
|
||||
* <ul>
|
||||
* <li>
|
||||
* If the {@code payload instanceof PublishRequest} it is used as is for publishing.
|
||||
* <li>If the {@code payload instanceof PublishRequest} it is used as is for publishing.
|
||||
* </li>
|
||||
* <li>
|
||||
* If the {@link #bodyExpression} is specified, it is used to be evaluated
|
||||
* against {@code requestMessage}.
|
||||
* </li>
|
||||
* <li>
|
||||
* If the evaluation result (or {@code payload}) is instance of {@link SnsBodyBuilder},
|
||||
* the SNS Message is built from there and the {@code messageStructure}
|
||||
* of the {@link PublishRequest} is set to {@code json}.
|
||||
* For the convenience the package {@code org.springframework.integration.aws.support} is imported
|
||||
* to the {@link #getEvaluationContext()} to allow bypass it for the {@link SnsBodyBuilder}
|
||||
* from the {@link #bodyExpression} definition. For example:
|
||||
* <li>If the {@link #bodyExpression} is specified, it is used to be evaluated against
|
||||
* {@code requestMessage}.</li>
|
||||
* <li>If the evaluation result (or {@code payload}) is instance of
|
||||
* {@link SnsBodyBuilder}, the SNS Message is built from there and the
|
||||
* {@code messageStructure} of the {@link PublishRequest} is set to {@code json}. For the
|
||||
* convenience the package {@code org.springframework.integration.aws.support} is imported
|
||||
* to the {@link #getEvaluationContext()} to allow bypass it for the
|
||||
* {@link SnsBodyBuilder} from the {@link #bodyExpression} definition. For example:
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
* String bodyExpression =
|
||||
* "SnsBodyBuilder.withDefault(payload).forProtocols(payload.substring(0, 140), 'sms')";
|
||||
* snsMessageHandler.setBodyExpression(spelExpressionParser.parseExpression(bodyExpression));
|
||||
* }
|
||||
* </pre>
|
||||
* </li>
|
||||
* <li>
|
||||
* Otherwise the {@code payload} (or the {@link #bodyExpression} evaluation result) is converted
|
||||
* to the {@link String} using {@link #getConversionService()}.
|
||||
* </li>
|
||||
* </pre></li>
|
||||
* <li>Otherwise the {@code payload} (or the {@link #bodyExpression} evaluation result) is
|
||||
* converted to the {@link String} using {@link #getConversionService()}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @see AmazonSNSAsync
|
||||
* @see PublishRequest
|
||||
* @see SnsBodyBuilder
|
||||
@@ -122,12 +114,13 @@ public class SnsMessageHandler extends AbstractAwsMessageHandler<Map<String, Mes
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link Expression} to produce the SNS notification message.
|
||||
* If it evaluates to the {@link SnsBodyBuilder} the {@code messageStructure}
|
||||
* of the {@link PublishRequest} is set to {@code json}.
|
||||
* Otherwise the {@link #getConversionService()} is used to convert the evaluation result
|
||||
* to the {@link String} without setting the {@code messageStructure}.
|
||||
* @param bodyExpression the {@link Expression} to produce the SNS notification message.
|
||||
* The {@link Expression} to produce the SNS notification message. If it evaluates to
|
||||
* the {@link SnsBodyBuilder} the {@code messageStructure} of the
|
||||
* {@link PublishRequest} is set to {@code json}. Otherwise the
|
||||
* {@link #getConversionService()} is used to convert the evaluation result to the
|
||||
* {@link String} without setting the {@code messageStructure}.
|
||||
* @param bodyExpression the {@link Expression} to produce the SNS notification
|
||||
* message.
|
||||
*/
|
||||
public void setBodyExpression(Expression bodyExpression) {
|
||||
Assert.notNull(bodyExpression, "bodyExpression must not be null.");
|
||||
@@ -135,7 +128,8 @@ public class SnsMessageHandler extends AbstractAwsMessageHandler<Map<String, Mes
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a {@link ResourceIdResolver} to resolve logical topic names to physical resource ids.
|
||||
* Specify a {@link ResourceIdResolver} to resolve logical topic names to physical
|
||||
* resource ids.
|
||||
* @param resourceIdResolver the {@link ResourceIdResolver} to use.
|
||||
*/
|
||||
public void setResourceIdResolver(ResourceIdResolver resourceIdResolver) {
|
||||
@@ -148,8 +142,8 @@ public class SnsMessageHandler extends AbstractAwsMessageHandler<Map<String, Mes
|
||||
TypeLocator typeLocator = getEvaluationContext().getTypeLocator();
|
||||
if (typeLocator instanceof StandardTypeLocator) {
|
||||
/*
|
||||
* Register the 'org.springframework.integration.aws.support' package
|
||||
* you don't need a FQCN for the 'SnsMessageBuilder'.
|
||||
* Register the 'org.springframework.integration.aws.support' package you
|
||||
* don't need a FQCN for the 'SnsMessageBuilder'.
|
||||
*/
|
||||
((StandardTypeLocator) typeLocator).registerImport("org.springframework.integration.aws.support");
|
||||
}
|
||||
@@ -185,8 +179,7 @@ public class SnsMessageHandler extends AbstractAwsMessageHandler<Map<String, Mes
|
||||
}
|
||||
|
||||
if (snsMessage instanceof SnsBodyBuilder) {
|
||||
publishRequest.withMessageStructure("json")
|
||||
.setMessage(((SnsBodyBuilder) snsMessage).build());
|
||||
publishRequest.withMessageStructure("json").setMessage(((SnsBodyBuilder) snsMessage).build());
|
||||
}
|
||||
else {
|
||||
publishRequest.setMessage(getConversionService().convert(snsMessage, String.class));
|
||||
|
||||
@@ -47,16 +47,16 @@ import com.amazonaws.services.sqs.model.SendMessageRequest;
|
||||
import com.amazonaws.services.sqs.model.SendMessageResult;
|
||||
|
||||
/**
|
||||
* The {@link AbstractMessageHandler} implementation for the Amazon SQS {@code sendMessage}.
|
||||
* The {@link AbstractMessageHandler} implementation for the Amazon SQS
|
||||
* {@code sendMessage}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @author Rahul Pilani
|
||||
* @author Taylor Wicksell
|
||||
* @author Seth Kelly
|
||||
*
|
||||
* @see AmazonSQSAsync#sendMessageAsync(SendMessageRequest, AsyncHandler)
|
||||
* @see com.amazonaws.handlers.AsyncHandler
|
||||
|
||||
*
|
||||
*/
|
||||
public class SqsMessageHandler extends AbstractAwsMessageHandler<Map<String, MessageAttributeValue>> {
|
||||
|
||||
@@ -74,7 +74,6 @@ public class SqsMessageHandler extends AbstractAwsMessageHandler<Map<String, Mes
|
||||
|
||||
private Expression messageDeduplicationIdExpression;
|
||||
|
||||
|
||||
public SqsMessageHandler(AmazonSQSAsync amazonSqs) {
|
||||
this(amazonSqs, (ResourceIdResolver) null);
|
||||
}
|
||||
@@ -163,8 +162,8 @@ public class SqsMessageHandler extends AbstractAwsMessageHandler<Map<String, Mes
|
||||
protected Future<?> handleMessageToAws(Message<?> message) {
|
||||
Object payload = message.getPayload();
|
||||
if (payload instanceof SendMessageBatchRequest) {
|
||||
AsyncHandler<SendMessageBatchRequest, SendMessageBatchResult> asyncHandler =
|
||||
obtainAsyncHandler(message, (SendMessageBatchRequest) payload);
|
||||
AsyncHandler<SendMessageBatchRequest, SendMessageBatchResult> asyncHandler = obtainAsyncHandler(message,
|
||||
(SendMessageBatchRequest) payload);
|
||||
return this.amazonSqs.sendMessageBatchAsync((SendMessageBatchRequest) payload, asyncHandler);
|
||||
}
|
||||
|
||||
@@ -177,9 +176,10 @@ public class SqsMessageHandler extends AbstractAwsMessageHandler<Map<String, Mes
|
||||
if (!StringUtils.hasText(queue) && this.queueExpression != null) {
|
||||
queue = this.queueExpression.getValue(getEvaluationContext(), message, String.class);
|
||||
}
|
||||
Assert.state(queue != null, "'queue' must not be null for sending an SQS message. " +
|
||||
"Consider configuring this handler with a 'queue'( or 'queueExpression') or supply an " +
|
||||
"'aws_queue' message header");
|
||||
Assert.state(queue != null,
|
||||
"'queue' must not be null for sending an SQS message. "
|
||||
+ "Consider configuring this handler with a 'queue'( or 'queueExpression') or supply an "
|
||||
+ "'aws_queue' message header");
|
||||
|
||||
String queueUrl = (String) this.destinationResolver.resolveDestination(queue);
|
||||
String messageBody = (String) this.messageConverter.fromMessage(message, String.class);
|
||||
@@ -191,14 +191,14 @@ public class SqsMessageHandler extends AbstractAwsMessageHandler<Map<String, Mes
|
||||
}
|
||||
|
||||
if (this.messageGroupIdExpression != null) {
|
||||
String messageGroupId =
|
||||
this.messageGroupIdExpression.getValue(getEvaluationContext(), message, String.class);
|
||||
String messageGroupId = this.messageGroupIdExpression.getValue(getEvaluationContext(), message,
|
||||
String.class);
|
||||
sendMessageRequest.setMessageGroupId(messageGroupId);
|
||||
}
|
||||
|
||||
if (this.messageDeduplicationIdExpression != null) {
|
||||
String messageDeduplicationId =
|
||||
this.messageDeduplicationIdExpression.getValue(getEvaluationContext(), message, String.class);
|
||||
String messageDeduplicationId = this.messageDeduplicationIdExpression.getValue(getEvaluationContext(),
|
||||
message, String.class);
|
||||
sendMessageRequest.setMessageDeduplicationId(messageDeduplicationId);
|
||||
}
|
||||
|
||||
@@ -207,8 +207,8 @@ public class SqsMessageHandler extends AbstractAwsMessageHandler<Map<String, Mes
|
||||
mapHeaders(message, sendMessageRequest, headerMapper);
|
||||
}
|
||||
}
|
||||
AsyncHandler<SendMessageRequest, SendMessageResult> asyncHandler =
|
||||
obtainAsyncHandler(message, sendMessageRequest);
|
||||
AsyncHandler<SendMessageRequest, SendMessageResult> asyncHandler = obtainAsyncHandler(message,
|
||||
sendMessageRequest);
|
||||
return this.amazonSqs.sendMessageAsync(sendMessageRequest, asyncHandler);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,38 +33,31 @@ import org.springframework.util.MimeType;
|
||||
import org.springframework.util.NumberUtils;
|
||||
|
||||
/**
|
||||
* Base {@link HeaderMapper} implementation for common logic in SQS and SNS
|
||||
* around message attributes mapping.
|
||||
* Base {@link HeaderMapper} implementation for common logic in SQS and SNS around message
|
||||
* attributes mapping.
|
||||
*
|
||||
* The {@link #toHeaders(Map)} is not supported.
|
||||
*
|
||||
* @param <A> the target message attribute type.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public abstract class AbstractMessageAttributesHeaderMapper<A> implements HeaderMapper<Map<String, A>> {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(SqsHeaderMapper.class);
|
||||
|
||||
private volatile String[] outboundHeaderNames = {
|
||||
"!" + MessageHeaders.ID,
|
||||
"!" + MessageHeaders.TIMESTAMP,
|
||||
"!" + AwsHeaders.MESSAGE_ID,
|
||||
"!" + AwsHeaders.QUEUE,
|
||||
"!" + AwsHeaders.TOPIC,
|
||||
"*" };
|
||||
private volatile String[] outboundHeaderNames = { "!" + MessageHeaders.ID, "!" + MessageHeaders.TIMESTAMP,
|
||||
"!" + AwsHeaders.MESSAGE_ID, "!" + AwsHeaders.QUEUE, "!" + AwsHeaders.TOPIC, "*" };
|
||||
|
||||
/**
|
||||
* Provide the header names that should be mapped to a AWS request object attributes
|
||||
* (for outbound adapters) from a Spring Integration Message's headers.
|
||||
* The values can also contain simple wildcard patterns (e.g. "foo*" or "*foo") to be matched.
|
||||
* Also supports negated ('!') patterns. First match wins (positive or negative).
|
||||
* To match the names starting with {@code !} symbol,
|
||||
* you have to escape it prepending with the {@code \} symbol in the pattern definition.
|
||||
* Defaults to map all ({@code *}) if the type is supported by SQS.
|
||||
* The {@link MessageHeaders#ID}, {@link MessageHeaders#TIMESTAMP}, {@link AwsHeaders#MESSAGE_ID},
|
||||
* (for outbound adapters) from a Spring Integration Message's headers. The values can
|
||||
* also contain simple wildcard patterns (e.g. "foo*" or "*foo") to be matched. Also
|
||||
* supports negated ('!') patterns. First match wins (positive or negative). To match
|
||||
* the names starting with {@code !} symbol, you have to escape it prepending with the
|
||||
* {@code \} symbol in the pattern definition. Defaults to map all ({@code *}) if the
|
||||
* type is supported by SQS. The {@link MessageHeaders#ID},
|
||||
* {@link MessageHeaders#TIMESTAMP}, {@link AwsHeaders#MESSAGE_ID},
|
||||
* {@link AwsHeaders#QUEUE} and {@link AwsHeaders#TOPIC} are ignored by default.
|
||||
* @param outboundHeaderNames The inbound header names.
|
||||
*/
|
||||
@@ -83,10 +76,8 @@ public abstract class AbstractMessageAttributesHeaderMapper<A> implements Header
|
||||
|
||||
if (Boolean.TRUE.equals(PatternMatchUtils.smartMatch(messageHeaderName, this.outboundHeaderNames))) {
|
||||
|
||||
if (messageHeaderValue instanceof UUID
|
||||
|| messageHeaderValue instanceof MimeType
|
||||
|| messageHeaderValue instanceof Boolean
|
||||
|| messageHeaderValue instanceof String) {
|
||||
if (messageHeaderValue instanceof UUID || messageHeaderValue instanceof MimeType
|
||||
|| messageHeaderValue instanceof Boolean || messageHeaderValue instanceof String) {
|
||||
|
||||
target.put(messageHeaderName, getStringMessageAttribute(messageHeaderValue.toString()));
|
||||
}
|
||||
@@ -102,11 +93,10 @@ public abstract class AbstractMessageAttributesHeaderMapper<A> implements Header
|
||||
}
|
||||
else {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn(
|
||||
String.format("Message header with name '%s' and type '%s' cannot be sent as" +
|
||||
" message attribute because it is not supported by SQS.",
|
||||
messageHeaderName,
|
||||
messageHeaderValue.getClass().getName()));
|
||||
logger.warn(String.format(
|
||||
"Message header with name '%s' and type '%s' cannot be sent as"
|
||||
+ " message attribute because it is not supported by SQS.",
|
||||
messageHeaderName, messageHeaderValue.getClass().getName()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,8 @@ public abstract class AwsHeaders {
|
||||
public static final String CHECKPOINTER = PREFIX + "checkpointer";
|
||||
|
||||
/**
|
||||
* The {@value SERVICE_RESULT} header represents a {@link com.amazonaws.AmazonWebServiceResult}.
|
||||
* The {@value SERVICE_RESULT} header represents a
|
||||
* {@link com.amazonaws.AmazonWebServiceResult}.
|
||||
*/
|
||||
public static final String SERVICE_RESULT = PREFIX + "serviceResult";
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import com.amazonaws.AmazonWebServiceRequest;
|
||||
* An exception that is the payload of an {@code ErrorMessage} when a send fails.
|
||||
*
|
||||
* @author Jacob Severson
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public class AwsRequestFailureException extends MessagingException {
|
||||
|
||||
@@ -24,10 +24,10 @@ import org.springframework.util.Assert;
|
||||
import com.amazonaws.services.s3.model.S3ObjectSummary;
|
||||
|
||||
/**
|
||||
* An Amazon S3 {@link org.springframework.integration.file.remote.FileInfo} implementation.
|
||||
* An Amazon S3 {@link org.springframework.integration.file.remote.FileInfo}
|
||||
* implementation.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public class S3FileInfo extends AbstractFileInfo<S3ObjectSummary> {
|
||||
@@ -65,9 +65,8 @@ public class S3FileInfo extends AbstractFileInfo<S3ObjectSummary> {
|
||||
}
|
||||
|
||||
/**
|
||||
* A permissions representation string.
|
||||
* Throws {@link UnsupportedOperationException} to avoid extra
|
||||
* {@link com.amazonaws.services.s3.AmazonS3#getObjectAcl} REST call.
|
||||
* A permissions representation string. Throws {@link UnsupportedOperationException}
|
||||
* to avoid extra {@link com.amazonaws.services.s3.AmazonS3#getObjectAcl} REST call.
|
||||
* The target application amy choose to do that by its logic.
|
||||
* @return the permissions representation string.
|
||||
*/
|
||||
@@ -83,10 +82,9 @@ public class S3FileInfo extends AbstractFileInfo<S3ObjectSummary> {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FileInfo [isDirectory=" + isDirectory() + ", isLink=" + isLink()
|
||||
+ ", Size=" + getSize() + ", ModifiedTime="
|
||||
+ new Date(getModified()) + ", Filename=" + getFilename()
|
||||
+ ", RemoteDirectory=" + getRemoteDirectory() + "]";
|
||||
return "FileInfo [isDirectory=" + isDirectory() + ", isLink=" + isLink() + ", Size=" + getSize()
|
||||
+ ", ModifiedTime=" + new Date(getModified()) + ", Filename=" + getFilename() + ", RemoteDirectory="
|
||||
+ getRemoteDirectory() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -67,17 +67,16 @@ public class S3Session implements Session<S3ObjectSummary> {
|
||||
public S3ObjectSummary[] list(String path) throws IOException {
|
||||
String[] bucketPrefix = splitPathToBucketAndKey(path, false);
|
||||
|
||||
ListObjectsRequest listObjectsRequest = new ListObjectsRequest()
|
||||
.withBucketName(bucketPrefix[0]);
|
||||
ListObjectsRequest listObjectsRequest = new ListObjectsRequest().withBucketName(bucketPrefix[0]);
|
||||
if (bucketPrefix.length > 1) {
|
||||
listObjectsRequest.setPrefix(bucketPrefix[1]);
|
||||
}
|
||||
|
||||
/*
|
||||
For listing objects, Amazon S3 returns up to 1,000 keys in the response.
|
||||
If you have more than 1,000 keys in your bucket, the response will be truncated.
|
||||
You should always check for if the response is truncated.
|
||||
*/
|
||||
* For listing objects, Amazon S3 returns up to 1,000 keys in the response. If you
|
||||
* have more than 1,000 keys in your bucket, the response will be truncated. You
|
||||
* should always check for if the response is truncated.
|
||||
*/
|
||||
ObjectListing objectListing;
|
||||
List<S3ObjectSummary> objectSummaries = new ArrayList<>();
|
||||
do {
|
||||
@@ -103,17 +102,16 @@ public class S3Session implements Session<S3ObjectSummary> {
|
||||
public String[] listNames(String path) throws IOException {
|
||||
String[] bucketPrefix = splitPathToBucketAndKey(path, false);
|
||||
|
||||
ListObjectsRequest listObjectsRequest = new ListObjectsRequest()
|
||||
.withBucketName(bucketPrefix[0]);
|
||||
ListObjectsRequest listObjectsRequest = new ListObjectsRequest().withBucketName(bucketPrefix[0]);
|
||||
if (bucketPrefix.length > 1) {
|
||||
listObjectsRequest.setPrefix(bucketPrefix[1]);
|
||||
}
|
||||
|
||||
/*
|
||||
For listing objects, Amazon S3 returns up to 1,000 keys in the response.
|
||||
If you have more than 1,000 keys in your bucket, the response will be truncated.
|
||||
You should always check for if the response is truncated.
|
||||
*/
|
||||
* For listing objects, Amazon S3 returns up to 1,000 keys in the response. If you
|
||||
* have more than 1,000 keys in your bucket, the response will be truncated. You
|
||||
* should always check for if the response is truncated.
|
||||
*/
|
||||
ObjectListing objectListing;
|
||||
List<String> names = new ArrayList<>();
|
||||
do {
|
||||
@@ -139,11 +137,11 @@ public class S3Session implements Session<S3ObjectSummary> {
|
||||
public void rename(String pathFrom, String pathTo) throws IOException {
|
||||
String[] bucketKeyFrom = splitPathToBucketAndKey(pathFrom, true);
|
||||
String[] bucketKeyTo = splitPathToBucketAndKey(pathTo, true);
|
||||
CopyObjectRequest copyRequest = new CopyObjectRequest(bucketKeyFrom[0], bucketKeyFrom[1],
|
||||
bucketKeyTo[0], bucketKeyTo[1]);
|
||||
CopyObjectRequest copyRequest = new CopyObjectRequest(bucketKeyFrom[0], bucketKeyFrom[1], bucketKeyTo[0],
|
||||
bucketKeyTo[1]);
|
||||
this.amazonS3.copyObject(copyRequest);
|
||||
|
||||
//Delete the source
|
||||
// Delete the source
|
||||
this.amazonS3.deleteObject(bucketKeyFrom[0], bucketKeyFrom[1]);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ import com.amazonaws.services.s3.AmazonS3ClientBuilder;
|
||||
import com.amazonaws.services.s3.model.S3ObjectSummary;
|
||||
|
||||
/**
|
||||
* An Amazon S3 specific {@link SessionFactory} implementation.
|
||||
* Also this class implements {@link SharedSessionCapable} around the single instance,
|
||||
* since the {@link S3Session} is simple thread-safe wrapper for the {@link AmazonS3}.
|
||||
* An Amazon S3 specific {@link SessionFactory} implementation. Also this class implements
|
||||
* {@link SharedSessionCapable} around the single instance, since the {@link S3Session} is
|
||||
* simple thread-safe wrapper for the {@link AmazonS3}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
|
||||
@@ -22,10 +22,9 @@ import java.util.Map;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An utility class to simplify an SNS Message body building.
|
||||
* Can be used from the {@code SnsMessageHandler#bodyExpression} definition or
|
||||
* directly in case of manual {@link com.amazonaws.services.sns.model.PublishRequest}
|
||||
* building.
|
||||
* An utility class to simplify an SNS Message body building. Can be used from the
|
||||
* {@code SnsMessageHandler#bodyExpression} definition or directly in case of manual
|
||||
* {@link com.amazonaws.services.sns.model.PublishRequest} building.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@@ -51,11 +50,8 @@ public final class SnsBodyBuilder {
|
||||
public String build() {
|
||||
StringBuilder stringBuilder = new StringBuilder("{");
|
||||
for (Map.Entry<String, String> entry : this.snsMessage.entrySet()) {
|
||||
stringBuilder.append("\"")
|
||||
.append(entry.getKey())
|
||||
.append("\":\"")
|
||||
.append(entry.getValue().replaceAll("\"", "\\\\\""))
|
||||
.append("\",");
|
||||
stringBuilder.append("\"").append(entry.getKey()).append("\":\"")
|
||||
.append(entry.getValue().replaceAll("\"", "\\\\\"")).append("\",");
|
||||
}
|
||||
return stringBuilder.substring(0, stringBuilder.length() - 1) + "}";
|
||||
}
|
||||
|
||||
@@ -21,22 +21,20 @@ import java.nio.ByteBuffer;
|
||||
import com.amazonaws.services.sns.model.MessageAttributeValue;
|
||||
|
||||
/**
|
||||
* The {@link AbstractMessageAttributesHeaderMapper} implementation for the mapping
|
||||
* from headers to SNS message attributes.
|
||||
* The {@link AbstractMessageAttributesHeaderMapper} implementation for the mapping from
|
||||
* headers to SNS message attributes.
|
||||
* <p>
|
||||
* On the Inbound side, the SNS message is fully mapped from the JSON to the message payload.
|
||||
* Only important HTTP headers are mapped to the message headers.
|
||||
* On the Inbound side, the SNS message is fully mapped from the JSON to the message
|
||||
* payload. Only important HTTP headers are mapped to the message headers.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SnsHeaderMapper extends AbstractMessageAttributesHeaderMapper<MessageAttributeValue> {
|
||||
|
||||
@Override
|
||||
protected MessageAttributeValue buildMessageAttribute(String dataType, Object value) {
|
||||
MessageAttributeValue messageAttributeValue = new MessageAttributeValue()
|
||||
.withDataType(dataType);
|
||||
MessageAttributeValue messageAttributeValue = new MessageAttributeValue().withDataType(dataType);
|
||||
if (value instanceof ByteBuffer) {
|
||||
return messageAttributeValue.withBinaryValue((ByteBuffer) value);
|
||||
}
|
||||
|
||||
@@ -23,22 +23,21 @@ import org.springframework.messaging.MessageHeaders;
|
||||
import com.amazonaws.services.sqs.model.MessageAttributeValue;
|
||||
|
||||
/**
|
||||
* The {@link AbstractMessageAttributesHeaderMapper} implementation for the mapping
|
||||
* from headers to SQS message attributes.
|
||||
* The {@link AbstractMessageAttributesHeaderMapper} implementation for the mapping from
|
||||
* headers to SQS message attributes.
|
||||
* <p>
|
||||
* The {@link org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer} maps
|
||||
* all the SQS message attributes to the {@link MessageHeaders}.
|
||||
* The
|
||||
* {@link org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer}
|
||||
* maps all the SQS message attributes to the {@link MessageHeaders}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SqsHeaderMapper extends AbstractMessageAttributesHeaderMapper<MessageAttributeValue> {
|
||||
|
||||
@Override
|
||||
protected MessageAttributeValue buildMessageAttribute(String dataType, Object value) {
|
||||
MessageAttributeValue messageAttributeValue = new MessageAttributeValue()
|
||||
.withDataType(dataType);
|
||||
MessageAttributeValue messageAttributeValue = new MessageAttributeValue().withDataType(dataType);
|
||||
if (value instanceof ByteBuffer) {
|
||||
return messageAttributeValue.withBinaryValue((ByteBuffer) value);
|
||||
}
|
||||
|
||||
@@ -16,15 +16,14 @@
|
||||
|
||||
package org.springframework.integration.aws.support.filters;
|
||||
|
||||
|
||||
import org.springframework.integration.file.filters.AbstractPersistentAcceptOnceFileListFilter;
|
||||
import org.springframework.integration.metadata.ConcurrentMetadataStore;
|
||||
|
||||
import com.amazonaws.services.s3.model.S3ObjectSummary;
|
||||
|
||||
/**
|
||||
* Persistent file list filter using the server's file timestamp to detect if we've already
|
||||
* 'seen' this file.
|
||||
* Persistent file list filter using the server's file timestamp to detect if we've
|
||||
* already 'seen' this file.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
|
||||
@@ -38,11 +38,10 @@ import com.amazonaws.services.dynamodbv2.AmazonDynamoDBAsync;
|
||||
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBAsyncClientBuilder;
|
||||
|
||||
/**
|
||||
* The {@link TestWatcher} implementation for local Amazon DynamoDB service.
|
||||
* See https://github.com/mhart/dynalite.
|
||||
* The {@link TestWatcher} implementation for local Amazon DynamoDB service. See
|
||||
* https://github.com/mhart/dynalite.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public final class DynamoDbLocalRunning extends TestWatcher {
|
||||
@@ -75,10 +74,7 @@ public final class DynamoDbLocalRunning extends TestWatcher {
|
||||
|
||||
this.amazonDynamoDB = AmazonDynamoDBAsyncClientBuilder.standard()
|
||||
.withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials("", "")))
|
||||
.withClientConfiguration(
|
||||
new ClientConfiguration()
|
||||
.withMaxErrorRetry(0)
|
||||
.withConnectionTimeout(1000))
|
||||
.withClientConfiguration(new ClientConfiguration().withMaxErrorRetry(0).withConnectionTimeout(1000))
|
||||
.withEndpointConfiguration(
|
||||
new AwsClientBuilder.EndpointConfiguration(url, Regions.DEFAULT_REGION.getName()))
|
||||
.build();
|
||||
@@ -93,7 +89,6 @@ public final class DynamoDbLocalRunning extends TestWatcher {
|
||||
return super.apply(base, description);
|
||||
}
|
||||
|
||||
|
||||
public static DynamoDbLocalRunning isRunning() {
|
||||
return isRunning(DEFAULT_PORT);
|
||||
}
|
||||
|
||||
@@ -39,11 +39,10 @@ import com.amazonaws.services.kinesis.AmazonKinesisAsync;
|
||||
import com.amazonaws.services.kinesis.AmazonKinesisAsyncClientBuilder;
|
||||
|
||||
/**
|
||||
* The {@link TestWatcher} implementation for local Amazon Kinesis service.
|
||||
* See https://github.com/mhart/kinesalite.
|
||||
* The {@link TestWatcher} implementation for local Amazon Kinesis service. See
|
||||
* https://github.com/mhart/kinesalite.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public final class KinesisLocalRunning extends TestWatcher {
|
||||
@@ -79,10 +78,7 @@ public final class KinesisLocalRunning extends TestWatcher {
|
||||
|
||||
this.amazonKinesis = AmazonKinesisAsyncClientBuilder.standard()
|
||||
.withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials("", "")))
|
||||
.withClientConfiguration(
|
||||
new ClientConfiguration()
|
||||
.withMaxErrorRetry(0)
|
||||
.withConnectionTimeout(1000))
|
||||
.withClientConfiguration(new ClientConfiguration().withMaxErrorRetry(0).withConnectionTimeout(1000))
|
||||
.withEndpointConfiguration(
|
||||
new AwsClientBuilder.EndpointConfiguration(url, Regions.DEFAULT_REGION.getName()))
|
||||
.build();
|
||||
@@ -95,7 +91,6 @@ public final class KinesisLocalRunning extends TestWatcher {
|
||||
assumeNoException(e);
|
||||
}
|
||||
|
||||
|
||||
return new Statement() {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -89,9 +89,9 @@ public class S3InboundChannelAdapterParserTests {
|
||||
|
||||
S3InboundFileSynchronizer fisync = TestUtils.getPropertyValue(inbound, "synchronizer",
|
||||
S3InboundFileSynchronizer.class);
|
||||
assertThat(TestUtils.getPropertyValue(fisync, "remoteDirectoryExpression", Expression.class)
|
||||
.getExpressionString())
|
||||
.isEqualTo("'foo/bar'");
|
||||
assertThat(
|
||||
TestUtils.getPropertyValue(fisync, "remoteDirectoryExpression", Expression.class).getExpressionString())
|
||||
.isEqualTo("'foo/bar'");
|
||||
assertThat(TestUtils.getPropertyValue(fisync, "localFilenameGeneratorExpression")).isNotNull();
|
||||
assertThat(TestUtils.getPropertyValue(fisync, "preserveTimestamp", Boolean.class)).isTrue();
|
||||
assertThat(TestUtils.getPropertyValue(fisync, "temporaryFileSuffix", String.class)).isEqualTo(".foo");
|
||||
@@ -111,8 +111,7 @@ public class S3InboundChannelAdapterParserTests {
|
||||
assertThat(TestUtils.getPropertyValue(fisync, "remoteFileTemplate.sessionFactory"))
|
||||
.isSameAs(this.s3SessionFactory);
|
||||
assertThat(TestUtils.getPropertyValue(inbound, "fileSource.scanner.filter.fileFilters", Collection.class)
|
||||
.contains(this.acceptAllFilter))
|
||||
.isTrue();
|
||||
.contains(this.acceptAllFilter)).isTrue();
|
||||
final AtomicReference<Method> genMethod = new AtomicReference<Method>();
|
||||
ReflectionUtils.doWithMethods(AbstractInboundFileSynchronizer.class, new ReflectionUtils.MethodCallback() {
|
||||
|
||||
|
||||
@@ -91,20 +91,16 @@ public class S3MessageHandlerParserTests {
|
||||
public void testS3OutboundChannelAdapterParser() {
|
||||
assertThat(TestUtils.getPropertyValue(this.s3OutboundChannelAdapterHandler, "transferManager.s3"))
|
||||
.isSameAs(this.amazonS3);
|
||||
assertThat(TestUtils.getPropertyValue(this.s3OutboundChannelAdapterHandler,
|
||||
"bucketExpression.literalValue"))
|
||||
assertThat(TestUtils.getPropertyValue(this.s3OutboundChannelAdapterHandler, "bucketExpression.literalValue"))
|
||||
.isEqualTo("foo");
|
||||
assertThat(TestUtils.getPropertyValue(this.s3OutboundChannelAdapterHandler,
|
||||
"destinationBucketExpression.expression"))
|
||||
.isEqualTo("'bar'");
|
||||
assertThat(TestUtils.getPropertyValue(this.s3OutboundChannelAdapterHandler,
|
||||
"destinationKeyExpression.expression"))
|
||||
.isEqualTo("'baz'");
|
||||
assertThat(TestUtils.getPropertyValue(this.s3OutboundChannelAdapterHandler,
|
||||
"keyExpression.expression"))
|
||||
"destinationBucketExpression.expression")).isEqualTo("'bar'");
|
||||
assertThat(
|
||||
TestUtils.getPropertyValue(this.s3OutboundChannelAdapterHandler, "destinationKeyExpression.expression"))
|
||||
.isEqualTo("'baz'");
|
||||
assertThat(TestUtils.getPropertyValue(this.s3OutboundChannelAdapterHandler, "keyExpression.expression"))
|
||||
.isEqualTo("payload.name");
|
||||
assertThat(TestUtils.getPropertyValue(this.s3OutboundChannelAdapterHandler,
|
||||
"objectAclExpression.expression"))
|
||||
assertThat(TestUtils.getPropertyValue(this.s3OutboundChannelAdapterHandler, "objectAclExpression.expression"))
|
||||
.isEqualTo("'qux'");
|
||||
assertThat(TestUtils.getPropertyValue(this.s3OutboundChannelAdapterHandler, "commandExpression.literalValue"))
|
||||
.isEqualTo(S3MessageHandler.Command.COPY.name());
|
||||
@@ -132,17 +128,16 @@ public class S3MessageHandlerParserTests {
|
||||
public void testS3OutboundGatewayParser() {
|
||||
assertThat(TestUtils.getPropertyValue(this.s3OutboundGatewayHandler, "transferManager"))
|
||||
.isSameAs(this.transferManager);
|
||||
assertThat(TestUtils.getPropertyValue(this.s3OutboundGatewayHandler,
|
||||
"bucketExpression.expression"))
|
||||
assertThat(TestUtils.getPropertyValue(this.s3OutboundGatewayHandler, "bucketExpression.expression"))
|
||||
.isEqualTo("'FOO'");
|
||||
Expression commandExpression =
|
||||
TestUtils.getPropertyValue(this.s3OutboundGatewayHandler, "commandExpression", Expression.class);
|
||||
Expression commandExpression = TestUtils.getPropertyValue(this.s3OutboundGatewayHandler, "commandExpression",
|
||||
Expression.class);
|
||||
assertThat(TestUtils.getPropertyValue(commandExpression, "expression"))
|
||||
.isEqualTo("'" + S3MessageHandler.Command.DOWNLOAD.name() + "'");
|
||||
|
||||
StandardEvaluationContext evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.beanFactory);
|
||||
S3MessageHandler.Command command =
|
||||
commandExpression.getValue(evaluationContext, S3MessageHandler.Command.class);
|
||||
S3MessageHandler.Command command = commandExpression.getValue(evaluationContext,
|
||||
S3MessageHandler.Command.class);
|
||||
|
||||
assertThat(command).isEqualTo(S3MessageHandler.Command.DOWNLOAD);
|
||||
|
||||
|
||||
@@ -73,9 +73,9 @@ public class S3StreamingInboundChannelAdapterParserTests {
|
||||
S3StreamingMessageSource source = TestUtils.getPropertyValue(this.s3Inbound, "source",
|
||||
S3StreamingMessageSource.class);
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(source, "remoteDirectoryExpression", Expression.class)
|
||||
.getExpressionString())
|
||||
.isEqualTo("foo/bar");
|
||||
assertThat(
|
||||
TestUtils.getPropertyValue(source, "remoteDirectoryExpression", Expression.class).getExpressionString())
|
||||
.isEqualTo("foo/bar");
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(source, "comparator")).isSameAs(this.comparator);
|
||||
String remoteFileSeparator = (String) TestUtils.getPropertyValue(source, "remoteFileSeparator");
|
||||
|
||||
@@ -54,17 +54,14 @@ public class SnsInboundChannelAdapterParserTests {
|
||||
@Qualifier("snsInboundChannelAdapter")
|
||||
private SnsInboundChannelAdapter snsInboundChannelAdapter;
|
||||
|
||||
|
||||
@Test
|
||||
public void testSnsInboundChannelAdapterParser() {
|
||||
assertThat(TestUtils.getPropertyValue(this.snsInboundChannelAdapter, "notificationStatusResolver.amazonSns"))
|
||||
.isSameAs(this.amazonSns);
|
||||
assertThat(TestUtils.getPropertyValue(this.snsInboundChannelAdapter, "handleNotificationStatus",
|
||||
Boolean.class))
|
||||
assertThat(TestUtils.getPropertyValue(this.snsInboundChannelAdapter, "handleNotificationStatus", Boolean.class))
|
||||
.isTrue();
|
||||
assertThat(TestUtils.getPropertyValue(this.snsInboundChannelAdapter, "requestMapping.pathPatterns",
|
||||
String[].class))
|
||||
.isEqualTo(new String[] { "/foo" });
|
||||
String[].class)).isEqualTo(new String[] { "/foo" });
|
||||
assertThat(TestUtils.getPropertyValue(this.snsInboundChannelAdapter, "payloadExpression.expression"))
|
||||
.isEqualTo("payload.Message");
|
||||
assertThat(this.snsInboundChannelAdapter.isRunning()).isFalse();
|
||||
|
||||
@@ -79,8 +79,7 @@ public class SnsOutboundChannelAdapterParserTests {
|
||||
public void testSnsOutboundChannelAdapterDefaultParser() {
|
||||
Object handler = TestUtils.getPropertyValue(this.defaultAdapter, "handler");
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "inputChannel"))
|
||||
.isSameAs(this.notificationChannel);
|
||||
assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "inputChannel")).isSameAs(this.notificationChannel);
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(this.defaultAdapterHandler, "amazonSns")).isSameAs(this.amazonSns);
|
||||
assertThat(TestUtils.getPropertyValue(this.defaultAdapterHandler, "evaluationContext")).isNotNull();
|
||||
@@ -101,14 +100,11 @@ public class SnsOutboundChannelAdapterParserTests {
|
||||
assertThat(TestUtils.getPropertyValue(this.defaultAdapterHandler, "errorMessageStrategy"))
|
||||
.isSameAs(this.errorMessageStrategy);
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(this.defaultAdapterHandler, "asyncHandler"))
|
||||
.isSameAs(this.asyncHandler);
|
||||
assertThat(TestUtils.getPropertyValue(this.defaultAdapterHandler, "asyncHandler")).isSameAs(this.asyncHandler);
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(this.defaultAdapterHandler, "sync", Boolean.class))
|
||||
.isFalse();
|
||||
assertThat(TestUtils.getPropertyValue(this.defaultAdapterHandler, "sync", Boolean.class)).isFalse();
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(this.defaultAdapterHandler,
|
||||
"sendTimeoutExpression.literalValue"))
|
||||
assertThat(TestUtils.getPropertyValue(this.defaultAdapterHandler, "sendTimeoutExpression.literalValue"))
|
||||
.isEqualTo("202");
|
||||
|
||||
}
|
||||
|
||||
@@ -75,17 +75,14 @@ public class SqsMessageDrivenChannelAdapterParserTests {
|
||||
@Bean
|
||||
public DestinationResolver<?> destinationResolver() {
|
||||
DestinationResolver<?> destinationResolver = Mockito.mock(DestinationResolver.class);
|
||||
willThrow(DestinationResolutionException.class)
|
||||
.given(destinationResolver)
|
||||
.resolveDestination(anyString());
|
||||
willThrow(DestinationResolutionException.class).given(destinationResolver).resolveDestination(anyString());
|
||||
return destinationResolver;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSqsMessageDrivenChannelAdapterParser() {
|
||||
SimpleMessageListenerContainer listenerContainer =
|
||||
TestUtils.getPropertyValue(this.sqsMessageDrivenChannelAdapter, "listenerContainer",
|
||||
SimpleMessageListenerContainer.class);
|
||||
SimpleMessageListenerContainer listenerContainer = TestUtils.getPropertyValue(
|
||||
this.sqsMessageDrivenChannelAdapter, "listenerContainer", SimpleMessageListenerContainer.class);
|
||||
assertThat(TestUtils.getPropertyValue(listenerContainer, "amazonSqs")).isSameAs(this.amazonSqs);
|
||||
assertThat(TestUtils.getPropertyValue(listenerContainer, "resourceIdResolver"))
|
||||
.isSameAs(this.resourceIdResolver);
|
||||
@@ -106,12 +103,10 @@ public class SqsMessageDrivenChannelAdapterParserTests {
|
||||
.isSameAs(this.errorChannel);
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsMessageDrivenChannelAdapter, "errorChannel"))
|
||||
.isSameAs(this.nullChannel);
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsMessageDrivenChannelAdapter,
|
||||
"messagingTemplate.sendTimeout"))
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsMessageDrivenChannelAdapter, "messagingTemplate.sendTimeout"))
|
||||
.isEqualTo(2000L);
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsMessageDrivenChannelAdapter, "messageDeletionPolicy",
|
||||
SqsMessageDeletionPolicy.class))
|
||||
.isEqualTo(SqsMessageDeletionPolicy.NEVER);
|
||||
SqsMessageDeletionPolicy.class)).isEqualTo(SqsMessageDeletionPolicy.NEVER);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -81,10 +81,8 @@ public class SqsMessageHandlerParserTests {
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsOutboundChannelAdapterHandler, "amazonSqs"))
|
||||
.isSameAs(this.amazonSqs);
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsOutboundChannelAdapterHandler,
|
||||
"destinationResolver.resourceIdResolver"))
|
||||
.isSameAs(this.resourceIdResolver);
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsOutboundChannelAdapterHandler,
|
||||
"queueExpression.literalValue"))
|
||||
"destinationResolver.resourceIdResolver")).isSameAs(this.resourceIdResolver);
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsOutboundChannelAdapterHandler, "queueExpression.literalValue"))
|
||||
.isEqualTo("foo");
|
||||
assertThat(this.sqsOutboundChannelAdapter.getPhase()).isEqualTo(100);
|
||||
assertThat(this.sqsOutboundChannelAdapter.isAutoStartup()).isFalse();
|
||||
@@ -94,17 +92,14 @@ public class SqsMessageHandlerParserTests {
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsOutboundChannelAdapter, "handler"))
|
||||
.isSameAs(this.sqsOutboundChannelAdapterHandler);
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsOutboundChannelAdapterHandler,
|
||||
"delayExpression.expression"))
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsOutboundChannelAdapterHandler, "delayExpression.expression"))
|
||||
.isEqualTo("'200'");
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsOutboundChannelAdapterHandler,
|
||||
"messageDeduplicationIdExpression.literalValue"))
|
||||
.isEqualTo("foo");
|
||||
"messageDeduplicationIdExpression.literalValue")).isEqualTo("foo");
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsOutboundChannelAdapterHandler,
|
||||
"messageGroupIdExpression.expression"))
|
||||
.isEqualTo("'bar'");
|
||||
"messageGroupIdExpression.expression")).isEqualTo("'bar'");
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsOutboundChannelAdapterHandler, "failureChannel"))
|
||||
.isSameAs(this.failureChannel);
|
||||
@@ -121,12 +116,11 @@ public class SqsMessageHandlerParserTests {
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsOutboundChannelAdapterHandler, "asyncHandler"))
|
||||
.isSameAs(this.asyncHandler);
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsOutboundChannelAdapterHandler, "sync", Boolean.class))
|
||||
.isFalse();
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsOutboundChannelAdapterHandler, "sync", Boolean.class)).isFalse();
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsOutboundChannelAdapterHandler,
|
||||
"sendTimeoutExpression.literalValue"))
|
||||
.isEqualTo("202");
|
||||
assertThat(
|
||||
TestUtils.getPropertyValue(this.sqsOutboundChannelAdapterHandler, "sendTimeoutExpression.literalValue"))
|
||||
.isEqualTo("202");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -73,7 +73,6 @@ import com.amazonaws.services.kinesis.model.StreamStatus;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@@ -108,9 +107,8 @@ public class KinesisMessageDrivenChannelAdapterTests {
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public void testKinesisMessageDrivenChannelAdapter() {
|
||||
this.kinesisMessageDrivenChannelAdapter.start();
|
||||
final Set<KinesisShardOffset> shardOffsets =
|
||||
TestUtils.getPropertyValue(this.kinesisMessageDrivenChannelAdapter, "shardOffsets", Set.class);
|
||||
|
||||
final Set<KinesisShardOffset> shardOffsets = TestUtils.getPropertyValue(this.kinesisMessageDrivenChannelAdapter,
|
||||
"shardOffsets", Set.class);
|
||||
|
||||
KinesisShardOffset testOffset1 = KinesisShardOffset.latest(STREAM1, "1");
|
||||
KinesisShardOffset testOffset2 = KinesisShardOffset.latest(STREAM1, "2");
|
||||
@@ -119,8 +117,8 @@ public class KinesisMessageDrivenChannelAdapterTests {
|
||||
assertThat(shardOffsets).doesNotContain(KinesisShardOffset.latest(STREAM1, "3"));
|
||||
}
|
||||
|
||||
Map<KinesisShardOffset, ?> shardConsumers =
|
||||
TestUtils.getPropertyValue(this.kinesisMessageDrivenChannelAdapter, "shardConsumers", Map.class);
|
||||
Map<KinesisShardOffset, ?> shardConsumers = TestUtils.getPropertyValue(this.kinesisMessageDrivenChannelAdapter,
|
||||
"shardConsumers", Map.class);
|
||||
|
||||
await().untilAsserted(() -> assertThat(shardConsumers.keySet()).contains(testOffset1, testOffset2));
|
||||
|
||||
@@ -154,14 +152,13 @@ public class KinesisMessageDrivenChannelAdapterTests {
|
||||
|
||||
this.kinesisMessageDrivenChannelAdapter.stop();
|
||||
|
||||
Map<?, ?> forLocking =
|
||||
TestUtils.getPropertyValue(this.kinesisMessageDrivenChannelAdapter,
|
||||
"shardConsumerManager.locks", Map.class);
|
||||
Map<?, ?> forLocking = TestUtils.getPropertyValue(this.kinesisMessageDrivenChannelAdapter,
|
||||
"shardConsumerManager.locks", Map.class);
|
||||
|
||||
await().untilAsserted(() -> assertThat(forLocking).hasSize(0));
|
||||
|
||||
final List consumerInvokers =
|
||||
TestUtils.getPropertyValue(this.kinesisMessageDrivenChannelAdapter, "consumerInvokers", List.class);
|
||||
final List consumerInvokers = TestUtils.getPropertyValue(this.kinesisMessageDrivenChannelAdapter,
|
||||
"consumerInvokers", List.class);
|
||||
await().untilAsserted(() -> assertThat(consumerInvokers).hasSize(0));
|
||||
|
||||
this.kinesisMessageDrivenChannelAdapter.setListenerMode(ListenerMode.batch);
|
||||
@@ -186,8 +183,9 @@ public class KinesisMessageDrivenChannelAdapterTests {
|
||||
assertThat(sequenceNumberHeader).isInstanceOf(List.class);
|
||||
assertThat((List<String>) sequenceNumberHeader).contains("2");
|
||||
|
||||
await().untilAsserted(() ->
|
||||
assertThat(this.checkpointStore.get("SpringIntegration" + ":" + STREAM1 + ":" + "1")).isEqualTo("2"));
|
||||
await().untilAsserted(
|
||||
() -> assertThat(this.checkpointStore.get("SpringIntegration" + ":" + STREAM1 + ":" + "1"))
|
||||
.isEqualTo("2"));
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(this.kinesisMessageDrivenChannelAdapter, "consumerInvokers", List.class))
|
||||
.hasSize(2);
|
||||
@@ -195,7 +193,6 @@ public class KinesisMessageDrivenChannelAdapterTests {
|
||||
this.kinesisMessageDrivenChannelAdapter.stop();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void testReshadring() throws InterruptedException {
|
||||
@@ -225,44 +222,29 @@ public class KinesisMessageDrivenChannelAdapterTests {
|
||||
public AmazonKinesis amazonKinesis() {
|
||||
AmazonKinesis amazonKinesis = mock(AmazonKinesis.class);
|
||||
|
||||
given(amazonKinesis.describeStream(new DescribeStreamRequest().withStreamName(STREAM1)))
|
||||
.willReturn(new DescribeStreamResult()
|
||||
.withStreamDescription(new StreamDescription()
|
||||
.withStreamName(STREAM1)
|
||||
.withStreamStatus(StreamStatus.UPDATING)),
|
||||
new DescribeStreamResult()
|
||||
.withStreamDescription(new StreamDescription()
|
||||
.withStreamName(STREAM1)
|
||||
.withStreamStatus(StreamStatus.ACTIVE)
|
||||
.withHasMoreShards(false)
|
||||
.withShards(new Shard()
|
||||
.withShardId("1")
|
||||
.withSequenceNumberRange(new SequenceNumberRange()),
|
||||
new Shard()
|
||||
.withShardId("2")
|
||||
.withSequenceNumberRange(new SequenceNumberRange()),
|
||||
new Shard()
|
||||
.withShardId("3")
|
||||
.withSequenceNumberRange(new SequenceNumberRange()
|
||||
.withEndingSequenceNumber("1")))));
|
||||
given(amazonKinesis.describeStream(new DescribeStreamRequest().withStreamName(STREAM1))).willReturn(
|
||||
new DescribeStreamResult().withStreamDescription(
|
||||
new StreamDescription().withStreamName(STREAM1).withStreamStatus(StreamStatus.UPDATING)),
|
||||
new DescribeStreamResult().withStreamDescription(new StreamDescription().withStreamName(STREAM1)
|
||||
.withStreamStatus(StreamStatus.ACTIVE).withHasMoreShards(false)
|
||||
.withShards(new Shard().withShardId("1").withSequenceNumberRange(new SequenceNumberRange()),
|
||||
new Shard().withShardId("2").withSequenceNumberRange(new SequenceNumberRange()),
|
||||
new Shard().withShardId("3").withSequenceNumberRange(
|
||||
new SequenceNumberRange().withEndingSequenceNumber("1")))));
|
||||
|
||||
String shard1Iterator1 = "shard1Iterator1";
|
||||
String shard1Iterator2 = "shard1Iterator2";
|
||||
|
||||
given(amazonKinesis.getShardIterator(KinesisShardOffset.latest(STREAM1, "1").toShardIteratorRequest()))
|
||||
.willReturn(
|
||||
new GetShardIteratorResult().withShardIterator(shard1Iterator1),
|
||||
.willReturn(new GetShardIteratorResult().withShardIterator(shard1Iterator1),
|
||||
new GetShardIteratorResult().withShardIterator(shard1Iterator2));
|
||||
|
||||
String shard2Iterator1 = "shard2Iterator1";
|
||||
|
||||
given(amazonKinesis.getShardIterator(KinesisShardOffset.latest(STREAM1, "2").toShardIteratorRequest()))
|
||||
.willReturn(new GetShardIteratorResult()
|
||||
.withShardIterator(shard2Iterator1));
|
||||
.willReturn(new GetShardIteratorResult().withShardIterator(shard2Iterator1));
|
||||
|
||||
given(amazonKinesis.getRecords(new GetRecordsRequest()
|
||||
.withShardIterator(shard1Iterator1)
|
||||
.withLimit(25)))
|
||||
given(amazonKinesis.getRecords(new GetRecordsRequest().withShardIterator(shard1Iterator1).withLimit(25)))
|
||||
.willThrow(new ProvisionedThroughputExceededException("Iterator throttled"))
|
||||
.willThrow(new ExpiredIteratorException("Iterator expired"));
|
||||
|
||||
@@ -270,47 +252,28 @@ public class KinesisMessageDrivenChannelAdapterTests {
|
||||
|
||||
String shard1Iterator3 = "shard1Iterator3";
|
||||
|
||||
given(amazonKinesis.getRecords(new GetRecordsRequest()
|
||||
.withShardIterator(shard1Iterator2)
|
||||
.withLimit(25)))
|
||||
.willReturn(new GetRecordsResult()
|
||||
.withNextShardIterator(shard1Iterator3)
|
||||
.withRecords(new Record()
|
||||
.withPartitionKey("partition1")
|
||||
.withSequenceNumber("1")
|
||||
.withData(ByteBuffer.wrap(serializingConverter.convert("foo"))),
|
||||
new Record()
|
||||
.withPartitionKey("partition1")
|
||||
.withSequenceNumber("2")
|
||||
.withData(ByteBuffer.wrap(serializingConverter.convert("bar")))));
|
||||
given(amazonKinesis.getRecords(new GetRecordsRequest().withShardIterator(shard1Iterator2).withLimit(25)))
|
||||
.willReturn(new GetRecordsResult().withNextShardIterator(shard1Iterator3).withRecords(
|
||||
new Record().withPartitionKey("partition1").withSequenceNumber("1")
|
||||
.withData(ByteBuffer.wrap(serializingConverter.convert("foo"))),
|
||||
new Record().withPartitionKey("partition1").withSequenceNumber("2")
|
||||
.withData(ByteBuffer.wrap(serializingConverter.convert("bar")))));
|
||||
|
||||
given(amazonKinesis.getRecords(new GetRecordsRequest()
|
||||
.withShardIterator(shard2Iterator1)
|
||||
.withLimit(25)))
|
||||
.willReturn(new GetRecordsResult()
|
||||
.withNextShardIterator(shard2Iterator1));
|
||||
given(amazonKinesis.getRecords(new GetRecordsRequest().withShardIterator(shard2Iterator1).withLimit(25)))
|
||||
.willReturn(new GetRecordsResult().withNextShardIterator(shard2Iterator1));
|
||||
|
||||
given(amazonKinesis.getRecords(new GetRecordsRequest()
|
||||
.withShardIterator(shard1Iterator3)
|
||||
.withLimit(25)))
|
||||
.willReturn(new GetRecordsResult()
|
||||
.withNextShardIterator(shard1Iterator3));
|
||||
given(amazonKinesis.getRecords(new GetRecordsRequest().withShardIterator(shard1Iterator3).withLimit(25)))
|
||||
.willReturn(new GetRecordsResult().withNextShardIterator(shard1Iterator3));
|
||||
|
||||
String shard1Iterator4 = "shard1Iterator4";
|
||||
|
||||
given(amazonKinesis.getShardIterator(KinesisShardOffset.afterSequenceNumber(STREAM1, "1", "1")
|
||||
.toShardIteratorRequest()))
|
||||
.willReturn(new GetShardIteratorResult()
|
||||
.withShardIterator(shard1Iterator4));
|
||||
given(amazonKinesis.getShardIterator(
|
||||
KinesisShardOffset.afterSequenceNumber(STREAM1, "1", "1").toShardIteratorRequest()))
|
||||
.willReturn(new GetShardIteratorResult().withShardIterator(shard1Iterator4));
|
||||
|
||||
given(amazonKinesis.getRecords(new GetRecordsRequest()
|
||||
.withShardIterator(shard1Iterator4)
|
||||
.withLimit(25)))
|
||||
.willReturn(new GetRecordsResult()
|
||||
.withNextShardIterator(shard1Iterator3)
|
||||
.withRecords(new Record()
|
||||
.withPartitionKey("partition1")
|
||||
.withSequenceNumber("2")
|
||||
given(amazonKinesis.getRecords(new GetRecordsRequest().withShardIterator(shard1Iterator4).withLimit(25)))
|
||||
.willReturn(new GetRecordsResult().withNextShardIterator(shard1Iterator3)
|
||||
.withRecords(new Record().withPartitionKey("partition1").withSequenceNumber("2")
|
||||
.withData(ByteBuffer.wrap(serializingConverter.convert("bar")))));
|
||||
|
||||
return amazonKinesis;
|
||||
@@ -326,8 +289,8 @@ public class KinesisMessageDrivenChannelAdapterTests {
|
||||
|
||||
@Bean
|
||||
public KinesisMessageDrivenChannelAdapter kinesisMessageDrivenChannelAdapter() {
|
||||
KinesisMessageDrivenChannelAdapter adapter =
|
||||
new KinesisMessageDrivenChannelAdapter(amazonKinesis(), STREAM1);
|
||||
KinesisMessageDrivenChannelAdapter adapter = new KinesisMessageDrivenChannelAdapter(amazonKinesis(),
|
||||
STREAM1);
|
||||
adapter.setAutoStartup(false);
|
||||
adapter.setOutputChannel(kinesisChannel());
|
||||
adapter.setCheckpointStore(checkpointStore());
|
||||
@@ -356,34 +319,21 @@ public class KinesisMessageDrivenChannelAdapterTests {
|
||||
AmazonKinesis amazonKinesis = mock(AmazonKinesis.class);
|
||||
|
||||
given(amazonKinesis.describeStream(new DescribeStreamRequest().withStreamName(STREAM_FOR_RESHARDING)))
|
||||
.willReturn(
|
||||
new DescribeStreamResult()
|
||||
.withStreamDescription(new StreamDescription()
|
||||
.withStreamName(STREAM_FOR_RESHARDING)
|
||||
.withStreamStatus(StreamStatus.ACTIVE)
|
||||
.withHasMoreShards(false)
|
||||
.withShards(new Shard()
|
||||
.withShardId("closedShard")
|
||||
.withSequenceNumberRange(new SequenceNumberRange()
|
||||
.withEndingSequenceNumber("1")))));
|
||||
.willReturn(new DescribeStreamResult()
|
||||
.withStreamDescription(new StreamDescription().withStreamName(STREAM_FOR_RESHARDING)
|
||||
.withStreamStatus(StreamStatus.ACTIVE).withHasMoreShards(false)
|
||||
.withShards(new Shard().withShardId("closedShard").withSequenceNumberRange(
|
||||
new SequenceNumberRange().withEndingSequenceNumber("1")))));
|
||||
|
||||
String shard1Iterator1 = "shard1Iterator1";
|
||||
|
||||
given(amazonKinesis.getShardIterator(KinesisShardOffset.latest(STREAM_FOR_RESHARDING, "closedShard")
|
||||
.toShardIteratorRequest()))
|
||||
.willReturn(
|
||||
new GetShardIteratorResult()
|
||||
.withShardIterator(shard1Iterator1));
|
||||
given(amazonKinesis.getShardIterator(
|
||||
KinesisShardOffset.latest(STREAM_FOR_RESHARDING, "closedShard").toShardIteratorRequest()))
|
||||
.willReturn(new GetShardIteratorResult().withShardIterator(shard1Iterator1));
|
||||
|
||||
|
||||
given(amazonKinesis.getRecords(new GetRecordsRequest()
|
||||
.withShardIterator(shard1Iterator1)
|
||||
.withLimit(25)))
|
||||
.willReturn(new GetRecordsResult()
|
||||
.withNextShardIterator(null)
|
||||
.withRecords(new Record()
|
||||
.withPartitionKey("partition1")
|
||||
.withSequenceNumber("1")
|
||||
given(amazonKinesis.getRecords(new GetRecordsRequest().withShardIterator(shard1Iterator1).withLimit(25)))
|
||||
.willReturn(new GetRecordsResult().withNextShardIterator(null)
|
||||
.withRecords(new Record().withPartitionKey("partition1").withSequenceNumber("1")
|
||||
.withData(ByteBuffer.wrap("foo".getBytes()))));
|
||||
|
||||
return amazonKinesis;
|
||||
@@ -391,8 +341,8 @@ public class KinesisMessageDrivenChannelAdapterTests {
|
||||
|
||||
@Bean
|
||||
public KinesisMessageDrivenChannelAdapter reshardingChannelAdapter() {
|
||||
KinesisMessageDrivenChannelAdapter adapter =
|
||||
new KinesisMessageDrivenChannelAdapter(amazonKinesisForResharding(), STREAM_FOR_RESHARDING);
|
||||
KinesisMessageDrivenChannelAdapter adapter = new KinesisMessageDrivenChannelAdapter(
|
||||
amazonKinesisForResharding(), STREAM_FOR_RESHARDING);
|
||||
adapter.setAutoStartup(false);
|
||||
adapter.setOutputChannel(kinesisChannel());
|
||||
adapter.setStartTimeout(10000);
|
||||
|
||||
@@ -178,8 +178,8 @@ public class S3InboundChannelAdapterTests {
|
||||
synchronizer.setPreserveTimestamp(true);
|
||||
synchronizer.setRemoteDirectory(S3_BUCKET);
|
||||
synchronizer.setFilter(new S3RegexPatternFileListFilter(".*\\.test$"));
|
||||
Expression expression =
|
||||
PARSER.parseExpression("(#this.contains('/') ? #this.substring(#this.lastIndexOf('/') + 1) : #this).toUpperCase() + '.a'");
|
||||
Expression expression = PARSER.parseExpression(
|
||||
"(#this.contains('/') ? #this.substring(#this.lastIndexOf('/') + 1) : #this).toUpperCase() + '.a'");
|
||||
synchronizer.setLocalFilenameGeneratorExpression(expression);
|
||||
return synchronizer;
|
||||
}
|
||||
@@ -187,8 +187,8 @@ public class S3InboundChannelAdapterTests {
|
||||
@Bean
|
||||
@InboundChannelAdapter(value = "s3FilesChannel", poller = @Poller(fixedDelay = "100"))
|
||||
public S3InboundFileSynchronizingMessageSource s3InboundFileSynchronizingMessageSource() {
|
||||
S3InboundFileSynchronizingMessageSource messageSource =
|
||||
new S3InboundFileSynchronizingMessageSource(s3InboundFileSynchronizer());
|
||||
S3InboundFileSynchronizingMessageSource messageSource = new S3InboundFileSynchronizingMessageSource(
|
||||
s3InboundFileSynchronizer());
|
||||
messageSource.setAutoCreateLocalDirectory(true);
|
||||
messageSource.setLocalDirectory(LOCAL_FOLDER);
|
||||
messageSource.setLocalFilter(new AcceptOnceFileListFilter<File>());
|
||||
|
||||
@@ -66,7 +66,6 @@ import com.amazonaws.services.s3.model.S3ObjectSummary;
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@@ -154,7 +153,6 @@ public class S3StreamingChannelAdapterTests {
|
||||
return amazonS3;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
@InboundChannelAdapter(value = "s3FilesChannel", poller = @Poller(fixedDelay = "100"))
|
||||
public S3StreamingMessageSource s3InboundStreamingMessageSource(AmazonS3 amazonS3) {
|
||||
@@ -163,8 +161,7 @@ public class S3StreamingChannelAdapterTests {
|
||||
S3StreamingMessageSource s3MessageSource = new S3StreamingMessageSource(s3FileTemplate,
|
||||
Comparator.comparing(S3ObjectSummary::getKey));
|
||||
s3MessageSource.setRemoteDirectory("/" + S3_BUCKET + "/subdir");
|
||||
s3MessageSource.setFilter(new S3PersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "streaming"
|
||||
));
|
||||
s3MessageSource.setFilter(new S3PersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "streaming"));
|
||||
|
||||
return s3MessageSource;
|
||||
}
|
||||
|
||||
@@ -89,9 +89,8 @@ public class SnsInboundChannelAdapterTests {
|
||||
|
||||
@Test
|
||||
public void testSubscriptionConfirmation() throws Exception {
|
||||
this.mockMvc.perform(
|
||||
post("/mySampleTopic")
|
||||
.header("x-amz-sns-message-type", "SubscriptionConfirmation")
|
||||
this.mockMvc
|
||||
.perform(post("/mySampleTopic").header("x-amz-sns-message-type", "SubscriptionConfirmation")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(StreamUtils.copyToByteArray(this.subscriptionConfirmation.getInputStream())))
|
||||
.andExpect(status().isNoContent());
|
||||
@@ -107,16 +106,14 @@ public class SnsInboundChannelAdapterTests {
|
||||
|
||||
notificationStatus.confirmSubscription();
|
||||
|
||||
verify(this.amazonSns)
|
||||
.confirmSubscription("arn:aws:sns:eu-west-1:111111111111:mySampleTopic", "111");
|
||||
verify(this.amazonSns).confirmSubscription("arn:aws:sns:eu-west-1:111111111111:mySampleTopic", "111");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testNotification() throws Exception {
|
||||
this.mockMvc.perform(
|
||||
post("/mySampleTopic")
|
||||
.header("x-amz-sns-message-type", "Notification")
|
||||
this.mockMvc
|
||||
.perform(post("/mySampleTopic").header("x-amz-sns-message-type", "Notification")
|
||||
.contentType(MediaType.TEXT_PLAIN)
|
||||
.content(StreamUtils.copyToByteArray(this.notificationMessage.getInputStream())))
|
||||
.andExpect(status().isNoContent());
|
||||
@@ -131,9 +128,8 @@ public class SnsInboundChannelAdapterTests {
|
||||
|
||||
@Test
|
||||
public void testUnsubscribe() throws Exception {
|
||||
this.mockMvc.perform(
|
||||
post("/mySampleTopic")
|
||||
.header("x-amz-sns-message-type", "UnsubscribeConfirmation")
|
||||
this.mockMvc
|
||||
.perform(post("/mySampleTopic").header("x-amz-sns-message-type", "UnsubscribeConfirmation")
|
||||
.contentType(MediaType.TEXT_PLAIN)
|
||||
.content(StreamUtils.copyToByteArray(this.unsubscribeConfirmation.getInputStream())))
|
||||
.andExpect(status().isNoContent());
|
||||
@@ -149,8 +145,7 @@ public class SnsInboundChannelAdapterTests {
|
||||
|
||||
notificationStatus.confirmSubscription();
|
||||
|
||||
verify(this.amazonSns)
|
||||
.confirmSubscription("arn:aws:sns:eu-west-1:111111111111:mySampleTopic", "233");
|
||||
verify(this.amazonSns).confirmSubscription("arn:aws:sns:eu-west-1:111111111111:mySampleTopic", "233");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
|
||||
@@ -73,9 +73,9 @@ public class SqsMessageDrivenChannelAdapterTests {
|
||||
|
||||
@Test
|
||||
public void testSqsMessageDrivenChannelAdapter() {
|
||||
assertThat(TestUtils.getPropertyValue(this.sqsMessageDrivenChannelAdapter,
|
||||
"listenerContainer.queueStopTimeout"))
|
||||
.isEqualTo(10000L);
|
||||
assertThat(
|
||||
TestUtils.getPropertyValue(this.sqsMessageDrivenChannelAdapter, "listenerContainer.queueStopTimeout"))
|
||||
.isEqualTo(10000L);
|
||||
org.springframework.messaging.Message<?> receive = this.inputChannel.receive(1000);
|
||||
assertThat(receive).isNotNull();
|
||||
assertThat((String) receive.getPayload()).isIn("messageContent", "messageContent2");
|
||||
@@ -89,7 +89,8 @@ public class SqsMessageDrivenChannelAdapterTests {
|
||||
this.controlBusInput.send(new GenericMessage<>("@sqsMessageDrivenChannelAdapter.stop('testQueue')"));
|
||||
}
|
||||
catch (Exception e) {
|
||||
// May fail with NPE. See https://github.com/spring-cloud/spring-cloud-aws/issues/232
|
||||
// May fail with NPE. See
|
||||
// https://github.com/spring-cloud/spring-cloud-aws/issues/232
|
||||
}
|
||||
this.controlBusInput.send(new GenericMessage<>("@sqsMessageDrivenChannelAdapter.isRunning('testQueue')"));
|
||||
|
||||
@@ -104,10 +105,10 @@ public class SqsMessageDrivenChannelAdapterTests {
|
||||
assertThat(receive).isNotNull();
|
||||
assertThat((Boolean) receive.getPayload()).isTrue();
|
||||
|
||||
assertThatThrownBy(() ->
|
||||
this.controlBusInput.send(new GenericMessage<>("@sqsMessageDrivenChannelAdapter.start('foo')")))
|
||||
.hasCauseExactlyInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("Queue with name 'foo' does not exist");
|
||||
assertThatThrownBy(
|
||||
() -> this.controlBusInput.send(new GenericMessage<>("@sqsMessageDrivenChannelAdapter.start('foo')")))
|
||||
.hasCauseExactlyInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("Queue with name 'foo' does not exist");
|
||||
|
||||
assertThat(this.sqsMessageDrivenChannelAdapter.getQueues()).isEqualTo(new String[] { "testQueue" });
|
||||
}
|
||||
@@ -122,16 +123,13 @@ public class SqsMessageDrivenChannelAdapterTests {
|
||||
given(sqs.getQueueUrl(new GetQueueUrlRequest("testQueue")))
|
||||
.willReturn(new GetQueueUrlResult().withQueueUrl("http://testQueue.amazonaws.com"));
|
||||
|
||||
|
||||
given(sqs.receiveMessage(new ReceiveMessageRequest("http://testQueue.amazonaws.com")
|
||||
.withAttributeNames("All")
|
||||
.withMessageAttributeNames("All")
|
||||
.withMaxNumberOfMessages(10)
|
||||
.withWaitTimeSeconds(20)))
|
||||
.willReturn(new ReceiveMessageResult()
|
||||
.withMessages(new Message().withBody("messageContent"),
|
||||
new Message().withBody("messageContent2")))
|
||||
.willReturn(new ReceiveMessageResult());
|
||||
given(sqs.receiveMessage(
|
||||
new ReceiveMessageRequest("http://testQueue.amazonaws.com").withAttributeNames("All")
|
||||
.withMessageAttributeNames("All").withMaxNumberOfMessages(10).withWaitTimeSeconds(20)))
|
||||
.willReturn(new ReceiveMessageResult().withMessages(
|
||||
new Message().withBody("messageContent"),
|
||||
new Message().withBody("messageContent2")))
|
||||
.willReturn(new ReceiveMessageResult());
|
||||
|
||||
given(sqs.getQueueAttributes(any(GetQueueAttributesRequest.class)))
|
||||
.willReturn(new GetQueueAttributesResult());
|
||||
|
||||
@@ -59,7 +59,6 @@ import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@@ -92,17 +91,12 @@ public class KinesisIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void testKinesisInboundOutbound() {
|
||||
this.kinesisSendChannel.send(
|
||||
MessageBuilder.withPayload("foo")
|
||||
.setHeader(AwsHeaders.STREAM, TEST_STREAM)
|
||||
.build());
|
||||
this.kinesisSendChannel
|
||||
.send(MessageBuilder.withPayload("foo").setHeader(AwsHeaders.STREAM, TEST_STREAM).build());
|
||||
|
||||
Date now = new Date();
|
||||
this.kinesisSendChannel.send(
|
||||
MessageBuilder.withPayload(now)
|
||||
.setHeader(AwsHeaders.STREAM, TEST_STREAM)
|
||||
.setHeader("foo", "BAR")
|
||||
.build());
|
||||
this.kinesisSendChannel.send(MessageBuilder.withPayload(now).setHeader(AwsHeaders.STREAM, TEST_STREAM)
|
||||
.setHeader("foo", "BAR").build());
|
||||
|
||||
Message<?> receive = this.kinesisReceiveChannel.receive(10_000);
|
||||
assertThat(receive).isNotNull();
|
||||
@@ -114,20 +108,16 @@ public class KinesisIntegrationTests {
|
||||
assertThat(errorMessage).isNotNull();
|
||||
assertThat(errorMessage.getHeaders().get(AwsHeaders.RAW_RECORD)).isNotNull();
|
||||
assertThat(((Exception) errorMessage.getPayload()).getMessage())
|
||||
.contains("Channel 'kinesisReceiveChannel' expected one of the following data types " +
|
||||
"[class java.util.Date], but received [class java.lang.String]");
|
||||
|
||||
.contains("Channel 'kinesisReceiveChannel' expected one of the following data types "
|
||||
+ "[class java.util.Date], but received [class java.lang.String]");
|
||||
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
this.kinesisSendChannel.send(
|
||||
MessageBuilder.withPayload(new Date())
|
||||
.setHeader(AwsHeaders.STREAM, TEST_STREAM)
|
||||
.build());
|
||||
this.kinesisSendChannel
|
||||
.send(MessageBuilder.withPayload(new Date()).setHeader(AwsHeaders.STREAM, TEST_STREAM).build());
|
||||
}
|
||||
|
||||
Set<String> receivedSequences = new HashSet<>();
|
||||
|
||||
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
receive = this.kinesisReceiveChannel.receive(10_000);
|
||||
assertThat(receive).isNotNull();
|
||||
@@ -165,8 +155,8 @@ public class KinesisIntegrationTests {
|
||||
}
|
||||
|
||||
private KinesisMessageDrivenChannelAdapter kinesisMessageDrivenChannelAdapter() {
|
||||
KinesisMessageDrivenChannelAdapter adapter =
|
||||
new KinesisMessageDrivenChannelAdapter(KINESIS_LOCAL_RUNNING.getKinesis(), TEST_STREAM);
|
||||
KinesisMessageDrivenChannelAdapter adapter = new KinesisMessageDrivenChannelAdapter(
|
||||
KINESIS_LOCAL_RUNNING.getKinesis(), TEST_STREAM);
|
||||
adapter.setOutputChannel(kinesisReceiveChannel());
|
||||
adapter.setErrorChannel(errorChannel());
|
||||
adapter.setErrorMessageStrategy(new KinesisMessageHeaderErrorMessageStrategy());
|
||||
|
||||
@@ -47,7 +47,6 @@ import com.amazonaws.waiters.WaiterParameters;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class DynamoDbLockRegistryLeaderInitiatorTests {
|
||||
@@ -64,13 +63,11 @@ public class DynamoDbLockRegistryLeaderInitiatorTests {
|
||||
try {
|
||||
dynamoDB.deleteTableAsync(DynamoDbLockRegistry.DEFAULT_TABLE_NAME);
|
||||
|
||||
Waiter<DescribeTableRequest> waiter =
|
||||
dynamoDB.waiters()
|
||||
.tableNotExists();
|
||||
Waiter<DescribeTableRequest> waiter = dynamoDB.waiters().tableNotExists();
|
||||
|
||||
waiter.run(new WaiterParameters<>(new DescribeTableRequest(DynamoDbLockRegistry.DEFAULT_TABLE_NAME))
|
||||
.withPollingStrategy(new PollingStrategy(new MaxAttemptsRetryStrategy(25),
|
||||
new FixedDelayStrategy(1))));
|
||||
.withPollingStrategy(
|
||||
new PollingStrategy(new MaxAttemptsRetryStrategy(25), new FixedDelayStrategy(1))));
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
||||
@@ -93,10 +90,8 @@ public class DynamoDbLockRegistryLeaderInitiatorTests {
|
||||
lockRepository.afterPropertiesSet();
|
||||
registries.add(lockRepository);
|
||||
|
||||
LockRegistryLeaderInitiator initiator =
|
||||
new LockRegistryLeaderInitiator(
|
||||
lockRepository,
|
||||
new DefaultCandidate("foo#" + i, "bar"));
|
||||
LockRegistryLeaderInitiator initiator = new LockRegistryLeaderInitiator(lockRepository,
|
||||
new DefaultCandidate("foo#" + i, "bar"));
|
||||
initiator.setExecutorService(
|
||||
Executors.newSingleThreadExecutor(new CustomizableThreadFactory("lock-leadership-" + i + "-")));
|
||||
initiator.setLeaderEventPublisher(countingPublisher);
|
||||
@@ -136,7 +131,8 @@ public class DynamoDbLockRegistryLeaderInitiatorTests {
|
||||
|
||||
initiator2.setLeaderEventPublisher(new CountingPublisher(granted2, revoked2, acquireLockFailed2));
|
||||
|
||||
// It's hard to see round-robin election, so let's make the yielding initiator to sleep long before restarting
|
||||
// It's hard to see round-robin election, so let's make the yielding initiator to
|
||||
// sleep long before restarting
|
||||
initiator1.setBusyWaitMillis(1000);
|
||||
|
||||
initiator1.getContext().yield();
|
||||
@@ -161,8 +157,8 @@ public class DynamoDbLockRegistryLeaderInitiatorTests {
|
||||
initiator2.stop();
|
||||
|
||||
CountDownLatch revoked11 = new CountDownLatch(1);
|
||||
initiator1.setLeaderEventPublisher(new CountingPublisher(new CountDownLatch(1), revoked11,
|
||||
new CountDownLatch(1)));
|
||||
initiator1.setLeaderEventPublisher(
|
||||
new CountingPublisher(new CountDownLatch(1), revoked11, new CountDownLatch(1)));
|
||||
|
||||
initiator1.getContext().yield();
|
||||
|
||||
|
||||
@@ -51,7 +51,6 @@ import com.amazonaws.waiters.WaiterParameters;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@@ -73,13 +72,11 @@ public class DynamoDbLockRegistryTests {
|
||||
try {
|
||||
dynamoDB.deleteTableAsync(DynamoDbLockRegistry.DEFAULT_TABLE_NAME);
|
||||
|
||||
Waiter<DescribeTableRequest> waiter =
|
||||
dynamoDB.waiters()
|
||||
.tableNotExists();
|
||||
Waiter<DescribeTableRequest> waiter = dynamoDB.waiters().tableNotExists();
|
||||
|
||||
waiter.run(new WaiterParameters<>(new DescribeTableRequest(DynamoDbLockRegistry.DEFAULT_TABLE_NAME))
|
||||
.withPollingStrategy(new PollingStrategy(new MaxAttemptsRetryStrategy(25),
|
||||
new FixedDelayStrategy(1))));
|
||||
.withPollingStrategy(
|
||||
new PollingStrategy(new MaxAttemptsRetryStrategy(25), new FixedDelayStrategy(1))));
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
||||
@@ -303,17 +300,16 @@ public class DynamoDbLockRegistryTests {
|
||||
lock.lockInterruptibly();
|
||||
final AtomicBoolean locked = new AtomicBoolean();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
Future<Object> result =
|
||||
this.taskExecutor.submit(() -> {
|
||||
try {
|
||||
lock.unlock();
|
||||
}
|
||||
catch (Exception e) {
|
||||
latch.countDown();
|
||||
return e;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
Future<Object> result = this.taskExecutor.submit(() -> {
|
||||
try {
|
||||
lock.unlock();
|
||||
}
|
||||
catch (Exception e) {
|
||||
latch.countDown();
|
||||
return e;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
assertThat(locked.get()).isFalse();
|
||||
@@ -324,7 +320,6 @@ public class DynamoDbLockRegistryTests {
|
||||
assertThat(((Exception) imse).getMessage()).contains("You do not own");
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
public static class ContextConfiguration {
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ import com.amazonaws.waiters.WaiterParameters;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 1.1
|
||||
*
|
||||
*/
|
||||
@@ -64,13 +63,10 @@ public class DynamoDbMetadataStoreTests {
|
||||
try {
|
||||
dynamoDB.deleteTableAsync(TEST_TABLE);
|
||||
|
||||
Waiter<DescribeTableRequest> waiter =
|
||||
dynamoDB.waiters()
|
||||
.tableNotExists();
|
||||
Waiter<DescribeTableRequest> waiter = dynamoDB.waiters().tableNotExists();
|
||||
|
||||
waiter.run(new WaiterParameters<>(new DescribeTableRequest(TEST_TABLE))
|
||||
.withPollingStrategy(new PollingStrategy(new MaxAttemptsRetryStrategy(25),
|
||||
new FixedDelayStrategy(1))));
|
||||
waiter.run(new WaiterParameters<>(new DescribeTableRequest(TEST_TABLE)).withPollingStrategy(
|
||||
new PollingStrategy(new MaxAttemptsRetryStrategy(25), new FixedDelayStrategy(1))));
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
||||
@@ -87,11 +83,8 @@ public class DynamoDbMetadataStoreTests {
|
||||
|
||||
createTableLatch.await();
|
||||
|
||||
DYNAMO_DB_RUNNING.getDynamoDB()
|
||||
.deleteItem(TEST_TABLE,
|
||||
Collections.singletonMap("KEY",
|
||||
new AttributeValue()
|
||||
.withS(this.file1)));
|
||||
DYNAMO_DB_RUNNING.getDynamoDB().deleteItem(TEST_TABLE,
|
||||
Collections.singletonMap("KEY", new AttributeValue().withS(this.file1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -58,7 +58,6 @@ import com.amazonaws.services.kinesis.model.PutRecordsRequestEntry;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@@ -100,18 +99,15 @@ public class KinesisMessageHandlerTests {
|
||||
assertThat(e.getMessage()).contains("'partitionKey' must not be null for sending a Kinesis record");
|
||||
}
|
||||
|
||||
message = MessageBuilder.fromMessage(message)
|
||||
.setHeader(AwsHeaders.PARTITION_KEY, "fooKey")
|
||||
.setHeader(AwsHeaders.SEQUENCE_NUMBER, "10")
|
||||
.setHeader("foo", "bar")
|
||||
.build();
|
||||
message = MessageBuilder.fromMessage(message).setHeader(AwsHeaders.PARTITION_KEY, "fooKey")
|
||||
.setHeader(AwsHeaders.SEQUENCE_NUMBER, "10").setHeader("foo", "bar").build();
|
||||
|
||||
this.kinesisSendChannel.send(message);
|
||||
|
||||
ArgumentCaptor<PutRecordRequest> putRecordRequestArgumentCaptor =
|
||||
ArgumentCaptor.forClass(PutRecordRequest.class);
|
||||
ArgumentCaptor<AsyncHandler<PutRecordRequest, PutRecordResult>> asyncHandlerArgumentCaptor =
|
||||
ArgumentCaptor.forClass((Class<AsyncHandler<PutRecordRequest, PutRecordResult>>) (Class<?>) AsyncHandler.class);
|
||||
ArgumentCaptor<PutRecordRequest> putRecordRequestArgumentCaptor = ArgumentCaptor
|
||||
.forClass(PutRecordRequest.class);
|
||||
ArgumentCaptor<AsyncHandler<PutRecordRequest, PutRecordResult>> asyncHandlerArgumentCaptor = ArgumentCaptor
|
||||
.forClass((Class<AsyncHandler<PutRecordRequest, PutRecordResult>>) (Class<?>) AsyncHandler.class);
|
||||
|
||||
verify(this.amazonKinesis).putRecordAsync(putRecordRequestArgumentCaptor.capture(),
|
||||
asyncHandlerArgumentCaptor.capture());
|
||||
@@ -136,28 +132,22 @@ public class KinesisMessageHandlerTests {
|
||||
|
||||
verify(this.asyncHandler).onError(eq(testingException));
|
||||
|
||||
message = new GenericMessage<>(new PutRecordsRequest()
|
||||
.withStreamName("myStream")
|
||||
.withRecords(new PutRecordsRequestEntry()
|
||||
.withData(ByteBuffer.wrap("test".getBytes()))
|
||||
.withPartitionKey("testKey")));
|
||||
message = new GenericMessage<>(new PutRecordsRequest().withStreamName("myStream").withRecords(
|
||||
new PutRecordsRequestEntry().withData(ByteBuffer.wrap("test".getBytes())).withPartitionKey("testKey")));
|
||||
|
||||
this.kinesisSendChannel.send(message);
|
||||
|
||||
ArgumentCaptor<PutRecordsRequest> putRecordsRequestArgumentCaptor =
|
||||
ArgumentCaptor.forClass(PutRecordsRequest.class);
|
||||
ArgumentCaptor<PutRecordsRequest> putRecordsRequestArgumentCaptor = ArgumentCaptor
|
||||
.forClass(PutRecordsRequest.class);
|
||||
verify(this.amazonKinesis).putRecordsAsync(putRecordsRequestArgumentCaptor.capture(), any(AsyncHandler.class));
|
||||
|
||||
PutRecordsRequest putRecordsRequest = putRecordsRequestArgumentCaptor.getValue();
|
||||
|
||||
assertThat(putRecordsRequest.getStreamName()).isEqualTo("myStream");
|
||||
assertThat(putRecordsRequest.getRecords())
|
||||
.containsExactlyInAnyOrder(new PutRecordsRequestEntry()
|
||||
.withData(ByteBuffer.wrap("test".getBytes()))
|
||||
.withPartitionKey("testKey"));
|
||||
assertThat(putRecordsRequest.getRecords()).containsExactlyInAnyOrder(
|
||||
new PutRecordsRequestEntry().withData(ByteBuffer.wrap("test".getBytes())).withPartitionKey("testKey"));
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableIntegration
|
||||
public static class ContextConfiguration {
|
||||
|
||||
@@ -57,7 +57,6 @@ import com.amazonaws.services.kinesis.model.PutRecordsResult;
|
||||
|
||||
/**
|
||||
* @author Jacob Severson
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@@ -99,10 +98,8 @@ public class KinesisProducingMessageHandlerTests {
|
||||
assertThat(e.getMessage()).contains("'partitionKey' must not be null for sending a Kinesis record");
|
||||
}
|
||||
|
||||
message = MessageBuilder.fromMessage(message)
|
||||
.setHeader(AwsHeaders.PARTITION_KEY, "fooKey")
|
||||
.setHeader(AwsHeaders.SEQUENCE_NUMBER, "10")
|
||||
.build();
|
||||
message = MessageBuilder.fromMessage(message).setHeader(AwsHeaders.PARTITION_KEY, "fooKey")
|
||||
.setHeader(AwsHeaders.SEQUENCE_NUMBER, "10").build();
|
||||
|
||||
this.kinesisSendChannel.send(message);
|
||||
|
||||
@@ -111,10 +108,8 @@ public class KinesisProducingMessageHandlerTests {
|
||||
assertThat(success.getHeaders().get(AwsHeaders.SEQUENCE_NUMBER)).isEqualTo("10");
|
||||
assertThat(success.getPayload()).isEqualTo("message");
|
||||
|
||||
message = MessageBuilder.fromMessage(message)
|
||||
.setHeader(AwsHeaders.PARTITION_KEY, "fooKey")
|
||||
.setHeader(AwsHeaders.SEQUENCE_NUMBER, "10")
|
||||
.build();
|
||||
message = MessageBuilder.fromMessage(message).setHeader(AwsHeaders.PARTITION_KEY, "fooKey")
|
||||
.setHeader(AwsHeaders.SEQUENCE_NUMBER, "10").build();
|
||||
|
||||
this.kinesisSendChannel.send(message);
|
||||
|
||||
@@ -125,28 +120,20 @@ public class KinesisProducingMessageHandlerTests {
|
||||
assertThat(((PutRecordRequest) putRecordFailure.getRequest()).getPartitionKey()).isEqualTo("fooKey");
|
||||
assertThat(((PutRecordRequest) putRecordFailure.getRequest()).getSequenceNumberForOrdering()).isEqualTo("10");
|
||||
assertThat(((PutRecordRequest) putRecordFailure.getRequest()).getExplicitHashKey()).isNull();
|
||||
assertThat(((PutRecordRequest) putRecordFailure.getRequest())
|
||||
.getData()).isEqualTo(ByteBuffer.wrap("message".getBytes()));
|
||||
assertThat(((PutRecordRequest) putRecordFailure.getRequest()).getData())
|
||||
.isEqualTo(ByteBuffer.wrap("message".getBytes()));
|
||||
|
||||
message = new GenericMessage<>(new PutRecordsRequest()
|
||||
.withStreamName("myStream")
|
||||
.withRecords(new PutRecordsRequestEntry()
|
||||
.withData(ByteBuffer.wrap("test".getBytes()))
|
||||
.withPartitionKey("testKey")));
|
||||
message = new GenericMessage<>(new PutRecordsRequest().withStreamName("myStream").withRecords(
|
||||
new PutRecordsRequestEntry().withData(ByteBuffer.wrap("test".getBytes())).withPartitionKey("testKey")));
|
||||
|
||||
this.kinesisSendChannel.send(message);
|
||||
|
||||
success = this.successChannel.receive(10000);
|
||||
assertThat(((PutRecordsRequest) success.getPayload()).getRecords())
|
||||
.containsExactlyInAnyOrder(new PutRecordsRequestEntry()
|
||||
.withData(ByteBuffer.wrap("test".getBytes()))
|
||||
.withPartitionKey("testKey"));
|
||||
assertThat(((PutRecordsRequest) success.getPayload()).getRecords()).containsExactlyInAnyOrder(
|
||||
new PutRecordsRequestEntry().withData(ByteBuffer.wrap("test".getBytes())).withPartitionKey("testKey"));
|
||||
|
||||
message = new GenericMessage<>(new PutRecordsRequest()
|
||||
.withStreamName("myStream")
|
||||
.withRecords(new PutRecordsRequestEntry()
|
||||
.withData(ByteBuffer.wrap("test".getBytes()))
|
||||
.withPartitionKey("testKey")));
|
||||
message = new GenericMessage<>(new PutRecordsRequest().withStreamName("myStream").withRecords(
|
||||
new PutRecordsRequestEntry().withData(ByteBuffer.wrap("test".getBytes())).withPartitionKey("testKey")));
|
||||
|
||||
this.kinesisSendChannel.send(message);
|
||||
|
||||
@@ -154,10 +141,8 @@ public class KinesisProducingMessageHandlerTests {
|
||||
AwsRequestFailureException putRecordsFailure = (AwsRequestFailureException) failed.getPayload();
|
||||
assertThat(putRecordsFailure.getCause().getMessage()).isEqualTo("putRecordsRequestEx");
|
||||
assertThat(((PutRecordsRequest) putRecordsFailure.getRequest()).getStreamName()).isEqualTo("myStream");
|
||||
assertThat(((PutRecordsRequest) putRecordsFailure.getRequest()).getRecords())
|
||||
.containsExactlyInAnyOrder(new PutRecordsRequestEntry()
|
||||
.withData(ByteBuffer.wrap("test".getBytes()))
|
||||
.withPartitionKey("testKey"));
|
||||
assertThat(((PutRecordsRequest) putRecordsFailure.getRequest()).getRecords()).containsExactlyInAnyOrder(
|
||||
new PutRecordsRequestEntry().withData(ByteBuffer.wrap("test".getBytes())).withPartitionKey("testKey"));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -169,30 +154,25 @@ public class KinesisProducingMessageHandlerTests {
|
||||
public AmazonKinesisAsync amazonKinesis() {
|
||||
AmazonKinesisAsync mock = mock(AmazonKinesisAsync.class);
|
||||
|
||||
given(mock.putRecordAsync(any(PutRecordRequest.class), any(AsyncHandler.class)))
|
||||
.willAnswer(invocation -> {
|
||||
PutRecordRequest request = invocation.getArgument(0);
|
||||
AsyncHandler<PutRecordRequest, PutRecordResult> handler = invocation.getArgument(1);
|
||||
PutRecordResult result = new PutRecordResult()
|
||||
.withSequenceNumber(request.getSequenceNumberForOrdering())
|
||||
.withShardId("shardId-1");
|
||||
handler.onSuccess(new PutRecordRequest(), result);
|
||||
return mock(Future.class);
|
||||
})
|
||||
.willAnswer(invocation -> {
|
||||
AsyncHandler<?, ?> handler = invocation.getArgument(1);
|
||||
handler.onError(new RuntimeException("putRecordRequestEx"));
|
||||
return mock(Future.class);
|
||||
});
|
||||
|
||||
given(mock.putRecordAsync(any(PutRecordRequest.class), any(AsyncHandler.class))).willAnswer(invocation -> {
|
||||
PutRecordRequest request = invocation.getArgument(0);
|
||||
AsyncHandler<PutRecordRequest, PutRecordResult> handler = invocation.getArgument(1);
|
||||
PutRecordResult result = new PutRecordResult()
|
||||
.withSequenceNumber(request.getSequenceNumberForOrdering()).withShardId("shardId-1");
|
||||
handler.onSuccess(new PutRecordRequest(), result);
|
||||
return mock(Future.class);
|
||||
}).willAnswer(invocation -> {
|
||||
AsyncHandler<?, ?> handler = invocation.getArgument(1);
|
||||
handler.onError(new RuntimeException("putRecordRequestEx"));
|
||||
return mock(Future.class);
|
||||
});
|
||||
|
||||
given(mock.putRecordsAsync(any(PutRecordsRequest.class), any(AsyncHandler.class)))
|
||||
.willAnswer(invocation -> {
|
||||
AsyncHandler<PutRecordsRequest, PutRecordsResult> handler = invocation.getArgument(1);
|
||||
handler.onSuccess(new PutRecordsRequest(), new PutRecordsResult());
|
||||
return mock(Future.class);
|
||||
})
|
||||
.willAnswer(invocation -> {
|
||||
}).willAnswer(invocation -> {
|
||||
AsyncHandler<?, ?> handler = invocation.getArgument(1);
|
||||
handler.onError(new RuntimeException("putRecordsRequestEx"));
|
||||
return mock(Future.class);
|
||||
|
||||
@@ -147,19 +147,16 @@ public class S3MessageHandlerTests {
|
||||
@Qualifier("s3MessageHandler")
|
||||
private S3MessageHandler s3MessageHandler;
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testUploadFile() throws IOException, InterruptedException {
|
||||
File file = this.temporaryFolder.newFile("foo.mp3");
|
||||
Message<?> message = MessageBuilder.withPayload(file)
|
||||
.setHeader("s3Command", S3MessageHandler.Command.UPLOAD.name())
|
||||
.build();
|
||||
.setHeader("s3Command", S3MessageHandler.Command.UPLOAD.name()).build();
|
||||
|
||||
this.s3SendChannel.send(message);
|
||||
|
||||
ArgumentCaptor<PutObjectRequest> putObjectRequestArgumentCaptor =
|
||||
ArgumentCaptor.forClass(PutObjectRequest.class);
|
||||
ArgumentCaptor<PutObjectRequest> putObjectRequestArgumentCaptor = ArgumentCaptor
|
||||
.forClass(PutObjectRequest.class);
|
||||
verify(this.amazonS3, atLeastOnce()).putObject(putObjectRequestArgumentCaptor.capture());
|
||||
|
||||
PutObjectRequest putObjectRequest = putObjectRequestArgumentCaptor.getValue();
|
||||
@@ -179,8 +176,8 @@ public class S3MessageHandlerTests {
|
||||
assertThat(this.transferCompletedLatch.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
assertThat(this.aclLatch.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
|
||||
ArgumentCaptor<SetObjectAclRequest> setObjectAclRequestArgumentCaptor =
|
||||
ArgumentCaptor.forClass(SetObjectAclRequest.class);
|
||||
ArgumentCaptor<SetObjectAclRequest> setObjectAclRequestArgumentCaptor = ArgumentCaptor
|
||||
.forClass(SetObjectAclRequest.class);
|
||||
verify(this.amazonS3).setObjectAcl(setObjectAclRequestArgumentCaptor.capture());
|
||||
|
||||
SetObjectAclRequest setObjectAclRequest = setObjectAclRequestArgumentCaptor.getValue();
|
||||
@@ -200,9 +197,7 @@ public class S3MessageHandlerTests {
|
||||
|
||||
InputStream payload = new StringInputStream("a");
|
||||
Message<?> message = MessageBuilder.withPayload(payload)
|
||||
.setHeader("s3Command", S3MessageHandler.Command.UPLOAD.name())
|
||||
.setHeader("key", "myStream")
|
||||
.build();
|
||||
.setHeader("s3Command", S3MessageHandler.Command.UPLOAD.name()).setHeader("key", "myStream").build();
|
||||
|
||||
assertThatThrownBy(() -> this.s3SendChannel.send(message))
|
||||
.hasCauseExactlyInstanceOf(IllegalStateException.class)
|
||||
@@ -212,8 +207,8 @@ public class S3MessageHandlerTests {
|
||||
|
||||
this.s3SendChannel.send(message);
|
||||
|
||||
ArgumentCaptor<PutObjectRequest> putObjectRequestArgumentCaptor =
|
||||
ArgumentCaptor.forClass(PutObjectRequest.class);
|
||||
ArgumentCaptor<PutObjectRequest> putObjectRequestArgumentCaptor = ArgumentCaptor
|
||||
.forClass(PutObjectRequest.class);
|
||||
verify(this.amazonS3, atLeastOnce()).putObject(putObjectRequestArgumentCaptor.capture());
|
||||
|
||||
PutObjectRequest putObjectRequest = putObjectRequestArgumentCaptor.getValue();
|
||||
@@ -234,9 +229,7 @@ public class S3MessageHandlerTests {
|
||||
File file = this.temporaryFolder.newFile("foo.mp3");
|
||||
FileInputStream fileInputStream = new FileInputStream(file);
|
||||
Message<?> message = MessageBuilder.withPayload(fileInputStream)
|
||||
.setHeader("s3Command", S3MessageHandler.Command.UPLOAD.name())
|
||||
.setHeader("key", "myStream")
|
||||
.build();
|
||||
.setHeader("s3Command", S3MessageHandler.Command.UPLOAD.name()).setHeader("key", "myStream").build();
|
||||
|
||||
try {
|
||||
this.s3SendChannel.send(message);
|
||||
@@ -252,14 +245,12 @@ public class S3MessageHandlerTests {
|
||||
public void testUploadByteArray() throws IOException {
|
||||
byte[] payload = "b".getBytes("UTF-8");
|
||||
Message<?> message = MessageBuilder.withPayload(payload)
|
||||
.setHeader("s3Command", S3MessageHandler.Command.UPLOAD.name())
|
||||
.setHeader("key", "myStream")
|
||||
.build();
|
||||
.setHeader("s3Command", S3MessageHandler.Command.UPLOAD.name()).setHeader("key", "myStream").build();
|
||||
|
||||
this.s3SendChannel.send(message);
|
||||
|
||||
ArgumentCaptor<PutObjectRequest> putObjectRequestArgumentCaptor =
|
||||
ArgumentCaptor.forClass(PutObjectRequest.class);
|
||||
ArgumentCaptor<PutObjectRequest> putObjectRequestArgumentCaptor = ArgumentCaptor
|
||||
.forClass(PutObjectRequest.class);
|
||||
verify(this.amazonS3, atLeastOnce()).putObject(putObjectRequestArgumentCaptor.capture());
|
||||
|
||||
PutObjectRequest putObjectRequest = putObjectRequestArgumentCaptor.getValue();
|
||||
@@ -279,8 +270,7 @@ public class S3MessageHandlerTests {
|
||||
public void testDownloadDirectory() throws IOException {
|
||||
File directoryForDownload = this.temporaryFolder.newFolder("myFolder");
|
||||
Message<?> message = MessageBuilder.withPayload(directoryForDownload)
|
||||
.setHeader("s3Command", S3MessageHandler.Command.DOWNLOAD)
|
||||
.build();
|
||||
.setHeader("s3Command", S3MessageHandler.Command.DOWNLOAD).build();
|
||||
|
||||
this.s3SendChannel.send(message);
|
||||
|
||||
@@ -404,21 +394,16 @@ public class S3MessageHandlerTests {
|
||||
else {
|
||||
return invocation.callRealMethod();
|
||||
}
|
||||
})
|
||||
.given(amazonS3)
|
||||
.getObject(any(GetObjectRequest.class));
|
||||
}).given(amazonS3).getObject(any(GetObjectRequest.class));
|
||||
|
||||
willAnswer(invocation -> {
|
||||
aclLatch().countDown();
|
||||
return null;
|
||||
})
|
||||
.given(amazonS3)
|
||||
.setObjectAcl(any(SetObjectAclRequest.class));
|
||||
}).given(amazonS3).setObjectAcl(any(SetObjectAclRequest.class));
|
||||
|
||||
return amazonS3;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public CountDownLatch aclLatch() {
|
||||
return new CountDownLatch(1);
|
||||
@@ -453,8 +438,8 @@ public class S3MessageHandlerTests {
|
||||
public MessageHandler s3MessageHandler() {
|
||||
S3MessageHandler s3MessageHandler = new S3MessageHandler(amazonS3(), S3_BUCKET_NAME);
|
||||
s3MessageHandler.setCommandExpression(PARSER.parseExpression("headers.s3Command"));
|
||||
Expression keyExpression =
|
||||
PARSER.parseExpression("payload instanceof T(java.io.File) ? payload.name : headers.key");
|
||||
Expression keyExpression = PARSER
|
||||
.parseExpression("payload instanceof T(java.io.File) ? payload.name : headers.key");
|
||||
s3MessageHandler.setKeyExpression(keyExpression);
|
||||
s3MessageHandler.setObjectAclExpression(new ValueExpression<>(CannedAccessControlList.PublicReadWrite));
|
||||
s3MessageHandler.setUploadMetadataProvider((metadata, message) -> {
|
||||
|
||||
@@ -43,9 +43,7 @@ public class SnsMessageBuilderTests {
|
||||
assertThat(message).isEqualTo("{\"default\":\"foo\"}");
|
||||
|
||||
try {
|
||||
SnsBodyBuilder.withDefault("foo")
|
||||
.forProtocols("{\"foo\" : \"bar\"}")
|
||||
.build();
|
||||
SnsBodyBuilder.withDefault("foo").forProtocols("{\"foo\" : \"bar\"}").build();
|
||||
fail("IllegalArgumentException expected");
|
||||
}
|
||||
catch (Exception e) {
|
||||
@@ -53,9 +51,7 @@ public class SnsMessageBuilderTests {
|
||||
assertThat(e.getMessage()).contains("protocols must not be empty.");
|
||||
}
|
||||
try {
|
||||
SnsBodyBuilder.withDefault("foo")
|
||||
.forProtocols("{\"foo\" : \"bar\"}", "")
|
||||
.build();
|
||||
SnsBodyBuilder.withDefault("foo").forProtocols("{\"foo\" : \"bar\"}", "").build();
|
||||
fail("IllegalArgumentException expected");
|
||||
}
|
||||
catch (Exception e) {
|
||||
@@ -63,9 +59,7 @@ public class SnsMessageBuilderTests {
|
||||
assertThat(e.getMessage()).contains("protocols must not contain empty elements.");
|
||||
}
|
||||
|
||||
message = SnsBodyBuilder.withDefault("foo")
|
||||
.forProtocols("{\"foo\" : \"bar\"}", "sms")
|
||||
.build();
|
||||
message = SnsBodyBuilder.withDefault("foo").forProtocols("{\"foo\" : \"bar\"}", "sms").build();
|
||||
|
||||
assertThat(message).isEqualTo("{\"default\":\"foo\",\"sms\":\"{\\\"foo\\\" : \\\"bar\\\"}\"}");
|
||||
}
|
||||
|
||||
@@ -77,14 +77,10 @@ public class SnsMessageHandlerTests {
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testSnsMessageHandler() {
|
||||
SnsBodyBuilder payload = SnsBodyBuilder.withDefault("foo")
|
||||
.forProtocols("{\"foo\" : \"bar\"}", "sms");
|
||||
SnsBodyBuilder payload = SnsBodyBuilder.withDefault("foo").forProtocols("{\"foo\" : \"bar\"}", "sms");
|
||||
|
||||
Message<?> message = MessageBuilder.withPayload(payload)
|
||||
.setHeader("topic", "topic")
|
||||
.setHeader("subject", "subject")
|
||||
.setHeader("foo", "bar")
|
||||
.build();
|
||||
Message<?> message = MessageBuilder.withPayload(payload).setHeader("topic", "topic")
|
||||
.setHeader("subject", "subject").setHeader("foo", "bar").build();
|
||||
|
||||
this.sendToSnsChannel.send(message);
|
||||
|
||||
@@ -128,9 +124,7 @@ public class SnsMessageHandlerTests {
|
||||
AsyncHandler<PublishRequest, PublishResult> asyncHandler = invocation.getArgument(1);
|
||||
asyncHandler.onSuccess(invocation.getArgument(0), publishResult);
|
||||
return new AsyncResult<>(publishResult);
|
||||
})
|
||||
.given(mock)
|
||||
.publishAsync(any(PublishRequest.class), any(AsyncHandler.class));
|
||||
}).given(mock).publishAsync(any(PublishRequest.class), any(AsyncHandler.class));
|
||||
|
||||
return mock;
|
||||
}
|
||||
|
||||
@@ -98,35 +98,31 @@ public class SqsMessageHandlerTests {
|
||||
|
||||
this.sqsMessageHandler.setQueue("foo");
|
||||
this.sqsSendChannel.send(message);
|
||||
ArgumentCaptor<SendMessageRequest> sendMessageRequestArgumentCaptor =
|
||||
ArgumentCaptor.forClass(SendMessageRequest.class);
|
||||
verify(this.amazonSqs)
|
||||
.sendMessageAsync(sendMessageRequestArgumentCaptor.capture(), any(AsyncHandler.class));
|
||||
assertThat(sendMessageRequestArgumentCaptor.getValue().getQueueUrl())
|
||||
.isEqualTo("https://queue-url.com/foo");
|
||||
ArgumentCaptor<SendMessageRequest> sendMessageRequestArgumentCaptor = ArgumentCaptor
|
||||
.forClass(SendMessageRequest.class);
|
||||
verify(this.amazonSqs).sendMessageAsync(sendMessageRequestArgumentCaptor.capture(), any(AsyncHandler.class));
|
||||
assertThat(sendMessageRequestArgumentCaptor.getValue().getQueueUrl()).isEqualTo("https://queue-url.com/foo");
|
||||
|
||||
message = MessageBuilder.withPayload("message").setHeader(AwsHeaders.QUEUE, "bar").build();
|
||||
this.sqsSendChannel.send(message);
|
||||
verify(this.amazonSqs, times(2))
|
||||
.sendMessageAsync(sendMessageRequestArgumentCaptor.capture(), any(AsyncHandler.class));
|
||||
verify(this.amazonSqs, times(2)).sendMessageAsync(sendMessageRequestArgumentCaptor.capture(),
|
||||
any(AsyncHandler.class));
|
||||
|
||||
assertThat(sendMessageRequestArgumentCaptor.getValue().getQueueUrl())
|
||||
.isEqualTo("https://queue-url.com/bar");
|
||||
assertThat(sendMessageRequestArgumentCaptor.getValue().getQueueUrl()).isEqualTo("https://queue-url.com/bar");
|
||||
|
||||
SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
|
||||
Expression expression = spelExpressionParser.parseExpression("headers.foo");
|
||||
this.sqsMessageHandler.setQueueExpression(expression);
|
||||
message = MessageBuilder.withPayload("message").setHeader("foo", "baz").build();
|
||||
this.sqsSendChannel.send(message);
|
||||
verify(this.amazonSqs, times(3))
|
||||
.sendMessageAsync(sendMessageRequestArgumentCaptor.capture(), any(AsyncHandler.class));
|
||||
verify(this.amazonSqs, times(3)).sendMessageAsync(sendMessageRequestArgumentCaptor.capture(),
|
||||
any(AsyncHandler.class));
|
||||
|
||||
SendMessageRequest sendMessageRequestArgumentCaptorValue = sendMessageRequestArgumentCaptor.getValue();
|
||||
assertThat(sendMessageRequestArgumentCaptorValue.getQueueUrl())
|
||||
.isEqualTo("https://queue-url.com/baz");
|
||||
assertThat(sendMessageRequestArgumentCaptorValue.getQueueUrl()).isEqualTo("https://queue-url.com/baz");
|
||||
|
||||
Map<String, MessageAttributeValue> messageAttributes =
|
||||
sendMessageRequestArgumentCaptorValue.getMessageAttributes();
|
||||
Map<String, MessageAttributeValue> messageAttributes = sendMessageRequestArgumentCaptorValue
|
||||
.getMessageAttributes();
|
||||
|
||||
assertThat(messageAttributes).doesNotContainKey(MessageHeaders.ID);
|
||||
assertThat(messageAttributes).doesNotContainKey(MessageHeaders.TIMESTAMP);
|
||||
@@ -142,17 +138,15 @@ public class SqsMessageHandlerTests {
|
||||
|
||||
this.sqsMessageHandlerWithAutoQueueCreate.setQueue("foo");
|
||||
this.sqsSendChannelWithAutoCreate.send(message);
|
||||
ArgumentCaptor<CreateQueueRequest> createQueueRequestArgumentCaptor =
|
||||
ArgumentCaptor.forClass(CreateQueueRequest.class);
|
||||
ArgumentCaptor<CreateQueueRequest> createQueueRequestArgumentCaptor = ArgumentCaptor
|
||||
.forClass(CreateQueueRequest.class);
|
||||
verify(this.amazonSqs).createQueue(createQueueRequestArgumentCaptor.capture());
|
||||
assertThat(createQueueRequestArgumentCaptor.getValue().getQueueName()).isEqualTo("foo");
|
||||
|
||||
ArgumentCaptor<SendMessageRequest> sendMessageRequestArgumentCaptor =
|
||||
ArgumentCaptor.forClass(SendMessageRequest.class);
|
||||
verify(this.amazonSqs)
|
||||
.sendMessageAsync(sendMessageRequestArgumentCaptor.capture(), any(AsyncHandler.class));
|
||||
assertThat(sendMessageRequestArgumentCaptor.getValue().getQueueUrl())
|
||||
.isEqualTo("https://queue-url.com/foo");
|
||||
ArgumentCaptor<SendMessageRequest> sendMessageRequestArgumentCaptor = ArgumentCaptor
|
||||
.forClass(SendMessageRequest.class);
|
||||
verify(this.amazonSqs).sendMessageAsync(sendMessageRequestArgumentCaptor.capture(), any(AsyncHandler.class));
|
||||
assertThat(sendMessageRequestArgumentCaptor.getValue().getQueueUrl()).isEqualTo("https://queue-url.com/foo");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -168,18 +162,14 @@ public class SqsMessageHandlerTests {
|
||||
GetQueueUrlResult queueUrl = new GetQueueUrlResult();
|
||||
queueUrl.setQueueUrl("https://queue-url.com/" + getQueueUrlRequest.getQueueName());
|
||||
return queueUrl;
|
||||
})
|
||||
.given(amazonSqs)
|
||||
.getQueueUrl(any(GetQueueUrlRequest.class));
|
||||
}).given(amazonSqs).getQueueUrl(any(GetQueueUrlRequest.class));
|
||||
|
||||
willAnswer(invocation -> {
|
||||
CreateQueueRequest createQueueRequest = (CreateQueueRequest) invocation.getArguments()[0];
|
||||
CreateQueueResult queueUrl = new CreateQueueResult();
|
||||
queueUrl.setQueueUrl("https://queue-url.com/" + createQueueRequest.getQueueName());
|
||||
return queueUrl;
|
||||
})
|
||||
.given(amazonSqs)
|
||||
.createQueue(any(CreateQueueRequest.class));
|
||||
}).given(amazonSqs).createQueue(any(CreateQueueRequest.class));
|
||||
|
||||
return amazonSqs;
|
||||
}
|
||||
@@ -193,7 +183,8 @@ public class SqsMessageHandlerTests {
|
||||
@Bean
|
||||
@ServiceActivator(inputChannel = "sqsSendChannelWithAutoCreate")
|
||||
public MessageHandler sqsMessageHandlerWithAutoQueueCreate() {
|
||||
DynamicQueueUrlDestinationResolver destinationResolver = new DynamicQueueUrlDestinationResolver(amazonSqs(), null);
|
||||
DynamicQueueUrlDestinationResolver destinationResolver = new DynamicQueueUrlDestinationResolver(amazonSqs(),
|
||||
null);
|
||||
destinationResolver.setAutoCreate(true);
|
||||
return new SqsMessageHandler(amazonSqs(), destinationResolver);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user