/*
* Copyright 2016-2017 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.kafka.dsl;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Function;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.dsl.ComponentsRegistration;
import org.springframework.integration.dsl.MessageHandlerSpec;
import org.springframework.integration.expression.FunctionExpression;
import org.springframework.integration.expression.ValueExpression;
import org.springframework.integration.kafka.outbound.KafkaProducerMessageHandler;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.support.ProducerListener;
import org.springframework.kafka.support.converter.RecordMessageConverter;
import org.springframework.messaging.Message;
/**
* A {@link MessageHandlerSpec} implementation for the {@link KafkaProducerMessageHandler}.
*
* @param <K> the key type.
* @param <V> the value type.
*
* @author Artem Bilan
* @author Biju Kunjummen
*
* @since 3.0
*/
public class KafkaProducerMessageHandlerSpec<K, V>
extends MessageHandlerSpec<KafkaProducerMessageHandlerSpec<K, V>, KafkaProducerMessageHandler<K, V>> {
protected final KafkaTemplate<K, V> kafkaTemplate;
KafkaProducerMessageHandlerSpec(KafkaTemplate<K, V> kafkaTemplate) {
this.target = new KafkaProducerMessageHandler<K, V>(kafkaTemplate);
this.kafkaTemplate = kafkaTemplate;
}
/**
* Configure the Kafka topic to send messages.
* @param topic the Kafka topic name.
* @return the spec.
*/
public KafkaProducerMessageHandlerSpec<K, V> topic(String topic) {
return topicExpression(new LiteralExpression(topic));
}
/**
* Configure a SpEL expression to determine the Kafka topic at runtime against
* request Message as a root object of evaluation context.
* @param topicExpression the topic SpEL expression.
* @return the spec.
*/
public KafkaProducerMessageHandlerSpec<K, V> topicExpression(String topicExpression) {
return topicExpression(PARSER.parseExpression(topicExpression));
}
/**
* Configure an {@link Expression} to determine the Kafka topic at runtime against
* request Message as a root object of evaluation context.
* @param topicExpression the topic expression.
* @return the spec.
*/
public KafkaProducerMessageHandlerSpec<K, V> topicExpression(Expression topicExpression) {
this.target.setTopicExpression(topicExpression);
return _this();
}
/**
* Configure a {@link Function} that will be invoked at run time to determine the topic to
* which a message will be sent. Typically used with a Java 8 Lambda expression:
* <pre class="code">
* {@code
* .<Foo>topic(m -> m.getPayload().getTopic())
* }
* </pre>
* @param topicFunction the topic function.
* @param <P> the expected payload type.
* @return the current {@link KafkaProducerMessageHandlerSpec}.
* @see FunctionExpression
*/
public <P> KafkaProducerMessageHandlerSpec<K, V> topic(Function<Message<P>, String> topicFunction) {
return topicExpression(new FunctionExpression<>(topicFunction));
}
/**
* Configure a SpEL expression to determine the Kafka message key to store at runtime against
* request Message as a root object of evaluation context.
* @param messageKeyExpression the message key SpEL expression.
* @return the spec.
*/
public KafkaProducerMessageHandlerSpec<K, V> messageKeyExpression(String messageKeyExpression) {
return messageKeyExpression(PARSER.parseExpression(messageKeyExpression));
}
/**
* Configure the message key to store message in Kafka topic.
* @param messageKey the message key to use.
* @return the spec.
*/
public KafkaProducerMessageHandlerSpec<K, V> messageKey(String messageKey) {
return messageKeyExpression(new LiteralExpression(messageKey));
}
/**
* Configure an {@link Expression} to determine the Kafka message key to store at runtime against
* request Message as a root object of evaluation context.
* @param messageKeyExpression the message key expression.
* @return the spec.
*/
public KafkaProducerMessageHandlerSpec<K, V> messageKeyExpression(Expression messageKeyExpression) {
this.target.setMessageKeyExpression(messageKeyExpression);
return _this();
}
/**
* Configure a {@link Function} that will be invoked at run time to determine the message key under
* which a message will be stored in the topic. Typically used with a Java 8 Lambda expression:
* <pre class="code">
* {@code
* .<Foo>messageKey(m -> m.getPayload().getKey())
* }
* </pre>
* @param messageKeyFunction the message key function.
* @param <P> the expected payload type.
* @return the current {@link KafkaProducerMessageHandlerSpec}.
* @see FunctionExpression
*/
public <P> KafkaProducerMessageHandlerSpec<K, V> messageKey(Function<Message<P>, ?> messageKeyFunction) {
return messageKeyExpression(new FunctionExpression<>(messageKeyFunction));
}
/**
* Configure a partitionId of Kafka topic.
* @param partitionId the partitionId to use.
* @return the spec.
*/
public KafkaProducerMessageHandlerSpec<K, V> partitionId(Integer partitionId) {
return partitionIdExpression(new ValueExpression<Integer>(partitionId));
}
/**
* Configure a SpEL expression to determine the topic partitionId at runtime against
* request Message as a root object of evaluation context.
* @param partitionIdExpression the partitionId expression to use.
* @return the spec.
*/
public KafkaProducerMessageHandlerSpec<K, V> partitionIdExpression(String partitionIdExpression) {
return partitionIdExpression(PARSER.parseExpression(partitionIdExpression));
}
/**
* Configure a {@link Function} that will be invoked at run time to determine the partition id under
* which a message will be stored in the topic. Typically used with a Java 8 Lambda expression:
* <pre class="code">
* {@code
* .partitionId(m -> m.getHeaders().get("partitionId", Integer.class))
* }
* </pre>
* @param partitionIdFunction the partitionId function.
* @param <P> the expected payload type.
* @return the spec.
*/
public <P> KafkaProducerMessageHandlerSpec<K, V> partitionId(Function<Message<P>, Integer> partitionIdFunction) {
return partitionIdExpression(new FunctionExpression<>(partitionIdFunction));
}
/**
* Configure an {@link Expression} to determine the topic partitionId at runtime against
* request Message as a root object of evaluation context.
* @param partitionIdExpression the partitionId expression to use.
* @return the spec.
*/
public KafkaProducerMessageHandlerSpec<K, V> partitionIdExpression(Expression partitionIdExpression) {
this.target.setPartitionIdExpression(partitionIdExpression);
return _this();
}
/**
* Configure a SpEL expression to determine the timestamp at runtime against a
* request Message as a root object of evaluation context.
* @param timestampExpression the timestamp expression to use.
* @return the spec.
*/
public KafkaProducerMessageHandlerSpec<K, V> timestampExpression(String timestampExpression) {
return this.timestampExpression(PARSER.parseExpression(timestampExpression));
}
/**
* Configure a {@link Function} that will be invoked at run time to determine the Kafka record timestamp
* will be stored in the topic. Typically used with a Java 8 Lambda expression:
* <pre class="code">
* {@code
* .timestamp(m -> m.getHeaders().get("mytimestamp_header", Long.class))
* }
* </pre>
* @param timestampFunction the partitionId function.
* @param <P> the expected payload type.
* @return the spec.
*/
public <P> KafkaProducerMessageHandlerSpec<K, V> timestamp(Function<Message<P>, Long> timestampFunction) {
return timestampExpression(new FunctionExpression<>(timestampFunction));
}
/**
* Configure an {@link Expression} to determine the timestamp at runtime against a
* request Message as a root object of evaluation context.
* @param timestampExpression the timestamp expression to use.
* @return the spec.
*
* @since 3.0
*/
public KafkaProducerMessageHandlerSpec<K, V> timestampExpression(Expression timestampExpression) {
this.target.setTimestampExpression(timestampExpression);
return _this();
}
/**
* A {@code boolean} indicating if the {@link KafkaProducerMessageHandler}
* should wait for the send operation results or not. Defaults to {@code false}.
* In {@code sync} mode a downstream send operation exception will be re-thrown.
* @param sync the send mode; async by default.
* @return the spec.
*/
public KafkaProducerMessageHandlerSpec<K, V> sync(boolean sync) {
this.target.setSync(sync);
return this;
}
/**
* Specify a timeout in milliseconds how long {@link KafkaProducerMessageHandler}
* should wait wait for send operation results. Defaults to 10 seconds.
* @param sendTimeout the timeout to wait for result fo send operation.
* @return the spec.
*/
public KafkaProducerMessageHandlerSpec<K, V> sendTimeout(long sendTimeout) {
this.target.setSendTimeout(sendTimeout);
return this;
}
/**
* A {@link KafkaTemplate}-based {@link KafkaProducerMessageHandlerSpec} extension.
*
* @param <K> the key type.
* @param <V> the value type.
*/
public static class KafkaProducerMessageHandlerTemplateSpec<K, V> extends KafkaProducerMessageHandlerSpec<K, V>
implements ComponentsRegistration {
KafkaProducerMessageHandlerTemplateSpec(ProducerFactory<K, V> producerFactory) {
super(new KafkaTemplate<>(producerFactory));
}
public KafkaProducerMessageHandlerTemplateSpec<K, V> producerListener(ProducerListener<K, V> producerListener) {
this.kafkaTemplate.setProducerListener(producerListener);
return this;
}
public KafkaProducerMessageHandlerTemplateSpec<K, V> messageConverter(RecordMessageConverter messageConverter) {
this.kafkaTemplate.setMessageConverter(messageConverter);
return this;
}
@Override
public Collection<Object> getComponentsToRegister() {
return Collections.singleton(this.kafkaTemplate);
}
}
}