Prepare for 2.1.0.RELEASE

* Remove KCL/KPL components since they are not for the current version
* Make Jackson dependency as mandatory
This commit is contained in:
Artem Bilan
2019-03-01 15:52:03 -05:00
parent 82c0135669
commit f4d22d5637
3 changed files with 2 additions and 699 deletions

View File

@@ -36,9 +36,7 @@ ext {
servletApiVersion = '4.0.1'
log4jVersion = '2.11.2'
springCloudAwsVersion = '2.1.1.BUILD-SNAPSHOT'
springIntegrationVersion = '5.1.4.BUILD-SNAPSHOT'
kinesisClientVersion = '2.0.5'
kinesisProducerVersion = '0.12.11'
springIntegrationVersion = '5.1.3.RELEASE'
idPrefix = 'aws'
@@ -88,14 +86,12 @@ checkstyle {
dependencies {
compile 'org.springframework.integration:spring-integration-core'
compile 'org.springframework.cloud:spring-cloud-aws-core'
compile "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
compile('org.springframework.cloud:spring-cloud-aws-messaging', optional)
compile('org.springframework.integration:spring-integration-file', optional)
compile('org.springframework.integration:spring-integration-http', optional)
compile("software.amazon.kinesis:amazon-kinesis-client:$kinesisClientVersion", optional)
compile("com.amazonaws:amazon-kinesis-producer:$kinesisProducerVersion", optional)
compile('com.amazonaws:aws-java-sdk-kinesis', optional)
compile('com.amazonaws:aws-java-sdk-dynamodb', optional)
compile("com.amazonaws:dynamodb-lock-client:$dynamodbLockClientVersion", optional)

View File

@@ -1,407 +0,0 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.aws.inbound.kinesis;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Executor;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.core.AttributeAccessor;
import org.springframework.integration.aws.support.AwsHeaders;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.integration.mapping.InboundMessageMapper;
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
import org.springframework.integration.support.ErrorMessageStrategy;
import org.springframework.integration.support.ErrorMessageUtils;
import org.springframework.integration.support.management.IntegrationManagedResource;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.kinesis.KinesisAsyncClient;
import software.amazon.kinesis.common.ConfigsBuilder;
import software.amazon.kinesis.common.InitialPositionInStream;
import software.amazon.kinesis.common.InitialPositionInStreamExtended;
import software.amazon.kinesis.coordinator.Scheduler;
import software.amazon.kinesis.exceptions.InvalidStateException;
import software.amazon.kinesis.exceptions.ShutdownException;
import software.amazon.kinesis.exceptions.ThrottlingException;
import software.amazon.kinesis.lifecycle.events.InitializationInput;
import software.amazon.kinesis.lifecycle.events.LeaseLostInput;
import software.amazon.kinesis.lifecycle.events.ProcessRecordsInput;
import software.amazon.kinesis.lifecycle.events.ShardEndedInput;
import software.amazon.kinesis.lifecycle.events.ShutdownRequestedInput;
import software.amazon.kinesis.processor.RecordProcessorCheckpointer;
import software.amazon.kinesis.processor.ShardRecordProcessor;
import software.amazon.kinesis.processor.ShardRecordProcessorFactory;
import software.amazon.kinesis.retrieval.KinesisClientRecord;
/**
* The {@link MessageProducerSupport} implementation for receiving data from Amazon
* Kinesis stream(s) using AWS KCL.
*
* @author Hervé Fortin
*
* @since 2.1.0
*/
@ManagedResource
@IntegrationManagedResource
public class KclMessageDrivenChannelAdapter extends MessageProducerSupport implements DisposableBean {
private static final ThreadLocal<AttributeAccessor> attributesHolder = new ThreadLocal<>();
private final String stream;
private String consumerGroup = "SpringIntegration";
private InboundMessageMapper<byte[]> embeddedHeadersMapper;
private Scheduler scheduler;
private final Executor executor;
private final KinesisAsyncClient kinesisClient;
private final CloudWatchAsyncClient cloudWatchClient;
private final DynamoDbAsyncClient dynamoDBClient;
private InitialPositionInStreamExtended streamInitialSequence =
InitialPositionInStreamExtended.newInitialPosition(InitialPositionInStream.LATEST);
private int idleBetweenPolls;
private int consumerBackoff;
private long checkpointsInterval = 60_000L;
public KclMessageDrivenChannelAdapter(String streams, Executor executor) {
this(streams, executor, KinesisAsyncClient.builder().build(),
CloudWatchAsyncClient.builder().build(), DynamoDbAsyncClient.builder().build());
}
public KclMessageDrivenChannelAdapter(String streams, Executor executor, Region region) {
this(streams, executor, KinesisAsyncClient.builder().region(region).build(),
CloudWatchAsyncClient.builder().region(region).build(), DynamoDbAsyncClient.builder().region(region).build());
}
public KclMessageDrivenChannelAdapter(String stream, Executor executor,
KinesisAsyncClient kinesisClient, CloudWatchAsyncClient cloudWatchClient, DynamoDbAsyncClient dynamoDBClient) {
Assert.notNull(stream, "'stream' must not be null.");
Assert.notNull(executor, "'executor' must not be null.");
Assert.notNull(kinesisClient, "'kinesisClient' must not be null.");
Assert.notNull(cloudWatchClient, "'cloudWatchClient' must not be null.");
Assert.notNull(dynamoDBClient, "'dynamoDBClient' must not be null.");
this.stream = stream;
this.executor = executor;
this.kinesisClient = kinesisClient;
this.cloudWatchClient = cloudWatchClient;
this.dynamoDBClient = dynamoDBClient;
}
public void setConsumerGroup(String consumerGroup) {
Assert.hasText(consumerGroup, "'consumerGroup' must not be empty");
this.consumerGroup = consumerGroup;
}
/**
* Specify an {@link InboundMessageMapper} to extract message headers embedded
* into the record data.
*
* @param embeddedHeadersMapper the {@link InboundMessageMapper} to use.
* @since 2.0
*/
public void setEmbeddedHeadersMapper(InboundMessageMapper<byte[]> embeddedHeadersMapper) {
this.embeddedHeadersMapper = embeddedHeadersMapper;
}
@Override
protected void onInit() {
super.onInit();
String workerId = UUID.randomUUID().toString();
RecordProcessorFactory recordProcessorFactory = new RecordProcessorFactory();
ConfigsBuilder configsBuilder = new ConfigsBuilder(this.stream, this.consumerGroup,
this.kinesisClient, this.dynamoDBClient, this.cloudWatchClient, workerId, recordProcessorFactory);
configsBuilder.retrievalConfig().initialPositionInStreamExtended(this.streamInitialSequence);
configsBuilder.retrievalConfig().listShardsBackoffTimeInMillis(this.consumerBackoff);
configsBuilder.coordinatorConfig().parentShardPollIntervalMillis(this.idleBetweenPolls);
this.scheduler = new Scheduler(configsBuilder.checkpointConfig(),
configsBuilder.coordinatorConfig(),
configsBuilder.leaseManagementConfig(),
configsBuilder.lifecycleConfig(),
configsBuilder.metricsConfig(),
configsBuilder.processorConfig(),
configsBuilder.retrievalConfig());
}
@Override
protected void doStart() {
super.doStart();
this.executor.execute(this.scheduler);
}
/**
* Takes no action by default. Subclasses may override this if they need
* lifecycle-managed behavior.
*/
@Override
protected void doStop() {
super.doStop();
this.scheduler.shutdown();
}
@Override
protected AttributeAccessor getErrorMessageAttributes(org.springframework.messaging.Message<?> message) {
AttributeAccessor attributes = attributesHolder.get();
if (attributes == null) {
return super.getErrorMessageAttributes(message);
}
else {
return attributes;
}
}
public void setStreamInitialSequence(InitialPositionInStream streamInitialSequence) {
setStreamInitialSequenceExtended(InitialPositionInStreamExtended.newInitialPosition(streamInitialSequence));
}
public void setStreamInitialSequenceExtended(InitialPositionInStreamExtended streamInitialSequence) {
Assert.notNull(streamInitialSequence, "'streamInitialSequence' must not be null");
this.streamInitialSequence = streamInitialSequence;
}
public void setIdleBetweenPolls(int idleBetweenPolls) {
this.idleBetweenPolls = Math.max(250, idleBetweenPolls);
}
public void setConsumerBackoff(int consumerBackoff) {
this.consumerBackoff = Math.max(1000, consumerBackoff);
}
/**
* Sets the interval between 2 checkpoints.
*
* @param checkpointsInterval interval between 2 checkpoints (in milliseconds)
*/
public void setCheckpointsInterval(long checkpointsInterval) {
this.checkpointsInterval = checkpointsInterval;
}
@Override
public String toString() {
return "KclMessageDrivenChannelAdapter{consumerGroup='" + this.consumerGroup + '\'' + ", stream='" + this.stream + "'}";
}
private class RecordProcessorFactory implements ShardRecordProcessorFactory {
@Override
public ShardRecordProcessor shardRecordProcessor() {
return new RecordProcessor();
}
}
/**
* Processes records and checkpoints progress.
*/
private class RecordProcessor implements ShardRecordProcessor {
private String shardId;
private long nextCheckpointTimeInMillis;
/** {@inheritDoc} */
@Override
public void initialize(InitializationInput initializationInput) {
this.shardId = initializationInput.shardId();
if (logger.isInfoEnabled()) {
logger.info("Initializing record processor for shard: " + this.shardId);
}
}
/** {@inheritDoc} */
@Override
public void leaseLost(LeaseLostInput leaseLostInput) {
logger.info("Lost lease, so terminating.");
}
/** {@inheritDoc} */
@Override
public void shardEnded(ShardEndedInput shardEndedInput) {
try {
logger.info("Reached shard end checkpointing.");
shardEndedInput.checkpointer().checkpoint();
}
catch (ShutdownException | InvalidStateException e) {
logger.error("Exception while checkpointing at shard end. Giving up", e);
}
}
/** {@inheritDoc} */
@Override
public void shutdownRequested(ShutdownRequestedInput shutdownRequestedInput) {
try {
logger.info("Scheduler is shutting down, checkpointing.");
shutdownRequestedInput.checkpointer().checkpoint();
}
catch (ShutdownException | InvalidStateException e) {
logger.error("Exception while checkpointing at requested shutdown. Giving up", e);
}
}
/**
* Process records. Skip "poison pill" records.
*
* @param records Data records to be processed.
*/
private void processRecords(List<KinesisClientRecord> records) {
for (KinesisClientRecord record : records) {
try {
processSingleRecord(record);
}
catch (Throwable t) {
logger.warn("Caught throwable while processing record " + record, t);
}
finally {
attributesHolder.remove();
}
}
}
/**
* Process a single record.
*
* @param record The record to be processed.
*/
private void processSingleRecord(KinesisClientRecord record) {
// Convert AWS Record in Spring Message.
performSend(prepareMessageForRecord(record), record);
}
private void performSend(AbstractIntegrationMessageBuilder<?> messageBuilder, Object rawRecord) {
Message<?> messageToSend = messageBuilder.build();
setAttributesIfNecessary(rawRecord, messageToSend);
try {
sendMessage(messageToSend);
}
catch (Exception e) {
logger.error("Got an exception during sending a '" + messageToSend + "'" + "\nfor the '" + rawRecord
+ "'.\n" + "Consider to use 'errorChannel' flow for the compensation logic.", e);
}
}
/**
* 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.
*/
private void setAttributesIfNecessary(Object record, Message<?> message) {
if (getErrorChannel() != null) {
AttributeAccessor attributes = ErrorMessageUtils.getAttributeAccessor(message, null);
attributesHolder.set(attributes);
attributes.setAttribute(AwsHeaders.RAW_RECORD, record);
}
}
private AbstractIntegrationMessageBuilder<Object> prepareMessageForRecord(KinesisClientRecord record) {
ByteBuffer data = record.data();
byte[] dataArray = new byte[data.remaining()];
Object payload = dataArray;
data.get(dataArray);
Message<?> messageToUse = null;
if (KclMessageDrivenChannelAdapter.this.embeddedHeadersMapper != null) {
try {
messageToUse = KclMessageDrivenChannelAdapter.this.embeddedHeadersMapper.toMessage((byte[]) payload);
payload = messageToUse.getPayload();
}
catch (Exception e) {
logger.warn("Could not parse embedded headers. Remain payload untouched.", e);
}
}
AbstractIntegrationMessageBuilder<Object> messageBuilder = getMessageBuilderFactory().withPayload(payload)
.setHeader(AwsHeaders.RECEIVED_PARTITION_KEY, record.partitionKey())
.setHeader(AwsHeaders.RECEIVED_SEQUENCE_NUMBER, record.sequenceNumber())
.setHeader(AwsHeaders.RECEIVED_STREAM, KclMessageDrivenChannelAdapter.this.stream)
.setHeader(AwsHeaders.SHARD, this.shardId);
if (messageToUse != null) {
messageBuilder.copyHeadersIfAbsent(messageToUse.getHeaders());
}
return messageBuilder;
}
/**
* Checkpoint with retries.
*
* @param checkpointer checkpointer
*/
private void checkpoint(RecordProcessorCheckpointer checkpointer) {
if (logger.isInfoEnabled()) {
logger.info("Checkpointing shard " + shardId);
}
try {
checkpointer.checkpoint();
}
catch (ShutdownException se) {
// Ignore checkpoint if the processor instance has been shutdown (fail over).
logger.info("Caught shutdown exception, skipping checkpoint.", se);
}
catch (ThrottlingException e) {
if (logger.isInfoEnabled()) {
logger.info("Transient issue when checkpointing", e);
}
}
catch (InvalidStateException e) {
// 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);
}
}
@Override
public void processRecords(ProcessRecordsInput processRecordsInput) {
List<KinesisClientRecord> records = processRecordsInput.records();
if (logger.isDebugEnabled()) {
logger.debug("Processing " + records.size() + " records from " + this.shardId);
}
// Process records and perform all exception handling.
processRecords(records);
// Checkpoint once every checkpoint interval.
if (System.currentTimeMillis() > nextCheckpointTimeInMillis) {
checkpoint(processRecordsInput.checkpointer());
this.nextCheckpointTimeInMillis = System.currentTimeMillis() + checkpointsInterval;
}
}
}
}

View File

@@ -1,286 +0,0 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.aws.outbound;
import java.nio.ByteBuffer;
import java.util.concurrent.Future;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.aws.support.AwsHeaders;
import org.springframework.integration.aws.support.AwsRequestFailureException;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.integration.mapping.HeaderMapper;
import org.springframework.integration.mapping.OutboundMessageMapper;
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
import org.springframework.integration.support.MutableMessage;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.handlers.AsyncHandler;
import com.amazonaws.services.kinesis.AmazonKinesisAsync;
import com.amazonaws.services.kinesis.model.PutRecordRequest;
import com.amazonaws.services.kinesis.model.PutRecordResult;
import com.amazonaws.services.kinesis.model.PutRecordsRequest;
import com.amazonaws.services.kinesis.producer.KinesisProducer;
import com.amazonaws.services.kinesis.producer.UserRecord;
import com.amazonaws.services.kinesis.producer.UserRecordResult;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
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)}.
*
* @author Arnaud Lecollaire
*
* @since 2.1.0
*
* @see AmazonKinesisAsync#putRecord(PutRecordRequest)
* @see AmazonKinesisAsync#putRecords(PutRecordsRequest)
* @see com.amazonaws.handlers.AsyncHandler
*/
public class KplMessageHandler extends AbstractAwsMessageHandler<Void> {
private final KinesisProducer kinesisProducer;
private Converter<Object, byte[]> converter = new SerializingConverter();
private Expression streamExpression;
private Expression partitionKeyExpression;
private Expression explicitHashKeyExpression;
private Expression sequenceNumberExpression;
private OutboundMessageMapper<byte[]> embeddedHeadersMapper;
public KplMessageHandler(KinesisProducer kinesisProducer) {
Assert.notNull(kinesisProducer, "'kinesisProducer' must not be null.");
this.kinesisProducer = kinesisProducer;
}
/**
* 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) {
Assert.notNull(converter, "'converter' must not be null.");
this.converter = converter;
}
public void setStream(String stream) {
setStreamExpression(new LiteralExpression(stream));
}
public void setStreamExpressionString(String streamExpression) {
setStreamExpression(EXPRESSION_PARSER.parseExpression(streamExpression));
}
public void setStreamExpression(Expression streamExpression) {
this.streamExpression = streamExpression;
}
public void setPartitionKey(String partitionKey) {
setPartitionKeyExpression(new LiteralExpression(partitionKey));
}
public void setPartitionKeyExpressionString(String partitionKeyExpression) {
setPartitionKeyExpression(EXPRESSION_PARSER.parseExpression(partitionKeyExpression));
}
public void setPartitionKeyExpression(Expression partitionKeyExpression) {
this.partitionKeyExpression = partitionKeyExpression;
}
public void setExplicitHashKey(String explicitHashKey) {
setExplicitHashKeyExpression(new LiteralExpression(explicitHashKey));
}
public void setExplicitHashKeyExpressionString(String explicitHashKeyExpression) {
setExplicitHashKeyExpression(EXPRESSION_PARSER.parseExpression(explicitHashKeyExpression));
}
public void setExplicitHashKeyExpression(Expression explicitHashKeyExpression) {
this.explicitHashKeyExpression = explicitHashKeyExpression;
}
public void setSequenceNumberExpressionString(String sequenceNumberExpression) {
setSequenceNumberExpression(EXPRESSION_PARSER.parseExpression(sequenceNumberExpression));
}
public void setSequenceNumberExpression(Expression sequenceNumberExpression) {
this.sequenceNumberExpression = sequenceNumberExpression;
}
/**
* 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
*/
public void setEmbeddedHeadersMapper(OutboundMessageMapper<byte[]> embeddedHeadersMapper) {
this.embeddedHeadersMapper = embeddedHeadersMapper;
}
/**
* Unsupported operation. Use {@link #setEmbeddedHeadersMapper} instead.
* @param headerMapper is not used.
* @see #setEmbeddedHeadersMapper
*/
@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.");
}
@Override
protected Future<?> handleMessageToAws(Message<?> message) throws Exception {
if (message.getPayload() instanceof PutRecordsRequest) {
throw new UnsupportedOperationException("not implemented");
}
else if (message.getPayload() instanceof UserRecord) {
return handleUserRecord(message, buildPutRecordRequest(message), (UserRecord) message.getPayload());
}
else {
final PutRecordRequest putRecordRequest =
(message.getPayload() instanceof PutRecordRequest)
? (PutRecordRequest) message.getPayload()
: buildPutRecordRequest(message);
// convert the PutRecordRequest to a UserRecord
UserRecord userRecord = new UserRecord();
userRecord.setExplicitHashKey(putRecordRequest.getExplicitHashKey());
userRecord.setData(putRecordRequest.getData());
userRecord.setPartitionKey(putRecordRequest.getPartitionKey());
userRecord.setStreamName(putRecordRequest.getStreamName());
return handleUserRecord(message, putRecordRequest, userRecord);
}
}
private Future<?> handleUserRecord(Message<?> message, final PutRecordRequest putRecordRequest,
UserRecord userRecord) {
ListenableFuture<UserRecordResult> recordResult = this.kinesisProducer.addUserRecord(userRecord);
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));
}
@Override
public void onSuccess(UserRecordResult result) {
asyncHandler.onSuccess(putRecordRequest, result);
}
};
Futures.addCallback(recordResult, callback, MoreExecutors.directExecutor());
return recordResult;
}
private PutRecordRequest buildPutRecordRequest(Message<?> message) throws Exception {
MessageHeaders messageHeaders = message.getHeaders();
String stream = messageHeaders.get(AwsHeaders.STREAM, String.class);
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.");
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.");
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) {
sequenceNumber = this.sequenceNumberExpression.getValue(getEvaluationContext(), message, String.class);
}
Object payload = message.getPayload();
ByteBuffer data = null;
Message<?> messageToEmbed = null;
if (payload instanceof ByteBuffer) {
data = (ByteBuffer) payload;
if (this.embeddedHeadersMapper != null) {
messageToEmbed = new MutableMessage<>(data.array(), messageHeaders);
}
}
else {
byte[] bytes =
payload instanceof byte[]
? (byte[]) payload
: this.converter.convert(payload);
if (this.embeddedHeadersMapper != null) {
messageToEmbed = new MutableMessage<>(bytes, messageHeaders);
}
else {
data = ByteBuffer.wrap(bytes);
}
}
if (messageToEmbed != null) {
data = ByteBuffer.wrap(this.embeddedHeadersMapper.fromMessage(messageToEmbed));
}
return new PutRecordRequest()
.withStreamName(stream)
.withPartitionKey(partitionKey)
.withExplicitHashKey(explicitHashKey)
.withSequenceNumberForOrdering(sequenceNumber)
.withData(data);
}
@Override
protected void additionalOnSuccessHeaders(AbstractIntegrationMessageBuilder<?> messageBuilder,
AmazonWebServiceRequest request, Object result) {
if (result instanceof PutRecordResult) {
messageBuilder
.setHeader(AwsHeaders.SHARD, ((PutRecordResult) result).getShardId())
.setHeader(AwsHeaders.SEQUENCE_NUMBER, ((PutRecordResult) result).getSequenceNumber());
}
}
}