diff --git a/samples/kafka/build.gradle b/samples/kafka/build.gradle
index 12748bf..0c3e6ee 100644
--- a/samples/kafka/build.gradle
+++ b/samples/kafka/build.gradle
@@ -1,27 +1,62 @@
description = 'Spring Integration Kafka Sample'
+
+buildscript {
+
+ repositories {
+ mavenCentral()
+ mavenLocal()
+ }
+ dependencies {
+ classpath 'org.apache.maven:maven-artifact:2.2.1' // 3.x won't work
+ classpath 'org.apache.avro:avro-compiler:1.7.3' // use Avro 1.7.4 to compile the Avro files
+ //classpath 'org.clojars.miguno:avro-gradle-plugin:1.7.2'
+ classpath "org.apache.avro:avro-gradle-plugin:1.7.2"
+ }
+
+}
+
+
apply plugin: 'base'
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'application'
apply plugin: 'idea'
apply plugin: 'maven'
+apply plugin: 'avro-gradle-plugin'
+
+ext {
+ avroTaskGroup = "Avro"
+ avroSource = "schemas"
+ avroDest = "target/generated-avro-sources/main/java"
+}
repositories {
- mavenLocal()
- mavenCentral()
- maven { url "http://repo.springsource.org/libs-snapshot" }
- maven { url 'http://repo.springsource.org/plugins-release' }
+
+mavenLocal()
+maven {
+ url 'https://repository.apache.org/content/groups/public'
+ }
+ maven { url 'https://repo.springsource.org/libs-milestone' }
+
}
dependencies {
+ compile([
+ "org.apache.avro:avro:1.7.3",
+ "org.apache.avro:avro-compiler:1.7.3"
+ ])
compile "org.springframework:spring-beans:3.1.3.RELEASE"
compile "org.springframework:spring-context:3.1.3.RELEASE"
compile "org.springframework:spring-expression:3.1.3.RELEASE"
compile "org.springframework.integration:spring-integration-stream:2.2.0.RELEASE"
compile("org.springframework.integration:spring-integration-kafka:0.5.0.BUILD-SNAPSHOT") {
exclude module: 'log4j'
+ exclude module: 'jms'
+ exclude module: 'jmxtools'
+ exclude module: 'jmxri'
}
compile("log4j:log4j:1.2.15") {
+ exclude module: 'mail'
exclude module: 'jms'
exclude module: 'jmx'
exclude module: 'jmxtools'
@@ -32,6 +67,30 @@ compile("log4j:log4j:1.2.15") {
compile "commons-logging:commons-logging:1.1.1"
}
+compileAvro.group = avroTaskGroup
+compileAvro.description = "Generates Java code from avro schema"
+compileAvro.source = avroSource
+compileAvro.destinationDir = file(avroDest)
+
+task cleanAvro(type: Delete) {
+ group = avroTaskGroup
+ description = "deletes generated avro code"
+ delete avroDest
+}
+
+compileJava.dependsOn compileAvro
+
+sourceSets {
+ main {
+ java {
+ srcDir avroDest
+ }
+ resources {
+ srcDir avroSource
+ }
+ }
+}
+
task wrapper(type: Wrapper) {
description = 'Generates gradlew[.bat] scripts'
gradleVersion = '1.3'
diff --git a/samples/kafka/schemas/user.avdl b/samples/kafka/schemas/user.avdl
new file mode 100644
index 0000000..f8f9e8b
--- /dev/null
+++ b/samples/kafka/schemas/user.avdl
@@ -0,0 +1,7 @@
+@namespace("org.springframework.integration.samples.kafka.user")
+protocol UserProtocol{
+record User {
+ string firstName;
+ string lastName;
+}
+}
\ No newline at end of file
diff --git a/samples/kafka/src/main/java/org/springframework/integration/samples/kafka/outbound/OutboundRunner.java b/samples/kafka/src/main/java/org/springframework/integration/samples/kafka/outbound/OutboundRunner.java
index fcb1c35..c27ebb7 100644
--- a/samples/kafka/src/main/java/org/springframework/integration/samples/kafka/outbound/OutboundRunner.java
+++ b/samples/kafka/src/main/java/org/springframework/integration/samples/kafka/outbound/OutboundRunner.java
@@ -19,6 +19,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.MessageChannel;
+import org.springframework.integration.samples.kafka.user.User;
import org.springframework.integration.support.MessageBuilder;
public class OutboundRunner {
@@ -34,8 +35,11 @@ public class OutboundRunner {
//sending 100,000 messages to Kafka server for topic test1
for (int i = 0; i < 500; i++) {
+ final User user = new User();
+ user.setFirstName("fname" + i);
+ user.setLastName("lname" + i);
channel.send(
- MessageBuilder.withPayload("hello Fom ob adapter test1 - " + i)
+ MessageBuilder.withPayload(user)
.setHeader("messageKey", String.valueOf(i))
.setHeader("topic", "test1").build());
diff --git a/samples/kafka/src/main/resources/org/springframework/integration/samples/kafka/inbound/kafkaInboundAdapterParserTests-context.xml b/samples/kafka/src/main/resources/org/springframework/integration/samples/kafka/inbound/kafkaInboundAdapterParserTests-context.xml
index a6e97d2..a7b8d51 100644
--- a/samples/kafka/src/main/resources/org/springframework/integration/samples/kafka/inbound/kafkaInboundAdapterParserTests-context.xml
+++ b/samples/kafka/src/main/resources/org/springframework/integration/samples/kafka/inbound/kafkaInboundAdapterParserTests-context.xml
@@ -27,22 +27,26 @@
-
+
+
+
+
+
+ max-messages="50">
diff --git a/samples/kafka/src/main/resources/org/springframework/integration/samples/kafka/outbound/kafkaOutboundAdapterParserTests-context.xml b/samples/kafka/src/main/resources/org/springframework/integration/samples/kafka/outbound/kafkaOutboundAdapterParserTests-context.xml
index 2c3926b..80d3eef 100644
--- a/samples/kafka/src/main/resources/org/springframework/integration/samples/kafka/outbound/kafkaOutboundAdapterParserTests-context.xml
+++ b/samples/kafka/src/main/resources/org/springframework/integration/samples/kafka/outbound/kafkaOutboundAdapterParserTests-context.xml
@@ -21,20 +21,24 @@
-
+
+
+
+
+
-
-
+
+
+
```
The key aspect in this configuration is the producer-context-ref. Producer context contains all the producer configuration for all the topics that this adapter is expected to handle.
@@ -73,21 +73,21 @@ the receive-timeout configuration. Then it will poll again with a delay of 1 sec
Producer context is at the heart of the kafka outbound adapter. Here is an example of how it is configured.
```xml
-
-
-
-
-
-
+
+
+
+
+
+
```
There are a few things going on here. So, lets go one by one. First of all, producer context is simply a holder of, as the name
@@ -96,18 +96,18 @@ is ultimately gets translated into a Kafka native producer. Each producer config
If you go by the above example, there are two producers generated from this configuration - one for topic named
test1 and another for test2. Each producer can take the following:
- broker-list list of comma separated brokers that this producer connects to
- topic topic name
- compression-codec any compression to be used. Default is no compression. Supported compression codec are gzip and snappy. Anything else would
- result in no compression
- value-encoder serializer to be used for encoding messages.
- key-encoder serializer to be used for encoding the partition key
- key-class-type Type of the key class. This will be ignored if no key-encoder is provided
- value-class-type The type of the value class. This will be ignored if no value-encoder is provided.
- partitioner custom implementation of a Kafka Partitioner interface.
- async true/false - default is false. Setting this to true would make the Kafka producer to use
- an async producer
- batch-num-messages number of messages to batch at the producer. If async is false, then this has no effect.
+ broker-list List of comma separated brokers that this producer connects to
+ topic Topic name
+ compression-codec Compression method to be used. Default is no compression. Supported compression codec are gzip and snappy.
+ Anything else would result in no compression
+ value-encoder Serializer to be used for encoding messages.
+ key-encoder Serializer to be used for encoding the partition key
+ key-class-type Type of the key class. This will be ignored if no key-encoder is provided
+ value-class-type Type of the value class. This will be ignored if no value-encoder is provided.
+ partitioner Custom implementation of a Kafka Partitioner interface.
+ async True/False - default is false. Setting this to true would make the Kafka producer to use
+ an async producer
+ batch-num-messages Number of messages to batch at the producer. If async is false, then this has no effect.
The value-encoder and key-encoder are referring to other spring beans. They are essentially implementations of an
interface provided by Kafka, the Encoder interface. Similarly, partitioner also refers a Spring bean which implements
@@ -116,9 +116,9 @@ the Kafka Partitioner interface.
Here is an example of configuring an encoder.
```xml
-
-
-
+
+
+
```
Spring Integration Kafaka adapter provides Apache Avro backed encoders out of the box, as this is a popular choice
@@ -134,6 +134,23 @@ If the encoders are default and the objets sent are not serializalbe, then that
it is totally up to the developer to configure how the objects are serialized. In that case, the objects may or may not implement
the Serializable interface.
+A bit more on the Avro support. There are two flavors of Avro encoders provided, one based on the Avro ReflectDatum and the other
+based on SpecificDatum. The encoding using reflection is fairly simple as you only have to configure your POJO or other class types
+along with the XML. Here is an example.
+
+```xml
+
+
+
+```
+
+Reflection based encoding may not be appropriate for large scale systems and Avro's SpecificDatum based encoders can be a better fit. In this case, you can
+generate a specific Avro object (a glorified POJO) from a schema definition. The generated object will store the schema as well. In order to
+do this, you need to generate the Avro object separately though. There are both maven and gradle plugins available to do code generation
+automatically. You have to provide the avdl or avsc files to specify your schema. Once you take care of these steps, you can simply configure
+a specific datum based Avro encoder (see the first example above) and pass along the fully qualified class name of the generated Avro object
+for which you want to encode instances. The samples project has examples of using both of these encoders.
+
Encoding String for key and value is a very common use case and Kafka provides a StringEncoder out of the box. It takes a Kafka specific VerifiableProperties object
along with its
constructor that wraps a regular Java.util.Properties object. The StringEncoder is great when writing a
@@ -150,54 +167,54 @@ Inbound Channel Adapter:
--------------------------------------------
The Inbound channel adapter is used to consume messages from Kafka. These messages will be placed into a channel as Spring Integration specific Messages.
-Kafka provides two types of consumer API's primarily. One is called the high level consumer and the other is the Simple Consumer. Highlevel consumer is
+Kafka provides two types of consumer API's primarily. One is called the High Level Consumer and the other is the Simple Consumer. High Level consumer is
pretty complex inside. Nonetheless, for the client, using the high level API is straightforward. Although easy to use, High level consumer
does not provide any offset management. So, if you want to rewind and re-fetch messages, it is not possible to do so using the
-high level consumer API. Offsets are managed by the Zookeeper internally in the high level consumer. If your use case does not require any offset management
+High Level Consumer API. Offsets are managed by the Zookeeper internally in the High Level Consumer. If your use case does not require any offset management
or re-reading messages from the same consumer, then high level consumer is a perfect fit. Spring Integration Kafka inbound channel adapter
-currently supports only the high level consumer. Here are the details of configuring one.
+currently supports only the High Level Consumer. Here are the details of configuring one.
```xml
-
-
+ kafka-consumer-context-ref="consumerContext"
+ auto-startup="false"
+ channel="inputFromKafka">
+
+
```
Since this inbound channel adapter uses a Polling Channel under the hood, it must be configured with a Poller. A notable difference
-between the poller configured with this inbound adapter and other pollers is that the receive-timeout specified here
+between the poller configured with this inbound adapter and other pollers used in Spring Integration is that the receive-timeout specified on this poller
does not have any effect. The reason for this is because of the way Kafka implements iterators on the consumer stream.
It is using a BlockingQueue internally and thus it would wait indefinitely. Instead of interrupting the underlying thread,
-we are leveraging on direct Kafka support for consumer time out. It is configured on the consumer context. Everything else
- is pretty much the same as in a regular inbound adapter. Any messages that it receives will be sent to the channel configured with it.
+we are leveraging a direct Kafka support for consumer time out. It is configured on the consumer context. Everything else
+is pretty much the same as in a regular inbound adapter. Any message that it receives will be sent to the channel configured with it.
Inbound Kafka Adapter must specify a kafka-consumer-context-ref element and here is how it is configured:
```xml
-
-
-
-
-
-
-
+ consumer-timeout="4000"
+ zookeeper-connect="zookeeperConnect">
+
+
+
+
+
+
+
```
Consumer context requires a reference to a zookeeper-connect which dictates all the zookeeper specific configuration details.
Here is how a zookeeper-connect is configured.
```xml
-
+
```
zk-connect attribute is where you would specify the zookeeper connection. All the other attributes get translated into their
@@ -207,19 +224,19 @@ In the above consumer context, you can also specify a consumer-timeout value whi
timeout the consumer in case of no messages to consume.
This timeout would be applicable to all the streams (threads) in the consumer.
The default value for this in Kafka is -1 which would make it wait
-indefinitely. However, Sping Integration overrides it to be 5 seconds in order to make sure that no
+indefinitely. However, Sping Integration overrides it to be 5 seconds by default in order to make sure that no
threads are blocking indefinitely in the lifecycle of the application and thereby
giving them a chance to free up any resources or locks that they hold. It is recommended to
override this value so as to meet any specific use case requirements.
-By providing a reasonable consumer-timeout and a fixed-delay value on the poller,
+By providing a reasonable consumer-timeout on the context and a fixed-delay value on the poller,
this inbound adapter is capable of simulating a message driven behaviour.
-consumer context takes consumer-configurations which are at the center piece of the inbound adapter. It is a group of one or more
+consumer context takes consumer-configurations which are at the core of the inbound adapter. It is a group of one or more
consumer-configuration elements which consists of a consumer group dictated by the group-id. Each consumer-configuration
-can be configured with one or more kafka-topic.
+can be configured with one or more kafka-topics.
In the above example provided, we have a single consumer-configuration that consumes messages from two topics each having 4 streams.
- These streams are fundamentally same as the number of partitions that a topic is configured
+ These streams are fundamentally equivalent to the number of partitions that a topic is configured
with in the producer. For instance, if you configure your topic with
4 partitions, then the maximum number of streams that you may have in the consumer is also 4.
Any more than this would be a no-op.
@@ -235,13 +252,24 @@ Consumer configuration can also be configured with optional decoders for key and
The default ones provided by Kafka are basically no-ops and would consume as byte arrays.
If you provide an encoder for key/value in the producer, then it is recommended to provide
corresponding decoders.
-Spring Integration Kafka adapter gives Apache Avro based data serialization components
-out of the box. You can use any serialization component for this purpose.
-Here is how you would configure a kafka decoder bean that is Avro backed.
+As disussed already in the outbound adapter, Spring Integration Kafka adapter gives Apache Avro based data serialization components
+out of the box. You can use any serialization component for this purpose as long as you implement the required encoder/decoder interfaces from Kafka.
+As with the Avro encoder support, decoders provided also
+implement Reflection and Specific datum based de-serialization. Here is how you would configure kafka decoder beans that is Avro backed.
+
+Using Avro Specific support:
```xml
-
-
+
+
+
+```
+
+Using Reflection support:
+
+```xml
+
+
```
@@ -249,13 +277,13 @@ Another important attribute for the consumer-configuration is the max-messages.
Please note that this is different from the max-messages-per-poll configured on the inbound adapter
element.
There it means the number of times the receive method called on the adapter.
-The max-messages on consumer configuration is different. Kafka is used mainly for big data purposes
-and usually that means the influx of large amount of data constantly. Because of this,
+The max-messages on consumer configuration is different. When you use Kafka for ingesting messages,
+it usually means an influx of large amount of data constantly. Because of this,
each time a receive is invoked on the adapter, you would basically get a collection of messages.
The maximum number of messages to retrieve for a topic in each execution of the
receive is what configured through the max-messages attribute on the consumer-configuration.
Basically, if the use case is to receive a constant stream of
-large number of data, simply specifying a receive-timeout alone would not be enough.
+large number of data, simply specifying a consumer-timeout alone would not be enough.
You would also need to specify the max number of messages to receive.
The type of the payload of the Message returned by the adapter is the following:
diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaConsumerContextParser.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaConsumerContextParser.java
index 0ac442b..e116eec 100644
--- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaConsumerContextParser.java
+++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaConsumerContextParser.java
@@ -15,6 +15,7 @@
*/
package org.springframework.integration.kafka.config.xml;
+import kafka.serializer.DefaultDecoder;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
@@ -32,7 +33,9 @@ import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
@@ -54,6 +57,7 @@ public class KafkaConsumerContextParser extends AbstractSingleBeanDefinitionPars
parseConsumerConfigurations(consumerConfigurations, parserContext, builder, element);
}
+ @SuppressWarnings("unchecked")
private void parseConsumerConfigurations(final Element consumerConfigurations, final ParserContext parserContext,
final BeanDefinitionBuilder builder, final Element parentElem) {
for (final Element consumerConfiguration : DomUtils.getChildElementsByTagName(consumerConfigurations, "consumer-configuration")) {
diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaProducerContextParser.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaProducerContextParser.java
index 5fc9937..dadfe47 100644
--- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaProducerContextParser.java
+++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaProducerContextParser.java
@@ -30,11 +30,15 @@ import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* @author Soby Chacko
* @since 0.5
*/
public class KafkaProducerContextParser extends AbstractSimpleBeanDefinitionParser {
+
@Override
protected Class> getBeanClass(final Element element) {
return KafkaProducerContext.class;
@@ -48,6 +52,7 @@ public class KafkaProducerContextParser extends AbstractSimpleBeanDefinitionPars
parseProducerConfigurations(topics, parserContext);
}
+ @SuppressWarnings("unchecked")
private void parseProducerConfigurations(final Element topics, final ParserContext parserContext) {
for (final Element producerConfiguration : DomUtils.getChildElementsByTagName(topics, "producer-configuration")){
final BeanDefinitionBuilder producerConfigurationBuilder = BeanDefinitionBuilder.genericBeanDefinition(ProducerConfiguration.class);
diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/inbound/KafkaHighLevelConsumerMessageSource.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/inbound/KafkaHighLevelConsumerMessageSource.java
index 83e225e..8926e7e 100644
--- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/inbound/KafkaHighLevelConsumerMessageSource.java
+++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/inbound/KafkaHighLevelConsumerMessageSource.java
@@ -28,11 +28,11 @@ import java.util.Map;
* @since 0.5
*
*/
-public class KafkaHighLevelConsumerMessageSource extends IntegrationObjectSupport implements MessageSource