Start version 2.3

This commit is contained in:
Artem Bilan
2019-07-10 10:26:16 -04:00
parent 213d793369
commit c6013240ab
65 changed files with 937 additions and 1327 deletions

View File

@@ -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'

View File

@@ -1 +1 @@
version=2.2.1.BUILD-SNAPSHOT
version=2.3.0.BUILD-SNAPSHOT

View File

@@ -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());

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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)) {

View File

@@ -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) {

View File

@@ -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";
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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
+ '\'' + '}';
}
}

View File

@@ -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 + "]";
}
}

View File

@@ -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 + '}';
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}
}

View File

@@ -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());
}
}

View File

@@ -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 {

View File

@@ -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));

View File

@@ -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);
}

View File

@@ -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()));
}
}

View File

@@ -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";

View File

@@ -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 {

View File

@@ -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() + "]";
}
}

View File

@@ -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]);
}

View File

@@ -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
*/

View File

@@ -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) + "}";
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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
*/

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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");

View File

@@ -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();

View File

@@ -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");
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}

View File

@@ -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);

View File

@@ -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>());

View 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;
}

View File

@@ -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

View File

@@ -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());

View File

@@ -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());

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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) -> {

View File

@@ -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\\\"}\"}");
}

View File

@@ -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;
}

View File

@@ -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);
}