/* * Copyright 2016 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 java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.springframework.core.convert.converter.Converter; import org.springframework.core.serializer.support.SerializingConverter; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.common.LiteralExpression; import org.springframework.integration.MessageTimeoutException; import org.springframework.integration.aws.support.AwsHeaders; import org.springframework.integration.expression.ExpressionUtils; import org.springframework.integration.expression.ValueExpression; import org.springframework.integration.handler.AbstractMessageHandler; import org.springframework.messaging.Message; 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.model.PutRecordsResult; /** * The {@link AbstractMessageHandler} implementation for the Amazon Kinesis {@code putRecord(s)}. * * @author Artem Bilan * @since 1.1 * * @see AmazonKinesisAsync#putRecord(PutRecordRequest) * @see com.amazonaws.handlers.AsyncHandler */ public class KinesisMessageHandler extends AbstractMessageHandler { private static final long DEFAULT_SEND_TIMEOUT = 10000; private final AmazonKinesisAsync amazonKinesis; private AsyncHandler<? extends AmazonWebServiceRequest, ?> asyncHandler; private Converter<Object, byte[]> converter = new SerializingConverter(); private EvaluationContext evaluationContext; private volatile Expression streamExpression; private volatile Expression partitionKeyExpression; private volatile Expression explicitHashKeyExpression; private volatile Expression sequenceNumberExpression; private boolean sync; private Expression sendTimeoutExpression = new ValueExpression<>(DEFAULT_SEND_TIMEOUT); public KinesisMessageHandler(AmazonKinesisAsync amazonKinesis) { Assert.notNull(amazonKinesis, "'amazonKinesis' must not be null."); this.amazonKinesis = amazonKinesis; } public void setAsyncHandler(AsyncHandler<? extends AmazonWebServiceRequest, ?> asyncHandler) { this.asyncHandler = asyncHandler; } 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 setSequenceNumberString(String sequenceNumberExpression) { setSequenceNumberExpression(EXPRESSION_PARSER.parseExpression(sequenceNumberExpression)); } public void setSequenceNumberExpression(Expression sequenceNumberExpression) { this.sequenceNumberExpression = sequenceNumberExpression; } public void setSync(boolean sync) { this.sync = sync; } public void setSendTimeoutExpression(Expression sendTimeoutExpression) { this.sendTimeoutExpression = sendTimeoutExpression; } @Override protected void onInit() throws Exception { super.onInit(); this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory()); } @Override @SuppressWarnings("unchecked") protected void handleMessageInternal(Message<?> message) throws Exception { Future<?> resultFuture = null; if (message.getPayload() instanceof PutRecordsRequest) { resultFuture = this.amazonKinesis.putRecordsAsync((PutRecordsRequest) message.getPayload(), (AsyncHandler<PutRecordsRequest, PutRecordsResult>) this.asyncHandler); } else { PutRecordRequest putRecordRequest = (message.getPayload() instanceof PutRecordRequest) ? (PutRecordRequest) message.getPayload() : buildPutRecordRequest(message); resultFuture = this.amazonKinesis.putRecordAsync(putRecordRequest, (AsyncHandler<PutRecordRequest, PutRecordResult>) this.asyncHandler); } if (this.sync) { Long sendTimeout = this.sendTimeoutExpression.getValue(this.evaluationContext, message, Long.class); if (sendTimeout == null || sendTimeout < 0) { resultFuture.get(); } else { try { resultFuture.get(sendTimeout, TimeUnit.MILLISECONDS); } catch (TimeoutException te) { throw new MessageTimeoutException(message, "Timeout waiting for response from AmazonKinesis", te); } } } } private PutRecordRequest buildPutRecordRequest(Message<?> message) { String stream = message.getHeaders().get(AwsHeaders.STREAM, String.class); if (!StringUtils.hasText(stream) && this.streamExpression != null) { stream = this.streamExpression.getValue(this.evaluationContext, 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 = message.getHeaders().get(AwsHeaders.PARTITION_KEY, String.class); if (!StringUtils.hasText(partitionKey) && this.partitionKeyExpression != null) { partitionKey = this.partitionKeyExpression.getValue(this.evaluationContext, 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(this.evaluationContext, message, String.class) : null); String sequenceNumber = message.getHeaders().get(AwsHeaders.SEQUENCE_NUMBER, String.class); if (!StringUtils.hasText(stream) && this.streamExpression != null) { partitionKey = this.sequenceNumberExpression.getValue(this.evaluationContext, message, String.class); } return new PutRecordRequest() .withStreamName(stream) .withPartitionKey(partitionKey) .withExplicitHashKey(explicitHashKey) .withSequenceNumberForOrdering(sequenceNumber) .withData(ByteBuffer.wrap(this.converter.convert(message.getPayload()))); } }