From e46cbfdcb4f0fe1bcb8efa2f29454adefaa360f6 Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Mon, 18 Mar 2013 23:31:53 -0400 Subject: [PATCH 1/2] INTEXT-44 - Add Kafka Support * Add Inbound adapter * Add Outbound adapter * Add serialization * Initial Documentation * Add unit tests * Add async producer * Support Kafka 0.8 * Add Kafka Sample See: https://jira.springsource.org/browse/INTEXT-44 --- samples/kafka/.gitignore | 2 + samples/kafka/README.md | 5 + samples/kafka/build.gradle | 46 ++ samples/kafka/gradle.properties | 7 + .../kafka/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 39860 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + samples/kafka/gradlew | 164 +++++++ samples/kafka/gradlew.bat | 90 ++++ .../samples/kafka/inbound/InboundRunner.java | 27 ++ .../kafka/outbound/CustomPartitioner.java | 37 ++ .../kafka/outbound/OutboundRunner.java | 55 +++ .../outbound/PartitionlessTransformer.java | 56 +++ .../kafka/src/main/resources/log4j.properties | 10 + ...kafkaInboundAdapterParserTests-context.xml | 56 +++ ...afkaOutboundAdapterParserTests-context.xml | 45 ++ spring-integration-kafka/.gitignore | 8 + spring-integration-kafka/README.md | 294 ++++++++++++ spring-integration-kafka/build.gradle | 304 ++++++++++++ spring-integration-kafka/gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 39770 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + spring-integration-kafka/gradlew | 164 +++++++ spring-integration-kafka/gradlew.bat | 90 ++++ spring-integration-kafka/publish-maven.gradle | 61 +++ .../src/api/overview.html | 22 + spring-integration-kafka/src/dist/license.txt | 201 ++++++++ spring-integration-kafka/src/dist/notice.txt | 21 + .../xml/KafkaConsumerContextParser.java | 122 +++++ .../xml/KafkaInboundChannelAdapterParser.java | 49 ++ .../config/xml/KafkaNamespaceHandler.java | 38 ++ .../KafkaOutboundChannelAdapterParser.java | 45 ++ .../xml/KafkaProducerContextParser.java | 89 ++++ .../config/xml/ZookeeperConnectParser.java | 46 ++ .../kafka/config/xml/package-info.java | 4 + .../kafka/core/KafkaConsumerDefaults.java | 40 ++ .../kafka/core/ZookeeperConnectDefaults.java | 31 ++ .../integration/kafka/core/package-info.java | 4 + .../KafkaHighLevelConsumerMessageSource.java | 47 ++ .../kafka/inbound/package-info.java | 4 + .../outbound/KafkaProducerMessageHandler.java | 41 ++ .../integration/kafka/package-info.java | 4 + .../avro/AvroBackedKafkaDecoder.java | 54 +++ .../avro/AvroBackedKafkaEncoder.java | 52 ++ .../kafka/serializer/avro/AvroSerializer.java | 52 ++ .../avro/AvroSpecificDatumSerializer.java | 52 ++ .../serializer/common/StringEncoder.java | 41 ++ .../support/ConsumerConfigFactoryBean.java | 59 +++ .../kafka/support/ConsumerConfiguration.java | 209 ++++++++ .../support/ConsumerConnectionProvider.java | 35 ++ .../kafka/support/ConsumerMetadata.java | 174 +++++++ .../kafka/support/DefaultPartitioner.java | 36 ++ .../kafka/support/KafkaConsumerContext.java | 76 +++ .../kafka/support/KafkaProducerContext.java | 63 +++ .../kafka/support/MessageLeftOverTracker.java | 44 ++ .../kafka/support/ProducerConfiguration.java | 102 ++++ .../kafka/support/ProducerFactoryBean.java | 74 +++ .../kafka/support/ProducerMetadata.java | 139 ++++++ .../kafka/support/ZookeeperConnect.java | 60 +++ .../kafka/support/package-info.java | 4 + .../main/resources/META-INF/spring.handlers | 1 + .../main/resources/META-INF/spring.schemas | 2 + .../main/resources/META-INF/spring.tooling | 4 + .../xml/spring-integration-kafka-1.0.xsd | 451 ++++++++++++++++++ .../config/xml/spring-integration-kafka.gif | Bin 0 -> 572 bytes .../docbook/SIAdapterLowerPrefix.xml | 72 +++ .../src/reference/docbook/history.xml | 4 + .../src/reference/docbook/images/logo.png | Bin 0 -> 9627 bytes .../src/reference/docbook/index.xml | 67 +++ .../src/reference/docbook/resources.xml | 15 + .../src/reference/docbook/whats-new.xml | 8 + .../xml/KafkaConsumerContextParserTests.java | 47 ++ .../xml/KafkaInboundAdapterParserTests.java | 45 ++ .../xml/KafkaOutboundAdapterParserTests.java | 46 ++ .../xml/KafkaProducerContextParserTests.java | 73 +++ .../xml/ZookeeperConnectParserTests.java | 59 +++ .../AvroBackedKafkaSerializerTest.java | 59 +++ .../support/ConsumerConfigurationTests.java | 306 ++++++++++++ .../support/KafkaConsumerContextTest.java | 80 ++++ .../support/ProducerConfigurationTests.java | 303 ++++++++++++ .../support/ProducerFactoryBeanTests.java | 63 +++ .../test/utils/NonSerializableTestKey.java | 35 ++ .../utils/NonSerializableTestPayload.java | 34 ++ .../integration/kafka/test/utils/TestKey.java | 38 ++ .../kafka/test/utils/TestObject.java | 38 ++ .../kafka/test/utils/TestPayload.java | 38 ++ .../src/test/resources/log4j.properties | 8 + ...afkaConsumerContextParserTests-context.xml | 30 ++ .../xml/kafkaInboundAdapterCommon-context.xml | 17 + ...kafkaInboundAdapterParserTests-context.xml | 40 ++ ...afkaOutboundAdapterParserTests-context.xml | 44 ++ ...afkaProducerContextParserTests-context.xml | 26 + .../zookeeperConnectParserTests-context.xml | 14 + 92 files changed, 5735 insertions(+) create mode 100644 samples/kafka/.gitignore create mode 100644 samples/kafka/README.md create mode 100644 samples/kafka/build.gradle create mode 100644 samples/kafka/gradle.properties create mode 100644 samples/kafka/gradle/wrapper/gradle-wrapper.jar create mode 100644 samples/kafka/gradle/wrapper/gradle-wrapper.properties create mode 100755 samples/kafka/gradlew create mode 100644 samples/kafka/gradlew.bat create mode 100644 samples/kafka/src/main/java/org/springframework/integration/samples/kafka/inbound/InboundRunner.java create mode 100644 samples/kafka/src/main/java/org/springframework/integration/samples/kafka/outbound/CustomPartitioner.java create mode 100644 samples/kafka/src/main/java/org/springframework/integration/samples/kafka/outbound/OutboundRunner.java create mode 100644 samples/kafka/src/main/java/org/springframework/integration/samples/kafka/outbound/PartitionlessTransformer.java create mode 100644 samples/kafka/src/main/resources/log4j.properties create mode 100644 samples/kafka/src/main/resources/org/springframework/integration/samples/kafka/inbound/kafkaInboundAdapterParserTests-context.xml create mode 100644 samples/kafka/src/main/resources/org/springframework/integration/samples/kafka/outbound/kafkaOutboundAdapterParserTests-context.xml create mode 100644 spring-integration-kafka/.gitignore create mode 100644 spring-integration-kafka/README.md create mode 100644 spring-integration-kafka/build.gradle create mode 100644 spring-integration-kafka/gradle.properties create mode 100644 spring-integration-kafka/gradle/wrapper/gradle-wrapper.jar create mode 100644 spring-integration-kafka/gradle/wrapper/gradle-wrapper.properties create mode 100755 spring-integration-kafka/gradlew create mode 100644 spring-integration-kafka/gradlew.bat create mode 100644 spring-integration-kafka/publish-maven.gradle create mode 100644 spring-integration-kafka/src/api/overview.html create mode 100644 spring-integration-kafka/src/dist/license.txt create mode 100644 spring-integration-kafka/src/dist/notice.txt create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaConsumerContextParser.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaInboundChannelAdapterParser.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaNamespaceHandler.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaOutboundChannelAdapterParser.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaProducerContextParser.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/ZookeeperConnectParser.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/package-info.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/KafkaConsumerDefaults.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/ZookeeperConnectDefaults.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/package-info.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/inbound/KafkaHighLevelConsumerMessageSource.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/inbound/package-info.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/outbound/KafkaProducerMessageHandler.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/package-info.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroBackedKafkaDecoder.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroBackedKafkaEncoder.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroSerializer.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroSpecificDatumSerializer.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/common/StringEncoder.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConfigFactoryBean.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConfiguration.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConnectionProvider.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerMetadata.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/DefaultPartitioner.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/KafkaConsumerContext.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/KafkaProducerContext.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/MessageLeftOverTracker.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerConfiguration.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerFactoryBean.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerMetadata.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ZookeeperConnect.java create mode 100644 spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/package-info.java create mode 100644 spring-integration-kafka/src/main/resources/META-INF/spring.handlers create mode 100644 spring-integration-kafka/src/main/resources/META-INF/spring.schemas create mode 100644 spring-integration-kafka/src/main/resources/META-INF/spring.tooling create mode 100644 spring-integration-kafka/src/main/resources/org/springframework/integration/config/xml/spring-integration-kafka-1.0.xsd create mode 100644 spring-integration-kafka/src/main/resources/org/springframework/integration/config/xml/spring-integration-kafka.gif create mode 100644 spring-integration-kafka/src/reference/docbook/SIAdapterLowerPrefix.xml create mode 100644 spring-integration-kafka/src/reference/docbook/history.xml create mode 100644 spring-integration-kafka/src/reference/docbook/images/logo.png create mode 100644 spring-integration-kafka/src/reference/docbook/index.xml create mode 100644 spring-integration-kafka/src/reference/docbook/resources.xml create mode 100644 spring-integration-kafka/src/reference/docbook/whats-new.xml create mode 100644 spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaConsumerContextParserTests.java create mode 100644 spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaInboundAdapterParserTests.java create mode 100644 spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaOutboundAdapterParserTests.java create mode 100644 spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaProducerContextParserTests.java create mode 100644 spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/ZookeeperConnectParserTests.java create mode 100644 spring-integration-kafka/src/test/java/org/springframework/integration/kafka/serializer/AvroBackedKafkaSerializerTest.java create mode 100644 spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ConsumerConfigurationTests.java create mode 100644 spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/KafkaConsumerContextTest.java create mode 100644 spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ProducerConfigurationTests.java create mode 100644 spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ProducerFactoryBeanTests.java create mode 100644 spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/NonSerializableTestKey.java create mode 100644 spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/NonSerializableTestPayload.java create mode 100644 spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestKey.java create mode 100644 spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestObject.java create mode 100644 spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestPayload.java create mode 100644 spring-integration-kafka/src/test/resources/log4j.properties create mode 100644 spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaConsumerContextParserTests-context.xml create mode 100644 spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaInboundAdapterCommon-context.xml create mode 100644 spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaInboundAdapterParserTests-context.xml create mode 100644 spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaOutboundAdapterParserTests-context.xml create mode 100644 spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaProducerContextParserTests-context.xml create mode 100644 spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/zookeeperConnectParserTests-context.xml diff --git a/samples/kafka/.gitignore b/samples/kafka/.gitignore new file mode 100644 index 0000000..38b0df2 --- /dev/null +++ b/samples/kafka/.gitignore @@ -0,0 +1,2 @@ +derby.log + diff --git a/samples/kafka/README.md b/samples/kafka/README.md new file mode 100644 index 0000000..d4cb9b3 --- /dev/null +++ b/samples/kafka/README.md @@ -0,0 +1,5 @@ +Spring Integration Kafka Sample +================================ + +This sample demonstrates the usage of the *[Spring Integration Kafka][]* adapters. + diff --git a/samples/kafka/build.gradle b/samples/kafka/build.gradle new file mode 100644 index 0000000..12748bf --- /dev/null +++ b/samples/kafka/build.gradle @@ -0,0 +1,46 @@ +description = 'Spring Integration Kafka Sample' +apply plugin: 'base' +apply plugin: 'java' +apply plugin: 'eclipse' +apply plugin: 'application' +apply plugin: 'idea' +apply plugin: 'maven' + +repositories { + mavenLocal() + mavenCentral() + maven { url "http://repo.springsource.org/libs-snapshot" } + maven { url 'http://repo.springsource.org/plugins-release' } +} + +dependencies { + 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' + } +compile("log4j:log4j:1.2.15") { + exclude module: 'jms' + exclude module: 'jmx' + exclude module: 'jmxtools' + exclude module: 'jmxri' + + } + + compile "commons-logging:commons-logging:1.1.1" +} + +task wrapper(type: Wrapper) { + description = 'Generates gradlew[.bat] scripts' + gradleVersion = '1.3' +} + +eclipse { + project { + name = "spring-integration-kafka-sample" + } +} + +defaultTasks 'clean', 'build' diff --git a/samples/kafka/gradle.properties b/samples/kafka/gradle.properties new file mode 100644 index 0000000..d03e972 --- /dev/null +++ b/samples/kafka/gradle.properties @@ -0,0 +1,7 @@ +springVersion = 3.1.3.RELEASE +springIntegrationVersion = 2.2.0.RELEASE +springIntegrationKafkaVersion = 0.5.0.BUILD-SNAPSHOT +version = 0.5.0.BUILD-SNAPSHOT + + + diff --git a/samples/kafka/gradle/wrapper/gradle-wrapper.jar b/samples/kafka/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e3b3376e6002f9d44543761c8d3dcd199eff1d82 GIT binary patch literal 39860 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%0A3!wsG4uToG`VrIW5Id|4fPyx?=aMz8G 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" +APP_HOME="`pwd -P`" +cd "$SAVED" + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query businessSystem maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/samples/kafka/gradlew.bat b/samples/kafka/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/samples/kafka/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/kafka/src/main/java/org/springframework/integration/samples/kafka/inbound/InboundRunner.java b/samples/kafka/src/main/java/org/springframework/integration/samples/kafka/inbound/InboundRunner.java new file mode 100644 index 0000000..0fdf9a8 --- /dev/null +++ b/samples/kafka/src/main/java/org/springframework/integration/samples/kafka/inbound/InboundRunner.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2013 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.samples.kafka.inbound; + +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public class InboundRunner { + private static final String CONFIG = "kafkaInboundAdapterParserTests-context.xml"; + + public static void main(final String args[]) { + final ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(CONFIG, InboundRunner.class); + ctx.start(); + } +} diff --git a/samples/kafka/src/main/java/org/springframework/integration/samples/kafka/outbound/CustomPartitioner.java b/samples/kafka/src/main/java/org/springframework/integration/samples/kafka/outbound/CustomPartitioner.java new file mode 100644 index 0000000..eff5c22 --- /dev/null +++ b/samples/kafka/src/main/java/org/springframework/integration/samples/kafka/outbound/CustomPartitioner.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2013 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.samples.kafka.outbound; + +import kafka.producer.Partitioner; + +/** + * @author Soby Chacko + * + * This class is for internal use only and therefore is at default access level + */ +class CustomPartitioner implements Partitioner { + /** + * Uses the key to calculate a partition bucket id for routing + * the data to the appropriate broker partition + * @return an integer between 0 and numPartitions-1 + */ + @Override + public int partition(final T key, final int numPartitions) { + final String s = (String) key; + final Integer i = Integer.parseInt(s); + return i % numPartitions; + } +} 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 new file mode 100644 index 0000000..fcb1c35 --- /dev/null +++ b/samples/kafka/src/main/java/org/springframework/integration/samples/kafka/outbound/OutboundRunner.java @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2013 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.samples.kafka.outbound; + +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.support.MessageBuilder; + +public class OutboundRunner { + private static final String CONFIG = "kafkaOutboundAdapterParserTests-context.xml"; + private static final Log LOG = LogFactory.getLog(OutboundRunner.class); + + public static void main(final String args[]) { + final ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(CONFIG, OutboundRunner.class); + ctx.start(); + + final MessageChannel channel = ctx.getBean("inputToKafka", MessageChannel.class); + LOG.info(channel.getClass()); + + //sending 100,000 messages to Kafka server for topic test1 + for (int i = 0; i < 500; i++) { + channel.send( + MessageBuilder.withPayload("hello Fom ob adapter test1 - " + i) + .setHeader("messageKey", String.valueOf(i)) + .setHeader("topic", "test1").build()); + + LOG.info("message sent " + i); + } + + //sending 5,000 messages to kafka server for topic test2 + for (int i = 0; i < 50; i++) { + channel.send( + MessageBuilder.withPayload("hello Fom ob adapter test2 - " + i) + .setHeader("messageKey", String.valueOf(i)) + .setHeader("topic", "test2").build()); + + LOG.info("message sent " + i); + } + } +} diff --git a/samples/kafka/src/main/java/org/springframework/integration/samples/kafka/outbound/PartitionlessTransformer.java b/samples/kafka/src/main/java/org/springframework/integration/samples/kafka/outbound/PartitionlessTransformer.java new file mode 100644 index 0000000..49470ad --- /dev/null +++ b/samples/kafka/src/main/java/org/springframework/integration/samples/kafka/outbound/PartitionlessTransformer.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2013 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.samples.kafka.outbound; + +import org.springframework.integration.Message; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.transformer.Transformer; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Soby Chacko + */ +public class PartitionlessTransformer implements Transformer { + @Override + @SuppressWarnings("unchecked") + public Message transform(final Message message) { + + final Map>> origData = + (Map>>) message.getPayload(); + + final Map> nonPartitionedData = new HashMap>(); + + for(final String topic : origData.keySet()) { + final Map> partitionedData = origData.get(topic); + final Collection> nonPartitionedDataFromTopic = partitionedData.values(); + + final List mergedList = new ArrayList(); + + for (final List l : nonPartitionedDataFromTopic){ + mergedList.addAll(l); + } + + nonPartitionedData.put(topic, mergedList); + } + + return MessageBuilder.withPayload(nonPartitionedData).build(); + } +} diff --git a/samples/kafka/src/main/resources/log4j.properties b/samples/kafka/src/main/resources/log4j.properties new file mode 100644 index 0000000..3c84b67 --- /dev/null +++ b/samples/kafka/src/main/resources/log4j.properties @@ -0,0 +1,10 @@ +log4j.rootCategory=WARN, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss.SSS} %-5p [%t][%c] %m%n + +#log4j.category.org.springframework.integration=WARN +log4j.category.org.springframework.integration.kafka=INFO +log4j.category.org.springframework.integration.samples.kafka=INFO +log4j.category.kafka.consumer=ERROR 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 new file mode 100644 index 0000000..a6e97d2 --- /dev/null +++ b/samples/kafka/src/main/resources/org/springframework/integration/samples/kafka/inbound/kafkaInboundAdapterParserTests-context.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 new file mode 100644 index 0000000..2c3926b --- /dev/null +++ b/samples/kafka/src/main/resources/org/springframework/integration/samples/kafka/outbound/kafkaOutboundAdapterParserTests-context.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-kafka/.gitignore b/spring-integration-kafka/.gitignore new file mode 100644 index 0000000..0c7fdb9 --- /dev/null +++ b/spring-integration-kafka/.gitignore @@ -0,0 +1,8 @@ +.classpath +.project +.settings/ +.gradle +build +target/ +bin/ +derby.log diff --git a/spring-integration-kafka/README.md b/spring-integration-kafka/README.md new file mode 100644 index 0000000..f2cf2d4 --- /dev/null +++ b/spring-integration-kafka/README.md @@ -0,0 +1,294 @@ +Spring Integration Kafka Adapter +================================================= + + +Welcome to the *Spring Integration Kafka adapter*. Apache Kafka is a distributed publish-subscribe messaging system that is designed for handling terra bytes of high throughput +data at constant time. For more information on Kafka and its design goals, please see [Kafka main page](http://kafka.apache.org/) + +Spring Integration Kafka adapters are built for Kafka 0.8 and since 0.8 is not backward compatible with any previous versions, Spring Integration will not +support any Kafka versions prior to 0.8. As of this writing, Kafka 0.8 is still WIP. + +Checking out and building +----------------------------- + +Currently Spring Integration Kafka adapter is built against kafka 0.8 that is backed by +Scala 2.9.2. + +In order to build the project: + + ./gradlew build + +In order to install this into your local maven cache: + + ./gradlew install + +Spring Integration Kafka project currently supports the following two components. Please keep in mind that +this is very early stage in development and do not yet fully make use of all the features that Kafka provides. + +* Outbound Channel Adapter +* Inbound Channel Adapter based on the High level consumer API + +Outbound Channel Adapter: +-------------------------------------------- + +The Outbound channel adapter is used to send messages to Kafka. Messages are read from a Spring Integration channel. One can specify this channel in the application context and then wire +this in the application where messages are sent to kafka. + +Once a channel is configured, then messages can be sent to Kafka through this channel. Obviously, Spring Integration specific messages are sent to the adapter and then it will +internally get converted into Kafka messages before sending. In the current version of the outbound adapter, +you have to specify a message key and the topic as header values and the message to send as the payload. +Here is an example. + +```java + final MessageChannel channel = ctx.getBean("inputToKafka", MessageChannel.class); + + channel.send( + MessageBuilder.withPayload(payload). + setHeader("messageKey", "key") + .setHeader("topic", "test").build()); +``` + +This would create a message with a payload. In addition to this, it also creates two header entries as key/value pairs - one for +the message key and another for the topic that this message belongs to. + +Here is how kafka outbound channel adapter is configured: + +```xml + + + +``` + +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. +A channel in which messages are arriving is configured with the adapter and therefore +any message sent to that channel will be handled by this adapter. You can also configure a poller +depending on the +type of the channel used. For example, in the above configuration, we use a queue based channel +and thus a poller is configured with a task executor. If no messages are available in the queue it will timeout immediately because of +the receive-timeout configuration. Then it will poll again with a delay of 1 second. + +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 +indicates, a context for the Kafa producer. It contains one ore more producer configurations. Each producer configuration +is ultimately gets translated into a Kafka native producer. Each producer configuration is per topic based right now. +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. + +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 +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 +for serialization in the big data spectrum. If no encoders are specified as beans, the default encoders provided +by Kafka will be used. On that not, if the encoder is configured only for the message and not for the key, the same encoder +will be used for both. These are standard Kafka behaviors. Spring Integration Kafka adapter does simply enforce those behaviours. +Kafka default encoder expects the data to come as byte arrays and it is a no-op encoder, i.e. it just takes the byte array as it is. +When default encoders are used, there are two ways a message can be sent. +Either, the sender of the message to the channel +can simply put byte arrays as message key and payload. Or, the key and value can be sent as Java Serializable object. +In the latter case, the Kafka adapter will automatically convert them to byte arrays before sending to Kafka broker. +If the encoders are default and the objets sent are not serializalbe, then that would cause an error. By providing explicit encoders +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. + +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 + direct Java client that talks to Kafka. +However, when using Spring Integration Kafka adapter, it introduces unnecessary steps to create these +properties objects. Therefore, we provide a wrapper class for this same StringEncoder as part of the SI kafka support, which makes +using it from Spring a bit easier. You can inject +any properties to it in the Spring way. Kafka StringEncoder looks at a specific property for the type of encoding scheme used. +In the wrapper bean provided, this property can simply be injected as a value without constructing any other objects. Spring Integration provided StringEncoder is available +in the package org.springframework.integration.kafka.serializer.common.StringEncoder. The avro support for serialization is +also available in a package called avro under serializer. + +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 +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 +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. + +```xml + + + +``` + +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 +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. + +Inbound Kafka Adapter must specify a kafka-consumer-context-ref element and here is how it is configured: + +```xml + + + + + + + + +``` + +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 +zookeeper counter-part attributes by the consumer. + +In the above consumer context, you can also specify a consumer-timeout value which would be used to +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 +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, +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-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. + +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 + 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. +If you have less number of streams than the available partitions, then messages from +multiple partitions will be sent to available streams. +Therefore, it is a good practice to limit the number of streams for a topic in the consumer +configuration to the number of partitions configured for the topic. There may be situations +in which a partition may be gone during runtime and in that case the stream receiving +data from the partition will simply timeout and whenever this partition comes back, +it would start read data from it again. + +Consumer configuration can also be configured with optional decoders for key and value. +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. + +```xml + + + +``` + +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, +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. +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: + +```java +Map>> +``` + +It is a java.util.Map that contains the topic string consumed as the key and another Map as the value. +The inner map's key is the stream (partition) number and value is a list of message payloads. +The reason for this complex return type is +due to the way Kafka orders the messages. In the high level consumer, +all the messages received in a single stream for a single partition +are guaranteed to be in order. For example, if I have a topic named test configured with +4 partitions and I have 4 corresponding streams +in the consumer, then I would receive data in all the consumer streams in the same order +as they were put in the corresponding partitions. This is another reason to set the number of +consumer streams for a topic same +as the number of broker partitions configured for that topic. Lets say that the number of streams +are less than the number of partitions. Then, normally, there is no +guarantee for any order other than just the fact that a single stream will contain messages +from multiple partitions and the messages from a given single partition received will +still be kept contiguously. By that time probably there is no way to find out which set of messages came from which partition. +By providing this complex map that contains the partition information for the topic, we make sure that the order sent by the producer +is preserved even if the number of streams used was less than the number of broker partitions. + +A downstream component which receives the data from the inbound adapter can cast the SI payload to the above +Map. + +If your use case does not require ordering of messages during consumption, then you can easily pass this +payload to a standard SI transformer and just get a full dump of the actual payload sent by Kafka. + + + + + diff --git a/spring-integration-kafka/build.gradle b/spring-integration-kafka/build.gradle new file mode 100644 index 0000000..b4da5f7 --- /dev/null +++ b/spring-integration-kafka/build.gradle @@ -0,0 +1,304 @@ +description = 'Spring Integration Kafka Adapter' + +buildscript { + repositories { + maven { url 'https://repo.springsource.org/plugins-snapshot' } + } + dependencies { + classpath 'org.springframework.build.gradle:docbook-reference-plugin:0.1.5' + } +} + +apply plugin: 'java' +apply from: "${rootProject.projectDir}/publish-maven.gradle" +apply plugin: 'eclipse' +apply plugin: 'idea' + +group = 'org.springframework.integration' + +repositories { + mavenLocal() + mavenCentral() + maven { url 'http://repo.springsource.org/libs-milestone' } + maven { url 'http://repo.springsource.org/plugins-release' } + maven { url 'https://repo.springsource.org/libs-snapshot' } +} + +sourceCompatibility=1.6 +targetCompatibility=1.6 + +ext { + junitVersion = '4.10' + log4jVersion = '1.2.12' + mockitoVersion = '1.9.0' + springVersion = '3.1.3.RELEASE' + springIntegrationVersion = '2.2.0.RELEASE' + + idPrefix = 'kafka' +} + +eclipse { + project { + natures += 'org.springframework.ide.eclipse.core.springnature' + } +} + +sourceSets { + test { + resources { + srcDirs = ['src/test/resources', 'src/test/java'] + } + } +} + +// See http://www.gradle.org/docs/current/userguide/dependency_management.html#sub:configurations +// and http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.ConfigurationContainer.html +configurations { + jacoco //Configuration Group used by Sonar to provide Code Coverage using JaCoCo +} + +dependencies { + compile "org.springframework:spring-beans:$springVersion" + compile "org.springframework:spring-context:$springVersion" + compile "org.springframework:spring-expression:$springVersion" + compile "org.springframework.integration:spring-integration-core:$springIntegrationVersion" + compile("org.apache:kafka_2.9.2:0.8.0-SNAPSHOT") { + exclude module: 'log4j' + } + compile("org.apache.zookeeper:zookeeper:3.3.4") { + exclude module: 'log4j' + exclude module: 'jline' + } + compile "org.scala-lang:scala-library:2.9.2" + compile "org.scala-lang:scala-compiler:2.9.2" + compile("log4j:log4j:1.2.15") { + exclude module: 'jms' + exclude module: 'jmx' + exclude module: 'jmxtools' + exclude module: 'jmxri' + + } + compile "org.slf4j:slf4j-simple:1.6.4" + compile "net.sf.jopt-simple:jopt-simple:3.2" + compile "org.xerial.snappy:snappy-java:1.0.4.1" + //runtime "zkclient:zkclient:20120522" + + runtime "com.101tec:zkclient:0.2" + runtime "com.yammer.metrics:metrics-core:2.2.0" + runtime "com.yammer.metrics:metrics-annotation:2.2.0" + + compile "org.apache.avro:avro:1.7.3" + compile "org.apache.avro:avro-compiler:1.7.3" + + testCompile "org.springframework.integration:spring-integration-test:$springIntegrationVersion" + testCompile "junit:junit-dep:$junitVersion" + testCompile("log4j:log4j:$log4jVersion") { + exclude module: 'jms' + exclude module: 'jmx' + exclude module: 'jmxtools' + exclude module: 'jmxri' +} + testCompile "org.mockito:mockito-all:$mockitoVersion" + testCompile "org.springframework:spring-test:$springVersion" + testCompile "org.springframework.integration:spring-integration-stream:$springIntegrationVersion" + jacoco group: "org.jacoco", name: "org.jacoco.agent", version: "0.5.6.201201232323", classifier: "runtime" +} + + +// enable all compiler warnings; individual projects may customize further +ext.xLintArg = '-Xlint:all' +[compileJava, compileTestJava]*.options*.compilerArgs = [xLintArg] + +test { + // suppress all console output during testing unless running `gradle -i` + logging.captureStandardOutput(LogLevel.INFO) + jvmArgs "-javaagent:${configurations.jacoco.asPath}=destfile=${buildDir}/jacoco.exec,includes=*" +} + +task sourcesJar(type: Jar) { + classifier = 'sources' + from sourceSets.main.allJava +} + +task javadocJar(type: Jar) { + classifier = 'javadoc' + from javadoc +} + +artifacts { + archives sourcesJar + archives javadocJar +} + +apply plugin: 'docbook-reference' + +reference { + sourceDir = file('src/reference/docbook') +} + +apply plugin: 'sonar' + +sonar { + + if (rootProject.hasProperty('sonarHostUrl')) { + server.url = rootProject.sonarHostUrl + } + + database { + if (rootProject.hasProperty('sonarJdbcUrl')) { + url = rootProject.sonarJdbcUrl + } + if (rootProject.hasProperty('sonarJdbcDriver')) { + driverClassName = rootProject.sonarJdbcDriver + } + if (rootProject.hasProperty('sonarJdbcUsername')) { + username = rootProject.sonarJdbcUsername + } + if (rootProject.hasProperty('sonarJdbcPassword')) { + password = rootProject.sonarJdbcPassword + } + } + + project { + dynamicAnalysis = "reuseReports" + withProjectProperties { props -> + props["sonar.core.codeCoveragePlugin"] = "jacoco" + props["sonar.jacoco.reportPath"] = "${buildDir.name}/jacoco.exec" + } + } + + logger.info("Sonar parameters used: server.url='${server.url}'; database.url='${database.url}'; database.driverClassName='${database.driverClassName}'; database.username='${database.username}'") +} + +task api(type: Javadoc) { + group = 'Documentation' + description = 'Generates the Javadoc API documentation.' + title = "${rootProject.description} ${version} API" + options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED + options.author = true + options.header = rootProject.description + options.overview = 'src/api/overview.html' + + source = sourceSets.main.allJava + classpath = project.sourceSets.main.compileClasspath + destinationDir = new File(buildDir, "api") +} + +task schemaZip(type: Zip) { + group = 'Distribution' + classifier = 'schema' + description = "Builds -${classifier} archive containing all " + + "XSDs for deployment at static.springframework.org/schema." + + def Properties schemas = new Properties(); + def shortName = idPrefix.replaceFirst("${idPrefix}-", '') + + project.sourceSets.main.resources.find { + it.path.endsWith('META-INF/spring.schemas') + }?.withInputStream { schemas.load(it) } + + for (def key : schemas.keySet()) { + File xsdFile = project.sourceSets.main.resources.find { + it.path.endsWith(schemas.get(key)) + } + assert xsdFile != null + into ("integration/${shortName}") { + from xsdFile.path + } + } + +} + +task docsZip(type: Zip) { + group = 'Distribution' + classifier = 'docs' + description = "Builds -${classifier} archive containing api and reference " + + "for deployment at static.springframework.org/spring-integration/docs." + + from('src/dist') { + include 'changelog.txt' + } + + from (api) { + into 'api' + } + + from (reference) { + into 'reference' + } +} + +task distZip(type: Zip, dependsOn: [docsZip, schemaZip]) { + group = 'Distribution' + classifier = 'dist' + description = "Builds -${classifier} archive, containing all jars and docs, " + + "suitable for community download page." + + ext.baseDir = "${project.name}-${project.version}"; + + from('src/dist') { + include 'readme.txt' + include 'license.txt' + include 'notice.txt' + into "${baseDir}" + } + + from(zipTree(docsZip.archivePath)) { + into "${baseDir}/docs" + } + + from(zipTree(schemaZip.archivePath)) { + into "${baseDir}/schema" + } + + into ("${baseDir}/libs") { + from project.jar + from project.sourcesJar + from project.javadocJar + } +} + +// Create an optional "with dependencies" distribution. +// Not published by default; only for use when building from source. +task depsZip(type: Zip, dependsOn: distZip) { zipTask -> + group = 'Distribution' + classifier = 'dist-with-deps' + description = "Builds -${classifier} archive, containing everything " + + "in the -${distZip.classifier} archive plus all dependencies." + + from zipTree(distZip.archivePath) + + gradle.taskGraph.whenReady { taskGraph -> + if (taskGraph.hasTask(":${zipTask.name}")) { + def projectName = rootProject.name + def artifacts = new HashSet() + + rootProject.configurations.runtime.resolvedConfiguration.resolvedArtifacts.each { artifact -> + def dependency = artifact.moduleVersion.id + if (!projectName.equals(dependency.name)) { + artifacts << artifact.file + } + } + + zipTask.from(artifacts) { + into "${distZip.baseDir}/deps" + } + } + } +} + +artifacts { + archives distZip + archives docsZip + archives schemaZip +} + +task dist(dependsOn: assemble) { + group = 'Distribution' + description = 'Builds -dist, -docs and -schema distribution archives.' +} + +task wrapper(type: Wrapper) { + description = 'Generates gradlew[.bat] scripts' + gradleVersion = '1.3' +} diff --git a/spring-integration-kafka/gradle.properties b/spring-integration-kafka/gradle.properties new file mode 100644 index 0000000..5364470 --- /dev/null +++ b/spring-integration-kafka/gradle.properties @@ -0,0 +1 @@ +version=0.5.0.BUILD-SNAPSHOT diff --git a/spring-integration-kafka/gradle/wrapper/gradle-wrapper.jar b/spring-integration-kafka/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..81dcde627d5cfbc12c96dc5113aac79b66a15118 GIT binary patch literal 39770 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" +APP_HOME="`pwd -P`" +cd "$SAVED" + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query businessSystem maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/spring-integration-kafka/gradlew.bat b/spring-integration-kafka/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/spring-integration-kafka/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/spring-integration-kafka/publish-maven.gradle b/spring-integration-kafka/publish-maven.gradle new file mode 100644 index 0000000..5532ec4 --- /dev/null +++ b/spring-integration-kafka/publish-maven.gradle @@ -0,0 +1,61 @@ +apply plugin: 'maven' + +ext.optionalDeps = [] +ext.providedDeps = [] + +ext.optional = { optionalDeps << it } +ext.provided = { providedDeps << it } + +install { + repositories.mavenInstaller { + customizePom(pom, project) + } +} + +def customizePom(pom, gradleProject) { + pom.whenConfigured { generatedPom -> + // respect 'optional' and 'provided' dependencies + gradleProject.optionalDeps.each { dep -> + generatedPom.dependencies.find { it.artifactId == dep.name }?.optional = true + } + gradleProject.providedDeps.each { dep -> + generatedPom.dependencies.find { it.artifactId == dep.name }?.scope = 'provided' + } + + // eliminate test-scoped dependencies (no need in maven central poms) + generatedPom.dependencies.removeAll { dep -> + dep.scope == 'test' + } + + // add all items necessary for maven central publication + generatedPom.project { + name = gradleProject.description + description = gradleProject.description + url = 'https://github.com/SpringSource/spring-integration-extensions' + organization { + name = 'SpringSource' + url = 'http://springsource.org' + } + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' + } + } + scm { + url = 'https://github.com/SpringSource/spring-integration-extensions' + connection = 'scm:git:git://github.com/SpringSource/spring-integration-extensions' + developerConnection = 'scm:git:git://github.com/SpringSource/spring-integration-extensions' + } + + developers { + developer { + id = 'not specified' + name = 'Soby Chacko' + email = 'not specified' + } + } + } + } +} diff --git a/spring-integration-kafka/src/api/overview.html b/spring-integration-kafka/src/api/overview.html new file mode 100644 index 0000000..d9e5faa --- /dev/null +++ b/spring-integration-kafka/src/api/overview.html @@ -0,0 +1,22 @@ + + +This document is the API specification for Spring Integration Kafka Extension +
+
+

+ For further API reference and developer documentation, see the + Spring + Integration reference documentation. + That documentation contains more detailed, developer-targeted + descriptions, with conceptual overviews, definitions of terms, + workarounds, and working code examples. +

+ +

+ If you are interested in commercial training, consultancy, and + support for Spring Integration, please visit + http://www.springsource.com +

+
+ + diff --git a/spring-integration-kafka/src/dist/license.txt b/spring-integration-kafka/src/dist/license.txt new file mode 100644 index 0000000..c94ec85 --- /dev/null +++ b/spring-integration-kafka/src/dist/license.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by testData1) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class testData1 and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [testData1 of copyright owner] + + 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. diff --git a/spring-integration-kafka/src/dist/notice.txt b/spring-integration-kafka/src/dist/notice.txt new file mode 100644 index 0000000..f62045a --- /dev/null +++ b/spring-integration-kafka/src/dist/notice.txt @@ -0,0 +1,21 @@ + ======================================================================== + == NOTICE file corresponding to section 4 d of the Apache License, == + == Version 2.0, in this case for the Spring Integration distribution. == + ======================================================================== + + This product includes software developed by + the Apache Software Foundation (http://www.apache.org). + + The end-user documentation included with a redistribution, if any, + must include the following acknowledgement: + + "This product includes software developed by the Spring Framework + Project (http://www.springframework.org)." + + Alternatively, this acknowledgement may appear in the software itself, + if and wherever such third-party acknowledgements normally appear. + + The names "Spring", "Spring Framework", and "Spring Integration" must + not be used to endorse or promote products derived from this software + without prior written permission. For written permission, please contact + enquiries@springsource.com. 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 new file mode 100644 index 0000000..3eac811 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaConsumerContextParser.java @@ -0,0 +1,122 @@ +/* + * Copyright 2002-2013 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.config.xml; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.kafka.support.ConsumerConfigFactoryBean; +import org.springframework.integration.kafka.support.ConsumerConfiguration; +import org.springframework.integration.kafka.support.ConsumerConnectionProvider; +import org.springframework.integration.kafka.support.ConsumerMetadata; +import org.springframework.integration.kafka.support.KafkaConsumerContext; +import org.springframework.integration.kafka.support.MessageLeftOverTracker; +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 + */ +public class KafkaConsumerContextParser extends AbstractSingleBeanDefinitionParser { + + @Override + protected Class getBeanClass(final Element element) { + return KafkaConsumerContext.class; + } + + @Override + protected void doParse(final Element element, final ParserContext parserContext, final BeanDefinitionBuilder builder) { + super.doParse(element, parserContext, builder); + + final Element consumerConfigurations = DomUtils.getChildElementByTagName(element, "consumer-configurations"); + parseConsumerConfigurations(consumerConfigurations, parserContext, builder, element); + } + + private void parseConsumerConfigurations(final Element consumerConfigurations, final ParserContext parserContext, + final BeanDefinitionBuilder builder, final Element parentElem) { + for (final Element consumerConfiguration : DomUtils.getChildElementsByTagName(consumerConfigurations, "consumer-configuration")) { + final BeanDefinitionBuilder consumerConfigurationBuilder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerConfiguration.class); + final BeanDefinitionBuilder consumerMetadataBuilder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerMetadata.class); + + IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerMetadataBuilder, consumerConfiguration, "group-id"); + + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(consumerMetadataBuilder, consumerConfiguration, "value-decoder"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(consumerMetadataBuilder, consumerConfiguration, "key-decoder"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerMetadataBuilder, consumerConfiguration, "key-class-type"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerMetadataBuilder, consumerConfiguration, "value-class-type"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerConfigurationBuilder, consumerConfiguration, "max-messages"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerMetadataBuilder, parentElem, "consumer-timeout"); + + final Map topicStreamsMap = new HashMap(); + + for (final Element topicConfiguration : DomUtils.getChildElementsByTagName(consumerConfiguration, "topic")) { + final String topic = topicConfiguration.getAttribute("id"); + final String streams = topicConfiguration.getAttribute("streams"); + final Integer streamsInt = Integer.valueOf(streams); + topicStreamsMap.put(topic, streamsInt); + } + + consumerMetadataBuilder.addPropertyValue("topicStreamMap", topicStreamsMap); + + final BeanDefinition consumerMetadataBeanDef = consumerMetadataBuilder.getBeanDefinition(); + registerBeanDefinition(new BeanDefinitionHolder(consumerMetadataBeanDef, "consumerMetadata_" + consumerConfiguration.getAttribute("group-id")), + parserContext.getRegistry()); + + final String zookeeperConnectBean = parentElem.getAttribute("zookeeper-connect"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, parentElem, zookeeperConnectBean); + + final BeanDefinitionBuilder consumerConfigFactoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerConfigFactoryBean.class); + consumerConfigFactoryBuilder.addConstructorArgReference("consumerMetadata_" + consumerConfiguration.getAttribute("group-id")); + + if (StringUtils.hasText(zookeeperConnectBean)) { + consumerConfigFactoryBuilder.addConstructorArgReference(zookeeperConnectBean); + } + + final BeanDefinition consumerConfigFactoryBuilderBeanDefinition = consumerConfigFactoryBuilder.getBeanDefinition(); + registerBeanDefinition(new BeanDefinitionHolder(consumerConfigFactoryBuilderBeanDefinition, "consumerConfigFactory_" + consumerConfiguration.getAttribute("group-id")), parserContext.getRegistry()); + + final BeanDefinitionBuilder consumerConnectionProviderBuilder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerConnectionProvider.class); + consumerConnectionProviderBuilder.addConstructorArgReference("consumerConfigFactory_" + consumerConfiguration.getAttribute("group-id")); + + final BeanDefinition consumerConnectionProviderBuilderBeanDefinition = consumerConnectionProviderBuilder.getBeanDefinition(); + registerBeanDefinition(new BeanDefinitionHolder(consumerConnectionProviderBuilderBeanDefinition, "consumerConnectionProvider_" + consumerConfiguration.getAttribute("group-id")), parserContext.getRegistry()); + + + final BeanDefinitionBuilder messageLeftOverBeanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(MessageLeftOverTracker.class); + final BeanDefinition messageLeftOverBeanDefinition = messageLeftOverBeanDefinitionBuilder.getBeanDefinition(); + registerBeanDefinition(new BeanDefinitionHolder(messageLeftOverBeanDefinition, "messageLeftOver_" + consumerConfiguration.getAttribute("group-id")), + parserContext.getRegistry()); + + consumerConfigurationBuilder.addConstructorArgReference("consumerMetadata_" + consumerConfiguration.getAttribute("group-id")); + consumerConfigurationBuilder.addConstructorArgReference("consumerConnectionProvider_" + consumerConfiguration.getAttribute("group-id")); + consumerConfigurationBuilder.addConstructorArgReference("messageLeftOver_" + consumerConfiguration.getAttribute("group-id")); + + final AbstractBeanDefinition consumerConfigurationBeanDefinition = consumerConfigurationBuilder.getBeanDefinition(); + + final String consumerConfigurationBeanName = "consumerConfiguration_" + consumerConfiguration.getAttribute("group-id"); + registerBeanDefinition(new BeanDefinitionHolder(consumerConfigurationBeanDefinition, consumerConfigurationBeanName), + parserContext.getRegistry()); + } + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaInboundChannelAdapterParser.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaInboundChannelAdapterParser.java new file mode 100644 index 0000000..cc52b1e --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaInboundChannelAdapterParser.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2013 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.config.xml; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.kafka.inbound.KafkaHighLevelConsumerMessageSource; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * The Kafka Inbound Channel adapter parser + * + * @author Soby Chacko + * + */ +public class KafkaInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser { + @Override + protected BeanMetadataElement parseSource(final Element element, final ParserContext parserContext) { + final BeanDefinitionBuilder highLevelConsumerMessageSourceBuilder = + BeanDefinitionBuilder.genericBeanDefinition(KafkaHighLevelConsumerMessageSource.class); + + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(highLevelConsumerMessageSourceBuilder, element, "kafka-decoder"); + + final String kafkaConsumerContext = element.getAttribute("kafka-consumer-context-ref"); + + if (StringUtils.hasText(kafkaConsumerContext)) { + highLevelConsumerMessageSourceBuilder.addConstructorArgReference(kafkaConsumerContext); + } + + return highLevelConsumerMessageSourceBuilder.getBeanDefinition(); + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaNamespaceHandler.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaNamespaceHandler.java new file mode 100644 index 0000000..92d7823 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaNamespaceHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2013 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.config.xml; + +import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler; + +/** + * The namespace handler for the Kafka namespace + * + * @author Soby Chacko + * + */ +public class KafkaNamespaceHandler extends AbstractIntegrationNamespaceHandler { + /* (non-Javadoc) + * @see org.springframework.beans.factory.xml.NamespaceHandler#init() + */ + @Override + public void init() { + registerBeanDefinitionParser("zookeeper-connect", new ZookeeperConnectParser()); + registerBeanDefinitionParser("inbound-channel-adapter", new KafkaInboundChannelAdapterParser()); + registerBeanDefinitionParser("outbound-channel-adapter", new KafkaOutboundChannelAdapterParser()); + registerBeanDefinitionParser("producer-context", new KafkaProducerContextParser()); + registerBeanDefinitionParser("consumer-context", new KafkaConsumerContextParser()); + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaOutboundChannelAdapterParser.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaOutboundChannelAdapterParser.java new file mode 100644 index 0000000..714dad3 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaOutboundChannelAdapterParser.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2013 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.config.xml; + +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; +import org.springframework.integration.kafka.outbound.KafkaProducerMessageHandler; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * + * @author Soby Chacko + * + */ +public class KafkaOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { + @Override + protected AbstractBeanDefinition parseConsumer(final Element element, final ParserContext parserContext) { + final BeanDefinitionBuilder kafkaProducerMessageHandlerBuilder = + BeanDefinitionBuilder.genericBeanDefinition(KafkaProducerMessageHandler.class); + + final String kafkaServerBeanName = element.getAttribute("kafka-producer-context-ref"); + + if (StringUtils.hasText(kafkaServerBeanName)) { + kafkaProducerMessageHandlerBuilder.addConstructorArgReference(kafkaServerBeanName); + } + + return kafkaProducerMessageHandlerBuilder.getBeanDefinition(); + } +} 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 new file mode 100644 index 0000000..02ed893 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaProducerContextParser.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2013 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.config.xml; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.kafka.support.KafkaProducerContext; +import org.springframework.integration.kafka.support.ProducerConfiguration; +import org.springframework.integration.kafka.support.ProducerFactoryBean; +import org.springframework.integration.kafka.support.ProducerMetadata; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +/** + * @author Soby Chacko + */ +public class KafkaProducerContextParser extends AbstractSimpleBeanDefinitionParser { + @Override + protected Class getBeanClass(final Element element) { + return KafkaProducerContext.class; + } + + @Override + protected void doParse(final Element element, final ParserContext parserContext, final BeanDefinitionBuilder builder) { + super.doParse(element, parserContext, builder); + + final Element topics = DomUtils.getChildElementByTagName(element, "producer-configurations"); + parseProducerConfigurations(topics, parserContext); + } + + 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); + + final BeanDefinitionBuilder producerMetadataBuilder = BeanDefinitionBuilder.genericBeanDefinition(ProducerMetadata.class); + producerMetadataBuilder.addConstructorArgValue(producerConfiguration.getAttribute("topic")); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "value-encoder"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "key-encoder"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "key-class-type"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "value-class-type"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "partitioner"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "compression-codec"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "async"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "batch-num-messages"); + + final BeanDefinition producerMetadataBeanDef = producerMetadataBuilder.getBeanDefinition(); + registerBeanDefinition(new BeanDefinitionHolder(producerMetadataBeanDef, "producerMetadata_" + producerConfiguration.getAttribute("topic")), + parserContext.getRegistry()); + + final BeanDefinitionBuilder producerFactoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(ProducerFactoryBean.class); + producerFactoryBuilder.addConstructorArgReference("producerMetadata_" + producerConfiguration.getAttribute("topic")); + + final String brokerList = producerConfiguration.getAttribute("broker-list"); + if (StringUtils.hasText(brokerList)) { + producerFactoryBuilder.addConstructorArgValue(producerConfiguration.getAttribute("broker-list")); + } + + final BeanDefinition producerfactoryBeanDefinition = producerFactoryBuilder.getBeanDefinition(); + registerBeanDefinition(new BeanDefinitionHolder(producerfactoryBeanDefinition, "prodFactory_" + producerConfiguration.getAttribute("topic")), parserContext.getRegistry()); + + producerConfigurationBuilder.addConstructorArgReference("producerMetadata_" + producerConfiguration.getAttribute("topic")); + producerConfigurationBuilder.addConstructorArgReference("prodFactory_" + producerConfiguration.getAttribute("topic")); + + final AbstractBeanDefinition producerConfigurationBeanDefinition = producerConfigurationBuilder.getBeanDefinition(); + final String producerConfigurationBeanName = "producerConfiguration_" + producerConfiguration.getAttribute("topic"); + registerBeanDefinition(new BeanDefinitionHolder(producerConfigurationBeanDefinition, producerConfigurationBeanName), + parserContext.getRegistry()); + } + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/ZookeeperConnectParser.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/ZookeeperConnectParser.java new file mode 100644 index 0000000..cac01e9 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/ZookeeperConnectParser.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2013 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.config.xml; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; +import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.kafka.support.ZookeeperConnect; +import org.w3c.dom.Element; + +/** + * @author Soby Chacko + */ +public class ZookeeperConnectParser extends AbstractSimpleBeanDefinitionParser { + @Override + protected Class getBeanClass(final Element element) { + return ZookeeperConnect.class; + } + + @Override + protected void doParse(final Element element, final ParserContext parserContext, final BeanDefinitionBuilder builder) { + super.doParse(element, parserContext, builder); + + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, + BeanDefinitionParserDelegate.SCOPE_ATTRIBUTE); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "zk-connect"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "zk-connection-timeout"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "zk-session-timeout"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "zk-sync-time"); + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/package-info.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/package-info.java new file mode 100644 index 0000000..3289f40 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides parser classes to provide Xml namespace support for the Kafka components. + */ +package org.springframework.integration.kafka.config.xml; diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/KafkaConsumerDefaults.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/KafkaConsumerDefaults.java new file mode 100644 index 0000000..212678f --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/KafkaConsumerDefaults.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2013 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.core; + +/** + * Kafka adapter specific message headers. + * + * @author Soby Chacko + */ +public class KafkaConsumerDefaults { + //High level consumer + public static final String GROUP_ID = "groupid"; + public static final String SOCKET_TIMEOUT = "30000"; + public static final String SOCKET_BUFFER_SIZE = "64*1024"; + public static final String FETCH_SIZE = "300 * 1024"; + public static final String BACKOFF_INCREMENT = "1000"; + public static final String QUEUED_CHUNKS_MAX = "100"; + public static final String AUTO_COMMIT_ENABLE = "true"; + public static final String AUTO_COMMIT_INTERVAL = "10000"; + public static final String AUTO_OFFSET_RESET = "smallest"; + //Overriding the default value of -1, which will make the consumer to wait indefinitely + public static final String CONSUMER_TIMEOUT = "5000"; + public static final String REBALANCE_RETRIES_MAX = "4"; + + private KafkaConsumerDefaults() { + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/ZookeeperConnectDefaults.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/ZookeeperConnectDefaults.java new file mode 100644 index 0000000..11138d1 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/ZookeeperConnectDefaults.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2013 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.core; + +/** + * + * @author Soby Chacko + * + */ +public class ZookeeperConnectDefaults { + public static final String ZK_CONNECT = "localhost:2181"; + public static final String ZK_CONNECTION_TIMEOUT = "6000"; + public static final String ZK_SESSION_TIMEOUT = "6000"; + public static final String ZK_SYNC_TIME = "2000"; + + private ZookeeperConnectDefaults() { + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/package-info.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/package-info.java new file mode 100644 index 0000000..f404ec8 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides core classes of the Kafka module. + */ +package org.springframework.integration.kafka.core; 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 new file mode 100644 index 0000000..acab37b --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/inbound/KafkaHighLevelConsumerMessageSource.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2013 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.inbound; + +import org.springframework.integration.Message; +import org.springframework.integration.context.IntegrationObjectSupport; +import org.springframework.integration.core.MessageSource; +import org.springframework.integration.kafka.support.KafkaConsumerContext; + +import java.util.List; +import java.util.Map; + +/** + * @author Soby Chacko + * + */ +public class KafkaHighLevelConsumerMessageSource extends IntegrationObjectSupport implements MessageSource>>> { + + private final KafkaConsumerContext kafkaConsumerContext; + + public KafkaHighLevelConsumerMessageSource(final KafkaConsumerContext kafkaConsumerContext) { + this.kafkaConsumerContext = kafkaConsumerContext; + } + + @Override + public Message>>> receive() { + return kafkaConsumerContext.receive(); + } + + @Override + public String getComponentType() { + return "kafka:inbound-channel-adapter"; + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/inbound/package-info.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/inbound/package-info.java new file mode 100644 index 0000000..2688d7b --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/inbound/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides inbound Spring Integration Kafka components. + */ +package org.springframework.integration.kafka.inbound; diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/outbound/KafkaProducerMessageHandler.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/outbound/KafkaProducerMessageHandler.java new file mode 100644 index 0000000..ee4923b --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/outbound/KafkaProducerMessageHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2013 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.outbound; + +import org.springframework.integration.Message; +import org.springframework.integration.handler.AbstractMessageHandler; +import org.springframework.integration.kafka.support.KafkaProducerContext; + +/** + * @author Soby Chacko + */ +public class KafkaProducerMessageHandler extends AbstractMessageHandler { + + private final KafkaProducerContext kafkaProducerContext; + + public KafkaProducerMessageHandler(final KafkaProducerContext kafkaProducerContext) { + this.kafkaProducerContext = kafkaProducerContext; + } + + public KafkaProducerContext getKafkaProducerContext() { + return kafkaProducerContext; + } + + @Override + protected void handleMessageInternal(final Message message) throws Exception { + kafkaProducerContext.send(message); + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/package-info.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/package-info.java new file mode 100644 index 0000000..4847590 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/package-info.java @@ -0,0 +1,4 @@ +/** + * Root package of the Kafka Module. + */ +package org.springframework.integration.kafka; diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroBackedKafkaDecoder.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroBackedKafkaDecoder.java new file mode 100644 index 0000000..f131659 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroBackedKafkaDecoder.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2013 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.serializer.avro; + + +import kafka.serializer.Decoder; +import org.apache.avro.Schema; +import org.apache.avro.reflect.ReflectData; + +import java.io.IOException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author Soby Chacko + */ +public class AvroBackedKafkaDecoder implements Decoder { + private static final Log LOG = LogFactory.getLog(AvroBackedKafkaDecoder.class); + + private final Class clazz; + + public AvroBackedKafkaDecoder(final Class clazz) { + this.clazz = clazz; + } + + @Override + @SuppressWarnings("unchecked") + public T fromBytes(final byte[] bytes) { + final Schema schema = ReflectData.get().getSchema(clazz); + final AvroSerializer avroSerializer = new AvroSerializer(); + + try { + return (T) avroSerializer.deserialize(bytes, schema); + } catch (IOException e) { + LOG.error("Failed to decode byte array for schema: " + schema.getFullName(), e); + } + + return null; + } +} + diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroBackedKafkaEncoder.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroBackedKafkaEncoder.java new file mode 100644 index 0000000..8bc426b --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroBackedKafkaEncoder.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2013 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.serializer.avro; + +import kafka.serializer.Encoder; +import org.apache.avro.Schema; +import org.apache.avro.reflect.ReflectData; + +import java.io.IOException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author Soby Chacko + */ +public class AvroBackedKafkaEncoder implements Encoder { + private static final Log LOG = LogFactory.getLog(AvroBackedKafkaEncoder.class); + + private final Class clazz; + + public AvroBackedKafkaEncoder(final Class clazz) { + this.clazz = clazz; + } + + @Override + @SuppressWarnings("unchecked") + public byte[] toBytes(final T source) { + final Schema schema = ReflectData.get().getSchema(clazz); + final AvroSerializer avroSerializer = new AvroSerializer(); + + try { + return avroSerializer.serialize(source, schema); + } catch (IOException e) { + LOG.error("Failed to encode source for schema: " + schema.getFullName()); + } + + return null; + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroSerializer.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroSerializer.java new file mode 100644 index 0000000..c3e751c --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroSerializer.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2013 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.serializer.avro; + +import org.apache.avro.Schema; +import org.apache.avro.io.DatumReader; +import org.apache.avro.io.DatumWriter; +import org.apache.avro.io.Decoder; +import org.apache.avro.io.DecoderFactory; +import org.apache.avro.io.Encoder; +import org.apache.avro.io.EncoderFactory; +import org.apache.avro.reflect.ReflectDatumReader; +import org.apache.avro.reflect.ReflectDatumWriter; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * @author Soby Chacko + */ +public class AvroSerializer { + public T deserialize(final byte[] bytes, final Schema schema) throws IOException { + final Decoder decoder = DecoderFactory.get().binaryDecoder(bytes, null); + final DatumReader reader = new ReflectDatumReader(schema); + + return reader.read(null, decoder); + } + + public byte[] serialize(final T input, final Schema schema) throws IOException { + final DatumWriter writer = new ReflectDatumWriter(schema); + final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + + final Encoder encoder = EncoderFactory.get().binaryEncoder(stream, null); + writer.write(input, encoder); + encoder.flush(); + + return stream.toByteArray(); + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroSpecificDatumSerializer.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroSpecificDatumSerializer.java new file mode 100644 index 0000000..12fb60d --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroSpecificDatumSerializer.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2013 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.serializer.avro; + +import org.apache.avro.Schema; +import org.apache.avro.io.DatumReader; +import org.apache.avro.io.DatumWriter; +import org.apache.avro.io.Decoder; +import org.apache.avro.io.DecoderFactory; +import org.apache.avro.io.Encoder; +import org.apache.avro.io.EncoderFactory; +import org.apache.avro.specific.SpecificDatumReader; +import org.apache.avro.specific.SpecificDatumWriter; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * @author Soby Chacko + */ +public class AvroSpecificDatumSerializer { + public T deserialize(final byte[] bytes, final Schema schema) throws IOException { + final Decoder decoder = DecoderFactory.get().binaryDecoder(bytes, null); + final DatumReader reader = new SpecificDatumReader(schema); + + return reader.read(null, decoder); + } + + public byte[] serialize(final T input, final Schema schema) throws IOException { + final DatumWriter writer = new SpecificDatumWriter(schema); + final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + + final Encoder encoder = EncoderFactory.get().binaryEncoder(stream, null); + writer.write(input, encoder); + encoder.flush(); + + return stream.toByteArray(); + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/common/StringEncoder.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/common/StringEncoder.java new file mode 100644 index 0000000..ce5e0f3 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/common/StringEncoder.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2013 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.serializer.common; + +import kafka.serializer.Encoder; +import kafka.utils.VerifiableProperties; + +import java.util.Properties; + +/** + * @author Soby Chacko + */ +public class StringEncoder implements Encoder { + private String encoding = "UTF8"; + + public void setEncoding(final String encoding){ + this.encoding = encoding; + } + + @Override + public byte[] toBytes(final Object o) { + final Properties props = new Properties(); + props.put("serializer.encoding", encoding); + + final VerifiableProperties verifiableProperties = new VerifiableProperties(props); + return new kafka.serializer.StringEncoder(verifiableProperties).toBytes((String)o); + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConfigFactoryBean.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConfigFactoryBean.java new file mode 100644 index 0000000..bd27da7 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConfigFactoryBean.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2013 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.support; + +import kafka.consumer.ConsumerConfig; +import org.springframework.beans.factory.FactoryBean; + +import java.util.Properties; + +/** + * @author Soby Chacko + */ +public class ConsumerConfigFactoryBean implements FactoryBean { + + private final ConsumerMetadata consumerMetadata; + private final ZookeeperConnect zookeeperConnect; + + public ConsumerConfigFactoryBean(final ConsumerMetadata consumerMetadata, + final ZookeeperConnect zookeeperConnect){ + this.consumerMetadata = consumerMetadata; + this.zookeeperConnect = zookeeperConnect; + } + + @Override + public ConsumerConfig getObject() throws Exception { + final Properties properties = new Properties(); + properties.put("zookeeper.connect", zookeeperConnect.getZkConnect()); + properties.put("zookeeper.session.timeout.ms", zookeeperConnect.getZkSessionTimeout()); + properties.put("zookeeper.sync.time.ms", zookeeperConnect.getZkSyncTime()); + properties.put("auto.commit.interval.ms", consumerMetadata.getAutoCommitInterval()); + properties.put("consumer.timeout.ms", consumerMetadata.getConsumerTimeout()); + properties.put("group.id", consumerMetadata.getGroupId()); + + return new ConsumerConfig(properties); + } + + @Override + public Class getObjectType() { + return ConsumerConfig.class; + } + + @Override + public boolean isSingleton() { + return true; + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConfiguration.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConfiguration.java new file mode 100644 index 0000000..c7bb878 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConfiguration.java @@ -0,0 +1,209 @@ +/* + * Copyright 2002-2013 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.support; + +import kafka.consumer.ConsumerTimeoutException; +import kafka.consumer.KafkaStream; +import kafka.javaapi.consumer.ConsumerConnector; +import kafka.message.MessageAndMetadata; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.integration.MessagingException; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +/** + * @author Soby Chacko + */ +public class ConsumerConfiguration { + private static final Log LOGGER = LogFactory.getLog(ConsumerConfiguration.class); + + private final ConsumerMetadata consumerMetadata; + private final ConsumerConnectionProvider consumerConnectionProvider; + private final MessageLeftOverTracker messageLeftOverTracker; + private ConsumerConnector consumerConnector; + private volatile int count = 0; + private int maxMessages = 1; + + private ExecutorService executorService = Executors.newCachedThreadPool(); + + public ConsumerConfiguration(final ConsumerMetadata consumerMetadata, + final ConsumerConnectionProvider consumerConnectionProvider, + final MessageLeftOverTracker messageLeftOverTracker) { + this.consumerMetadata = consumerMetadata; + this.consumerConnectionProvider = consumerConnectionProvider; + this.messageLeftOverTracker = messageLeftOverTracker; + } + + public ConsumerMetadata getConsumerMetadata() { + return consumerMetadata; + } + + public Map>> receive() { + count = messageLeftOverTracker.getCurrentCount(); + + final List>> tasks = new LinkedList>>(); + final Object lock = new Object(); + + final Map>> consumerMap = getConsumerMapWithMessageStreams(); + for (final List> streams : consumerMap.values()) { + for (final KafkaStream stream : streams) { + tasks.add(new Callable>() { + @Override + public List call() throws Exception { + final List rawMessages = new ArrayList(); + try { + while (count < maxMessages) { + final MessageAndMetadata messageAndMetadata = stream.iterator().next(); + synchronized (lock) { + if (count < maxMessages) { + rawMessages.add(messageAndMetadata); + count++; + } else { + messageLeftOverTracker.addMessageAndMetadata(messageAndMetadata); + } + } + } + } catch (ConsumerTimeoutException cte) { + LOGGER.info("Consumer timed out"); + } + return rawMessages; + } + }); + } + } + + return executeTasks(tasks); + } + + private Map>> executeTasks(final List>> tasks) { + + final Map>> messages = new ConcurrentHashMap>>(); + messages.putAll(getLeftOverMessageMap()); + + try { + for (final Future> result : executorService.invokeAll(tasks)) { + if (!result.get().isEmpty()) { + final String topic = result.get().get(0).topic(); + if (!messages.containsKey(topic)) { + messages.put(topic, getPayload(result.get())); + } else { + + final Map> existingPayloadMap = messages.get(topic); + getPayload(result.get(), existingPayloadMap); + } + } + } + } catch (Exception e) { + throw new MessagingException("Consuming from Kafka failed", e); + } + + if (messages.isEmpty()) { + return null; + } + + return messages; + } + + private Map>> getLeftOverMessageMap() { + + final Map>> messages = new ConcurrentHashMap>>(); + + for (final MessageAndMetadata mamd : messageLeftOverTracker.getMessageLeftOverFromPreviousPoll()) { + final String topic = mamd.topic(); + + if (!messages.containsKey(topic)) { + final List l = new ArrayList(); + l.add(mamd); + messages.put(topic, getPayload(l)); + } else { + final Map> existingPayloadMap = messages.get(topic); + final List l = new ArrayList(); + l.add(mamd); + getPayload(l, existingPayloadMap); + } + } + messageLeftOverTracker.clearMessagesLeftOver(); + return messages; + } + + private Map> getPayload(final List messageAndMetadatas) { + final Map> payloadMap = new ConcurrentHashMap>(); + + for (final MessageAndMetadata messageAndMetadata : messageAndMetadatas) { + if (!payloadMap.containsKey(messageAndMetadata.partition())) { + final List payload = new ArrayList(); + payload.add(messageAndMetadata.message()); + payloadMap.put(messageAndMetadata.partition(), payload); + } else { + final List payload = payloadMap.get(messageAndMetadata.partition()); + payload.add(messageAndMetadata.message()); + } + + } + + return payloadMap; + } + + private void getPayload(final List messageAndMetadatas, final Map> existingPayloadMap) { + for (final MessageAndMetadata messageAndMetadata : messageAndMetadatas) { + if (!existingPayloadMap.containsKey(messageAndMetadata.partition())) { + final List payload = new ArrayList(); + payload.add(messageAndMetadata.message()); + existingPayloadMap.put(messageAndMetadata.partition(), payload); + } else { + final List payload = existingPayloadMap.get(messageAndMetadata.partition()); + payload.add(messageAndMetadata.message()); + } + } + } + + @SuppressWarnings("unchecked") + public Map>> getConsumerMapWithMessageStreams() { + if (consumerMetadata.getValueDecoder() != null) { + return getConsumerConnector().createMessageStreams( + consumerMetadata.getTopicStreamMap(), + consumerMetadata.getValueDecoder(), + consumerMetadata.getValueDecoder()); + } + + return getConsumerConnector().createMessageStreams(consumerMetadata.getTopicStreamMap()); + } + + public int getMaxMessages() { + return maxMessages; + } + + public void setMaxMessages(final int maxMessages) { + this.maxMessages = maxMessages; + } + + public ConsumerConnector getConsumerConnector() { + if (consumerConnector == null) { + consumerConnector = consumerConnectionProvider.getConsumerConnector(); + } + + return consumerConnector; + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConnectionProvider.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConnectionProvider.java new file mode 100644 index 0000000..d2287fd --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConnectionProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2013 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.support; + +import kafka.consumer.ConsumerConfig; +import kafka.javaapi.consumer.ConsumerConnector; + +/** + * @author Soby Chacko + */ +public class ConsumerConnectionProvider { + + private final ConsumerConfig consumerConfig; + + public ConsumerConnectionProvider(final ConsumerConfig consumerConfig) { + this.consumerConfig = consumerConfig; + } + + public ConsumerConnector getConsumerConnector() { + return kafka.consumer.Consumer.createJavaConsumerConnector(consumerConfig); + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerMetadata.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerMetadata.java new file mode 100644 index 0000000..eb4cac5 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerMetadata.java @@ -0,0 +1,174 @@ +/* + * Copyright 2002-2013 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.support; + +import kafka.serializer.Decoder; +import org.springframework.integration.kafka.core.KafkaConsumerDefaults; + +import java.util.Map; + +/** + * @author Soby Chacko + */ +public class ConsumerMetadata { + + //High level consumer defaults + private String groupId = KafkaConsumerDefaults.GROUP_ID; + private String socketTimeout = KafkaConsumerDefaults.SOCKET_TIMEOUT; + private String socketBufferSize = KafkaConsumerDefaults.SOCKET_BUFFER_SIZE; + private String fetchSize = KafkaConsumerDefaults.FETCH_SIZE; + private String backoffIncrement = KafkaConsumerDefaults.BACKOFF_INCREMENT; + private String queuedChunksMax = KafkaConsumerDefaults.QUEUED_CHUNKS_MAX; + private String autoCommitEnable = KafkaConsumerDefaults.AUTO_COMMIT_ENABLE; + private String autoCommitInterval = KafkaConsumerDefaults.AUTO_COMMIT_INTERVAL; + private String autoOffsetReset = KafkaConsumerDefaults.AUTO_OFFSET_RESET; + private String rebalanceRetriesMax = KafkaConsumerDefaults.REBALANCE_RETRIES_MAX; + private String consumerTimeout = KafkaConsumerDefaults.CONSUMER_TIMEOUT; + + private String topic; + private int streams; + private Decoder valueDecoder; + private Decoder keyDecoder; + private Map topicStreamMap; + + public String getGroupId() { + return groupId; + } + + public void setGroupId(final String groupId) { + this.groupId = groupId; + } + + public String getSocketTimeout() { + return socketTimeout; + } + + public void setSocketTimeout(final String socketTimeout) { + this.socketTimeout = socketTimeout; + } + + public String getSocketBufferSize() { + return socketBufferSize; + } + + public void setSocketBufferSize(final String socketBufferSize) { + this.socketBufferSize = socketBufferSize; + } + + public String getFetchSize() { + return fetchSize; + } + + public void setFetchSize(final String fetchSize) { + this.fetchSize = fetchSize; + } + + public String getBackoffIncrement() { + return backoffIncrement; + } + + public void setBackoffIncrement(final String backoffIncrement) { + this.backoffIncrement = backoffIncrement; + } + + public String getQueuedChunksMax() { + return queuedChunksMax; + } + + public void setQueuedChunksMax(final String queuedChunksMax) { + this.queuedChunksMax = queuedChunksMax; + } + + public String getAutoCommitEnable() { + return autoCommitEnable; + } + + public void setAutoCommitEnable(final String autoCommitEnable) { + this.autoCommitEnable = autoCommitEnable; + } + + public String getAutoCommitInterval() { + return autoCommitInterval; + } + + public void setAutoCommitInterval(final String autoCommitInterval) { + this.autoCommitInterval = autoCommitInterval; + } + + public String getAutoOffsetReset() { + return autoOffsetReset; + } + + public void setAutoOffsetReset(final String autoOffsetReset) { + this.autoOffsetReset = autoOffsetReset; + } + + public String getRebalanceRetriesMax() { + return rebalanceRetriesMax; + } + + public void setRebalanceRetriesMax(final String rebalanceRetriesMax) { + this.rebalanceRetriesMax = rebalanceRetriesMax; + } + + public String getConsumerTimeout() { + return consumerTimeout; + } + + public void setConsumerTimeout(final String consumerTimeout) { + this.consumerTimeout = consumerTimeout; + } + + public String getTopic() { + return topic; + } + + public void setTopic(final String topic) { + this.topic = topic; + } + + public int getStreams() { + return streams; + } + + public void setStreams(final int streams) { + this.streams = streams; + } + + public Decoder getValueDecoder() { + return valueDecoder; + } + + public void setValueDecoder(final Decoder valueDecoder) { + this.valueDecoder = valueDecoder; + } + + public Decoder getKeyDecoder() { + return keyDecoder; + } + + public void setKeyDecoder(final Decoder keyDecoder) { + this.keyDecoder = keyDecoder; + } + + public Map getTopicStreamMap() { + return topicStreamMap; + } + + public void setTopicStreamMap(final Map topicStreamMap) { + this.topicStreamMap = topicStreamMap; + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/DefaultPartitioner.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/DefaultPartitioner.java new file mode 100644 index 0000000..3147b9d --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/DefaultPartitioner.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2013 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.support; + +import kafka.producer.Partitioner; +import kafka.utils.Utils; + +/** + * @author Soby Chacko + * + * This class is for internal use only and therefore is at default access level + */ +class DefaultPartitioner implements Partitioner { + /** + * Uses the key to calculate a partition bucket id for routing + * the data to the appropriate broker partition + * @return an integer between 0 and numPartitions-1 + */ + @Override + public int partition(final T key, final int numPartitions) { + return Utils.abs(key.hashCode()) % numPartitions; + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/KafkaConsumerContext.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/KafkaConsumerContext.java new file mode 100644 index 0000000..a42e458 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/KafkaConsumerContext.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2013 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.support; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.integration.Message; +import org.springframework.integration.kafka.core.KafkaConsumerDefaults; +import org.springframework.integration.support.MessageBuilder; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Soby Chacko + */ +public class KafkaConsumerContext implements BeanFactoryAware { + private Map consumerConfigurations; + private String consumerTimeout = KafkaConsumerDefaults.CONSUMER_TIMEOUT; + private ZookeeperConnect zookeeperConnect; + + public Collection getConsumerConfigurations() { + return consumerConfigurations.values(); + } + + @Override + public void setBeanFactory(final BeanFactory beanFactory) throws BeansException { + consumerConfigurations = ((ListableBeanFactory) beanFactory).getBeansOfType(ConsumerConfiguration.class); + } + + public Message>>> receive() { + final Map>> consumedData = new HashMap>>(); + + for (final ConsumerConfiguration consumerConfiguration : getConsumerConfigurations()) { + final Map>> messages = consumerConfiguration.receive(); + + if (messages != null){ + consumedData.putAll(messages); + } + } + return MessageBuilder.withPayload(consumedData).build(); + } + + public String getConsumerTimeout() { + return consumerTimeout; + } + + public void setConsumerTimeout(final String consumerTimeout) { + this.consumerTimeout = consumerTimeout; + } + + public ZookeeperConnect getZookeeperConnect() { + return zookeeperConnect; + } + + public void setZookeeperConnect(final ZookeeperConnect zookeeperConnect) { + this.zookeeperConnect = zookeeperConnect; + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/KafkaProducerContext.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/KafkaProducerContext.java new file mode 100644 index 0000000..8a815d5 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/KafkaProducerContext.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2013 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.support; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.integration.Message; + +import java.util.Collection; +import java.util.Map; + +/** + * @author Soby Chacko + */ +public class KafkaProducerContext implements BeanFactoryAware { + private Map topicsConfiguration; + + @SuppressWarnings("unchecked") + public void send(final Message message) throws Exception { + final ProducerConfiguration producerConfiguration = + getTopicConfiguration(message.getHeaders().get("topic", String.class)); + + if (producerConfiguration != null) { + producerConfiguration.send(message); + } + } + + private ProducerConfiguration getTopicConfiguration(final String topic){ + final Collection topics = topicsConfiguration.values(); + + for (final ProducerConfiguration producerConfiguration : topics){ + if (producerConfiguration.getProducerMetadata().getTopic().equals(topic)){ + return producerConfiguration; + } + } + + return null; + } + + public Map getTopicsConfiguration() { + return topicsConfiguration; + } + + @Override + public void setBeanFactory(final BeanFactory beanFactory) throws BeansException { + topicsConfiguration = ((ListableBeanFactory)beanFactory).getBeansOfType(ProducerConfiguration.class); + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/MessageLeftOverTracker.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/MessageLeftOverTracker.java new file mode 100644 index 0000000..a7d23e0 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/MessageLeftOverTracker.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2013 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.support; + +import kafka.message.MessageAndMetadata; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Soby Chacko + */ +public class MessageLeftOverTracker { + private final List messageLeftOverFromPreviousPoll = new ArrayList(); + + public void addMessageAndMetadata(final MessageAndMetadata messageAndMetadata){ + messageLeftOverFromPreviousPoll.add(messageAndMetadata); + } + + public List getMessageLeftOverFromPreviousPoll(){ + return messageLeftOverFromPreviousPoll; + } + + public void clearMessagesLeftOver(){ + messageLeftOverFromPreviousPoll.clear(); + } + + public int getCurrentCount() { + return messageLeftOverFromPreviousPoll.size(); + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerConfiguration.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerConfiguration.java new file mode 100644 index 0000000..93e5c66 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerConfiguration.java @@ -0,0 +1,102 @@ +/* + * Copyright 2002-2013 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.support; + +import kafka.javaapi.producer.Producer; +import kafka.producer.KeyedMessage; +import kafka.serializer.DefaultEncoder; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.springframework.integration.Message; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; + +/** + * @author Soby Chacko + */ +public class ProducerConfiguration { + private final Producer producer; + private final ProducerMetadata producerMetadata; + + public ProducerConfiguration(final ProducerMetadata producerMetadata, final Producer producer){ + this.producerMetadata = producerMetadata; + this.producer = producer; + } + + public ProducerMetadata getProducerMetadata() { + return producerMetadata; + } + + public void send(final Message message) throws Exception { + final V v = getPayload(message); + + if (message.getHeaders().containsKey("messageKey")) { + producer.send(new KeyedMessage(producerMetadata.getTopic(), getKey(message), v)); + } else { + producer.send(new KeyedMessage(producerMetadata.getTopic(), v)); + } + } + + @SuppressWarnings("unchecked") + private V getPayload(final Message message) throws Exception { + if (producerMetadata.getValueEncoder().getClass().isAssignableFrom(DefaultEncoder.class)) { + return (V) getByteStream(message.getPayload()); + } else if (message.getPayload().getClass().isAssignableFrom(producerMetadata.getValueClassType())) { + return producerMetadata.getValueClassType().cast(message.getPayload()); + } + + throw new Exception("Message payload type is not matching with what is configured"); + } + + @SuppressWarnings("unchecked") + private K getKey(final Message message) throws Exception { + final Object key = message.getHeaders().get("messageKey"); + + if (producerMetadata.getKeyEncoder().getClass().isAssignableFrom(DefaultEncoder.class)) { + return (K) getByteStream(key); + } + + return message.getHeaders().get("messageKey", producerMetadata.getKeyClassType()); + } + + private static boolean isRawByteArray(final Object obj){ + return obj instanceof byte[]; + } + + private static byte[] getByteStream(final Object obj) throws IOException { + if (isRawByteArray(obj)){ + return (byte[])obj; + } + + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final ObjectOutputStream os = new ObjectOutputStream(out); + os.writeObject(obj); + + return out.toByteArray(); + } + + @Override + public boolean equals(final Object obj){ + return EqualsBuilder.reflectionEquals(this, obj); + } + + @Override + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerFactoryBean.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerFactoryBean.java new file mode 100644 index 0000000..801c8e5 --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerFactoryBean.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2013 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.support; + +import kafka.javaapi.producer.Producer; +import kafka.producer.ProducerConfig; +import kafka.producer.ProducerPool; +import kafka.producer.async.DefaultEventHandler; +import kafka.producer.async.EventHandler; +import org.springframework.beans.factory.FactoryBean; +import scala.collection.mutable.HashMap; + +import java.util.Properties; + +/** + * @author Soby Chacko + */ +public class ProducerFactoryBean implements FactoryBean> { + + private final String brokerList; + private final ProducerMetadata producerMetadata; + + public ProducerFactoryBean(final ProducerMetadata producerMetadata, final String brokerList){ + this.producerMetadata = producerMetadata; + this.brokerList = brokerList; + } + + @Override + public Producer getObject() throws Exception { + final Properties props = new Properties(); + props.put("metadata.broker.list", brokerList); + props.put("compression.codec", producerMetadata.getCompressionCodec()); + + if (producerMetadata.isAsync()){ + props.put("producer.type", "async"); + if (producerMetadata.getBatchNumMessages() != null){ + props.put("batch.num.messages", producerMetadata.getBatchNumMessages()); + } + } + + final ProducerConfig config = new ProducerConfig(props); + final EventHandler eventHandler = new DefaultEventHandler(config, + producerMetadata.getPartitioner() == null ? new DefaultPartitioner() : producerMetadata.getPartitioner(), + producerMetadata.getValueEncoder(), producerMetadata.getKeyEncoder(), + new ProducerPool(config), new HashMap()); + + final kafka.producer.Producer prod = new kafka.producer.Producer(config, + eventHandler); + return new Producer(prod); + } + + @Override + public Class getObjectType() { + return Producer.class; + } + + @Override + public boolean isSingleton() { + return true; + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerMetadata.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerMetadata.java new file mode 100644 index 0000000..a10a4fa --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerMetadata.java @@ -0,0 +1,139 @@ +/* + * Copyright 2002-2013 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.support; + +import kafka.producer.Partitioner; +import kafka.serializer.DefaultEncoder; +import kafka.serializer.Encoder; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.springframework.beans.factory.InitializingBean; + +/** + * @author Soby Chacko + */ +public class ProducerMetadata implements InitializingBean { + private Encoder keyEncoder; + private Encoder valueEncoder; + private Class keyClassType; + private Class valueClassType; + private final String topic; + private String compressionCodec = "default"; + private Partitioner partitioner; + private boolean async = false; + private String batchNumMessages; + + public ProducerMetadata(final String topic) { + this.topic = topic; + } + + public String getTopic() { + return topic; + } + + public Encoder getKeyEncoder() { + return keyEncoder; + } + + public void setKeyEncoder(final Encoder keyEncoder) { + this.keyEncoder = keyEncoder; + } + + public Encoder getValueEncoder() { + return valueEncoder; + } + + public void setValueEncoder(final Encoder valueEncoder) { + this.valueEncoder = valueEncoder; + } + + public Class getKeyClassType() { + return keyClassType; + } + + public void setKeyClassType(final Class keyClassType) { + this.keyClassType = keyClassType; + } + + public Class getValueClassType() { + return valueClassType; + } + + public void setValueClassType(final Class valueClassType) { + this.valueClassType = valueClassType; + } + + //TODO: Use an enum + public String getCompressionCodec() { + if (compressionCodec.equalsIgnoreCase("gzip")) { + return "1"; + } else if (compressionCodec.equalsIgnoreCase("snappy")) { + return "2"; + } + + return "0"; + } + + public void setCompressionCodec(final String compressionCodec) { + this.compressionCodec = compressionCodec; + } + + public Partitioner getPartitioner() { + return partitioner; + } + + public void setPartitioner(final Partitioner partitioner) { + this.partitioner = partitioner; + } + + @Override + @SuppressWarnings("unchecked") + public void afterPropertiesSet() throws Exception { + if (valueEncoder == null) { + setValueEncoder((Encoder) new DefaultEncoder(null)); + } + + if (keyEncoder == null) { + setKeyEncoder((Encoder) getValueEncoder()); + } + } + + public boolean isAsync() { + return async; + } + + public void setAsync(final boolean async) { + this.async = async; + } + + public String getBatchNumMessages() { + return batchNumMessages; + } + + public void setBatchNumMessages(final String batchNumMessages) { + this.batchNumMessages = batchNumMessages; + } + + @Override + public boolean equals(final Object obj){ + return EqualsBuilder.reflectionEquals(this, obj); + } + + @Override + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ZookeeperConnect.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ZookeeperConnect.java new file mode 100644 index 0000000..aa31a4d --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ZookeeperConnect.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2013 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.support; + +import org.springframework.integration.kafka.core.ZookeeperConnectDefaults; + +/** + * @author Soby Chacko + */ +public class ZookeeperConnect { + private String zkConnect = ZookeeperConnectDefaults.ZK_CONNECT; + private String zkConnectionTimeout = ZookeeperConnectDefaults.ZK_CONNECTION_TIMEOUT; + private String zkSessionTimeout = ZookeeperConnectDefaults.ZK_SESSION_TIMEOUT; + private String zkSyncTime = ZookeeperConnectDefaults.ZK_SYNC_TIME; + + public String getZkConnect() { + return zkConnect; + } + + public void setZkConnect(final String zkConnect) { + this.zkConnect = zkConnect; + } + + public String getZkConnectionTimeout() { + return zkConnectionTimeout; + } + + public void setZkConnectionTimeout(final String zkConnectionTimeout) { + this.zkConnectionTimeout = zkConnectionTimeout; + } + + public String getZkSessionTimeout() { + return zkSessionTimeout; + } + + public void setZkSessionTimeout(final String zkSessionTimeout) { + this.zkSessionTimeout = zkSessionTimeout; + } + + public String getZkSyncTime() { + return zkSyncTime; + } + + public void setZkSyncTime(final String zkSyncTime) { + this.zkSyncTime = zkSyncTime; + } +} diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/package-info.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/package-info.java new file mode 100644 index 0000000..0ec591d --- /dev/null +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides various support classes used across Spring Integration Kafka Components. + */ +package org.springframework.integration.kafka.support; diff --git a/spring-integration-kafka/src/main/resources/META-INF/spring.handlers b/spring-integration-kafka/src/main/resources/META-INF/spring.handlers new file mode 100644 index 0000000..1bee2b0 --- /dev/null +++ b/spring-integration-kafka/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.springframework.org/schema/integration/kafka=org.springframework.integration.kafka.config.xml.KafkaNamespaceHandler diff --git a/spring-integration-kafka/src/main/resources/META-INF/spring.schemas b/spring-integration-kafka/src/main/resources/META-INF/spring.schemas new file mode 100644 index 0000000..b5240f8 --- /dev/null +++ b/spring-integration-kafka/src/main/resources/META-INF/spring.schemas @@ -0,0 +1,2 @@ +http\://www.springframework.org/schema/integration/kafka/spring-integration-kafka-1.0.xsd=org/springframework/integration/config/xml/spring-integration-kafka-1.0.xsd +http\://www.springframework.org/schema/integration/kafka/spring-integration-kafka.xsd=org/springframework/integration/config/xml/spring-integration-kafka-1.0.xsd diff --git a/spring-integration-kafka/src/main/resources/META-INF/spring.tooling b/spring-integration-kafka/src/main/resources/META-INF/spring.tooling new file mode 100644 index 0000000..d407981 --- /dev/null +++ b/spring-integration-kafka/src/main/resources/META-INF/spring.tooling @@ -0,0 +1,4 @@ +# Tooling related information for the integration Kafka namespace +http\://www.springframework.org/schema/integration/kafka@name=integration Kafka Namespace +http\://www.springframework.org/schema/integration/kafka@prefix=int-kafka +http\://www.springframework.org/schema/integration/kafka@icon=org/springframework/integration/config/xml/spring-integration-kafka.gif diff --git a/spring-integration-kafka/src/main/resources/org/springframework/integration/config/xml/spring-integration-kafka-1.0.xsd b/spring-integration-kafka/src/main/resources/org/springframework/integration/config/xml/spring-integration-kafka-1.0.xsd new file mode 100644 index 0000000..1c716fe --- /dev/null +++ b/spring-integration-kafka/src/main/resources/org/springframework/integration/config/xml/spring-integration-kafka-1.0.xsd @@ -0,0 +1,451 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The topic configured by this configuration. + + + + + + + + + + + + + + + + + Custom implemenation of a Kafka Encoder for encoding message values. + + + + + + + Custom implemenation of a Kafka Encoder for encoding message keys. + + + + + + + Class type used for the key + + + + + + + Class type used for the value + + + + + + + + + + + + + + + + + Custom Kafka key partitioner. + + + + + + + Indicates if this producer is async or not. + + + + + + + number of messages to batch at this producer. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Kafka Server Bean Name + + + + + + + Kafka Server Bean Name + + + + + + + + + + + + + + + + + + + + + + + + + Kafka Server Bean Name + + + + + + + + + + The definition for the Spring Integration Kafka + Inbound Channel Adapter. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Kafka Server Bean Name + + + + + + + + + + + Identifies the underlying Spring bean definition, which is an + instance of either 'EventDrivenConsumer' or 'PollingConsumer', + depending on whether the component's input channel is a + 'SubscribableChannel' or 'PollableChannel'. + + + + + + + Flag to indicate that the component should start automatically + on startup (default true). + + + + + + + + + + + + Defines kafka outbound channel adapter that writes the contents of the + Message to kafka broker. + + + + + + + + + + + Identifies the underlying Spring bean definition, which is an + instance of either 'EventDrivenConsumer' or 'PollingConsumer', + depending on whether the component's input channel is a + 'SubscribableChannel' or 'PollableChannel'. + + + + + + + Flag to indicate that the component should start automatically + on startup (default true). + + + + + + + + + + Kafka producer context reference. + + + + + + + + + + + + + + + diff --git a/spring-integration-kafka/src/main/resources/org/springframework/integration/config/xml/spring-integration-kafka.gif b/spring-integration-kafka/src/main/resources/org/springframework/integration/config/xml/spring-integration-kafka.gif new file mode 100644 index 0000000000000000000000000000000000000000..41b369fece0e576ecd920400b221fd33abbaf5c4 GIT binary patch literal 572 zcmZ?wbhEHb6krfwc*Xz%|NsBj^POrMHnn5wI!WKA$M#t>l?H4BPzO?b&{cRWSFFSB< z^|`x}P0QyVy|U`SiF40CoO%A?(2mZdyE;$q?mn}(`^J&Ji~DRU^KH0`_S=*f zu`(xWd1~172%niTfzv{QrUeE70}32q)Fc#tvM@3*q%i1!3doJ z$QZroAYKk;o)!}g_c$BZP}??kHkSD;%JZ2d6u9}BT8+&ujM`ePV%-d#6T`zi);F^_ zIR!9@O3I6^oV(z-tdN`(pX{#l&0dZgror~-%^VCYeSN$`0@iCL`334n#7Fs`w{uBO x(B)aWd{Qfm5=#n`xT2uIN}aG328D}QI)&6(d>(9QbkfS;6w=UeP!nLV1^^EL*)#wE literal 0 HcmV?d00001 diff --git a/spring-integration-kafka/src/reference/docbook/SIAdapterLowerPrefix.xml b/spring-integration-kafka/src/reference/docbook/SIAdapterLowerPrefix.xml new file mode 100644 index 0000000..2ff42a4 --- /dev/null +++ b/spring-integration-kafka/src/reference/docbook/SIAdapterLowerPrefix.xml @@ -0,0 +1,72 @@ + + + Kafka Adapter + + The Spring Integration Kafka Adapter provides... + + + + Outbound Channel adapter + + + Outbound Gateway + + + Inbound Channel Adapter + + + +
+ Java Implementation + Each of the provided components will use the + org.springframework.integration.kafka.core.KafkaExecutor + class... + +
+
+ Common Configuration Attributes + + Certain configuration parameters are shared amongst all Kafka + components and are described below: + + + auto-startup + + Lifecycle attribute signaling if this component should + be started during Application Context startup. + Defaults to true. + Optional. + + + id + + Identifies the underlying Spring bean definition, which + is an instance of either EventDrivenConsumer + or PollingConsumer. + Optional. + + +
+ +
+ Outbound Channel Adapter + + The Kafka Outbound channel adapter allows you to... + +
+
+ Outbound Gateway + + Outbound gateways are similar to outbound channel adapters except that it can also be used to + get a result on the reply channel after performing + the given... + +
+
+ Inbound Channel Adapter + + An inbound channel adapter is used to execute... + +
+ +
diff --git a/spring-integration-kafka/src/reference/docbook/history.xml b/spring-integration-kafka/src/reference/docbook/history.xml new file mode 100644 index 0000000..71b4f74 --- /dev/null +++ b/spring-integration-kafka/src/reference/docbook/history.xml @@ -0,0 +1,4 @@ + + + Change History + diff --git a/spring-integration-kafka/src/reference/docbook/images/logo.png b/spring-integration-kafka/src/reference/docbook/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e1c2a0e3c7864b4c2495fa75fed9733af2558ff9 GIT binary patch literal 9627 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 R@&Et;07*qoM6N<$f(p*0Lx literal 0 HcmV?d00001 diff --git a/spring-integration-kafka/src/reference/docbook/index.xml b/spring-integration-kafka/src/reference/docbook/index.xml new file mode 100644 index 0000000..bdb072f --- /dev/null +++ b/spring-integration-kafka/src/reference/docbook/index.xml @@ -0,0 +1,67 @@ + + + + Spring Integration Kafka Adapter + Kafka Adapter ${version} + Spring Integration + ${version} + + + + + + + + + + + + + + Soby Chacko + + + © SpringSource Inc., 2012 + + + + + + + What's new? + + + For those who are already familiar with Spring Integration, this + chapter + provides a brief overview of the new features of version 2.2. If you are + interested in the changes and features, that were introduced in + earlier + versions, please take a look at chapter: + + + + + + + + + + Integration Adapters + + This section covers the various Channel Adapters and Messaging + Gateways provided + by Spring Integration to support Message-based communication with + external systems. + + + + + + Appendices + + Advanced Topics and Additional Resources + + + + diff --git a/spring-integration-kafka/src/reference/docbook/resources.xml b/spring-integration-kafka/src/reference/docbook/resources.xml new file mode 100644 index 0000000..456ebba --- /dev/null +++ b/spring-integration-kafka/src/reference/docbook/resources.xml @@ -0,0 +1,15 @@ + + + Additional Resources + +
+ Spring Integration Home + + The definitive source of information about Spring Integration is the + Spring Integration Home at + http://www.springsource.org. That site serves as a hub of + information and is the best place to find up-to-date announcements about the project as well as links to + articles, blogs, and new sample applications. + +
+
diff --git a/spring-integration-kafka/src/reference/docbook/whats-new.xml b/spring-integration-kafka/src/reference/docbook/whats-new.xml new file mode 100644 index 0000000..ddf2480 --- /dev/null +++ b/spring-integration-kafka/src/reference/docbook/whats-new.xml @@ -0,0 +1,8 @@ + + + What's new? + + This chapter provides an overview of the new features and improvements + that have been added to the Kafka Adapter: + + diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaConsumerContextParserTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaConsumerContextParserTests.java new file mode 100644 index 0000000..50ae6fc --- /dev/null +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaConsumerContextParserTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2013 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.config.xml; + +import junit.framework.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.integration.kafka.support.ConsumerMetadata; +import org.springframework.integration.kafka.support.KafkaConsumerContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Soby Chacko + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class KafkaConsumerContextParserTests { + + @Autowired + private ApplicationContext appContext; + + @Test + @SuppressWarnings("unchecked") + public void testConsumerContextConfiguration() { + final KafkaConsumerContext consumerContext = appContext.getBean("consumerContext", KafkaConsumerContext.class); + Assert.assertNotNull(consumerContext); + + final ConsumerMetadata cm = appContext.getBean("consumerMetadata_default1", ConsumerMetadata.class); + Assert.assertNotNull(cm); + } +} diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaInboundAdapterParserTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaInboundAdapterParserTests.java new file mode 100644 index 0000000..e7525b9 --- /dev/null +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaInboundAdapterParserTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2013 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.config.xml; + +import junit.framework.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.integration.endpoint.SourcePollingChannelAdapter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class KafkaInboundAdapterParserTests { + + @Autowired + private ApplicationContext appContext; + + /** + * Test method for {@link org.springframework.integration.kafka.config.xml.KafkaInboundChannelAdapterParser#parseSource(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext)}. + */ + @Test + public void testParseSourceElementParserContext() throws Exception { + final SourcePollingChannelAdapter adapter = appContext.getBean("kafkaInboundChannelAdapter", + SourcePollingChannelAdapter.class); + + Assert.assertNotNull(adapter); + Assert.assertFalse(adapter.isAutoStartup()); + } +} diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaOutboundAdapterParserTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaOutboundAdapterParserTests.java new file mode 100644 index 0000000..3c69fcf --- /dev/null +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaOutboundAdapterParserTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2013 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.config.xml; + +import junit.framework.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.integration.endpoint.PollingConsumer; +import org.springframework.integration.kafka.outbound.KafkaProducerMessageHandler; +import org.springframework.integration.kafka.support.KafkaProducerContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class KafkaOutboundAdapterParserTests { + + @Autowired + private ApplicationContext appContext; + + @Test + public void testOutboundAdapterConfiguration(){ + final PollingConsumer pollingConsumer = appContext.getBean("kafkaOutboundChannelAdapter", PollingConsumer.class); + final KafkaProducerMessageHandler messageHandler = appContext.getBean(KafkaProducerMessageHandler.class); + Assert.assertNotNull(pollingConsumer); + Assert.assertNotNull(messageHandler); + final KafkaProducerContext producerContext = messageHandler.getKafkaProducerContext(); + Assert.assertNotNull(producerContext); + Assert.assertEquals(producerContext.getTopicsConfiguration().size(), 2); + } +} diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaProducerContextParserTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaProducerContextParserTests.java new file mode 100644 index 0000000..ddf9718 --- /dev/null +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaProducerContextParserTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2013 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.config.xml; + +import junit.framework.Assert; +import kafka.javaapi.producer.Producer; +import kafka.serializer.Encoder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.integration.kafka.support.KafkaProducerContext; +import org.springframework.integration.kafka.support.ProducerConfiguration; +import org.springframework.integration.kafka.support.ProducerMetadata; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.Map; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class KafkaProducerContextParserTests { + + @Autowired + private ApplicationContext appContext; + + @Test + @SuppressWarnings("unchecked") + public void testProducerContextConfiguration(){ + final KafkaProducerContext producerContext = appContext.getBean("producerContext", KafkaProducerContext.class); + Assert.assertNotNull(producerContext); + + final Map topicConfigurations = producerContext.getTopicsConfiguration(); + Assert.assertEquals(topicConfigurations.size(), 2); + + final ProducerConfiguration producerConfigurationTest1 = topicConfigurations.get("producerConfiguration_test1"); + Assert.assertNotNull(producerConfigurationTest1); + final ProducerMetadata producerMetadataTest1 = producerConfigurationTest1.getProducerMetadata(); + Assert.assertEquals(producerMetadataTest1.getTopic(), "test1"); + Assert.assertEquals(producerMetadataTest1.getCompressionCodec(), "0"); + Assert.assertEquals(producerMetadataTest1.getKeyClassType(), java.lang.String.class); + Assert.assertEquals(producerMetadataTest1.getValueClassType(), java.lang.String.class); + + final Encoder valueEncoder = appContext.getBean("valueEncoder", Encoder.class); + Assert.assertEquals(producerMetadataTest1.getValueEncoder(), valueEncoder); + Assert.assertEquals(producerMetadataTest1.getKeyEncoder(), valueEncoder); + + final Producer producerTest1 = appContext.getBean("prodFactory_test1", Producer.class); + Assert.assertEquals(producerConfigurationTest1, new ProducerConfiguration(producerMetadataTest1, producerTest1)); + + final ProducerConfiguration producerConfigurationTest2 = topicConfigurations.get("producerConfiguration_" + "test2"); + Assert.assertNotNull(producerConfigurationTest2); + final ProducerMetadata producerMetadataTest2 = producerConfigurationTest2.getProducerMetadata(); + Assert.assertEquals(producerMetadataTest2.getTopic(), "test2"); + Assert.assertEquals(producerMetadataTest2.getCompressionCodec(), "0"); + + final Producer producerTest2 = appContext.getBean("prodFactory_test2", Producer.class); + Assert.assertEquals(producerConfigurationTest2, new ProducerConfiguration(producerMetadataTest2, producerTest2)); + } +} diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/ZookeeperConnectParserTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/ZookeeperConnectParserTests.java new file mode 100644 index 0000000..6acfa87 --- /dev/null +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/ZookeeperConnectParserTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2013 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.config.xml; + +import junit.framework.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.integration.kafka.core.ZookeeperConnectDefaults; +import org.springframework.integration.kafka.support.ZookeeperConnect; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Soby Chacko + * @since 1.0 + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class ZookeeperConnectParserTests { + + @Autowired + private ApplicationContext appContext; + + @Test + public void testCustomKafkaBrokerConfiguration() { + final ZookeeperConnect broker = appContext.getBean("zookeeperConnect", ZookeeperConnect.class); + + Assert.assertEquals("localhost:2181", broker.getZkConnect()); + Assert.assertEquals("10000", broker.getZkConnectionTimeout()); + Assert.assertEquals("10000", broker.getZkSessionTimeout()); + Assert.assertEquals("200", broker.getZkSyncTime()); + } + + @Test + public void testDefaultKafkaBrokerConfiguration() { + final ZookeeperConnect broker = appContext.getBean("defaultZookeeperConnect", ZookeeperConnect.class); + + Assert.assertEquals(ZookeeperConnectDefaults.ZK_CONNECT, broker.getZkConnect()); + Assert.assertEquals(ZookeeperConnectDefaults.ZK_CONNECTION_TIMEOUT, broker.getZkConnectionTimeout()); + Assert.assertEquals(ZookeeperConnectDefaults.ZK_SESSION_TIMEOUT, broker.getZkSessionTimeout()); + Assert.assertEquals(ZookeeperConnectDefaults.ZK_SYNC_TIME, broker.getZkSyncTime()); + } +} diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/serializer/AvroBackedKafkaSerializerTest.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/serializer/AvroBackedKafkaSerializerTest.java new file mode 100644 index 0000000..a12e908 --- /dev/null +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/serializer/AvroBackedKafkaSerializerTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2013 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.serializer; + +import junit.framework.Assert; +import org.junit.Test; +import org.springframework.integration.kafka.serializer.avro.AvroBackedKafkaDecoder; +import org.springframework.integration.kafka.serializer.avro.AvroBackedKafkaEncoder; +import org.springframework.integration.kafka.test.utils.TestObject; + +/** + * @author Soby Chacko + * @since 1.0 + */ +public class AvroBackedKafkaSerializerTest { + @Test + @SuppressWarnings("unchecked") + public void testDecodePlainSchema() { + final AvroBackedKafkaEncoder avroBackedKafkaEncoder = new AvroBackedKafkaEncoder(TestObject.class); + + final TestObject testObject = new TestObject(); + testObject.setTestData1("\"Test Data1\""); + testObject.setTestData2(1); + + final byte[] data = avroBackedKafkaEncoder.toBytes(testObject); + + final AvroBackedKafkaDecoder avroBackedKafkaDecoder = new AvroBackedKafkaDecoder(TestObject.class); + final TestObject decodedFbu = (TestObject) avroBackedKafkaDecoder.fromBytes(data); + + Assert.assertEquals(testObject.getTestData1(), decodedFbu.getTestData1()); + Assert.assertEquals(testObject.getTestData2(), decodedFbu.getTestData2()); + } + + @Test + @SuppressWarnings("unchecked") + public void anotherTest() { + final AvroBackedKafkaEncoder avroBackedKafkaEncoder = new AvroBackedKafkaEncoder(java.lang.String.class); + final String testString = "Testing Avro"; + final byte[] data = avroBackedKafkaEncoder.toBytes(testString); + + final AvroBackedKafkaDecoder avroBackedKafkaDecoder = new AvroBackedKafkaDecoder(java.lang.String.class); + final String decodedS = (String) avroBackedKafkaDecoder.fromBytes(data); + + Assert.assertEquals(testString, decodedS); + } +} diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ConsumerConfigurationTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ConsumerConfigurationTests.java new file mode 100644 index 0000000..f41f18f --- /dev/null +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ConsumerConfigurationTests.java @@ -0,0 +1,306 @@ +/* + * Copyright 2002-2013 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.support; + +import junit.framework.Assert; +import kafka.consumer.ConsumerIterator; +import kafka.consumer.KafkaStream; +import kafka.javaapi.consumer.ConsumerConnector; +import kafka.message.MessageAndMetadata; +import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Soby Chacko + */ +public class ConsumerConfigurationTests { + @Test + @SuppressWarnings("unchecked") + public void testReceiveMessageForSingleTopicFromSingleStream() { + final ConsumerMetadata consumerMetadata = Mockito.mock(ConsumerMetadata.class); + final ConsumerConnectionProvider consumerConnectionProvider = + Mockito.mock(ConsumerConnectionProvider.class); + final MessageLeftOverTracker messageLeftOverTracker = Mockito.mock(MessageLeftOverTracker.class); + final ConsumerConnector consumerConnector = Mockito.mock(ConsumerConnector.class); + + Mockito.when(consumerConnectionProvider.getConsumerConnector()).thenReturn(consumerConnector); + + final ConsumerConfiguration consumerConfiguration = new ConsumerConfiguration(consumerMetadata, + consumerConnectionProvider, messageLeftOverTracker); + consumerConfiguration.setMaxMessages(1); + + final KafkaStream stream = Mockito.mock(KafkaStream.class); + final List> streams = new ArrayList>(); + streams.add(stream); + final Map>> messageStreams = new HashMap>>(); + messageStreams.put("topic", streams); + + Mockito.when(consumerConfiguration.getConsumerMapWithMessageStreams()).thenReturn(messageStreams); + final ConsumerIterator iterator = Mockito.mock(ConsumerIterator.class); + Mockito.when(stream.iterator()).thenReturn(iterator); + final MessageAndMetadata messageAndMetadata = Mockito.mock(MessageAndMetadata.class); + Mockito.when(iterator.next()).thenReturn(messageAndMetadata); + Mockito.when(messageAndMetadata.message()).thenReturn("got message"); + Mockito.when(messageAndMetadata.topic()).thenReturn("topic"); + Mockito.when(messageAndMetadata.partition()).thenReturn(1); + + final Map>> messages = consumerConfiguration.receive(); + Assert.assertEquals(messages.size(), 1); + Assert.assertEquals(messages.get("topic").size(), 1); + Assert.assertEquals(messages.get("topic").get(1).get(0), "got message"); + + Mockito.verify(stream, Mockito.times(1)).iterator(); + Mockito.verify(iterator, Mockito.times(1)).next(); + Mockito.verify(messageAndMetadata, Mockito.times(1)).message(); + Mockito.verify(messageAndMetadata, Mockito.times(1)).topic(); + } + + @Test + @SuppressWarnings("unchecked") + public void testReceiveMessageForSingleTopicFromMultipleStreams() { + final ConsumerMetadata consumerMetadata = Mockito.mock(ConsumerMetadata.class); + final ConsumerConnectionProvider consumerConnectionProvider = + Mockito.mock(ConsumerConnectionProvider.class); + final MessageLeftOverTracker messageLeftOverTracker = Mockito.mock(MessageLeftOverTracker.class); + + final ConsumerConnector consumerConnector = Mockito.mock(ConsumerConnector.class); + + Mockito.when(consumerConnectionProvider.getConsumerConnector()).thenReturn(consumerConnector); + + final ConsumerConfiguration consumerConfiguration = new ConsumerConfiguration(consumerMetadata, + consumerConnectionProvider, messageLeftOverTracker); + consumerConfiguration.setMaxMessages(3); + + final KafkaStream stream1 = Mockito.mock(KafkaStream.class); + final KafkaStream stream2 = Mockito.mock(KafkaStream.class); + final KafkaStream stream3 = Mockito.mock(KafkaStream.class); + final List> streams = new ArrayList>(); + streams.add(stream1); + streams.add(stream2); + streams.add(stream3); + final Map>> messageStreams = new HashMap>>(); + messageStreams.put("topic", streams); + + Mockito.when(consumerConfiguration.getConsumerMapWithMessageStreams()).thenReturn(messageStreams); + final ConsumerIterator iterator1 = Mockito.mock(ConsumerIterator.class); + final ConsumerIterator iterator2 = Mockito.mock(ConsumerIterator.class); + final ConsumerIterator iterator3 = Mockito.mock(ConsumerIterator.class); + + Mockito.when(stream1.iterator()).thenReturn(iterator1); + Mockito.when(stream2.iterator()).thenReturn(iterator2); + Mockito.when(stream3.iterator()).thenReturn(iterator3); + final MessageAndMetadata messageAndMetadata1 = Mockito.mock(MessageAndMetadata.class); + final MessageAndMetadata messageAndMetadata2 = Mockito.mock(MessageAndMetadata.class); + final MessageAndMetadata messageAndMetadata3 = Mockito.mock(MessageAndMetadata.class); + + Mockito.when(iterator1.next()).thenReturn(messageAndMetadata1); + Mockito.when(iterator2.next()).thenReturn(messageAndMetadata2); + Mockito.when(iterator3.next()).thenReturn(messageAndMetadata3); + + Mockito.when(messageAndMetadata1.message()).thenReturn("got message"); + Mockito.when(messageAndMetadata1.topic()).thenReturn("topic"); + Mockito.when(messageAndMetadata1.partition()).thenReturn(1); + + Mockito.when(messageAndMetadata2.message()).thenReturn("got message"); + Mockito.when(messageAndMetadata2.topic()).thenReturn("topic"); + Mockito.when(messageAndMetadata2.partition()).thenReturn(2); + + Mockito.when(messageAndMetadata3.message()).thenReturn("got message"); + Mockito.when(messageAndMetadata3.topic()).thenReturn("topic"); + Mockito.when(messageAndMetadata3.partition()).thenReturn(3); + + final Map>> messages = consumerConfiguration.receive(); + Assert.assertEquals(messages.size(), 1); + int sum = 0; + + final Map> values = messages.get("topic"); + + for (final List l : values.values()) { + sum += l.size(); + } + + Assert.assertEquals(sum, 3); + } + + @Test + @SuppressWarnings("unchecked") + public void testReceiveMessageForMultipleTopicsFromMultipleStreams() { + final ConsumerMetadata consumerMetadata = Mockito.mock(ConsumerMetadata.class); + final ConsumerConnectionProvider consumerConnectionProvider = + Mockito.mock(ConsumerConnectionProvider.class); + final MessageLeftOverTracker messageLeftOverTracker = Mockito.mock(MessageLeftOverTracker.class); + + final ConsumerConnector consumerConnector = Mockito.mock(ConsumerConnector.class); + + Mockito.when(consumerConnectionProvider.getConsumerConnector()).thenReturn(consumerConnector); + + final ConsumerConfiguration consumerConfiguration = new ConsumerConfiguration(consumerMetadata, + consumerConnectionProvider, messageLeftOverTracker); + consumerConfiguration.setMaxMessages(9); + + final KafkaStream stream1 = Mockito.mock(KafkaStream.class); + final KafkaStream stream2 = Mockito.mock(KafkaStream.class); + final KafkaStream stream3 = Mockito.mock(KafkaStream.class); + final List> streams = new ArrayList>(); + streams.add(stream1); + streams.add(stream2); + streams.add(stream3); + final Map>> messageStreams = new HashMap>>(); + messageStreams.put("topic1", streams); + messageStreams.put("topic2", streams); + messageStreams.put("topic3", streams); + + Mockito.when(consumerConfiguration.getConsumerMapWithMessageStreams()).thenReturn(messageStreams); + final ConsumerIterator iterator1 = Mockito.mock(ConsumerIterator.class); + final ConsumerIterator iterator2 = Mockito.mock(ConsumerIterator.class); + final ConsumerIterator iterator3 = Mockito.mock(ConsumerIterator.class); + + Mockito.when(stream1.iterator()).thenReturn(iterator1); + Mockito.when(stream2.iterator()).thenReturn(iterator2); + Mockito.when(stream3.iterator()).thenReturn(iterator3); + final MessageAndMetadata messageAndMetadata1 = Mockito.mock(MessageAndMetadata.class); + final MessageAndMetadata messageAndMetadata2 = Mockito.mock(MessageAndMetadata.class); + final MessageAndMetadata messageAndMetadata3 = Mockito.mock(MessageAndMetadata.class); + + Mockito.when(iterator1.next()).thenReturn(messageAndMetadata1); + Mockito.when(iterator2.next()).thenReturn(messageAndMetadata2); + Mockito.when(iterator3.next()).thenReturn(messageAndMetadata3); + + Mockito.when(messageAndMetadata1.message()).thenReturn("got message1"); + Mockito.when(messageAndMetadata1.topic()).thenReturn("topic1"); + Mockito.when(messageAndMetadata1.partition()).thenAnswer(getAnswer()); + + Mockito.when(messageAndMetadata2.message()).thenReturn("got message2"); + Mockito.when(messageAndMetadata2.topic()).thenReturn("topic2"); + Mockito.when(messageAndMetadata1.partition()).thenAnswer(getAnswer()); + + Mockito.when(messageAndMetadata3.message()).thenReturn("got message3"); + Mockito.when(messageAndMetadata3.topic()).thenReturn("topic3"); + Mockito.when(messageAndMetadata1.partition()).thenAnswer(getAnswer()); + + final Map>> messages = consumerConfiguration.receive(); + int sum = 0; + + final Collection>> values = messages.values(); + + for (final Map> m : values) { + for (final List l : m.values()) { + sum += l.size(); + } + } + + Assert.assertEquals(sum, 9); + } + + private Answer getAnswer() { + return new Answer() { + private int count = 0; + + @Override + public Object answer(final InvocationOnMock invocation) throws Throwable { + if (count++ == 1) { + return 1; + } else if (count++ == 2) { + return 2; + } + + return 3; + } + }; + } + + @Test + @SuppressWarnings("unchecked") + public void testReceiveMessageAndVerifyMessageLeftoverFromPreviousPollAreTakenFirst() { + final ConsumerMetadata consumerMetadata = Mockito.mock(ConsumerMetadata.class); + final ConsumerConnectionProvider consumerConnectionProvider = + Mockito.mock(ConsumerConnectionProvider.class); + final MessageLeftOverTracker messageLeftOverTracker = Mockito.mock(MessageLeftOverTracker.class); + final ConsumerConnector consumerConnector = Mockito.mock(ConsumerConnector.class); + + Mockito.when(messageLeftOverTracker.getCurrentCount()).thenReturn(3); + final MessageAndMetadata m1 = new MessageAndMetadata("key1", "value1", "topic1", 1, 1L); + final MessageAndMetadata m2 = new MessageAndMetadata("key2", "value2", "topic2", 1, 1L); + final MessageAndMetadata m3 = new MessageAndMetadata("key1", "value3", "topic3", 1, 1L); + + final List mList = new ArrayList(); + mList.add(m1); + mList.add(m2); + mList.add(m3); + + Mockito.when(messageLeftOverTracker.getMessageLeftOverFromPreviousPoll()).thenReturn(mList); + + Mockito.when(consumerConnectionProvider.getConsumerConnector()).thenReturn(consumerConnector); + + final ConsumerConfiguration consumerConfiguration = new ConsumerConfiguration(consumerMetadata, + consumerConnectionProvider, messageLeftOverTracker); + consumerConfiguration.setMaxMessages(5); + + final KafkaStream stream = Mockito.mock(KafkaStream.class); + final List> streams = new ArrayList>(); + streams.add(stream); + final Map>> messageStreams = new HashMap>>(); + messageStreams.put("topic1", streams); + + Mockito.when(consumerConfiguration.getConsumerMapWithMessageStreams()).thenReturn(messageStreams); + final ConsumerIterator iterator = Mockito.mock(ConsumerIterator.class); + Mockito.when(stream.iterator()).thenReturn(iterator); + final MessageAndMetadata messageAndMetadata = Mockito.mock(MessageAndMetadata.class); + Mockito.when(iterator.next()).thenReturn(messageAndMetadata); + Mockito.when(messageAndMetadata.message()).thenReturn("got message"); + Mockito.when(messageAndMetadata.topic()).thenReturn("topic1"); + Mockito.when(messageAndMetadata.partition()).thenReturn(1); + + final Map>> messages = consumerConfiguration.receive(); + int sum = 0; + + final Collection>> values = messages.values(); + + for (final Map> m : values) { + for (final List l : m.values()) { + sum += l.size(); + } + + } + Assert.assertEquals(sum, 5); + + Assert.assertTrue(messages.containsKey("topic1")); + Assert.assertTrue(messages.containsKey("topic2")); + Assert.assertTrue(messages.containsKey("topic3")); + + Assert.assertTrue(valueFound(messages.get("topic1").get(1), "value1")); + Assert.assertTrue(valueFound(messages.get("topic2").get(1), "value2")); + Assert.assertTrue(valueFound(messages.get("topic3").get(1), "value3")); + } + + private boolean valueFound(final List l, final String value){ + for (final Object o : l){ + if (value.equals(o)){ + return true; + } + } + + return false; + } +} diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/KafkaConsumerContextTest.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/KafkaConsumerContextTest.java new file mode 100644 index 0000000..12bc189 --- /dev/null +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/KafkaConsumerContextTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2013 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.support; + +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.integration.Message; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Soby Chacko + */ +public class KafkaConsumerContextTest { + + @Test + public void testMergeResultsFromMultipleConsumerConfiguration() { + final KafkaConsumerContext kafkaConsumerContext = new KafkaConsumerContext(); + final ListableBeanFactory beanFactory = Mockito.mock(ListableBeanFactory.class); + final ConsumerConfiguration consumerConfiguration1 = Mockito.mock(ConsumerConfiguration.class); + final ConsumerConfiguration consumerConfiguration2 = Mockito.mock(ConsumerConfiguration.class); + + final Map map = new HashMap(); + map.put("config1", consumerConfiguration1); + map.put("config2", consumerConfiguration2); + + Mockito.when(beanFactory.getBeansOfType(ConsumerConfiguration.class)).thenReturn(map); + kafkaConsumerContext.setBeanFactory(beanFactory); + + final Map>> result1 = new HashMap>>(); + final List l1 = new ArrayList(); + l1.add("got message1 - l1"); + l1.add("got message2 - l1"); + final Map> innerMap1 = new HashMap>(); + innerMap1.put(1, l1); + result1.put("topic1", innerMap1); + + final Map>> result2 = new HashMap>>(); + final List l2 = new ArrayList(); + l2.add("got message1 - l2"); + l2.add("got message2 - l2"); + l2.add("got message3 - l2"); + + final Map> innerMap2 = new HashMap>(); + innerMap2.put(1, l2); + result1.put("topic2", innerMap2); + + Mockito.when(consumerConfiguration1.receive()).thenReturn(result1); + Mockito.when(consumerConfiguration2.receive()).thenReturn(result2); + + final Message>>> messages = kafkaConsumerContext.receive(); + Assert.assertEquals(messages.getPayload().size(), 2); + Assert.assertEquals(messages.getPayload().get("topic1").size(), 1); + Assert.assertEquals(messages.getPayload().get("topic1").get(1).get(0), "got message1 - l1"); + Assert.assertEquals(messages.getPayload().get("topic1").get(1).get(1), "got message2 - l1"); + + Assert.assertEquals(messages.getPayload().get("topic2").size(), 1); + Assert.assertEquals(messages.getPayload().get("topic2").get(1).get(0), "got message1 - l2"); + Assert.assertEquals(messages.getPayload().get("topic2").get(1).get(1), "got message2 - l2"); + Assert.assertEquals(messages.getPayload().get("topic2").get(1).get(2), "got message3 - l2"); + } +} diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ProducerConfigurationTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ProducerConfigurationTests.java new file mode 100644 index 0000000..2fa2021 --- /dev/null +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ProducerConfigurationTests.java @@ -0,0 +1,303 @@ +/* + * Copyright 2002-2013 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.support; + +import kafka.javaapi.producer.Producer; +import kafka.producer.KeyedMessage; +import kafka.serializer.DefaultEncoder; +import kafka.serializer.StringEncoder; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.springframework.integration.Message; +import org.springframework.integration.kafka.serializer.avro.AvroBackedKafkaEncoder; +import org.springframework.integration.kafka.test.utils.NonSerializableTestKey; +import org.springframework.integration.kafka.test.utils.NonSerializableTestPayload; +import org.springframework.integration.kafka.test.utils.TestKey; +import org.springframework.integration.kafka.test.utils.TestPayload; +import org.springframework.integration.support.MessageBuilder; + +import java.io.ByteArrayInputStream; +import java.io.NotSerializableException; +import java.io.ObjectInputStream; + +/** + * @author Soby Chacko + */ +public class ProducerConfigurationTests { + @Test + @SuppressWarnings("unchecked") + public void testSendMessageWithNonDefaultKeyAndValueEncoders() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + producerMetadata.setValueEncoder(new StringEncoder(null)); + producerMetadata.setKeyEncoder(new StringEncoder(null)); + producerMetadata.setKeyClassType(String.class); + producerMetadata.setValueClassType(String.class); + final Producer producer = Mockito.mock(Producer.class); + + final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); + + final Message message = MessageBuilder.withPayload("test message"). + setHeader("messageKey", "key") + .setHeader("topic", "test").build(); + + configuration.send(message); + + Mockito.verify(producer, Mockito.times(1)).send(Mockito.any(KeyedMessage.class)); + + final ArgumentCaptor argument = ArgumentCaptor.forClass(KeyedMessage.class); + Mockito.verify(producer).send(argument.capture()); + + final KeyedMessage capturedKeyMessage = argument.getValue(); + + Assert.assertEquals(capturedKeyMessage.key(), "key"); + Assert.assertEquals(capturedKeyMessage.message(), "test message"); + Assert.assertEquals(capturedKeyMessage.topic(), "test"); + } + + /** + * User does not set an explicit key/value encoder, but send a serializable object for both key/value + */ + @Test + @SuppressWarnings("unchecked") + public void testSendMessageWithDefaultKeyAndValueEncodersAndCustomSerializableKeyAndPayloadObject() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + producerMetadata.setValueEncoder(new DefaultEncoder(null)); + producerMetadata.setKeyEncoder(new DefaultEncoder(null)); + final Producer producer = Mockito.mock(Producer.class); + + final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); + + final Message message = MessageBuilder.withPayload(new TestPayload("part1", "part2")). + setHeader("messageKey", new TestKey("compositePart1", "compositePart2")) + .setHeader("topic", "test").build(); + + configuration.send(message); + + Mockito.verify(producer, Mockito.times(1)).send(Mockito.any(KeyedMessage.class)); + + final ArgumentCaptor argument = ArgumentCaptor.forClass(KeyedMessage.class); + Mockito.verify(producer).send(argument.capture()); + + final KeyedMessage capturedKeyMessage = argument.getValue(); + + final byte[] keyBytes = (byte[])capturedKeyMessage.key(); + + final ByteArrayInputStream keyInputStream = new ByteArrayInputStream (keyBytes); + final ObjectInputStream keyObjectInputStream = new ObjectInputStream (keyInputStream); + final Object keyObj = keyObjectInputStream.readObject(); + + final TestKey tk = (TestKey)keyObj; + + Assert.assertEquals(tk.getKeyPart1(), "compositePart1"); + Assert.assertEquals(tk.getKeyPart2(), "compositePart2"); + + final byte[] messageBytes = (byte[])capturedKeyMessage.message(); + + final ByteArrayInputStream messageInputStream = new ByteArrayInputStream (messageBytes); + final ObjectInputStream messageObjectInputStream = new ObjectInputStream (messageInputStream); + final Object messageObj = messageObjectInputStream.readObject(); + + final TestPayload tp = (TestPayload)messageObj; + + Assert.assertEquals(tp.getPart1(), "part1"); + Assert.assertEquals(tp.getPart2(), "part2"); + + Assert.assertEquals(capturedKeyMessage.topic(), "test"); + } + + /** + * User does not set an explicit key encoder, but a value encoder, and sends the corresponding data + */ + @Test + @SuppressWarnings("unchecked") + public void testSendMessageWithDefaultKeyEncoderAndNonDefaultValueEncoderAndCorrespondingData() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + final AvroBackedKafkaEncoder encoder = new AvroBackedKafkaEncoder(TestPayload.class); + producerMetadata.setValueEncoder(encoder); + producerMetadata.setKeyEncoder(new DefaultEncoder(null)); + producerMetadata.setValueClassType(TestPayload.class); + final Producer producer = Mockito.mock(Producer.class); + + final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); + final TestPayload tp = new TestPayload("part1", "part2"); + final Message message = MessageBuilder.withPayload(tp). + setHeader("messageKey", "key") + .setHeader("topic", "test").build(); + + configuration.send(message); + + Mockito.verify(producer, Mockito.times(1)).send(Mockito.any(KeyedMessage.class)); + + final ArgumentCaptor argument = ArgumentCaptor.forClass(KeyedMessage.class); + Mockito.verify(producer).send(argument.capture()); + + final KeyedMessage capturedKeyMessage = argument.getValue(); + + final byte[] keyBytes = (byte[])capturedKeyMessage.key(); + + final ByteArrayInputStream keyInputStream = new ByteArrayInputStream (keyBytes); + final ObjectInputStream keyObjectInputStream = new ObjectInputStream (keyInputStream); + final Object keyObj = keyObjectInputStream.readObject(); + + Assert.assertEquals("key", keyObj); + Assert.assertEquals(capturedKeyMessage.message(), tp); + + Assert.assertEquals(capturedKeyMessage.topic(), "test"); + } + + /** + * User does set an explicit key encoder, but not a value encoder, and sends the corresponding data + */ + @Test + @SuppressWarnings("unchecked") + public void testSendMessageWithNonDefaultKeyEncoderAndDefaultValueEncoderAndCorrespondingData() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + final AvroBackedKafkaEncoder encoder = new AvroBackedKafkaEncoder(TestKey.class); + producerMetadata.setKeyEncoder(encoder); + producerMetadata.setValueEncoder(new DefaultEncoder(null)); + producerMetadata.setKeyClassType(TestKey.class); + final Producer producer = Mockito.mock(Producer.class); + + final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); + final TestKey tk = new TestKey("part1", "part2"); + final Message message = MessageBuilder.withPayload("test message"). + setHeader("messageKey", tk) + .setHeader("topic", "test").build(); + + configuration.send(message); + + Mockito.verify(producer, Mockito.times(1)).send(Mockito.any(KeyedMessage.class)); + + final ArgumentCaptor argument = ArgumentCaptor.forClass(KeyedMessage.class); + Mockito.verify(producer).send(argument.capture()); + + final KeyedMessage capturedKeyMessage = argument.getValue(); + + Assert.assertEquals(capturedKeyMessage.key(), tk); + + final byte[] payloadBytes = (byte[])capturedKeyMessage.message(); + + final ByteArrayInputStream payloadBis = new ByteArrayInputStream (payloadBytes); + final ObjectInputStream payloadOis = new ObjectInputStream (payloadBis); + final Object payloadObj = payloadOis.readObject(); + + Assert.assertEquals("test message", payloadObj); + + Assert.assertEquals(capturedKeyMessage.topic(), "test"); + } + + /** + * User does not set an explicit key/value encoder, but send a serializable String key/value pair + */ + @Test + @SuppressWarnings("unchecked") + public void testSendMessageWithDefaultKeyAndValueEncodersAndStringKeyAndValue() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + producerMetadata.setValueEncoder(new DefaultEncoder(null)); + producerMetadata.setKeyEncoder(new DefaultEncoder(null)); + final Producer producer = Mockito.mock(Producer.class); + + final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); + + final Message message = MessageBuilder.withPayload("test message"). + setHeader("messageKey", "key") + .setHeader("topic", "test").build(); + + configuration.send(message); + + Mockito.verify(producer, Mockito.times(1)).send(Mockito.any(KeyedMessage.class)); + + final ArgumentCaptor argument = ArgumentCaptor.forClass(KeyedMessage.class); + Mockito.verify(producer).send(argument.capture()); + + final KeyedMessage capturedKeyMessage = argument.getValue(); + final byte[] keyBytes = (byte[])capturedKeyMessage.key(); + + final ByteArrayInputStream keyBis = new ByteArrayInputStream (keyBytes); + final ObjectInputStream keyOis = new ObjectInputStream (keyBis); + final Object keyObj = keyOis.readObject(); + + Assert.assertEquals("key", keyObj); + + final byte[] payloadBytes = (byte[])capturedKeyMessage.message(); + + final ByteArrayInputStream payloadBis = new ByteArrayInputStream (payloadBytes); + final ObjectInputStream payloadOis = new ObjectInputStream (payloadBis); + final Object payloadObj = payloadOis.readObject(); + + Assert.assertEquals("test message", payloadObj); + Assert.assertEquals(capturedKeyMessage.topic(), "test"); + } + + /** + * User does not set an explicit key/value encoder, but send non-serializable object for both key/value + */ + @Test(expected = NotSerializableException.class) + @SuppressWarnings("unchecked") + public void testSendMessageWithDefaultKeyAndValueEncodersButNonSerializableKeyAndValue() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + producerMetadata.setValueEncoder(new DefaultEncoder(null)); + producerMetadata.setKeyEncoder(new DefaultEncoder(null)); + final Producer producer = Mockito.mock(Producer.class); + + final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); + + final Message message = MessageBuilder.withPayload(new NonSerializableTestPayload("part1", "part2")). + setHeader("messageKey", new NonSerializableTestKey("compositePart1", "compositePart2")) + .setHeader("topic", "test").build(); + configuration.send(message); + } + + /** + * User does not set an explicit key/value encoder, but send non-serializable key and serializable value + */ + @Test(expected = NotSerializableException.class) + @SuppressWarnings("unchecked") + public void testSendMessageWithDefaultKeyAndValueEncodersButNonSerializableKeyAndSerializableValue() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + producerMetadata.setValueEncoder(new DefaultEncoder(null)); + producerMetadata.setKeyEncoder(new DefaultEncoder(null)); + final Producer producer = Mockito.mock(Producer.class); + + final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); + + final Message message = MessageBuilder.withPayload(new TestPayload("part1", "part2")). + setHeader("messageKey", new NonSerializableTestKey("compositePart1", "compositePart2")) + .setHeader("topic", "test").build(); + configuration.send(message); + } + + /** + * User does not set an explicit key/value encoder, but send serializable key and non-serializable value + */ + @Test(expected = NotSerializableException.class) + @SuppressWarnings("unchecked") + public void testSendMessageWithDefaultKeyAndValueEncodersButSerializableKeyAndNonSerializableValue() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + producerMetadata.setValueEncoder(new DefaultEncoder(null)); + producerMetadata.setKeyEncoder(new DefaultEncoder(null)); + final Producer producer = Mockito.mock(Producer.class); + + final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); + + final Message message = MessageBuilder.withPayload(new NonSerializableTestPayload("part1", "part2")). + setHeader("messageKey", new TestKey("compositePart1", "compositePart2")) + .setHeader("topic", "test").build(); + configuration.send(message); + } +} diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ProducerFactoryBeanTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ProducerFactoryBeanTests.java new file mode 100644 index 0000000..0089e73 --- /dev/null +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ProducerFactoryBeanTests.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2013 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.support; + +import junit.framework.Assert; +import kafka.javaapi.producer.Producer; +import org.junit.Test; +import org.mockito.Mockito; + +/** + * @author Soby Chacko + */ +public class ProducerFactoryBeanTests { + + @Test + public void createProducerWithDefaultMetadata() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + final ProducerMetadata tm = Mockito.spy(producerMetadata); + final ProducerFactoryBean producerFactoryBean = new ProducerFactoryBean(tm, "localhost:9092"); + final Producer producer = producerFactoryBean.getObject(); + + Assert.assertTrue(producer != null); + + Mockito.verify(tm, Mockito.times(1)).getPartitioner(); + Mockito.verify(tm, Mockito.times(1)).getCompressionCodec(); + Mockito.verify(tm, Mockito.times(1)).getValueEncoder(); + Mockito.verify(tm, Mockito.times(1)).getKeyEncoder(); + Mockito.verify(tm, Mockito.times(1)).isAsync(); + Mockito.verify(tm, Mockito.times(0)).getBatchNumMessages(); + } + + @Test + public void createProducerWithAsyncFeatures() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + producerMetadata.setAsync(true); + producerMetadata.setBatchNumMessages("300"); + final ProducerMetadata tm = Mockito.spy(producerMetadata); + final ProducerFactoryBean producerFactoryBean = new ProducerFactoryBean(tm, "localhost:9092"); + final Producer producer = producerFactoryBean.getObject(); + + Assert.assertTrue(producer != null); + + Mockito.verify(tm, Mockito.times(1)).getPartitioner(); + Mockito.verify(tm, Mockito.times(1)).getCompressionCodec(); + Mockito.verify(tm, Mockito.times(1)).getValueEncoder(); + Mockito.verify(tm, Mockito.times(1)).getKeyEncoder(); + Mockito.verify(tm, Mockito.times(1)).isAsync(); + Mockito.verify(tm, Mockito.times(2)).getBatchNumMessages(); + } +} diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/NonSerializableTestKey.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/NonSerializableTestKey.java new file mode 100644 index 0000000..eef22df --- /dev/null +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/NonSerializableTestKey.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2013 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.test.utils; + +public class NonSerializableTestKey { + private final String keyPart1; + private final String keyPart2; + + public NonSerializableTestKey(final String keyPart1, final String keyPart2) { + this.keyPart1 = keyPart1; + this.keyPart2 = keyPart2; + } + + public String getKeyPart1() { + return keyPart1; + } + + public String getKeyPart2() { + return keyPart2; + } + +} diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/NonSerializableTestPayload.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/NonSerializableTestPayload.java new file mode 100644 index 0000000..6b2bb2f --- /dev/null +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/NonSerializableTestPayload.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2013 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.test.utils; + +public class NonSerializableTestPayload { + private final String part1; + private final String part2; + + public NonSerializableTestPayload(final String part1, final String part2) { + this.part1 = part1; + this.part2 = part2; + } + + public String getPart1() { + return part1; + } + + public String getPart2() { + return part2; + } +} diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestKey.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestKey.java new file mode 100644 index 0000000..10e0480 --- /dev/null +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestKey.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2013 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.test.utils; + +import java.io.Serializable; + +public class TestKey implements Serializable { + private static final long serialVersionUID = -6415387283545560656L; + + private final String keyPart1; + private final String keyPart2; + + public TestKey(final String keyPart1, final String keyPart2) { + this.keyPart1 = keyPart1; + this.keyPart2 = keyPart2; + } + + public String getKeyPart1() { + return keyPart1; + } + + public String getKeyPart2() { + return keyPart2; + } +} diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestObject.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestObject.java new file mode 100644 index 0000000..42c2ec1 --- /dev/null +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestObject.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2013 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.test.utils; + +public class TestObject { + public String testData1; + public int testData2; + + public String getTestData1() { + return testData1; + } + + public void setTestData1(final String testData1) { + this.testData1 = testData1; + } + + public int getTestData2() { + return testData2; + } + + public void setTestData2(final int testData2) { + this.testData2 = testData2; + } + +} diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestPayload.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestPayload.java new file mode 100644 index 0000000..3ac94f8 --- /dev/null +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestPayload.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2013 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.test.utils; + +import java.io.Serializable; + +public class TestPayload implements Serializable { + private static final long serialVersionUID = -8560378224929007403L; + + private final String part1; + private final String part2; + + public TestPayload(final String part1, final String part2){ + this.part1 = part1; + this.part2 = part2; + } + + public String getPart1() { + return part1; + } + + public String getPart2() { + return part2; + } +} diff --git a/spring-integration-kafka/src/test/resources/log4j.properties b/spring-integration-kafka/src/test/resources/log4j.properties new file mode 100644 index 0000000..f428439 --- /dev/null +++ b/spring-integration-kafka/src/test/resources/log4j.properties @@ -0,0 +1,8 @@ +log4j.rootCategory=WARN, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss.SSS} %-5p [%t][%c] %m%n + +log4j.category.org.springframework.integration=WARN +log4j.category.org.springframework.integration.kafka=INFO diff --git a/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaConsumerContextParserTests-context.xml b/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaConsumerContextParserTests-context.xml new file mode 100644 index 0000000..e0978bb --- /dev/null +++ b/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaConsumerContextParserTests-context.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaInboundAdapterCommon-context.xml b/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaInboundAdapterCommon-context.xml new file mode 100644 index 0000000..f7fecae --- /dev/null +++ b/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaInboundAdapterCommon-context.xml @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaInboundAdapterParserTests-context.xml b/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaInboundAdapterParserTests-context.xml new file mode 100644 index 0000000..70266d3 --- /dev/null +++ b/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaInboundAdapterParserTests-context.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaOutboundAdapterParserTests-context.xml b/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaOutboundAdapterParserTests-context.xml new file mode 100644 index 0000000..52eef96 --- /dev/null +++ b/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaOutboundAdapterParserTests-context.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaProducerContextParserTests-context.xml b/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaProducerContextParserTests-context.xml new file mode 100644 index 0000000..8f9750a --- /dev/null +++ b/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/kafkaProducerContextParserTests-context.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + diff --git a/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/zookeeperConnectParserTests-context.xml b/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/zookeeperConnectParserTests-context.xml new file mode 100644 index 0000000..f5d0784 --- /dev/null +++ b/spring-integration-kafka/src/test/resources/org/springframework/integration/kafka/config/xml/zookeeperConnectParserTests-context.xml @@ -0,0 +1,14 @@ + + + + + + + From 5bf4b9871e65a4616e2faa671dec4a4573cdf0f2 Mon Sep 17 00:00:00 2001 From: Gunnar Hillert Date: Mon, 3 Jun 2013 00:02:47 -0400 Subject: [PATCH 2/2] INTEXT-44 - Polishing * Upgrade to Gradle 1.6 * Upgrade Sonar plugin to SonarRunner * Upgrade a few dependencies (e.g. JUnit, Spring Integration) * Remove deprecations (e.g. JUnit) * Convert spaces to tabs * Add author tags * Add @since --- spring-integration-kafka/build.gradle | 146 +++--- .../gradle/wrapper/gradle-wrapper.jar | Bin 39770 -> 49875 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- spring-integration-kafka/gradlew | 8 +- spring-integration-kafka/gradlew.bat | 180 +++---- spring-integration-kafka/publish-maven.gradle | 9 +- .../xml/KafkaConsumerContextParser.java | 119 ++--- .../xml/KafkaInboundChannelAdapterParser.java | 21 +- .../config/xml/KafkaNamespaceHandler.java | 11 +- .../KafkaOutboundChannelAdapterParser.java | 21 +- .../xml/KafkaProducerContextParser.java | 85 ++-- .../config/xml/ZookeeperConnectParser.java | 29 +- .../kafka/core/KafkaConsumerDefaults.java | 31 +- .../kafka/core/ZookeeperConnectDefaults.java | 15 +- .../KafkaHighLevelConsumerMessageSource.java | 25 +- .../outbound/KafkaProducerMessageHandler.java | 15 +- .../avro/AvroBackedKafkaDecoder.java | 35 +- .../avro/AvroBackedKafkaEncoder.java | 35 +- .../kafka/serializer/avro/AvroSerializer.java | 27 +- .../avro/AvroSpecificDatumSerializer.java | 27 +- .../serializer/common/StringEncoder.java | 23 +- .../support/ConsumerConfigFactoryBean.java | 53 +-- .../kafka/support/ConsumerConfiguration.java | 281 +++++------ .../support/ConsumerConnectionProvider.java | 15 +- .../kafka/support/ConsumerMetadata.java | 227 ++++----- .../kafka/support/DefaultPartitioner.java | 19 +- .../kafka/support/KafkaConsumerContext.java | 65 +-- .../kafka/support/KafkaProducerContext.java | 51 +- .../kafka/support/MessageLeftOverTracker.java | 27 +- .../kafka/support/ProducerConfiguration.java | 109 ++--- .../kafka/support/ProducerFactoryBean.java | 69 +-- .../kafka/support/ProducerMetadata.java | 175 +++---- .../kafka/support/ZookeeperConnect.java | 57 +-- .../config/xml/spring-integration-kafka.gif | Bin 572 -> 539 bytes .../docbook/SIAdapterLowerPrefix.xml | 72 --- .../src/reference/docbook/history.xml | 4 - .../src/reference/docbook/images/logo.png | Bin 9627 -> 0 bytes .../src/reference/docbook/index.xml | 67 --- .../src/reference/docbook/resources.xml | 15 - .../src/reference/docbook/whats-new.xml | 8 - .../xml/KafkaConsumerContextParserTests.java | 23 +- .../xml/KafkaInboundAdapterParserTests.java | 8 +- .../xml/KafkaOutboundAdapterParserTests.java | 30 +- .../xml/KafkaProducerContextParserTests.java | 64 +-- .../xml/ZookeeperConnectParserTests.java | 27 +- .../AvroBackedKafkaSerializerTest.java | 50 +- .../support/ConsumerConfigurationTests.java | 429 ++++++++--------- .../support/KafkaConsumerContextTest.java | 77 +-- .../support/ProducerConfigurationTests.java | 440 +++++++++--------- .../support/ProducerFactoryBeanTests.java | 63 +-- .../test/utils/NonSerializableTestKey.java | 28 +- .../utils/NonSerializableTestPayload.java | 28 +- .../integration/kafka/test/utils/TestKey.java | 30 +- .../kafka/test/utils/TestObject.java | 32 +- .../kafka/test/utils/TestPayload.java | 30 +- 55 files changed, 1706 insertions(+), 1833 deletions(-) delete mode 100644 spring-integration-kafka/src/reference/docbook/SIAdapterLowerPrefix.xml delete mode 100644 spring-integration-kafka/src/reference/docbook/history.xml delete mode 100644 spring-integration-kafka/src/reference/docbook/images/logo.png delete mode 100644 spring-integration-kafka/src/reference/docbook/index.xml delete mode 100644 spring-integration-kafka/src/reference/docbook/resources.xml delete mode 100644 spring-integration-kafka/src/reference/docbook/whats-new.xml diff --git a/spring-integration-kafka/build.gradle b/spring-integration-kafka/build.gradle index b4da5f7..fce4040 100644 --- a/spring-integration-kafka/build.gradle +++ b/spring-integration-kafka/build.gradle @@ -1,12 +1,9 @@ -description = 'Spring Integration Kafka Adapter' +description = 'Spring Integration Kafka Support' buildscript { repositories { maven { url 'https://repo.springsource.org/plugins-snapshot' } } - dependencies { - classpath 'org.springframework.build.gradle:docbook-reference-plugin:0.1.5' - } } apply plugin: 'java' @@ -17,24 +14,28 @@ apply plugin: 'idea' group = 'org.springframework.integration' repositories { - mavenLocal() - mavenCentral() - maven { url 'http://repo.springsource.org/libs-milestone' } - maven { url 'http://repo.springsource.org/plugins-release' } - maven { url 'https://repo.springsource.org/libs-snapshot' } + maven { url 'https://repo.springsource.org/libs-snapshot' } } sourceCompatibility=1.6 targetCompatibility=1.6 ext { - junitVersion = '4.10' + junitVersion = '4.11' log4jVersion = '1.2.12' - mockitoVersion = '1.9.0' + mockitoVersion = '1.9.5' springVersion = '3.1.3.RELEASE' - springIntegrationVersion = '2.2.0.RELEASE' + springIntegrationVersion = '2.2.3.RELEASE' idPrefix = 'kafka' + + linkHomepage = 'https://github.com/SpringSource/spring-integration-extensions' + linkCi = 'https://build.springsource.org/browse/INTEXT' + linkIssue = 'https://jira.springsource.org/browse/INTEXT' + linkScmUrl = 'https://github.com/SpringSource/spring-integration-extensions' + linkScmConnection = 'https://github.com/SpringSource/spring-integration-extensions.git' + linkScmDevConnection = 'git@github.com:SpringSource/spring-integration-extensions.git' + } eclipse { @@ -61,47 +62,48 @@ dependencies { compile "org.springframework:spring-beans:$springVersion" compile "org.springframework:spring-context:$springVersion" compile "org.springframework:spring-expression:$springVersion" - compile "org.springframework.integration:spring-integration-core:$springIntegrationVersion" - compile("org.apache:kafka_2.9.2:0.8.0-SNAPSHOT") { - exclude module: 'log4j' - } - compile("org.apache.zookeeper:zookeeper:3.3.4") { - exclude module: 'log4j' - exclude module: 'jline' - } - compile "org.scala-lang:scala-library:2.9.2" - compile "org.scala-lang:scala-compiler:2.9.2" - compile("log4j:log4j:1.2.15") { - exclude module: 'jms' - exclude module: 'jmx' - exclude module: 'jmxtools' - exclude module: 'jmxri' + compile "org.springframework.integration:spring-integration-core:$springIntegrationVersion" + compile("org.apache:kafka_2.9.2:0.8.0-SNAPSHOT") { + exclude module: 'log4j' + } + compile("org.apache.zookeeper:zookeeper:3.3.4") { + exclude module: 'log4j' + exclude module: 'jline' + } + compile "org.scala-lang:scala-library:2.9.2" + compile "org.scala-lang:scala-compiler:2.9.2" + compile("log4j:log4j:1.2.15") { + exclude module: 'jms' + exclude module: 'jmx' + exclude module: 'jmxtools' + exclude module: 'jmxri' + } + compile "org.slf4j:slf4j-simple:1.6.4" + compile "net.sf.jopt-simple:jopt-simple:3.2" + compile "org.xerial.snappy:snappy-java:1.0.4.1" + //runtime "zkclient:zkclient:20120522" - } - compile "org.slf4j:slf4j-simple:1.6.4" - compile "net.sf.jopt-simple:jopt-simple:3.2" - compile "org.xerial.snappy:snappy-java:1.0.4.1" - //runtime "zkclient:zkclient:20120522" + runtime "com.101tec:zkclient:0.2" + runtime "com.yammer.metrics:metrics-core:2.2.0" + runtime "com.yammer.metrics:metrics-annotation:2.2.0" - runtime "com.101tec:zkclient:0.2" - runtime "com.yammer.metrics:metrics-core:2.2.0" - runtime "com.yammer.metrics:metrics-annotation:2.2.0" - - compile "org.apache.avro:avro:1.7.3" - compile "org.apache.avro:avro-compiler:1.7.3" + compile "org.apache.avro:avro:1.7.3" + compile "org.apache.avro:avro-compiler:1.7.3" testCompile "org.springframework.integration:spring-integration-test:$springIntegrationVersion" testCompile "junit:junit-dep:$junitVersion" testCompile("log4j:log4j:$log4jVersion") { - exclude module: 'jms' - exclude module: 'jmx' - exclude module: 'jmxtools' - exclude module: 'jmxri' -} + exclude module: 'jms' + exclude module: 'jmx' + exclude module: 'jmxtools' + exclude module: 'jmxri' + } + testCompile "org.mockito:mockito-all:$mockitoVersion" testCompile "org.springframework:spring-test:$springVersion" - testCompile "org.springframework.integration:spring-integration-stream:$springIntegrationVersion" - jacoco group: "org.jacoco", name: "org.jacoco.agent", version: "0.5.6.201201232323", classifier: "runtime" + testCompile "org.springframework.integration:spring-integration-stream:$springIntegrationVersion" + + jacoco group: "org.jacoco", name: "org.jacoco.agent", version: "0.6.2.201302030002", classifier: "runtime" } @@ -130,44 +132,18 @@ artifacts { archives javadocJar } -apply plugin: 'docbook-reference' +apply plugin: 'sonar-runner' -reference { - sourceDir = file('src/reference/docbook') -} - -apply plugin: 'sonar' - -sonar { - - if (rootProject.hasProperty('sonarHostUrl')) { - server.url = rootProject.sonarHostUrl +sonarRunner { + sonarProperties { + property "sonar.jacoco.reportPath", "${buildDir.name}/jacoco.exec" + property "sonar.links.homepage", linkHomepage + property "sonar.links.ci", linkCi + property "sonar.links.issue", linkIssue + property "sonar.links.scm", linkScmUrl + property "sonar.links.scm_dev", linkScmDevConnection + property "sonar.java.coveragePlugin", "jacoco" } - - database { - if (rootProject.hasProperty('sonarJdbcUrl')) { - url = rootProject.sonarJdbcUrl - } - if (rootProject.hasProperty('sonarJdbcDriver')) { - driverClassName = rootProject.sonarJdbcDriver - } - if (rootProject.hasProperty('sonarJdbcUsername')) { - username = rootProject.sonarJdbcUsername - } - if (rootProject.hasProperty('sonarJdbcPassword')) { - password = rootProject.sonarJdbcPassword - } - } - - project { - dynamicAnalysis = "reuseReports" - withProjectProperties { props -> - props["sonar.core.codeCoveragePlugin"] = "jacoco" - props["sonar.jacoco.reportPath"] = "${buildDir.name}/jacoco.exec" - } - } - - logger.info("Sonar parameters used: server.url='${server.url}'; database.url='${database.url}'; database.driverClassName='${database.driverClassName}'; database.username='${database.username}'") } task api(type: Javadoc) { @@ -212,7 +188,7 @@ task schemaZip(type: Zip) { task docsZip(type: Zip) { group = 'Distribution' classifier = 'docs' - description = "Builds -${classifier} archive containing api and reference " + + description = "Builds -${classifier} archive containing api " + "for deployment at static.springframework.org/spring-integration/docs." from('src/dist') { @@ -222,10 +198,6 @@ task docsZip(type: Zip) { from (api) { into 'api' } - - from (reference) { - into 'reference' - } } task distZip(type: Zip, dependsOn: [docsZip, schemaZip]) { @@ -300,5 +272,5 @@ task dist(dependsOn: assemble) { task wrapper(type: Wrapper) { description = 'Generates gradlew[.bat] scripts' - gradleVersion = '1.3' + gradleVersion = '1.6' } diff --git a/spring-integration-kafka/gradle/wrapper/gradle-wrapper.jar b/spring-integration-kafka/gradle/wrapper/gradle-wrapper.jar index 81dcde627d5cfbc12c96dc5113aac79b66a15118..a7634b071cb255e91a4572934e55b8cd8877b3e4 100644 GIT binary patch literal 49875 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 DCvKB) delta 34809 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 tUacb(E$p0}bI={E2)*7xC<^GF{C1<_&- APP_HOME="`pwd -P`" -cd "$SAVED" +cd "$SAVED" >&- CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -101,7 +101,7 @@ if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else - warn "Could not query businessSystem maximum file descriptor limit: $MAX_FD_LIMIT" + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi diff --git a/spring-integration-kafka/gradlew.bat b/spring-integration-kafka/gradlew.bat index 8a0b282..aec9973 100644 --- a/spring-integration-kafka/gradlew.bat +++ b/spring-integration-kafka/gradlew.bat @@ -1,90 +1,90 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/spring-integration-kafka/publish-maven.gradle b/spring-integration-kafka/publish-maven.gradle index 5532ec4..9321d3b 100644 --- a/spring-integration-kafka/publish-maven.gradle +++ b/spring-integration-kafka/publish-maven.gradle @@ -31,7 +31,7 @@ def customizePom(pom, gradleProject) { generatedPom.project { name = gradleProject.description description = gradleProject.description - url = 'https://github.com/SpringSource/spring-integration-extensions' + url = linkHomepage organization { name = 'SpringSource' url = 'http://springsource.org' @@ -43,10 +43,11 @@ def customizePom(pom, gradleProject) { distribution 'repo' } } + scm { - url = 'https://github.com/SpringSource/spring-integration-extensions' - connection = 'scm:git:git://github.com/SpringSource/spring-integration-extensions' - developerConnection = 'scm:git:git://github.com/SpringSource/spring-integration-extensions' + url = linkScmUrl + connection = 'scm:git:' + linkScmConnection + developerConnection = 'scm:git:' + linkScmDevConnection } developers { 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 3eac811..0ac442b 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 @@ -37,86 +37,87 @@ import java.util.Map; /** * @author Soby Chacko + * @since 0.5 */ public class KafkaConsumerContextParser extends AbstractSingleBeanDefinitionParser { - @Override - protected Class getBeanClass(final Element element) { - return KafkaConsumerContext.class; - } + @Override + protected Class getBeanClass(final Element element) { + return KafkaConsumerContext.class; + } - @Override - protected void doParse(final Element element, final ParserContext parserContext, final BeanDefinitionBuilder builder) { - super.doParse(element, parserContext, builder); + @Override + protected void doParse(final Element element, final ParserContext parserContext, final BeanDefinitionBuilder builder) { + super.doParse(element, parserContext, builder); - final Element consumerConfigurations = DomUtils.getChildElementByTagName(element, "consumer-configurations"); - parseConsumerConfigurations(consumerConfigurations, parserContext, builder, element); - } + final Element consumerConfigurations = DomUtils.getChildElementByTagName(element, "consumer-configurations"); + parseConsumerConfigurations(consumerConfigurations, parserContext, builder, element); + } - private void parseConsumerConfigurations(final Element consumerConfigurations, final ParserContext parserContext, - final BeanDefinitionBuilder builder, final Element parentElem) { - for (final Element consumerConfiguration : DomUtils.getChildElementsByTagName(consumerConfigurations, "consumer-configuration")) { - final BeanDefinitionBuilder consumerConfigurationBuilder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerConfiguration.class); - final BeanDefinitionBuilder consumerMetadataBuilder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerMetadata.class); + private void parseConsumerConfigurations(final Element consumerConfigurations, final ParserContext parserContext, + final BeanDefinitionBuilder builder, final Element parentElem) { + for (final Element consumerConfiguration : DomUtils.getChildElementsByTagName(consumerConfigurations, "consumer-configuration")) { + final BeanDefinitionBuilder consumerConfigurationBuilder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerConfiguration.class); + final BeanDefinitionBuilder consumerMetadataBuilder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerMetadata.class); - IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerMetadataBuilder, consumerConfiguration, "group-id"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerMetadataBuilder, consumerConfiguration, "group-id"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(consumerMetadataBuilder, consumerConfiguration, "value-decoder"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(consumerMetadataBuilder, consumerConfiguration, "key-decoder"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerMetadataBuilder, consumerConfiguration, "key-class-type"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerMetadataBuilder, consumerConfiguration, "value-class-type"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerConfigurationBuilder, consumerConfiguration, "max-messages"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerMetadataBuilder, parentElem, "consumer-timeout"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(consumerMetadataBuilder, consumerConfiguration, "value-decoder"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(consumerMetadataBuilder, consumerConfiguration, "key-decoder"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerMetadataBuilder, consumerConfiguration, "key-class-type"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerMetadataBuilder, consumerConfiguration, "value-class-type"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerConfigurationBuilder, consumerConfiguration, "max-messages"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerMetadataBuilder, parentElem, "consumer-timeout"); - final Map topicStreamsMap = new HashMap(); + final Map topicStreamsMap = new HashMap(); - for (final Element topicConfiguration : DomUtils.getChildElementsByTagName(consumerConfiguration, "topic")) { - final String topic = topicConfiguration.getAttribute("id"); - final String streams = topicConfiguration.getAttribute("streams"); - final Integer streamsInt = Integer.valueOf(streams); - topicStreamsMap.put(topic, streamsInt); - } + for (final Element topicConfiguration : DomUtils.getChildElementsByTagName(consumerConfiguration, "topic")) { + final String topic = topicConfiguration.getAttribute("id"); + final String streams = topicConfiguration.getAttribute("streams"); + final Integer streamsInt = Integer.valueOf(streams); + topicStreamsMap.put(topic, streamsInt); + } - consumerMetadataBuilder.addPropertyValue("topicStreamMap", topicStreamsMap); + consumerMetadataBuilder.addPropertyValue("topicStreamMap", topicStreamsMap); - final BeanDefinition consumerMetadataBeanDef = consumerMetadataBuilder.getBeanDefinition(); - registerBeanDefinition(new BeanDefinitionHolder(consumerMetadataBeanDef, "consumerMetadata_" + consumerConfiguration.getAttribute("group-id")), - parserContext.getRegistry()); + final BeanDefinition consumerMetadataBeanDef = consumerMetadataBuilder.getBeanDefinition(); + registerBeanDefinition(new BeanDefinitionHolder(consumerMetadataBeanDef, "consumerMetadata_" + consumerConfiguration.getAttribute("group-id")), + parserContext.getRegistry()); - final String zookeeperConnectBean = parentElem.getAttribute("zookeeper-connect"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, parentElem, zookeeperConnectBean); + final String zookeeperConnectBean = parentElem.getAttribute("zookeeper-connect"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, parentElem, zookeeperConnectBean); - final BeanDefinitionBuilder consumerConfigFactoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerConfigFactoryBean.class); - consumerConfigFactoryBuilder.addConstructorArgReference("consumerMetadata_" + consumerConfiguration.getAttribute("group-id")); + final BeanDefinitionBuilder consumerConfigFactoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerConfigFactoryBean.class); + consumerConfigFactoryBuilder.addConstructorArgReference("consumerMetadata_" + consumerConfiguration.getAttribute("group-id")); - if (StringUtils.hasText(zookeeperConnectBean)) { - consumerConfigFactoryBuilder.addConstructorArgReference(zookeeperConnectBean); - } + if (StringUtils.hasText(zookeeperConnectBean)) { + consumerConfigFactoryBuilder.addConstructorArgReference(zookeeperConnectBean); + } - final BeanDefinition consumerConfigFactoryBuilderBeanDefinition = consumerConfigFactoryBuilder.getBeanDefinition(); - registerBeanDefinition(new BeanDefinitionHolder(consumerConfigFactoryBuilderBeanDefinition, "consumerConfigFactory_" + consumerConfiguration.getAttribute("group-id")), parserContext.getRegistry()); + final BeanDefinition consumerConfigFactoryBuilderBeanDefinition = consumerConfigFactoryBuilder.getBeanDefinition(); + registerBeanDefinition(new BeanDefinitionHolder(consumerConfigFactoryBuilderBeanDefinition, "consumerConfigFactory_" + consumerConfiguration.getAttribute("group-id")), parserContext.getRegistry()); - final BeanDefinitionBuilder consumerConnectionProviderBuilder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerConnectionProvider.class); - consumerConnectionProviderBuilder.addConstructorArgReference("consumerConfigFactory_" + consumerConfiguration.getAttribute("group-id")); + final BeanDefinitionBuilder consumerConnectionProviderBuilder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerConnectionProvider.class); + consumerConnectionProviderBuilder.addConstructorArgReference("consumerConfigFactory_" + consumerConfiguration.getAttribute("group-id")); - final BeanDefinition consumerConnectionProviderBuilderBeanDefinition = consumerConnectionProviderBuilder.getBeanDefinition(); - registerBeanDefinition(new BeanDefinitionHolder(consumerConnectionProviderBuilderBeanDefinition, "consumerConnectionProvider_" + consumerConfiguration.getAttribute("group-id")), parserContext.getRegistry()); + final BeanDefinition consumerConnectionProviderBuilderBeanDefinition = consumerConnectionProviderBuilder.getBeanDefinition(); + registerBeanDefinition(new BeanDefinitionHolder(consumerConnectionProviderBuilderBeanDefinition, "consumerConnectionProvider_" + consumerConfiguration.getAttribute("group-id")), parserContext.getRegistry()); - final BeanDefinitionBuilder messageLeftOverBeanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(MessageLeftOverTracker.class); - final BeanDefinition messageLeftOverBeanDefinition = messageLeftOverBeanDefinitionBuilder.getBeanDefinition(); - registerBeanDefinition(new BeanDefinitionHolder(messageLeftOverBeanDefinition, "messageLeftOver_" + consumerConfiguration.getAttribute("group-id")), - parserContext.getRegistry()); + final BeanDefinitionBuilder messageLeftOverBeanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(MessageLeftOverTracker.class); + final BeanDefinition messageLeftOverBeanDefinition = messageLeftOverBeanDefinitionBuilder.getBeanDefinition(); + registerBeanDefinition(new BeanDefinitionHolder(messageLeftOverBeanDefinition, "messageLeftOver_" + consumerConfiguration.getAttribute("group-id")), + parserContext.getRegistry()); - consumerConfigurationBuilder.addConstructorArgReference("consumerMetadata_" + consumerConfiguration.getAttribute("group-id")); - consumerConfigurationBuilder.addConstructorArgReference("consumerConnectionProvider_" + consumerConfiguration.getAttribute("group-id")); - consumerConfigurationBuilder.addConstructorArgReference("messageLeftOver_" + consumerConfiguration.getAttribute("group-id")); + consumerConfigurationBuilder.addConstructorArgReference("consumerMetadata_" + consumerConfiguration.getAttribute("group-id")); + consumerConfigurationBuilder.addConstructorArgReference("consumerConnectionProvider_" + consumerConfiguration.getAttribute("group-id")); + consumerConfigurationBuilder.addConstructorArgReference("messageLeftOver_" + consumerConfiguration.getAttribute("group-id")); - final AbstractBeanDefinition consumerConfigurationBeanDefinition = consumerConfigurationBuilder.getBeanDefinition(); + final AbstractBeanDefinition consumerConfigurationBeanDefinition = consumerConfigurationBuilder.getBeanDefinition(); - final String consumerConfigurationBeanName = "consumerConfiguration_" + consumerConfiguration.getAttribute("group-id"); - registerBeanDefinition(new BeanDefinitionHolder(consumerConfigurationBeanDefinition, consumerConfigurationBeanName), - parserContext.getRegistry()); - } - } + final String consumerConfigurationBeanName = "consumerConfiguration_" + consumerConfiguration.getAttribute("group-id"); + registerBeanDefinition(new BeanDefinitionHolder(consumerConfigurationBeanDefinition, consumerConfigurationBeanName), + parserContext.getRegistry()); + } + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaInboundChannelAdapterParser.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaInboundChannelAdapterParser.java index cc52b1e..9b9735c 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaInboundChannelAdapterParser.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaInboundChannelAdapterParser.java @@ -28,22 +28,23 @@ import org.w3c.dom.Element; * The Kafka Inbound Channel adapter parser * * @author Soby Chacko + * @since 0.5 * */ public class KafkaInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser { - @Override - protected BeanMetadataElement parseSource(final Element element, final ParserContext parserContext) { - final BeanDefinitionBuilder highLevelConsumerMessageSourceBuilder = - BeanDefinitionBuilder.genericBeanDefinition(KafkaHighLevelConsumerMessageSource.class); + @Override + protected BeanMetadataElement parseSource(final Element element, final ParserContext parserContext) { + final BeanDefinitionBuilder highLevelConsumerMessageSourceBuilder = + BeanDefinitionBuilder.genericBeanDefinition(KafkaHighLevelConsumerMessageSource.class); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(highLevelConsumerMessageSourceBuilder, element, "kafka-decoder"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(highLevelConsumerMessageSourceBuilder, element, "kafka-decoder"); - final String kafkaConsumerContext = element.getAttribute("kafka-consumer-context-ref"); + final String kafkaConsumerContext = element.getAttribute("kafka-consumer-context-ref"); - if (StringUtils.hasText(kafkaConsumerContext)) { - highLevelConsumerMessageSourceBuilder.addConstructorArgReference(kafkaConsumerContext); - } + if (StringUtils.hasText(kafkaConsumerContext)) { + highLevelConsumerMessageSourceBuilder.addConstructorArgReference(kafkaConsumerContext); + } - return highLevelConsumerMessageSourceBuilder.getBeanDefinition(); + return highLevelConsumerMessageSourceBuilder.getBeanDefinition(); } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaNamespaceHandler.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaNamespaceHandler.java index 92d7823..3cc177b 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaNamespaceHandler.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaNamespaceHandler.java @@ -21,18 +21,19 @@ import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHa * The namespace handler for the Kafka namespace * * @author Soby Chacko + * @since 0.5 * */ public class KafkaNamespaceHandler extends AbstractIntegrationNamespaceHandler { /* (non-Javadoc) * @see org.springframework.beans.factory.xml.NamespaceHandler#init() */ - @Override + @Override public void init() { - registerBeanDefinitionParser("zookeeper-connect", new ZookeeperConnectParser()); + registerBeanDefinitionParser("zookeeper-connect", new ZookeeperConnectParser()); registerBeanDefinitionParser("inbound-channel-adapter", new KafkaInboundChannelAdapterParser()); - registerBeanDefinitionParser("outbound-channel-adapter", new KafkaOutboundChannelAdapterParser()); - registerBeanDefinitionParser("producer-context", new KafkaProducerContextParser()); - registerBeanDefinitionParser("consumer-context", new KafkaConsumerContextParser()); + registerBeanDefinitionParser("outbound-channel-adapter", new KafkaOutboundChannelAdapterParser()); + registerBeanDefinitionParser("producer-context", new KafkaProducerContextParser()); + registerBeanDefinitionParser("consumer-context", new KafkaConsumerContextParser()); } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaOutboundChannelAdapterParser.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaOutboundChannelAdapterParser.java index 714dad3..2df108f 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaOutboundChannelAdapterParser.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaOutboundChannelAdapterParser.java @@ -26,20 +26,21 @@ import org.w3c.dom.Element; /** * * @author Soby Chacko + * @since 0.5 * */ public class KafkaOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { - @Override - protected AbstractBeanDefinition parseConsumer(final Element element, final ParserContext parserContext) { - final BeanDefinitionBuilder kafkaProducerMessageHandlerBuilder = - BeanDefinitionBuilder.genericBeanDefinition(KafkaProducerMessageHandler.class); + @Override + protected AbstractBeanDefinition parseConsumer(final Element element, final ParserContext parserContext) { + final BeanDefinitionBuilder kafkaProducerMessageHandlerBuilder = + BeanDefinitionBuilder.genericBeanDefinition(KafkaProducerMessageHandler.class); - final String kafkaServerBeanName = element.getAttribute("kafka-producer-context-ref"); + final String kafkaServerBeanName = element.getAttribute("kafka-producer-context-ref"); - if (StringUtils.hasText(kafkaServerBeanName)) { - kafkaProducerMessageHandlerBuilder.addConstructorArgReference(kafkaServerBeanName); - } + if (StringUtils.hasText(kafkaServerBeanName)) { + kafkaProducerMessageHandlerBuilder.addConstructorArgReference(kafkaServerBeanName); + } - return kafkaProducerMessageHandlerBuilder.getBeanDefinition(); - } + return kafkaProducerMessageHandlerBuilder.getBeanDefinition(); + } } 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 02ed893..5fc9937 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 @@ -32,58 +32,59 @@ import org.w3c.dom.Element; /** * @author Soby Chacko + * @since 0.5 */ public class KafkaProducerContextParser extends AbstractSimpleBeanDefinitionParser { - @Override - protected Class getBeanClass(final Element element) { - return KafkaProducerContext.class; - } + @Override + protected Class getBeanClass(final Element element) { + return KafkaProducerContext.class; + } - @Override - protected void doParse(final Element element, final ParserContext parserContext, final BeanDefinitionBuilder builder) { - super.doParse(element, parserContext, builder); + @Override + protected void doParse(final Element element, final ParserContext parserContext, final BeanDefinitionBuilder builder) { + super.doParse(element, parserContext, builder); - final Element topics = DomUtils.getChildElementByTagName(element, "producer-configurations"); - parseProducerConfigurations(topics, parserContext); - } + final Element topics = DomUtils.getChildElementByTagName(element, "producer-configurations"); + parseProducerConfigurations(topics, parserContext); + } - 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); + 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); - final BeanDefinitionBuilder producerMetadataBuilder = BeanDefinitionBuilder.genericBeanDefinition(ProducerMetadata.class); - producerMetadataBuilder.addConstructorArgValue(producerConfiguration.getAttribute("topic")); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "value-encoder"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "key-encoder"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "key-class-type"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "value-class-type"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "partitioner"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "compression-codec"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "async"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "batch-num-messages"); + final BeanDefinitionBuilder producerMetadataBuilder = BeanDefinitionBuilder.genericBeanDefinition(ProducerMetadata.class); + producerMetadataBuilder.addConstructorArgValue(producerConfiguration.getAttribute("topic")); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "value-encoder"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "key-encoder"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "key-class-type"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "value-class-type"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "partitioner"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "compression-codec"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "async"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(producerMetadataBuilder, producerConfiguration, "batch-num-messages"); - final BeanDefinition producerMetadataBeanDef = producerMetadataBuilder.getBeanDefinition(); - registerBeanDefinition(new BeanDefinitionHolder(producerMetadataBeanDef, "producerMetadata_" + producerConfiguration.getAttribute("topic")), - parserContext.getRegistry()); + final BeanDefinition producerMetadataBeanDef = producerMetadataBuilder.getBeanDefinition(); + registerBeanDefinition(new BeanDefinitionHolder(producerMetadataBeanDef, "producerMetadata_" + producerConfiguration.getAttribute("topic")), + parserContext.getRegistry()); - final BeanDefinitionBuilder producerFactoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(ProducerFactoryBean.class); - producerFactoryBuilder.addConstructorArgReference("producerMetadata_" + producerConfiguration.getAttribute("topic")); + final BeanDefinitionBuilder producerFactoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(ProducerFactoryBean.class); + producerFactoryBuilder.addConstructorArgReference("producerMetadata_" + producerConfiguration.getAttribute("topic")); - final String brokerList = producerConfiguration.getAttribute("broker-list"); - if (StringUtils.hasText(brokerList)) { - producerFactoryBuilder.addConstructorArgValue(producerConfiguration.getAttribute("broker-list")); - } + final String brokerList = producerConfiguration.getAttribute("broker-list"); + if (StringUtils.hasText(brokerList)) { + producerFactoryBuilder.addConstructorArgValue(producerConfiguration.getAttribute("broker-list")); + } - final BeanDefinition producerfactoryBeanDefinition = producerFactoryBuilder.getBeanDefinition(); - registerBeanDefinition(new BeanDefinitionHolder(producerfactoryBeanDefinition, "prodFactory_" + producerConfiguration.getAttribute("topic")), parserContext.getRegistry()); + final BeanDefinition producerfactoryBeanDefinition = producerFactoryBuilder.getBeanDefinition(); + registerBeanDefinition(new BeanDefinitionHolder(producerfactoryBeanDefinition, "prodFactory_" + producerConfiguration.getAttribute("topic")), parserContext.getRegistry()); - producerConfigurationBuilder.addConstructorArgReference("producerMetadata_" + producerConfiguration.getAttribute("topic")); - producerConfigurationBuilder.addConstructorArgReference("prodFactory_" + producerConfiguration.getAttribute("topic")); + producerConfigurationBuilder.addConstructorArgReference("producerMetadata_" + producerConfiguration.getAttribute("topic")); + producerConfigurationBuilder.addConstructorArgReference("prodFactory_" + producerConfiguration.getAttribute("topic")); - final AbstractBeanDefinition producerConfigurationBeanDefinition = producerConfigurationBuilder.getBeanDefinition(); - final String producerConfigurationBeanName = "producerConfiguration_" + producerConfiguration.getAttribute("topic"); - registerBeanDefinition(new BeanDefinitionHolder(producerConfigurationBeanDefinition, producerConfigurationBeanName), - parserContext.getRegistry()); - } - } + final AbstractBeanDefinition producerConfigurationBeanDefinition = producerConfigurationBuilder.getBeanDefinition(); + final String producerConfigurationBeanName = "producerConfiguration_" + producerConfiguration.getAttribute("topic"); + registerBeanDefinition(new BeanDefinitionHolder(producerConfigurationBeanDefinition, producerConfigurationBeanName), + parserContext.getRegistry()); + } + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/ZookeeperConnectParser.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/ZookeeperConnectParser.java index cac01e9..3cda6c3 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/ZookeeperConnectParser.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/ZookeeperConnectParser.java @@ -25,22 +25,23 @@ import org.w3c.dom.Element; /** * @author Soby Chacko + * @since 0.5 */ public class ZookeeperConnectParser extends AbstractSimpleBeanDefinitionParser { - @Override - protected Class getBeanClass(final Element element) { - return ZookeeperConnect.class; - } + @Override + protected Class getBeanClass(final Element element) { + return ZookeeperConnect.class; + } - @Override - protected void doParse(final Element element, final ParserContext parserContext, final BeanDefinitionBuilder builder) { - super.doParse(element, parserContext, builder); + @Override + protected void doParse(final Element element, final ParserContext parserContext, final BeanDefinitionBuilder builder) { + super.doParse(element, parserContext, builder); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, - BeanDefinitionParserDelegate.SCOPE_ATTRIBUTE); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "zk-connect"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "zk-connection-timeout"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "zk-session-timeout"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "zk-sync-time"); - } + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, + BeanDefinitionParserDelegate.SCOPE_ATTRIBUTE); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "zk-connect"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "zk-connection-timeout"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "zk-session-timeout"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "zk-sync-time"); + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/KafkaConsumerDefaults.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/KafkaConsumerDefaults.java index 212678f..e258f90 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/KafkaConsumerDefaults.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/KafkaConsumerDefaults.java @@ -19,22 +19,23 @@ package org.springframework.integration.kafka.core; * Kafka adapter specific message headers. * * @author Soby Chacko + * @since 0.5 */ -public class KafkaConsumerDefaults { - //High level consumer - public static final String GROUP_ID = "groupid"; - public static final String SOCKET_TIMEOUT = "30000"; - public static final String SOCKET_BUFFER_SIZE = "64*1024"; - public static final String FETCH_SIZE = "300 * 1024"; - public static final String BACKOFF_INCREMENT = "1000"; - public static final String QUEUED_CHUNKS_MAX = "100"; - public static final String AUTO_COMMIT_ENABLE = "true"; - public static final String AUTO_COMMIT_INTERVAL = "10000"; - public static final String AUTO_OFFSET_RESET = "smallest"; - //Overriding the default value of -1, which will make the consumer to wait indefinitely - public static final String CONSUMER_TIMEOUT = "5000"; - public static final String REBALANCE_RETRIES_MAX = "4"; +public final class KafkaConsumerDefaults { + //High level consumer + public static final String GROUP_ID = "groupid"; + public static final String SOCKET_TIMEOUT = "30000"; + public static final String SOCKET_BUFFER_SIZE = "64*1024"; + public static final String FETCH_SIZE = "300 * 1024"; + public static final String BACKOFF_INCREMENT = "1000"; + public static final String QUEUED_CHUNKS_MAX = "100"; + public static final String AUTO_COMMIT_ENABLE = "true"; + public static final String AUTO_COMMIT_INTERVAL = "10000"; + public static final String AUTO_OFFSET_RESET = "smallest"; + //Overriding the default value of -1, which will make the consumer to wait indefinitely + public static final String CONSUMER_TIMEOUT = "5000"; + public static final String REBALANCE_RETRIES_MAX = "4"; private KafkaConsumerDefaults() { - } + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/ZookeeperConnectDefaults.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/ZookeeperConnectDefaults.java index 11138d1..03428d7 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/ZookeeperConnectDefaults.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/core/ZookeeperConnectDefaults.java @@ -18,14 +18,15 @@ package org.springframework.integration.kafka.core; /** * * @author Soby Chacko + * @since 0.5 * */ -public class ZookeeperConnectDefaults { - public static final String ZK_CONNECT = "localhost:2181"; - public static final String ZK_CONNECTION_TIMEOUT = "6000"; - public static final String ZK_SESSION_TIMEOUT = "6000"; - public static final String ZK_SYNC_TIME = "2000"; +public final class ZookeeperConnectDefaults { + public static final String ZK_CONNECT = "localhost:2181"; + public static final String ZK_CONNECTION_TIMEOUT = "6000"; + public static final String ZK_SESSION_TIMEOUT = "6000"; + public static final String ZK_SYNC_TIME = "2000"; - private ZookeeperConnectDefaults() { - } + private ZookeeperConnectDefaults() { + } } 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 acab37b..83e225e 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 @@ -25,23 +25,24 @@ import java.util.Map; /** * @author Soby Chacko + * @since 0.5 * */ public class KafkaHighLevelConsumerMessageSource extends IntegrationObjectSupport implements MessageSource>>> { - private final KafkaConsumerContext kafkaConsumerContext; + private final KafkaConsumerContext kafkaConsumerContext; - public KafkaHighLevelConsumerMessageSource(final KafkaConsumerContext kafkaConsumerContext) { - this.kafkaConsumerContext = kafkaConsumerContext; - } + public KafkaHighLevelConsumerMessageSource(final KafkaConsumerContext kafkaConsumerContext) { + this.kafkaConsumerContext = kafkaConsumerContext; + } - @Override - public Message>>> receive() { - return kafkaConsumerContext.receive(); - } + @Override + public Message>>> receive() { + return kafkaConsumerContext.receive(); + } - @Override - public String getComponentType() { - return "kafka:inbound-channel-adapter"; - } + @Override + public String getComponentType() { + return "kafka:inbound-channel-adapter"; + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/outbound/KafkaProducerMessageHandler.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/outbound/KafkaProducerMessageHandler.java index ee4923b..fdf918c 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/outbound/KafkaProducerMessageHandler.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/outbound/KafkaProducerMessageHandler.java @@ -21,21 +21,22 @@ import org.springframework.integration.kafka.support.KafkaProducerContext; /** * @author Soby Chacko + * @since 0.5 */ public class KafkaProducerMessageHandler extends AbstractMessageHandler { - private final KafkaProducerContext kafkaProducerContext; + private final KafkaProducerContext kafkaProducerContext; - public KafkaProducerMessageHandler(final KafkaProducerContext kafkaProducerContext) { - this.kafkaProducerContext = kafkaProducerContext; - } + public KafkaProducerMessageHandler(final KafkaProducerContext kafkaProducerContext) { + this.kafkaProducerContext = kafkaProducerContext; + } public KafkaProducerContext getKafkaProducerContext() { return kafkaProducerContext; } @Override - protected void handleMessageInternal(final Message message) throws Exception { - kafkaProducerContext.send(message); - } + protected void handleMessageInternal(final Message message) throws Exception { + kafkaProducerContext.send(message); + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroBackedKafkaDecoder.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroBackedKafkaDecoder.java index f131659..d702492 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroBackedKafkaDecoder.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroBackedKafkaDecoder.java @@ -26,29 +26,30 @@ import org.apache.commons.logging.LogFactory; /** * @author Soby Chacko + * @since 0.5 */ public class AvroBackedKafkaDecoder implements Decoder { - private static final Log LOG = LogFactory.getLog(AvroBackedKafkaDecoder.class); + private static final Log LOG = LogFactory.getLog(AvroBackedKafkaDecoder.class); - private final Class clazz; + private final Class clazz; - public AvroBackedKafkaDecoder(final Class clazz) { - this.clazz = clazz; - } + public AvroBackedKafkaDecoder(final Class clazz) { + this.clazz = clazz; + } - @Override - @SuppressWarnings("unchecked") - public T fromBytes(final byte[] bytes) { - final Schema schema = ReflectData.get().getSchema(clazz); - final AvroSerializer avroSerializer = new AvroSerializer(); + @Override + @SuppressWarnings("unchecked") + public T fromBytes(final byte[] bytes) { + final Schema schema = ReflectData.get().getSchema(clazz); + final AvroSerializer avroSerializer = new AvroSerializer(); - try { - return (T) avroSerializer.deserialize(bytes, schema); - } catch (IOException e) { - LOG.error("Failed to decode byte array for schema: " + schema.getFullName(), e); - } + try { + return (T) avroSerializer.deserialize(bytes, schema); + } catch (IOException e) { + LOG.error("Failed to decode byte array for schema: " + schema.getFullName(), e); + } - return null; - } + return null; + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroBackedKafkaEncoder.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroBackedKafkaEncoder.java index 8bc426b..6ec8bfe 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroBackedKafkaEncoder.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroBackedKafkaEncoder.java @@ -25,28 +25,29 @@ import org.apache.commons.logging.LogFactory; /** * @author Soby Chacko + * @since 0.5 */ public class AvroBackedKafkaEncoder implements Encoder { - private static final Log LOG = LogFactory.getLog(AvroBackedKafkaEncoder.class); + private static final Log LOG = LogFactory.getLog(AvroBackedKafkaEncoder.class); - private final Class clazz; + private final Class clazz; - public AvroBackedKafkaEncoder(final Class clazz) { - this.clazz = clazz; - } + public AvroBackedKafkaEncoder(final Class clazz) { + this.clazz = clazz; + } - @Override - @SuppressWarnings("unchecked") - public byte[] toBytes(final T source) { - final Schema schema = ReflectData.get().getSchema(clazz); - final AvroSerializer avroSerializer = new AvroSerializer(); + @Override + @SuppressWarnings("unchecked") + public byte[] toBytes(final T source) { + final Schema schema = ReflectData.get().getSchema(clazz); + final AvroSerializer avroSerializer = new AvroSerializer(); - try { - return avroSerializer.serialize(source, schema); - } catch (IOException e) { - LOG.error("Failed to encode source for schema: " + schema.getFullName()); - } + try { + return avroSerializer.serialize(source, schema); + } catch (IOException e) { + LOG.error("Failed to encode source for schema: " + schema.getFullName()); + } - return null; - } + return null; + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroSerializer.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroSerializer.java index c3e751c..7bc08f8 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroSerializer.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroSerializer.java @@ -30,23 +30,24 @@ import java.io.IOException; /** * @author Soby Chacko + * @since 0.5 */ public class AvroSerializer { - public T deserialize(final byte[] bytes, final Schema schema) throws IOException { - final Decoder decoder = DecoderFactory.get().binaryDecoder(bytes, null); - final DatumReader reader = new ReflectDatumReader(schema); + public T deserialize(final byte[] bytes, final Schema schema) throws IOException { + final Decoder decoder = DecoderFactory.get().binaryDecoder(bytes, null); + final DatumReader reader = new ReflectDatumReader(schema); - return reader.read(null, decoder); - } + return reader.read(null, decoder); + } - public byte[] serialize(final T input, final Schema schema) throws IOException { - final DatumWriter writer = new ReflectDatumWriter(schema); - final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + public byte[] serialize(final T input, final Schema schema) throws IOException { + final DatumWriter writer = new ReflectDatumWriter(schema); + final ByteArrayOutputStream stream = new ByteArrayOutputStream(); - final Encoder encoder = EncoderFactory.get().binaryEncoder(stream, null); - writer.write(input, encoder); - encoder.flush(); + final Encoder encoder = EncoderFactory.get().binaryEncoder(stream, null); + writer.write(input, encoder); + encoder.flush(); - return stream.toByteArray(); - } + return stream.toByteArray(); + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroSpecificDatumSerializer.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroSpecificDatumSerializer.java index 12fb60d..b7b6064 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroSpecificDatumSerializer.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/avro/AvroSpecificDatumSerializer.java @@ -30,23 +30,24 @@ import java.io.IOException; /** * @author Soby Chacko + * @since 0.5 */ public class AvroSpecificDatumSerializer { - public T deserialize(final byte[] bytes, final Schema schema) throws IOException { - final Decoder decoder = DecoderFactory.get().binaryDecoder(bytes, null); - final DatumReader reader = new SpecificDatumReader(schema); + public T deserialize(final byte[] bytes, final Schema schema) throws IOException { + final Decoder decoder = DecoderFactory.get().binaryDecoder(bytes, null); + final DatumReader reader = new SpecificDatumReader(schema); - return reader.read(null, decoder); - } + return reader.read(null, decoder); + } - public byte[] serialize(final T input, final Schema schema) throws IOException { - final DatumWriter writer = new SpecificDatumWriter(schema); - final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + public byte[] serialize(final T input, final Schema schema) throws IOException { + final DatumWriter writer = new SpecificDatumWriter(schema); + final ByteArrayOutputStream stream = new ByteArrayOutputStream(); - final Encoder encoder = EncoderFactory.get().binaryEncoder(stream, null); - writer.write(input, encoder); - encoder.flush(); + final Encoder encoder = EncoderFactory.get().binaryEncoder(stream, null); + writer.write(input, encoder); + encoder.flush(); - return stream.toByteArray(); - } + return stream.toByteArray(); + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/common/StringEncoder.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/common/StringEncoder.java index ce5e0f3..7dc5105 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/common/StringEncoder.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/serializer/common/StringEncoder.java @@ -22,20 +22,21 @@ import java.util.Properties; /** * @author Soby Chacko + * @since 0.5 */ public class StringEncoder implements Encoder { - private String encoding = "UTF8"; + private String encoding = "UTF8"; - public void setEncoding(final String encoding){ - this.encoding = encoding; - } + public void setEncoding(final String encoding){ + this.encoding = encoding; + } - @Override - public byte[] toBytes(final Object o) { - final Properties props = new Properties(); - props.put("serializer.encoding", encoding); + @Override + public byte[] toBytes(final Object o) { + final Properties props = new Properties(); + props.put("serializer.encoding", encoding); - final VerifiableProperties verifiableProperties = new VerifiableProperties(props); - return new kafka.serializer.StringEncoder(verifiableProperties).toBytes((String)o); - } + final VerifiableProperties verifiableProperties = new VerifiableProperties(props); + return new kafka.serializer.StringEncoder(verifiableProperties).toBytes((String)o); + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConfigFactoryBean.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConfigFactoryBean.java index bd27da7..793162f 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConfigFactoryBean.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConfigFactoryBean.java @@ -22,38 +22,39 @@ import java.util.Properties; /** * @author Soby Chacko + * @since 0.5 */ public class ConsumerConfigFactoryBean implements FactoryBean { - private final ConsumerMetadata consumerMetadata; - private final ZookeeperConnect zookeeperConnect; + private final ConsumerMetadata consumerMetadata; + private final ZookeeperConnect zookeeperConnect; - public ConsumerConfigFactoryBean(final ConsumerMetadata consumerMetadata, - final ZookeeperConnect zookeeperConnect){ - this.consumerMetadata = consumerMetadata; - this.zookeeperConnect = zookeeperConnect; - } + public ConsumerConfigFactoryBean(final ConsumerMetadata consumerMetadata, + final ZookeeperConnect zookeeperConnect){ + this.consumerMetadata = consumerMetadata; + this.zookeeperConnect = zookeeperConnect; + } - @Override - public ConsumerConfig getObject() throws Exception { - final Properties properties = new Properties(); - properties.put("zookeeper.connect", zookeeperConnect.getZkConnect()); - properties.put("zookeeper.session.timeout.ms", zookeeperConnect.getZkSessionTimeout()); - properties.put("zookeeper.sync.time.ms", zookeeperConnect.getZkSyncTime()); - properties.put("auto.commit.interval.ms", consumerMetadata.getAutoCommitInterval()); - properties.put("consumer.timeout.ms", consumerMetadata.getConsumerTimeout()); - properties.put("group.id", consumerMetadata.getGroupId()); + @Override + public ConsumerConfig getObject() throws Exception { + final Properties properties = new Properties(); + properties.put("zookeeper.connect", zookeeperConnect.getZkConnect()); + properties.put("zookeeper.session.timeout.ms", zookeeperConnect.getZkSessionTimeout()); + properties.put("zookeeper.sync.time.ms", zookeeperConnect.getZkSyncTime()); + properties.put("auto.commit.interval.ms", consumerMetadata.getAutoCommitInterval()); + properties.put("consumer.timeout.ms", consumerMetadata.getConsumerTimeout()); + properties.put("group.id", consumerMetadata.getGroupId()); - return new ConsumerConfig(properties); - } + return new ConsumerConfig(properties); + } - @Override - public Class getObjectType() { - return ConsumerConfig.class; - } + @Override + public Class getObjectType() { + return ConsumerConfig.class; + } - @Override - public boolean isSingleton() { - return true; - } + @Override + public boolean isSingleton() { + return true; + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConfiguration.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConfiguration.java index c7bb878..047ba70 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConfiguration.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConfiguration.java @@ -35,175 +35,176 @@ import java.util.concurrent.Future; /** * @author Soby Chacko + * @since 0.5 */ public class ConsumerConfiguration { - private static final Log LOGGER = LogFactory.getLog(ConsumerConfiguration.class); + private static final Log LOGGER = LogFactory.getLog(ConsumerConfiguration.class); - private final ConsumerMetadata consumerMetadata; - private final ConsumerConnectionProvider consumerConnectionProvider; - private final MessageLeftOverTracker messageLeftOverTracker; - private ConsumerConnector consumerConnector; - private volatile int count = 0; - private int maxMessages = 1; + private final ConsumerMetadata consumerMetadata; + private final ConsumerConnectionProvider consumerConnectionProvider; + private final MessageLeftOverTracker messageLeftOverTracker; + private ConsumerConnector consumerConnector; + private volatile int count = 0; + private int maxMessages = 1; - private ExecutorService executorService = Executors.newCachedThreadPool(); + private ExecutorService executorService = Executors.newCachedThreadPool(); - public ConsumerConfiguration(final ConsumerMetadata consumerMetadata, - final ConsumerConnectionProvider consumerConnectionProvider, - final MessageLeftOverTracker messageLeftOverTracker) { - this.consumerMetadata = consumerMetadata; - this.consumerConnectionProvider = consumerConnectionProvider; - this.messageLeftOverTracker = messageLeftOverTracker; - } + public ConsumerConfiguration(final ConsumerMetadata consumerMetadata, + final ConsumerConnectionProvider consumerConnectionProvider, + final MessageLeftOverTracker messageLeftOverTracker) { + this.consumerMetadata = consumerMetadata; + this.consumerConnectionProvider = consumerConnectionProvider; + this.messageLeftOverTracker = messageLeftOverTracker; + } - public ConsumerMetadata getConsumerMetadata() { - return consumerMetadata; - } + public ConsumerMetadata getConsumerMetadata() { + return consumerMetadata; + } - public Map>> receive() { - count = messageLeftOverTracker.getCurrentCount(); + public Map>> receive() { + count = messageLeftOverTracker.getCurrentCount(); - final List>> tasks = new LinkedList>>(); - final Object lock = new Object(); + final List>> tasks = new LinkedList>>(); + final Object lock = new Object(); - final Map>> consumerMap = getConsumerMapWithMessageStreams(); - for (final List> streams : consumerMap.values()) { - for (final KafkaStream stream : streams) { - tasks.add(new Callable>() { - @Override - public List call() throws Exception { - final List rawMessages = new ArrayList(); - try { - while (count < maxMessages) { - final MessageAndMetadata messageAndMetadata = stream.iterator().next(); - synchronized (lock) { - if (count < maxMessages) { - rawMessages.add(messageAndMetadata); - count++; - } else { - messageLeftOverTracker.addMessageAndMetadata(messageAndMetadata); - } - } - } - } catch (ConsumerTimeoutException cte) { - LOGGER.info("Consumer timed out"); - } - return rawMessages; - } - }); - } - } + final Map>> consumerMap = getConsumerMapWithMessageStreams(); + for (final List> streams : consumerMap.values()) { + for (final KafkaStream stream : streams) { + tasks.add(new Callable>() { + @Override + public List call() throws Exception { + final List rawMessages = new ArrayList(); + try { + while (count < maxMessages) { + final MessageAndMetadata messageAndMetadata = stream.iterator().next(); + synchronized (lock) { + if (count < maxMessages) { + rawMessages.add(messageAndMetadata); + count++; + } else { + messageLeftOverTracker.addMessageAndMetadata(messageAndMetadata); + } + } + } + } catch (ConsumerTimeoutException cte) { + LOGGER.info("Consumer timed out"); + } + return rawMessages; + } + }); + } + } - return executeTasks(tasks); - } + return executeTasks(tasks); + } - private Map>> executeTasks(final List>> tasks) { + private Map>> executeTasks(final List>> tasks) { - final Map>> messages = new ConcurrentHashMap>>(); - messages.putAll(getLeftOverMessageMap()); + final Map>> messages = new ConcurrentHashMap>>(); + messages.putAll(getLeftOverMessageMap()); - try { - for (final Future> result : executorService.invokeAll(tasks)) { - if (!result.get().isEmpty()) { - final String topic = result.get().get(0).topic(); - if (!messages.containsKey(topic)) { - messages.put(topic, getPayload(result.get())); - } else { + try { + for (final Future> result : executorService.invokeAll(tasks)) { + if (!result.get().isEmpty()) { + final String topic = result.get().get(0).topic(); + if (!messages.containsKey(topic)) { + messages.put(topic, getPayload(result.get())); + } else { - final Map> existingPayloadMap = messages.get(topic); - getPayload(result.get(), existingPayloadMap); - } - } - } - } catch (Exception e) { - throw new MessagingException("Consuming from Kafka failed", e); - } + final Map> existingPayloadMap = messages.get(topic); + getPayload(result.get(), existingPayloadMap); + } + } + } + } catch (Exception e) { + throw new MessagingException("Consuming from Kafka failed", e); + } - if (messages.isEmpty()) { - return null; - } + if (messages.isEmpty()) { + return null; + } - return messages; - } + return messages; + } - private Map>> getLeftOverMessageMap() { + private Map>> getLeftOverMessageMap() { - final Map>> messages = new ConcurrentHashMap>>(); + final Map>> messages = new ConcurrentHashMap>>(); - for (final MessageAndMetadata mamd : messageLeftOverTracker.getMessageLeftOverFromPreviousPoll()) { - final String topic = mamd.topic(); + for (final MessageAndMetadata mamd : messageLeftOverTracker.getMessageLeftOverFromPreviousPoll()) { + final String topic = mamd.topic(); - if (!messages.containsKey(topic)) { - final List l = new ArrayList(); - l.add(mamd); - messages.put(topic, getPayload(l)); - } else { - final Map> existingPayloadMap = messages.get(topic); - final List l = new ArrayList(); - l.add(mamd); - getPayload(l, existingPayloadMap); - } - } - messageLeftOverTracker.clearMessagesLeftOver(); - return messages; - } + if (!messages.containsKey(topic)) { + final List l = new ArrayList(); + l.add(mamd); + messages.put(topic, getPayload(l)); + } else { + final Map> existingPayloadMap = messages.get(topic); + final List l = new ArrayList(); + l.add(mamd); + getPayload(l, existingPayloadMap); + } + } + messageLeftOverTracker.clearMessagesLeftOver(); + return messages; + } - private Map> getPayload(final List messageAndMetadatas) { - final Map> payloadMap = new ConcurrentHashMap>(); + private Map> getPayload(final List messageAndMetadatas) { + final Map> payloadMap = new ConcurrentHashMap>(); - for (final MessageAndMetadata messageAndMetadata : messageAndMetadatas) { - if (!payloadMap.containsKey(messageAndMetadata.partition())) { - final List payload = new ArrayList(); - payload.add(messageAndMetadata.message()); - payloadMap.put(messageAndMetadata.partition(), payload); - } else { - final List payload = payloadMap.get(messageAndMetadata.partition()); - payload.add(messageAndMetadata.message()); - } + for (final MessageAndMetadata messageAndMetadata : messageAndMetadatas) { + if (!payloadMap.containsKey(messageAndMetadata.partition())) { + final List payload = new ArrayList(); + payload.add(messageAndMetadata.message()); + payloadMap.put(messageAndMetadata.partition(), payload); + } else { + final List payload = payloadMap.get(messageAndMetadata.partition()); + payload.add(messageAndMetadata.message()); + } - } + } - return payloadMap; - } + return payloadMap; + } - private void getPayload(final List messageAndMetadatas, final Map> existingPayloadMap) { - for (final MessageAndMetadata messageAndMetadata : messageAndMetadatas) { - if (!existingPayloadMap.containsKey(messageAndMetadata.partition())) { - final List payload = new ArrayList(); - payload.add(messageAndMetadata.message()); - existingPayloadMap.put(messageAndMetadata.partition(), payload); - } else { - final List payload = existingPayloadMap.get(messageAndMetadata.partition()); - payload.add(messageAndMetadata.message()); - } - } - } + private void getPayload(final List messageAndMetadatas, final Map> existingPayloadMap) { + for (final MessageAndMetadata messageAndMetadata : messageAndMetadatas) { + if (!existingPayloadMap.containsKey(messageAndMetadata.partition())) { + final List payload = new ArrayList(); + payload.add(messageAndMetadata.message()); + existingPayloadMap.put(messageAndMetadata.partition(), payload); + } else { + final List payload = existingPayloadMap.get(messageAndMetadata.partition()); + payload.add(messageAndMetadata.message()); + } + } + } - @SuppressWarnings("unchecked") - public Map>> getConsumerMapWithMessageStreams() { - if (consumerMetadata.getValueDecoder() != null) { - return getConsumerConnector().createMessageStreams( - consumerMetadata.getTopicStreamMap(), - consumerMetadata.getValueDecoder(), - consumerMetadata.getValueDecoder()); - } + @SuppressWarnings("unchecked") + public Map>> getConsumerMapWithMessageStreams() { + if (consumerMetadata.getValueDecoder() != null) { + return getConsumerConnector().createMessageStreams( + consumerMetadata.getTopicStreamMap(), + consumerMetadata.getValueDecoder(), + consumerMetadata.getValueDecoder()); + } - return getConsumerConnector().createMessageStreams(consumerMetadata.getTopicStreamMap()); - } + return getConsumerConnector().createMessageStreams(consumerMetadata.getTopicStreamMap()); + } - public int getMaxMessages() { - return maxMessages; - } + public int getMaxMessages() { + return maxMessages; + } - public void setMaxMessages(final int maxMessages) { - this.maxMessages = maxMessages; - } + public void setMaxMessages(final int maxMessages) { + this.maxMessages = maxMessages; + } - public ConsumerConnector getConsumerConnector() { - if (consumerConnector == null) { - consumerConnector = consumerConnectionProvider.getConsumerConnector(); - } + public ConsumerConnector getConsumerConnector() { + if (consumerConnector == null) { + consumerConnector = consumerConnectionProvider.getConsumerConnector(); + } - return consumerConnector; - } + return consumerConnector; + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConnectionProvider.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConnectionProvider.java index d2287fd..6b542a4 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConnectionProvider.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerConnectionProvider.java @@ -20,16 +20,17 @@ import kafka.javaapi.consumer.ConsumerConnector; /** * @author Soby Chacko + * @since 0.5 */ public class ConsumerConnectionProvider { - private final ConsumerConfig consumerConfig; + private final ConsumerConfig consumerConfig; - public ConsumerConnectionProvider(final ConsumerConfig consumerConfig) { - this.consumerConfig = consumerConfig; - } + public ConsumerConnectionProvider(final ConsumerConfig consumerConfig) { + this.consumerConfig = consumerConfig; + } - public ConsumerConnector getConsumerConnector() { - return kafka.consumer.Consumer.createJavaConsumerConnector(consumerConfig); - } + public ConsumerConnector getConsumerConnector() { + return kafka.consumer.Consumer.createJavaConsumerConnector(consumerConfig); + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerMetadata.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerMetadata.java index eb4cac5..d906bd7 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerMetadata.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ConsumerMetadata.java @@ -22,153 +22,154 @@ import java.util.Map; /** * @author Soby Chacko + * @since 0.5 */ public class ConsumerMetadata { - //High level consumer defaults - private String groupId = KafkaConsumerDefaults.GROUP_ID; - private String socketTimeout = KafkaConsumerDefaults.SOCKET_TIMEOUT; - private String socketBufferSize = KafkaConsumerDefaults.SOCKET_BUFFER_SIZE; - private String fetchSize = KafkaConsumerDefaults.FETCH_SIZE; - private String backoffIncrement = KafkaConsumerDefaults.BACKOFF_INCREMENT; - private String queuedChunksMax = KafkaConsumerDefaults.QUEUED_CHUNKS_MAX; - private String autoCommitEnable = KafkaConsumerDefaults.AUTO_COMMIT_ENABLE; - private String autoCommitInterval = KafkaConsumerDefaults.AUTO_COMMIT_INTERVAL; - private String autoOffsetReset = KafkaConsumerDefaults.AUTO_OFFSET_RESET; - private String rebalanceRetriesMax = KafkaConsumerDefaults.REBALANCE_RETRIES_MAX; - private String consumerTimeout = KafkaConsumerDefaults.CONSUMER_TIMEOUT; + //High level consumer defaults + private String groupId = KafkaConsumerDefaults.GROUP_ID; + private String socketTimeout = KafkaConsumerDefaults.SOCKET_TIMEOUT; + private String socketBufferSize = KafkaConsumerDefaults.SOCKET_BUFFER_SIZE; + private String fetchSize = KafkaConsumerDefaults.FETCH_SIZE; + private String backoffIncrement = KafkaConsumerDefaults.BACKOFF_INCREMENT; + private String queuedChunksMax = KafkaConsumerDefaults.QUEUED_CHUNKS_MAX; + private String autoCommitEnable = KafkaConsumerDefaults.AUTO_COMMIT_ENABLE; + private String autoCommitInterval = KafkaConsumerDefaults.AUTO_COMMIT_INTERVAL; + private String autoOffsetReset = KafkaConsumerDefaults.AUTO_OFFSET_RESET; + private String rebalanceRetriesMax = KafkaConsumerDefaults.REBALANCE_RETRIES_MAX; + private String consumerTimeout = KafkaConsumerDefaults.CONSUMER_TIMEOUT; - private String topic; - private int streams; - private Decoder valueDecoder; - private Decoder keyDecoder; - private Map topicStreamMap; + private String topic; + private int streams; + private Decoder valueDecoder; + private Decoder keyDecoder; + private Map topicStreamMap; - public String getGroupId() { - return groupId; - } + public String getGroupId() { + return groupId; + } - public void setGroupId(final String groupId) { - this.groupId = groupId; - } + public void setGroupId(final String groupId) { + this.groupId = groupId; + } - public String getSocketTimeout() { - return socketTimeout; - } + public String getSocketTimeout() { + return socketTimeout; + } - public void setSocketTimeout(final String socketTimeout) { - this.socketTimeout = socketTimeout; - } + public void setSocketTimeout(final String socketTimeout) { + this.socketTimeout = socketTimeout; + } - public String getSocketBufferSize() { - return socketBufferSize; - } + public String getSocketBufferSize() { + return socketBufferSize; + } - public void setSocketBufferSize(final String socketBufferSize) { - this.socketBufferSize = socketBufferSize; - } + public void setSocketBufferSize(final String socketBufferSize) { + this.socketBufferSize = socketBufferSize; + } - public String getFetchSize() { - return fetchSize; - } + public String getFetchSize() { + return fetchSize; + } - public void setFetchSize(final String fetchSize) { - this.fetchSize = fetchSize; - } + public void setFetchSize(final String fetchSize) { + this.fetchSize = fetchSize; + } - public String getBackoffIncrement() { - return backoffIncrement; - } + public String getBackoffIncrement() { + return backoffIncrement; + } - public void setBackoffIncrement(final String backoffIncrement) { - this.backoffIncrement = backoffIncrement; - } + public void setBackoffIncrement(final String backoffIncrement) { + this.backoffIncrement = backoffIncrement; + } - public String getQueuedChunksMax() { - return queuedChunksMax; - } + public String getQueuedChunksMax() { + return queuedChunksMax; + } - public void setQueuedChunksMax(final String queuedChunksMax) { - this.queuedChunksMax = queuedChunksMax; - } + public void setQueuedChunksMax(final String queuedChunksMax) { + this.queuedChunksMax = queuedChunksMax; + } - public String getAutoCommitEnable() { - return autoCommitEnable; - } + public String getAutoCommitEnable() { + return autoCommitEnable; + } - public void setAutoCommitEnable(final String autoCommitEnable) { - this.autoCommitEnable = autoCommitEnable; - } + public void setAutoCommitEnable(final String autoCommitEnable) { + this.autoCommitEnable = autoCommitEnable; + } - public String getAutoCommitInterval() { - return autoCommitInterval; - } + public String getAutoCommitInterval() { + return autoCommitInterval; + } - public void setAutoCommitInterval(final String autoCommitInterval) { - this.autoCommitInterval = autoCommitInterval; - } + public void setAutoCommitInterval(final String autoCommitInterval) { + this.autoCommitInterval = autoCommitInterval; + } - public String getAutoOffsetReset() { - return autoOffsetReset; - } + public String getAutoOffsetReset() { + return autoOffsetReset; + } - public void setAutoOffsetReset(final String autoOffsetReset) { - this.autoOffsetReset = autoOffsetReset; - } + public void setAutoOffsetReset(final String autoOffsetReset) { + this.autoOffsetReset = autoOffsetReset; + } - public String getRebalanceRetriesMax() { - return rebalanceRetriesMax; - } + public String getRebalanceRetriesMax() { + return rebalanceRetriesMax; + } - public void setRebalanceRetriesMax(final String rebalanceRetriesMax) { - this.rebalanceRetriesMax = rebalanceRetriesMax; - } + public void setRebalanceRetriesMax(final String rebalanceRetriesMax) { + this.rebalanceRetriesMax = rebalanceRetriesMax; + } - public String getConsumerTimeout() { - return consumerTimeout; - } + public String getConsumerTimeout() { + return consumerTimeout; + } - public void setConsumerTimeout(final String consumerTimeout) { - this.consumerTimeout = consumerTimeout; - } + public void setConsumerTimeout(final String consumerTimeout) { + this.consumerTimeout = consumerTimeout; + } - public String getTopic() { - return topic; - } + public String getTopic() { + return topic; + } - public void setTopic(final String topic) { - this.topic = topic; - } + public void setTopic(final String topic) { + this.topic = topic; + } - public int getStreams() { - return streams; - } + public int getStreams() { + return streams; + } - public void setStreams(final int streams) { - this.streams = streams; - } + public void setStreams(final int streams) { + this.streams = streams; + } - public Decoder getValueDecoder() { - return valueDecoder; - } + public Decoder getValueDecoder() { + return valueDecoder; + } - public void setValueDecoder(final Decoder valueDecoder) { - this.valueDecoder = valueDecoder; - } + public void setValueDecoder(final Decoder valueDecoder) { + this.valueDecoder = valueDecoder; + } - public Decoder getKeyDecoder() { - return keyDecoder; - } + public Decoder getKeyDecoder() { + return keyDecoder; + } - public void setKeyDecoder(final Decoder keyDecoder) { - this.keyDecoder = keyDecoder; - } + public void setKeyDecoder(final Decoder keyDecoder) { + this.keyDecoder = keyDecoder; + } - public Map getTopicStreamMap() { - return topicStreamMap; - } + public Map getTopicStreamMap() { + return topicStreamMap; + } - public void setTopicStreamMap(final Map topicStreamMap) { - this.topicStreamMap = topicStreamMap; - } + public void setTopicStreamMap(final Map topicStreamMap) { + this.topicStreamMap = topicStreamMap; + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/DefaultPartitioner.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/DefaultPartitioner.java index 3147b9d..929ef89 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/DefaultPartitioner.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/DefaultPartitioner.java @@ -20,17 +20,18 @@ import kafka.utils.Utils; /** * @author Soby Chacko + * @since 0.5 * * This class is for internal use only and therefore is at default access level */ class DefaultPartitioner implements Partitioner { - /** - * Uses the key to calculate a partition bucket id for routing - * the data to the appropriate broker partition - * @return an integer between 0 and numPartitions-1 - */ - @Override - public int partition(final T key, final int numPartitions) { - return Utils.abs(key.hashCode()) % numPartitions; - } + /** + * Uses the key to calculate a partition bucket id for routing + * the data to the appropriate broker partition + * @return an integer between 0 and numPartitions-1 + */ + @Override + public int partition(final T key, final int numPartitions) { + return Utils.abs(key.hashCode()) % numPartitions; + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/KafkaConsumerContext.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/KafkaConsumerContext.java index a42e458..a75b6ab 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/KafkaConsumerContext.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/KafkaConsumerContext.java @@ -30,47 +30,48 @@ import java.util.Map; /** * @author Soby Chacko + * @since 0.5 */ public class KafkaConsumerContext implements BeanFactoryAware { - private Map consumerConfigurations; - private String consumerTimeout = KafkaConsumerDefaults.CONSUMER_TIMEOUT; - private ZookeeperConnect zookeeperConnect; + private Map consumerConfigurations; + private String consumerTimeout = KafkaConsumerDefaults.CONSUMER_TIMEOUT; + private ZookeeperConnect zookeeperConnect; - public Collection getConsumerConfigurations() { - return consumerConfigurations.values(); - } + public Collection getConsumerConfigurations() { + return consumerConfigurations.values(); + } - @Override - public void setBeanFactory(final BeanFactory beanFactory) throws BeansException { - consumerConfigurations = ((ListableBeanFactory) beanFactory).getBeansOfType(ConsumerConfiguration.class); - } + @Override + public void setBeanFactory(final BeanFactory beanFactory) throws BeansException { + consumerConfigurations = ((ListableBeanFactory) beanFactory).getBeansOfType(ConsumerConfiguration.class); + } - public Message>>> receive() { - final Map>> consumedData = new HashMap>>(); + public Message>>> receive() { + final Map>> consumedData = new HashMap>>(); - for (final ConsumerConfiguration consumerConfiguration : getConsumerConfigurations()) { - final Map>> messages = consumerConfiguration.receive(); + for (final ConsumerConfiguration consumerConfiguration : getConsumerConfigurations()) { + final Map>> messages = consumerConfiguration.receive(); - if (messages != null){ - consumedData.putAll(messages); - } - } - return MessageBuilder.withPayload(consumedData).build(); - } + if (messages != null){ + consumedData.putAll(messages); + } + } + return MessageBuilder.withPayload(consumedData).build(); + } - public String getConsumerTimeout() { - return consumerTimeout; - } + public String getConsumerTimeout() { + return consumerTimeout; + } - public void setConsumerTimeout(final String consumerTimeout) { - this.consumerTimeout = consumerTimeout; - } + public void setConsumerTimeout(final String consumerTimeout) { + this.consumerTimeout = consumerTimeout; + } - public ZookeeperConnect getZookeeperConnect() { - return zookeeperConnect; - } + public ZookeeperConnect getZookeeperConnect() { + return zookeeperConnect; + } - public void setZookeeperConnect(final ZookeeperConnect zookeeperConnect) { - this.zookeeperConnect = zookeeperConnect; - } + public void setZookeeperConnect(final ZookeeperConnect zookeeperConnect) { + this.zookeeperConnect = zookeeperConnect; + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/KafkaProducerContext.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/KafkaProducerContext.java index 8a815d5..f85207d 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/KafkaProducerContext.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/KafkaProducerContext.java @@ -26,38 +26,39 @@ import java.util.Map; /** * @author Soby Chacko + * @since 0.5 */ public class KafkaProducerContext implements BeanFactoryAware { - private Map topicsConfiguration; + private Map topicsConfiguration; - @SuppressWarnings("unchecked") - public void send(final Message message) throws Exception { - final ProducerConfiguration producerConfiguration = - getTopicConfiguration(message.getHeaders().get("topic", String.class)); + @SuppressWarnings("unchecked") + public void send(final Message message) throws Exception { + final ProducerConfiguration producerConfiguration = + getTopicConfiguration(message.getHeaders().get("topic", String.class)); - if (producerConfiguration != null) { - producerConfiguration.send(message); - } - } + if (producerConfiguration != null) { + producerConfiguration.send(message); + } + } - private ProducerConfiguration getTopicConfiguration(final String topic){ - final Collection topics = topicsConfiguration.values(); + private ProducerConfiguration getTopicConfiguration(final String topic){ + final Collection topics = topicsConfiguration.values(); - for (final ProducerConfiguration producerConfiguration : topics){ - if (producerConfiguration.getProducerMetadata().getTopic().equals(topic)){ - return producerConfiguration; - } - } + for (final ProducerConfiguration producerConfiguration : topics){ + if (producerConfiguration.getProducerMetadata().getTopic().equals(topic)){ + return producerConfiguration; + } + } - return null; - } + return null; + } - public Map getTopicsConfiguration() { - return topicsConfiguration; - } + public Map getTopicsConfiguration() { + return topicsConfiguration; + } - @Override - public void setBeanFactory(final BeanFactory beanFactory) throws BeansException { - topicsConfiguration = ((ListableBeanFactory)beanFactory).getBeansOfType(ProducerConfiguration.class); - } + @Override + public void setBeanFactory(final BeanFactory beanFactory) throws BeansException { + topicsConfiguration = ((ListableBeanFactory)beanFactory).getBeansOfType(ProducerConfiguration.class); + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/MessageLeftOverTracker.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/MessageLeftOverTracker.java index a7d23e0..82f3461 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/MessageLeftOverTracker.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/MessageLeftOverTracker.java @@ -22,23 +22,24 @@ import java.util.List; /** * @author Soby Chacko + * @since 0.5 */ public class MessageLeftOverTracker { - private final List messageLeftOverFromPreviousPoll = new ArrayList(); + private final List messageLeftOverFromPreviousPoll = new ArrayList(); - public void addMessageAndMetadata(final MessageAndMetadata messageAndMetadata){ - messageLeftOverFromPreviousPoll.add(messageAndMetadata); - } + public void addMessageAndMetadata(final MessageAndMetadata messageAndMetadata){ + messageLeftOverFromPreviousPoll.add(messageAndMetadata); + } - public List getMessageLeftOverFromPreviousPoll(){ - return messageLeftOverFromPreviousPoll; - } + public List getMessageLeftOverFromPreviousPoll(){ + return messageLeftOverFromPreviousPoll; + } - public void clearMessagesLeftOver(){ - messageLeftOverFromPreviousPoll.clear(); - } + public void clearMessagesLeftOver(){ + messageLeftOverFromPreviousPoll.clear(); + } - public int getCurrentCount() { - return messageLeftOverFromPreviousPoll.size(); - } + public int getCurrentCount() { + return messageLeftOverFromPreviousPoll.size(); + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerConfiguration.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerConfiguration.java index 93e5c66..71026ae 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerConfiguration.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerConfiguration.java @@ -28,75 +28,76 @@ import java.io.ObjectOutputStream; /** * @author Soby Chacko + * @since 0.5 */ public class ProducerConfiguration { - private final Producer producer; - private final ProducerMetadata producerMetadata; + private final Producer producer; + private final ProducerMetadata producerMetadata; - public ProducerConfiguration(final ProducerMetadata producerMetadata, final Producer producer){ - this.producerMetadata = producerMetadata; - this.producer = producer; - } + public ProducerConfiguration(final ProducerMetadata producerMetadata, final Producer producer){ + this.producerMetadata = producerMetadata; + this.producer = producer; + } - public ProducerMetadata getProducerMetadata() { - return producerMetadata; - } + public ProducerMetadata getProducerMetadata() { + return producerMetadata; + } - public void send(final Message message) throws Exception { - final V v = getPayload(message); + public void send(final Message message) throws Exception { + final V v = getPayload(message); - if (message.getHeaders().containsKey("messageKey")) { - producer.send(new KeyedMessage(producerMetadata.getTopic(), getKey(message), v)); - } else { - producer.send(new KeyedMessage(producerMetadata.getTopic(), v)); - } - } + if (message.getHeaders().containsKey("messageKey")) { + producer.send(new KeyedMessage(producerMetadata.getTopic(), getKey(message), v)); + } else { + producer.send(new KeyedMessage(producerMetadata.getTopic(), v)); + } + } - @SuppressWarnings("unchecked") - private V getPayload(final Message message) throws Exception { - if (producerMetadata.getValueEncoder().getClass().isAssignableFrom(DefaultEncoder.class)) { - return (V) getByteStream(message.getPayload()); - } else if (message.getPayload().getClass().isAssignableFrom(producerMetadata.getValueClassType())) { - return producerMetadata.getValueClassType().cast(message.getPayload()); - } + @SuppressWarnings("unchecked") + private V getPayload(final Message message) throws Exception { + if (producerMetadata.getValueEncoder().getClass().isAssignableFrom(DefaultEncoder.class)) { + return (V) getByteStream(message.getPayload()); + } else if (message.getPayload().getClass().isAssignableFrom(producerMetadata.getValueClassType())) { + return producerMetadata.getValueClassType().cast(message.getPayload()); + } - throw new Exception("Message payload type is not matching with what is configured"); - } + throw new Exception("Message payload type is not matching with what is configured"); + } - @SuppressWarnings("unchecked") - private K getKey(final Message message) throws Exception { - final Object key = message.getHeaders().get("messageKey"); + @SuppressWarnings("unchecked") + private K getKey(final Message message) throws Exception { + final Object key = message.getHeaders().get("messageKey"); - if (producerMetadata.getKeyEncoder().getClass().isAssignableFrom(DefaultEncoder.class)) { - return (K) getByteStream(key); - } + if (producerMetadata.getKeyEncoder().getClass().isAssignableFrom(DefaultEncoder.class)) { + return (K) getByteStream(key); + } - return message.getHeaders().get("messageKey", producerMetadata.getKeyClassType()); - } + return message.getHeaders().get("messageKey", producerMetadata.getKeyClassType()); + } - private static boolean isRawByteArray(final Object obj){ - return obj instanceof byte[]; - } + private static boolean isRawByteArray(final Object obj){ + return obj instanceof byte[]; + } - private static byte[] getByteStream(final Object obj) throws IOException { - if (isRawByteArray(obj)){ - return (byte[])obj; - } + private static byte[] getByteStream(final Object obj) throws IOException { + if (isRawByteArray(obj)){ + return (byte[])obj; + } - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - final ObjectOutputStream os = new ObjectOutputStream(out); - os.writeObject(obj); + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final ObjectOutputStream os = new ObjectOutputStream(out); + os.writeObject(obj); - return out.toByteArray(); - } + return out.toByteArray(); + } - @Override - public boolean equals(final Object obj){ - return EqualsBuilder.reflectionEquals(this, obj); - } + @Override + public boolean equals(final Object obj){ + return EqualsBuilder.reflectionEquals(this, obj); + } - @Override - public int hashCode() { - return HashCodeBuilder.reflectionHashCode(this); - } + @Override + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerFactoryBean.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerFactoryBean.java index 801c8e5..d49fa2c 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerFactoryBean.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerFactoryBean.java @@ -27,48 +27,49 @@ import java.util.Properties; /** * @author Soby Chacko + * @since 0.5 */ public class ProducerFactoryBean implements FactoryBean> { - private final String brokerList; - private final ProducerMetadata producerMetadata; + private final String brokerList; + private final ProducerMetadata producerMetadata; - public ProducerFactoryBean(final ProducerMetadata producerMetadata, final String brokerList){ - this.producerMetadata = producerMetadata; - this.brokerList = brokerList; - } + public ProducerFactoryBean(final ProducerMetadata producerMetadata, final String brokerList){ + this.producerMetadata = producerMetadata; + this.brokerList = brokerList; + } - @Override - public Producer getObject() throws Exception { - final Properties props = new Properties(); - props.put("metadata.broker.list", brokerList); - props.put("compression.codec", producerMetadata.getCompressionCodec()); + @Override + public Producer getObject() throws Exception { + final Properties props = new Properties(); + props.put("metadata.broker.list", brokerList); + props.put("compression.codec", producerMetadata.getCompressionCodec()); - if (producerMetadata.isAsync()){ - props.put("producer.type", "async"); - if (producerMetadata.getBatchNumMessages() != null){ - props.put("batch.num.messages", producerMetadata.getBatchNumMessages()); - } - } + if (producerMetadata.isAsync()){ + props.put("producer.type", "async"); + if (producerMetadata.getBatchNumMessages() != null){ + props.put("batch.num.messages", producerMetadata.getBatchNumMessages()); + } + } - final ProducerConfig config = new ProducerConfig(props); - final EventHandler eventHandler = new DefaultEventHandler(config, - producerMetadata.getPartitioner() == null ? new DefaultPartitioner() : producerMetadata.getPartitioner(), - producerMetadata.getValueEncoder(), producerMetadata.getKeyEncoder(), - new ProducerPool(config), new HashMap()); + final ProducerConfig config = new ProducerConfig(props); + final EventHandler eventHandler = new DefaultEventHandler(config, + producerMetadata.getPartitioner() == null ? new DefaultPartitioner() : producerMetadata.getPartitioner(), + producerMetadata.getValueEncoder(), producerMetadata.getKeyEncoder(), + new ProducerPool(config), new HashMap()); - final kafka.producer.Producer prod = new kafka.producer.Producer(config, - eventHandler); - return new Producer(prod); - } + final kafka.producer.Producer prod = new kafka.producer.Producer(config, + eventHandler); + return new Producer(prod); + } - @Override - public Class getObjectType() { - return Producer.class; - } + @Override + public Class getObjectType() { + return Producer.class; + } - @Override - public boolean isSingleton() { - return true; - } + @Override + public boolean isSingleton() { + return true; + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerMetadata.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerMetadata.java index a10a4fa..69b432e 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerMetadata.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ProducerMetadata.java @@ -24,116 +24,117 @@ import org.springframework.beans.factory.InitializingBean; /** * @author Soby Chacko + * @since 0.5 */ public class ProducerMetadata implements InitializingBean { - private Encoder keyEncoder; - private Encoder valueEncoder; - private Class keyClassType; - private Class valueClassType; - private final String topic; - private String compressionCodec = "default"; - private Partitioner partitioner; - private boolean async = false; - private String batchNumMessages; + private Encoder keyEncoder; + private Encoder valueEncoder; + private Class keyClassType; + private Class valueClassType; + private final String topic; + private String compressionCodec = "default"; + private Partitioner partitioner; + private boolean async = false; + private String batchNumMessages; - public ProducerMetadata(final String topic) { - this.topic = topic; - } + public ProducerMetadata(final String topic) { + this.topic = topic; + } - public String getTopic() { - return topic; - } + public String getTopic() { + return topic; + } - public Encoder getKeyEncoder() { - return keyEncoder; - } + public Encoder getKeyEncoder() { + return keyEncoder; + } - public void setKeyEncoder(final Encoder keyEncoder) { - this.keyEncoder = keyEncoder; - } + public void setKeyEncoder(final Encoder keyEncoder) { + this.keyEncoder = keyEncoder; + } - public Encoder getValueEncoder() { - return valueEncoder; - } + public Encoder getValueEncoder() { + return valueEncoder; + } - public void setValueEncoder(final Encoder valueEncoder) { - this.valueEncoder = valueEncoder; - } + public void setValueEncoder(final Encoder valueEncoder) { + this.valueEncoder = valueEncoder; + } - public Class getKeyClassType() { - return keyClassType; - } + public Class getKeyClassType() { + return keyClassType; + } - public void setKeyClassType(final Class keyClassType) { - this.keyClassType = keyClassType; - } + public void setKeyClassType(final Class keyClassType) { + this.keyClassType = keyClassType; + } - public Class getValueClassType() { - return valueClassType; - } + public Class getValueClassType() { + return valueClassType; + } - public void setValueClassType(final Class valueClassType) { - this.valueClassType = valueClassType; - } + public void setValueClassType(final Class valueClassType) { + this.valueClassType = valueClassType; + } - //TODO: Use an enum - public String getCompressionCodec() { - if (compressionCodec.equalsIgnoreCase("gzip")) { - return "1"; - } else if (compressionCodec.equalsIgnoreCase("snappy")) { - return "2"; - } + //TODO: Use an enum + public String getCompressionCodec() { + if (compressionCodec.equalsIgnoreCase("gzip")) { + return "1"; + } else if (compressionCodec.equalsIgnoreCase("snappy")) { + return "2"; + } - return "0"; - } + return "0"; + } - public void setCompressionCodec(final String compressionCodec) { - this.compressionCodec = compressionCodec; - } + public void setCompressionCodec(final String compressionCodec) { + this.compressionCodec = compressionCodec; + } - public Partitioner getPartitioner() { - return partitioner; - } + public Partitioner getPartitioner() { + return partitioner; + } - public void setPartitioner(final Partitioner partitioner) { - this.partitioner = partitioner; - } + public void setPartitioner(final Partitioner partitioner) { + this.partitioner = partitioner; + } - @Override - @SuppressWarnings("unchecked") - public void afterPropertiesSet() throws Exception { - if (valueEncoder == null) { - setValueEncoder((Encoder) new DefaultEncoder(null)); - } + @Override + @SuppressWarnings("unchecked") + public void afterPropertiesSet() throws Exception { + if (valueEncoder == null) { + setValueEncoder((Encoder) new DefaultEncoder(null)); + } - if (keyEncoder == null) { - setKeyEncoder((Encoder) getValueEncoder()); - } - } + if (keyEncoder == null) { + setKeyEncoder((Encoder) getValueEncoder()); + } + } - public boolean isAsync() { - return async; - } + public boolean isAsync() { + return async; + } - public void setAsync(final boolean async) { - this.async = async; - } + public void setAsync(final boolean async) { + this.async = async; + } - public String getBatchNumMessages() { - return batchNumMessages; - } + public String getBatchNumMessages() { + return batchNumMessages; + } - public void setBatchNumMessages(final String batchNumMessages) { - this.batchNumMessages = batchNumMessages; - } + public void setBatchNumMessages(final String batchNumMessages) { + this.batchNumMessages = batchNumMessages; + } - @Override - public boolean equals(final Object obj){ - return EqualsBuilder.reflectionEquals(this, obj); - } + @Override + public boolean equals(final Object obj){ + return EqualsBuilder.reflectionEquals(this, obj); + } - @Override - public int hashCode() { - return HashCodeBuilder.reflectionHashCode(this); - } + @Override + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ZookeeperConnect.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ZookeeperConnect.java index aa31a4d..99c304b 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ZookeeperConnect.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/support/ZookeeperConnect.java @@ -19,42 +19,43 @@ import org.springframework.integration.kafka.core.ZookeeperConnectDefaults; /** * @author Soby Chacko + * @since 0.5 */ public class ZookeeperConnect { - private String zkConnect = ZookeeperConnectDefaults.ZK_CONNECT; - private String zkConnectionTimeout = ZookeeperConnectDefaults.ZK_CONNECTION_TIMEOUT; - private String zkSessionTimeout = ZookeeperConnectDefaults.ZK_SESSION_TIMEOUT; - private String zkSyncTime = ZookeeperConnectDefaults.ZK_SYNC_TIME; + private String zkConnect = ZookeeperConnectDefaults.ZK_CONNECT; + private String zkConnectionTimeout = ZookeeperConnectDefaults.ZK_CONNECTION_TIMEOUT; + private String zkSessionTimeout = ZookeeperConnectDefaults.ZK_SESSION_TIMEOUT; + private String zkSyncTime = ZookeeperConnectDefaults.ZK_SYNC_TIME; - public String getZkConnect() { - return zkConnect; - } + public String getZkConnect() { + return zkConnect; + } - public void setZkConnect(final String zkConnect) { - this.zkConnect = zkConnect; - } + public void setZkConnect(final String zkConnect) { + this.zkConnect = zkConnect; + } - public String getZkConnectionTimeout() { - return zkConnectionTimeout; - } + public String getZkConnectionTimeout() { + return zkConnectionTimeout; + } - public void setZkConnectionTimeout(final String zkConnectionTimeout) { - this.zkConnectionTimeout = zkConnectionTimeout; - } + public void setZkConnectionTimeout(final String zkConnectionTimeout) { + this.zkConnectionTimeout = zkConnectionTimeout; + } - public String getZkSessionTimeout() { - return zkSessionTimeout; - } + public String getZkSessionTimeout() { + return zkSessionTimeout; + } - public void setZkSessionTimeout(final String zkSessionTimeout) { - this.zkSessionTimeout = zkSessionTimeout; - } + public void setZkSessionTimeout(final String zkSessionTimeout) { + this.zkSessionTimeout = zkSessionTimeout; + } - public String getZkSyncTime() { - return zkSyncTime; - } + public String getZkSyncTime() { + return zkSyncTime; + } - public void setZkSyncTime(final String zkSyncTime) { - this.zkSyncTime = zkSyncTime; - } + public void setZkSyncTime(final String zkSyncTime) { + this.zkSyncTime = zkSyncTime; + } } diff --git a/spring-integration-kafka/src/main/resources/org/springframework/integration/config/xml/spring-integration-kafka.gif b/spring-integration-kafka/src/main/resources/org/springframework/integration/config/xml/spring-integration-kafka.gif index 41b369fece0e576ecd920400b221fd33abbaf5c4..210e0764fa4c1e5baebcdea156bc3f3e1f97a2c9 100644 GIT binary patch delta 223 zcmdnPGMh!l-P6s&GEsm*fZ-Vf2>k#5zhmmUxhwYUK74UvakX5Ir{2{`v3t9c*EPl; zZwV<4FrV96vUzsb(T31G2ldI!j9rrh7;S-AuwL;e3nK%A7lRJS9FU0&>=g}OP0cN> zZS5UiVp7tgolUwb+!B^bo#NbDF7g`I3LTQl!ZL;i`c@|Gvd)?!YVNi++U-0ZMrJ~G zt`5c>ays@-f{qq?or>z7ZUW|}{YulyTioH(#+4t%gM#T&cV;c S$;-#zeDnV2hy5Lm4AuZLRYElY delta 337 zcmV-X0j~a=1iS=)M@dFFIbje05CG-?00000|Ns9kO_eoPm57zD8BM31sJy$w(NA!l zP#PA_Qat j7a - - Kafka Adapter - - The Spring Integration Kafka Adapter provides... - - - - Outbound Channel adapter - - - Outbound Gateway - - - Inbound Channel Adapter - - - -
- Java Implementation - Each of the provided components will use the - org.springframework.integration.kafka.core.KafkaExecutor - class... - -
-
- Common Configuration Attributes - - Certain configuration parameters are shared amongst all Kafka - components and are described below: - - - auto-startup - - Lifecycle attribute signaling if this component should - be started during Application Context startup. - Defaults to true. - Optional. - - - id - - Identifies the underlying Spring bean definition, which - is an instance of either EventDrivenConsumer - or PollingConsumer. - Optional. - - -
- -
- Outbound Channel Adapter - - The Kafka Outbound channel adapter allows you to... - -
-
- Outbound Gateway - - Outbound gateways are similar to outbound channel adapters except that it can also be used to - get a result on the reply channel after performing - the given... - -
-
- Inbound Channel Adapter - - An inbound channel adapter is used to execute... - -
- -
diff --git a/spring-integration-kafka/src/reference/docbook/history.xml b/spring-integration-kafka/src/reference/docbook/history.xml deleted file mode 100644 index 71b4f74..0000000 --- a/spring-integration-kafka/src/reference/docbook/history.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - Change History - diff --git a/spring-integration-kafka/src/reference/docbook/images/logo.png b/spring-integration-kafka/src/reference/docbook/images/logo.png deleted file mode 100644 index e1c2a0e3c7864b4c2495fa75fed9733af2558ff9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9627 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 R@&Et;07*qoM6N<$f(p*0Lx diff --git a/spring-integration-kafka/src/reference/docbook/index.xml b/spring-integration-kafka/src/reference/docbook/index.xml deleted file mode 100644 index bdb072f..0000000 --- a/spring-integration-kafka/src/reference/docbook/index.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - Spring Integration Kafka Adapter - Kafka Adapter ${version} - Spring Integration - ${version} - - - - - - - - - - - - - - Soby Chacko - - - © SpringSource Inc., 2012 - - - - - - - What's new? - - - For those who are already familiar with Spring Integration, this - chapter - provides a brief overview of the new features of version 2.2. If you are - interested in the changes and features, that were introduced in - earlier - versions, please take a look at chapter: - - - - - - - - - - Integration Adapters - - This section covers the various Channel Adapters and Messaging - Gateways provided - by Spring Integration to support Message-based communication with - external systems. - - - - - - Appendices - - Advanced Topics and Additional Resources - - - - diff --git a/spring-integration-kafka/src/reference/docbook/resources.xml b/spring-integration-kafka/src/reference/docbook/resources.xml deleted file mode 100644 index 456ebba..0000000 --- a/spring-integration-kafka/src/reference/docbook/resources.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - Additional Resources - -
- Spring Integration Home - - The definitive source of information about Spring Integration is the - Spring Integration Home at - http://www.springsource.org. That site serves as a hub of - information and is the best place to find up-to-date announcements about the project as well as links to - articles, blogs, and new sample applications. - -
-
diff --git a/spring-integration-kafka/src/reference/docbook/whats-new.xml b/spring-integration-kafka/src/reference/docbook/whats-new.xml deleted file mode 100644 index ddf2480..0000000 --- a/spring-integration-kafka/src/reference/docbook/whats-new.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - What's new? - - This chapter provides an overview of the new features and improvements - that have been added to the Kafka Adapter: - - diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaConsumerContextParserTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaConsumerContextParserTests.java index 50ae6fc..4b46b4e 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaConsumerContextParserTests.java +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaConsumerContextParserTests.java @@ -15,7 +15,7 @@ */ package org.springframework.integration.kafka.config.xml; -import junit.framework.Assert; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -27,21 +27,22 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @author Soby Chacko + * @since 0.5 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration public class KafkaConsumerContextParserTests { - @Autowired - private ApplicationContext appContext; + @Autowired + private ApplicationContext appContext; - @Test - @SuppressWarnings("unchecked") - public void testConsumerContextConfiguration() { - final KafkaConsumerContext consumerContext = appContext.getBean("consumerContext", KafkaConsumerContext.class); - Assert.assertNotNull(consumerContext); + @Test + @SuppressWarnings("unchecked") + public void testConsumerContextConfiguration() { + final KafkaConsumerContext consumerContext = appContext.getBean("consumerContext", KafkaConsumerContext.class); + Assert.assertNotNull(consumerContext); - final ConsumerMetadata cm = appContext.getBean("consumerMetadata_default1", ConsumerMetadata.class); - Assert.assertNotNull(cm); - } + final ConsumerMetadata cm = appContext.getBean("consumerMetadata_default1", ConsumerMetadata.class); + Assert.assertNotNull(cm); + } } diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaInboundAdapterParserTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaInboundAdapterParserTests.java index e7525b9..f5e9486 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaInboundAdapterParserTests.java +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaInboundAdapterParserTests.java @@ -15,7 +15,7 @@ */ package org.springframework.integration.kafka.config.xml; -import junit.framework.Assert; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -24,6 +24,10 @@ import org.springframework.integration.endpoint.SourcePollingChannelAdapter; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +/** + * @author Soby Chacko + * @since 0.5 + */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration public class KafkaInboundAdapterParserTests { @@ -39,7 +43,7 @@ public class KafkaInboundAdapterParserTests { final SourcePollingChannelAdapter adapter = appContext.getBean("kafkaInboundChannelAdapter", SourcePollingChannelAdapter.class); - Assert.assertNotNull(adapter); + Assert.assertNotNull(adapter); Assert.assertFalse(adapter.isAutoStartup()); } } diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaOutboundAdapterParserTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaOutboundAdapterParserTests.java index 3c69fcf..504e298 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaOutboundAdapterParserTests.java +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaOutboundAdapterParserTests.java @@ -15,7 +15,7 @@ */ package org.springframework.integration.kafka.config.xml; -import junit.framework.Assert; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -26,21 +26,25 @@ import org.springframework.integration.kafka.support.KafkaProducerContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +/** + * @author Soby Chacko + * @since 0.5 + */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration public class KafkaOutboundAdapterParserTests { - @Autowired - private ApplicationContext appContext; + @Autowired + private ApplicationContext appContext; - @Test - public void testOutboundAdapterConfiguration(){ - final PollingConsumer pollingConsumer = appContext.getBean("kafkaOutboundChannelAdapter", PollingConsumer.class); - final KafkaProducerMessageHandler messageHandler = appContext.getBean(KafkaProducerMessageHandler.class); - Assert.assertNotNull(pollingConsumer); - Assert.assertNotNull(messageHandler); - final KafkaProducerContext producerContext = messageHandler.getKafkaProducerContext(); - Assert.assertNotNull(producerContext); - Assert.assertEquals(producerContext.getTopicsConfiguration().size(), 2); - } + @Test + public void testOutboundAdapterConfiguration(){ + final PollingConsumer pollingConsumer = appContext.getBean("kafkaOutboundChannelAdapter", PollingConsumer.class); + final KafkaProducerMessageHandler messageHandler = appContext.getBean(KafkaProducerMessageHandler.class); + Assert.assertNotNull(pollingConsumer); + Assert.assertNotNull(messageHandler); + final KafkaProducerContext producerContext = messageHandler.getKafkaProducerContext(); + Assert.assertNotNull(producerContext); + Assert.assertEquals(producerContext.getTopicsConfiguration().size(), 2); + } } diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaProducerContextParserTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaProducerContextParserTests.java index ddf9718..411d02d 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaProducerContextParserTests.java +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaProducerContextParserTests.java @@ -15,7 +15,7 @@ */ package org.springframework.integration.kafka.config.xml; -import junit.framework.Assert; +import org.junit.Assert; import kafka.javaapi.producer.Producer; import kafka.serializer.Encoder; import org.junit.Test; @@ -30,44 +30,48 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.Map; +/** + * @author Soby Chacko + * @since 0.5 + */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration public class KafkaProducerContextParserTests { - @Autowired - private ApplicationContext appContext; + @Autowired + private ApplicationContext appContext; - @Test - @SuppressWarnings("unchecked") - public void testProducerContextConfiguration(){ - final KafkaProducerContext producerContext = appContext.getBean("producerContext", KafkaProducerContext.class); - Assert.assertNotNull(producerContext); + @Test + @SuppressWarnings("unchecked") + public void testProducerContextConfiguration(){ + final KafkaProducerContext producerContext = appContext.getBean("producerContext", KafkaProducerContext.class); + Assert.assertNotNull(producerContext); - final Map topicConfigurations = producerContext.getTopicsConfiguration(); - Assert.assertEquals(topicConfigurations.size(), 2); + final Map topicConfigurations = producerContext.getTopicsConfiguration(); + Assert.assertEquals(topicConfigurations.size(), 2); - final ProducerConfiguration producerConfigurationTest1 = topicConfigurations.get("producerConfiguration_test1"); - Assert.assertNotNull(producerConfigurationTest1); - final ProducerMetadata producerMetadataTest1 = producerConfigurationTest1.getProducerMetadata(); - Assert.assertEquals(producerMetadataTest1.getTopic(), "test1"); - Assert.assertEquals(producerMetadataTest1.getCompressionCodec(), "0"); - Assert.assertEquals(producerMetadataTest1.getKeyClassType(), java.lang.String.class); - Assert.assertEquals(producerMetadataTest1.getValueClassType(), java.lang.String.class); + final ProducerConfiguration producerConfigurationTest1 = topicConfigurations.get("producerConfiguration_test1"); + Assert.assertNotNull(producerConfigurationTest1); + final ProducerMetadata producerMetadataTest1 = producerConfigurationTest1.getProducerMetadata(); + Assert.assertEquals(producerMetadataTest1.getTopic(), "test1"); + Assert.assertEquals(producerMetadataTest1.getCompressionCodec(), "0"); + Assert.assertEquals(producerMetadataTest1.getKeyClassType(), java.lang.String.class); + Assert.assertEquals(producerMetadataTest1.getValueClassType(), java.lang.String.class); - final Encoder valueEncoder = appContext.getBean("valueEncoder", Encoder.class); - Assert.assertEquals(producerMetadataTest1.getValueEncoder(), valueEncoder); - Assert.assertEquals(producerMetadataTest1.getKeyEncoder(), valueEncoder); + final Encoder valueEncoder = appContext.getBean("valueEncoder", Encoder.class); + Assert.assertEquals(producerMetadataTest1.getValueEncoder(), valueEncoder); + Assert.assertEquals(producerMetadataTest1.getKeyEncoder(), valueEncoder); - final Producer producerTest1 = appContext.getBean("prodFactory_test1", Producer.class); - Assert.assertEquals(producerConfigurationTest1, new ProducerConfiguration(producerMetadataTest1, producerTest1)); + final Producer producerTest1 = appContext.getBean("prodFactory_test1", Producer.class); + Assert.assertEquals(producerConfigurationTest1, new ProducerConfiguration(producerMetadataTest1, producerTest1)); - final ProducerConfiguration producerConfigurationTest2 = topicConfigurations.get("producerConfiguration_" + "test2"); - Assert.assertNotNull(producerConfigurationTest2); - final ProducerMetadata producerMetadataTest2 = producerConfigurationTest2.getProducerMetadata(); - Assert.assertEquals(producerMetadataTest2.getTopic(), "test2"); - Assert.assertEquals(producerMetadataTest2.getCompressionCodec(), "0"); + final ProducerConfiguration producerConfigurationTest2 = topicConfigurations.get("producerConfiguration_" + "test2"); + Assert.assertNotNull(producerConfigurationTest2); + final ProducerMetadata producerMetadataTest2 = producerConfigurationTest2.getProducerMetadata(); + Assert.assertEquals(producerMetadataTest2.getTopic(), "test2"); + Assert.assertEquals(producerMetadataTest2.getCompressionCodec(), "0"); - final Producer producerTest2 = appContext.getBean("prodFactory_test2", Producer.class); - Assert.assertEquals(producerConfigurationTest2, new ProducerConfiguration(producerMetadataTest2, producerTest2)); - } + final Producer producerTest2 = appContext.getBean("prodFactory_test2", Producer.class); + Assert.assertEquals(producerConfigurationTest2, new ProducerConfiguration(producerMetadataTest2, producerTest2)); + } } diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/ZookeeperConnectParserTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/ZookeeperConnectParserTests.java index 6acfa87..f520fbb 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/ZookeeperConnectParserTests.java +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/ZookeeperConnectParserTests.java @@ -15,7 +15,7 @@ */ package org.springframework.integration.kafka.config.xml; -import junit.framework.Assert; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -27,8 +27,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @author Soby Chacko - * @since 1.0 - * + * @since 0.5 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration @@ -42,18 +41,18 @@ public class ZookeeperConnectParserTests { final ZookeeperConnect broker = appContext.getBean("zookeeperConnect", ZookeeperConnect.class); Assert.assertEquals("localhost:2181", broker.getZkConnect()); - Assert.assertEquals("10000", broker.getZkConnectionTimeout()); - Assert.assertEquals("10000", broker.getZkSessionTimeout()); - Assert.assertEquals("200", broker.getZkSyncTime()); + Assert.assertEquals("10000", broker.getZkConnectionTimeout()); + Assert.assertEquals("10000", broker.getZkSessionTimeout()); + Assert.assertEquals("200", broker.getZkSyncTime()); } - @Test - public void testDefaultKafkaBrokerConfiguration() { - final ZookeeperConnect broker = appContext.getBean("defaultZookeeperConnect", ZookeeperConnect.class); + @Test + public void testDefaultKafkaBrokerConfiguration() { + final ZookeeperConnect broker = appContext.getBean("defaultZookeeperConnect", ZookeeperConnect.class); - Assert.assertEquals(ZookeeperConnectDefaults.ZK_CONNECT, broker.getZkConnect()); - Assert.assertEquals(ZookeeperConnectDefaults.ZK_CONNECTION_TIMEOUT, broker.getZkConnectionTimeout()); - Assert.assertEquals(ZookeeperConnectDefaults.ZK_SESSION_TIMEOUT, broker.getZkSessionTimeout()); - Assert.assertEquals(ZookeeperConnectDefaults.ZK_SYNC_TIME, broker.getZkSyncTime()); - } + Assert.assertEquals(ZookeeperConnectDefaults.ZK_CONNECT, broker.getZkConnect()); + Assert.assertEquals(ZookeeperConnectDefaults.ZK_CONNECTION_TIMEOUT, broker.getZkConnectionTimeout()); + Assert.assertEquals(ZookeeperConnectDefaults.ZK_SESSION_TIMEOUT, broker.getZkSessionTimeout()); + Assert.assertEquals(ZookeeperConnectDefaults.ZK_SYNC_TIME, broker.getZkSyncTime()); + } } diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/serializer/AvroBackedKafkaSerializerTest.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/serializer/AvroBackedKafkaSerializerTest.java index a12e908..6afea82 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/serializer/AvroBackedKafkaSerializerTest.java +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/serializer/AvroBackedKafkaSerializerTest.java @@ -15,7 +15,7 @@ */ package org.springframework.integration.kafka.serializer; -import junit.framework.Assert; +import org.junit.Assert; import org.junit.Test; import org.springframework.integration.kafka.serializer.avro.AvroBackedKafkaDecoder; import org.springframework.integration.kafka.serializer.avro.AvroBackedKafkaEncoder; @@ -23,37 +23,37 @@ import org.springframework.integration.kafka.test.utils.TestObject; /** * @author Soby Chacko - * @since 1.0 + * @since 0.5 */ public class AvroBackedKafkaSerializerTest { - @Test - @SuppressWarnings("unchecked") - public void testDecodePlainSchema() { - final AvroBackedKafkaEncoder avroBackedKafkaEncoder = new AvroBackedKafkaEncoder(TestObject.class); + @Test + @SuppressWarnings("unchecked") + public void testDecodePlainSchema() { + final AvroBackedKafkaEncoder avroBackedKafkaEncoder = new AvroBackedKafkaEncoder(TestObject.class); - final TestObject testObject = new TestObject(); - testObject.setTestData1("\"Test Data1\""); - testObject.setTestData2(1); + final TestObject testObject = new TestObject(); + testObject.setTestData1("\"Test Data1\""); + testObject.setTestData2(1); - final byte[] data = avroBackedKafkaEncoder.toBytes(testObject); + final byte[] data = avroBackedKafkaEncoder.toBytes(testObject); - final AvroBackedKafkaDecoder avroBackedKafkaDecoder = new AvroBackedKafkaDecoder(TestObject.class); - final TestObject decodedFbu = (TestObject) avroBackedKafkaDecoder.fromBytes(data); + final AvroBackedKafkaDecoder avroBackedKafkaDecoder = new AvroBackedKafkaDecoder(TestObject.class); + final TestObject decodedFbu = (TestObject) avroBackedKafkaDecoder.fromBytes(data); - Assert.assertEquals(testObject.getTestData1(), decodedFbu.getTestData1()); - Assert.assertEquals(testObject.getTestData2(), decodedFbu.getTestData2()); - } + Assert.assertEquals(testObject.getTestData1(), decodedFbu.getTestData1()); + Assert.assertEquals(testObject.getTestData2(), decodedFbu.getTestData2()); + } - @Test - @SuppressWarnings("unchecked") - public void anotherTest() { - final AvroBackedKafkaEncoder avroBackedKafkaEncoder = new AvroBackedKafkaEncoder(java.lang.String.class); - final String testString = "Testing Avro"; - final byte[] data = avroBackedKafkaEncoder.toBytes(testString); + @Test + @SuppressWarnings("unchecked") + public void anotherTest() { + final AvroBackedKafkaEncoder avroBackedKafkaEncoder = new AvroBackedKafkaEncoder(java.lang.String.class); + final String testString = "Testing Avro"; + final byte[] data = avroBackedKafkaEncoder.toBytes(testString); - final AvroBackedKafkaDecoder avroBackedKafkaDecoder = new AvroBackedKafkaDecoder(java.lang.String.class); - final String decodedS = (String) avroBackedKafkaDecoder.fromBytes(data); + final AvroBackedKafkaDecoder avroBackedKafkaDecoder = new AvroBackedKafkaDecoder(java.lang.String.class); + final String decodedS = (String) avroBackedKafkaDecoder.fromBytes(data); - Assert.assertEquals(testString, decodedS); - } + Assert.assertEquals(testString, decodedS); + } } diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ConsumerConfigurationTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ConsumerConfigurationTests.java index f41f18f..d4583dc 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ConsumerConfigurationTests.java +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ConsumerConfigurationTests.java @@ -15,7 +15,7 @@ */ package org.springframework.integration.kafka.support; -import junit.framework.Assert; +import org.junit.Assert; import kafka.consumer.ConsumerIterator; import kafka.consumer.KafkaStream; import kafka.javaapi.consumer.ConsumerConnector; @@ -33,274 +33,275 @@ import java.util.Map; /** * @author Soby Chacko + * @since 0.5 */ public class ConsumerConfigurationTests { - @Test - @SuppressWarnings("unchecked") - public void testReceiveMessageForSingleTopicFromSingleStream() { - final ConsumerMetadata consumerMetadata = Mockito.mock(ConsumerMetadata.class); - final ConsumerConnectionProvider consumerConnectionProvider = - Mockito.mock(ConsumerConnectionProvider.class); - final MessageLeftOverTracker messageLeftOverTracker = Mockito.mock(MessageLeftOverTracker.class); - final ConsumerConnector consumerConnector = Mockito.mock(ConsumerConnector.class); + @Test + @SuppressWarnings("unchecked") + public void testReceiveMessageForSingleTopicFromSingleStream() { + final ConsumerMetadata consumerMetadata = Mockito.mock(ConsumerMetadata.class); + final ConsumerConnectionProvider consumerConnectionProvider = + Mockito.mock(ConsumerConnectionProvider.class); + final MessageLeftOverTracker messageLeftOverTracker = Mockito.mock(MessageLeftOverTracker.class); + final ConsumerConnector consumerConnector = Mockito.mock(ConsumerConnector.class); - Mockito.when(consumerConnectionProvider.getConsumerConnector()).thenReturn(consumerConnector); + Mockito.when(consumerConnectionProvider.getConsumerConnector()).thenReturn(consumerConnector); - final ConsumerConfiguration consumerConfiguration = new ConsumerConfiguration(consumerMetadata, - consumerConnectionProvider, messageLeftOverTracker); - consumerConfiguration.setMaxMessages(1); + final ConsumerConfiguration consumerConfiguration = new ConsumerConfiguration(consumerMetadata, + consumerConnectionProvider, messageLeftOverTracker); + consumerConfiguration.setMaxMessages(1); - final KafkaStream stream = Mockito.mock(KafkaStream.class); - final List> streams = new ArrayList>(); - streams.add(stream); - final Map>> messageStreams = new HashMap>>(); - messageStreams.put("topic", streams); + final KafkaStream stream = Mockito.mock(KafkaStream.class); + final List> streams = new ArrayList>(); + streams.add(stream); + final Map>> messageStreams = new HashMap>>(); + messageStreams.put("topic", streams); - Mockito.when(consumerConfiguration.getConsumerMapWithMessageStreams()).thenReturn(messageStreams); - final ConsumerIterator iterator = Mockito.mock(ConsumerIterator.class); - Mockito.when(stream.iterator()).thenReturn(iterator); - final MessageAndMetadata messageAndMetadata = Mockito.mock(MessageAndMetadata.class); - Mockito.when(iterator.next()).thenReturn(messageAndMetadata); - Mockito.when(messageAndMetadata.message()).thenReturn("got message"); - Mockito.when(messageAndMetadata.topic()).thenReturn("topic"); - Mockito.when(messageAndMetadata.partition()).thenReturn(1); + Mockito.when(consumerConfiguration.getConsumerMapWithMessageStreams()).thenReturn(messageStreams); + final ConsumerIterator iterator = Mockito.mock(ConsumerIterator.class); + Mockito.when(stream.iterator()).thenReturn(iterator); + final MessageAndMetadata messageAndMetadata = Mockito.mock(MessageAndMetadata.class); + Mockito.when(iterator.next()).thenReturn(messageAndMetadata); + Mockito.when(messageAndMetadata.message()).thenReturn("got message"); + Mockito.when(messageAndMetadata.topic()).thenReturn("topic"); + Mockito.when(messageAndMetadata.partition()).thenReturn(1); - final Map>> messages = consumerConfiguration.receive(); - Assert.assertEquals(messages.size(), 1); - Assert.assertEquals(messages.get("topic").size(), 1); - Assert.assertEquals(messages.get("topic").get(1).get(0), "got message"); + final Map>> messages = consumerConfiguration.receive(); + Assert.assertEquals(messages.size(), 1); + Assert.assertEquals(messages.get("topic").size(), 1); + Assert.assertEquals(messages.get("topic").get(1).get(0), "got message"); - Mockito.verify(stream, Mockito.times(1)).iterator(); - Mockito.verify(iterator, Mockito.times(1)).next(); - Mockito.verify(messageAndMetadata, Mockito.times(1)).message(); - Mockito.verify(messageAndMetadata, Mockito.times(1)).topic(); - } + Mockito.verify(stream, Mockito.times(1)).iterator(); + Mockito.verify(iterator, Mockito.times(1)).next(); + Mockito.verify(messageAndMetadata, Mockito.times(1)).message(); + Mockito.verify(messageAndMetadata, Mockito.times(1)).topic(); + } - @Test - @SuppressWarnings("unchecked") - public void testReceiveMessageForSingleTopicFromMultipleStreams() { - final ConsumerMetadata consumerMetadata = Mockito.mock(ConsumerMetadata.class); - final ConsumerConnectionProvider consumerConnectionProvider = - Mockito.mock(ConsumerConnectionProvider.class); - final MessageLeftOverTracker messageLeftOverTracker = Mockito.mock(MessageLeftOverTracker.class); + @Test + @SuppressWarnings("unchecked") + public void testReceiveMessageForSingleTopicFromMultipleStreams() { + final ConsumerMetadata consumerMetadata = Mockito.mock(ConsumerMetadata.class); + final ConsumerConnectionProvider consumerConnectionProvider = + Mockito.mock(ConsumerConnectionProvider.class); + final MessageLeftOverTracker messageLeftOverTracker = Mockito.mock(MessageLeftOverTracker.class); - final ConsumerConnector consumerConnector = Mockito.mock(ConsumerConnector.class); + final ConsumerConnector consumerConnector = Mockito.mock(ConsumerConnector.class); - Mockito.when(consumerConnectionProvider.getConsumerConnector()).thenReturn(consumerConnector); + Mockito.when(consumerConnectionProvider.getConsumerConnector()).thenReturn(consumerConnector); - final ConsumerConfiguration consumerConfiguration = new ConsumerConfiguration(consumerMetadata, - consumerConnectionProvider, messageLeftOverTracker); - consumerConfiguration.setMaxMessages(3); + final ConsumerConfiguration consumerConfiguration = new ConsumerConfiguration(consumerMetadata, + consumerConnectionProvider, messageLeftOverTracker); + consumerConfiguration.setMaxMessages(3); - final KafkaStream stream1 = Mockito.mock(KafkaStream.class); - final KafkaStream stream2 = Mockito.mock(KafkaStream.class); - final KafkaStream stream3 = Mockito.mock(KafkaStream.class); - final List> streams = new ArrayList>(); - streams.add(stream1); - streams.add(stream2); - streams.add(stream3); - final Map>> messageStreams = new HashMap>>(); - messageStreams.put("topic", streams); + final KafkaStream stream1 = Mockito.mock(KafkaStream.class); + final KafkaStream stream2 = Mockito.mock(KafkaStream.class); + final KafkaStream stream3 = Mockito.mock(KafkaStream.class); + final List> streams = new ArrayList>(); + streams.add(stream1); + streams.add(stream2); + streams.add(stream3); + final Map>> messageStreams = new HashMap>>(); + messageStreams.put("topic", streams); - Mockito.when(consumerConfiguration.getConsumerMapWithMessageStreams()).thenReturn(messageStreams); - final ConsumerIterator iterator1 = Mockito.mock(ConsumerIterator.class); - final ConsumerIterator iterator2 = Mockito.mock(ConsumerIterator.class); - final ConsumerIterator iterator3 = Mockito.mock(ConsumerIterator.class); + Mockito.when(consumerConfiguration.getConsumerMapWithMessageStreams()).thenReturn(messageStreams); + final ConsumerIterator iterator1 = Mockito.mock(ConsumerIterator.class); + final ConsumerIterator iterator2 = Mockito.mock(ConsumerIterator.class); + final ConsumerIterator iterator3 = Mockito.mock(ConsumerIterator.class); - Mockito.when(stream1.iterator()).thenReturn(iterator1); - Mockito.when(stream2.iterator()).thenReturn(iterator2); - Mockito.when(stream3.iterator()).thenReturn(iterator3); - final MessageAndMetadata messageAndMetadata1 = Mockito.mock(MessageAndMetadata.class); - final MessageAndMetadata messageAndMetadata2 = Mockito.mock(MessageAndMetadata.class); - final MessageAndMetadata messageAndMetadata3 = Mockito.mock(MessageAndMetadata.class); + Mockito.when(stream1.iterator()).thenReturn(iterator1); + Mockito.when(stream2.iterator()).thenReturn(iterator2); + Mockito.when(stream3.iterator()).thenReturn(iterator3); + final MessageAndMetadata messageAndMetadata1 = Mockito.mock(MessageAndMetadata.class); + final MessageAndMetadata messageAndMetadata2 = Mockito.mock(MessageAndMetadata.class); + final MessageAndMetadata messageAndMetadata3 = Mockito.mock(MessageAndMetadata.class); - Mockito.when(iterator1.next()).thenReturn(messageAndMetadata1); - Mockito.when(iterator2.next()).thenReturn(messageAndMetadata2); - Mockito.when(iterator3.next()).thenReturn(messageAndMetadata3); + Mockito.when(iterator1.next()).thenReturn(messageAndMetadata1); + Mockito.when(iterator2.next()).thenReturn(messageAndMetadata2); + Mockito.when(iterator3.next()).thenReturn(messageAndMetadata3); - Mockito.when(messageAndMetadata1.message()).thenReturn("got message"); - Mockito.when(messageAndMetadata1.topic()).thenReturn("topic"); - Mockito.when(messageAndMetadata1.partition()).thenReturn(1); + Mockito.when(messageAndMetadata1.message()).thenReturn("got message"); + Mockito.when(messageAndMetadata1.topic()).thenReturn("topic"); + Mockito.when(messageAndMetadata1.partition()).thenReturn(1); - Mockito.when(messageAndMetadata2.message()).thenReturn("got message"); - Mockito.when(messageAndMetadata2.topic()).thenReturn("topic"); - Mockito.when(messageAndMetadata2.partition()).thenReturn(2); + Mockito.when(messageAndMetadata2.message()).thenReturn("got message"); + Mockito.when(messageAndMetadata2.topic()).thenReturn("topic"); + Mockito.when(messageAndMetadata2.partition()).thenReturn(2); - Mockito.when(messageAndMetadata3.message()).thenReturn("got message"); - Mockito.when(messageAndMetadata3.topic()).thenReturn("topic"); - Mockito.when(messageAndMetadata3.partition()).thenReturn(3); + Mockito.when(messageAndMetadata3.message()).thenReturn("got message"); + Mockito.when(messageAndMetadata3.topic()).thenReturn("topic"); + Mockito.when(messageAndMetadata3.partition()).thenReturn(3); - final Map>> messages = consumerConfiguration.receive(); - Assert.assertEquals(messages.size(), 1); - int sum = 0; + final Map>> messages = consumerConfiguration.receive(); + Assert.assertEquals(messages.size(), 1); + int sum = 0; - final Map> values = messages.get("topic"); + final Map> values = messages.get("topic"); - for (final List l : values.values()) { - sum += l.size(); - } + for (final List l : values.values()) { + sum += l.size(); + } - Assert.assertEquals(sum, 3); - } + Assert.assertEquals(sum, 3); + } - @Test - @SuppressWarnings("unchecked") - public void testReceiveMessageForMultipleTopicsFromMultipleStreams() { - final ConsumerMetadata consumerMetadata = Mockito.mock(ConsumerMetadata.class); - final ConsumerConnectionProvider consumerConnectionProvider = - Mockito.mock(ConsumerConnectionProvider.class); - final MessageLeftOverTracker messageLeftOverTracker = Mockito.mock(MessageLeftOverTracker.class); + @Test + @SuppressWarnings("unchecked") + public void testReceiveMessageForMultipleTopicsFromMultipleStreams() { + final ConsumerMetadata consumerMetadata = Mockito.mock(ConsumerMetadata.class); + final ConsumerConnectionProvider consumerConnectionProvider = + Mockito.mock(ConsumerConnectionProvider.class); + final MessageLeftOverTracker messageLeftOverTracker = Mockito.mock(MessageLeftOverTracker.class); - final ConsumerConnector consumerConnector = Mockito.mock(ConsumerConnector.class); + final ConsumerConnector consumerConnector = Mockito.mock(ConsumerConnector.class); - Mockito.when(consumerConnectionProvider.getConsumerConnector()).thenReturn(consumerConnector); + Mockito.when(consumerConnectionProvider.getConsumerConnector()).thenReturn(consumerConnector); - final ConsumerConfiguration consumerConfiguration = new ConsumerConfiguration(consumerMetadata, - consumerConnectionProvider, messageLeftOverTracker); - consumerConfiguration.setMaxMessages(9); + final ConsumerConfiguration consumerConfiguration = new ConsumerConfiguration(consumerMetadata, + consumerConnectionProvider, messageLeftOverTracker); + consumerConfiguration.setMaxMessages(9); - final KafkaStream stream1 = Mockito.mock(KafkaStream.class); - final KafkaStream stream2 = Mockito.mock(KafkaStream.class); - final KafkaStream stream3 = Mockito.mock(KafkaStream.class); - final List> streams = new ArrayList>(); - streams.add(stream1); - streams.add(stream2); - streams.add(stream3); - final Map>> messageStreams = new HashMap>>(); - messageStreams.put("topic1", streams); - messageStreams.put("topic2", streams); - messageStreams.put("topic3", streams); + final KafkaStream stream1 = Mockito.mock(KafkaStream.class); + final KafkaStream stream2 = Mockito.mock(KafkaStream.class); + final KafkaStream stream3 = Mockito.mock(KafkaStream.class); + final List> streams = new ArrayList>(); + streams.add(stream1); + streams.add(stream2); + streams.add(stream3); + final Map>> messageStreams = new HashMap>>(); + messageStreams.put("topic1", streams); + messageStreams.put("topic2", streams); + messageStreams.put("topic3", streams); - Mockito.when(consumerConfiguration.getConsumerMapWithMessageStreams()).thenReturn(messageStreams); - final ConsumerIterator iterator1 = Mockito.mock(ConsumerIterator.class); - final ConsumerIterator iterator2 = Mockito.mock(ConsumerIterator.class); - final ConsumerIterator iterator3 = Mockito.mock(ConsumerIterator.class); + Mockito.when(consumerConfiguration.getConsumerMapWithMessageStreams()).thenReturn(messageStreams); + final ConsumerIterator iterator1 = Mockito.mock(ConsumerIterator.class); + final ConsumerIterator iterator2 = Mockito.mock(ConsumerIterator.class); + final ConsumerIterator iterator3 = Mockito.mock(ConsumerIterator.class); - Mockito.when(stream1.iterator()).thenReturn(iterator1); - Mockito.when(stream2.iterator()).thenReturn(iterator2); - Mockito.when(stream3.iterator()).thenReturn(iterator3); - final MessageAndMetadata messageAndMetadata1 = Mockito.mock(MessageAndMetadata.class); - final MessageAndMetadata messageAndMetadata2 = Mockito.mock(MessageAndMetadata.class); - final MessageAndMetadata messageAndMetadata3 = Mockito.mock(MessageAndMetadata.class); + Mockito.when(stream1.iterator()).thenReturn(iterator1); + Mockito.when(stream2.iterator()).thenReturn(iterator2); + Mockito.when(stream3.iterator()).thenReturn(iterator3); + final MessageAndMetadata messageAndMetadata1 = Mockito.mock(MessageAndMetadata.class); + final MessageAndMetadata messageAndMetadata2 = Mockito.mock(MessageAndMetadata.class); + final MessageAndMetadata messageAndMetadata3 = Mockito.mock(MessageAndMetadata.class); - Mockito.when(iterator1.next()).thenReturn(messageAndMetadata1); - Mockito.when(iterator2.next()).thenReturn(messageAndMetadata2); - Mockito.when(iterator3.next()).thenReturn(messageAndMetadata3); + Mockito.when(iterator1.next()).thenReturn(messageAndMetadata1); + Mockito.when(iterator2.next()).thenReturn(messageAndMetadata2); + Mockito.when(iterator3.next()).thenReturn(messageAndMetadata3); - Mockito.when(messageAndMetadata1.message()).thenReturn("got message1"); - Mockito.when(messageAndMetadata1.topic()).thenReturn("topic1"); - Mockito.when(messageAndMetadata1.partition()).thenAnswer(getAnswer()); + Mockito.when(messageAndMetadata1.message()).thenReturn("got message1"); + Mockito.when(messageAndMetadata1.topic()).thenReturn("topic1"); + Mockito.when(messageAndMetadata1.partition()).thenAnswer(getAnswer()); - Mockito.when(messageAndMetadata2.message()).thenReturn("got message2"); - Mockito.when(messageAndMetadata2.topic()).thenReturn("topic2"); - Mockito.when(messageAndMetadata1.partition()).thenAnswer(getAnswer()); + Mockito.when(messageAndMetadata2.message()).thenReturn("got message2"); + Mockito.when(messageAndMetadata2.topic()).thenReturn("topic2"); + Mockito.when(messageAndMetadata1.partition()).thenAnswer(getAnswer()); - Mockito.when(messageAndMetadata3.message()).thenReturn("got message3"); - Mockito.when(messageAndMetadata3.topic()).thenReturn("topic3"); - Mockito.when(messageAndMetadata1.partition()).thenAnswer(getAnswer()); + Mockito.when(messageAndMetadata3.message()).thenReturn("got message3"); + Mockito.when(messageAndMetadata3.topic()).thenReturn("topic3"); + Mockito.when(messageAndMetadata1.partition()).thenAnswer(getAnswer()); - final Map>> messages = consumerConfiguration.receive(); - int sum = 0; + final Map>> messages = consumerConfiguration.receive(); + int sum = 0; - final Collection>> values = messages.values(); + final Collection>> values = messages.values(); - for (final Map> m : values) { - for (final List l : m.values()) { - sum += l.size(); - } - } + for (final Map> m : values) { + for (final List l : m.values()) { + sum += l.size(); + } + } - Assert.assertEquals(sum, 9); - } + Assert.assertEquals(sum, 9); + } - private Answer getAnswer() { - return new Answer() { - private int count = 0; + private Answer getAnswer() { + return new Answer() { + private int count = 0; - @Override - public Object answer(final InvocationOnMock invocation) throws Throwable { - if (count++ == 1) { - return 1; - } else if (count++ == 2) { - return 2; - } + @Override + public Object answer(final InvocationOnMock invocation) throws Throwable { + if (count++ == 1) { + return 1; + } else if (count++ == 2) { + return 2; + } - return 3; - } - }; - } + return 3; + } + }; + } - @Test - @SuppressWarnings("unchecked") - public void testReceiveMessageAndVerifyMessageLeftoverFromPreviousPollAreTakenFirst() { - final ConsumerMetadata consumerMetadata = Mockito.mock(ConsumerMetadata.class); - final ConsumerConnectionProvider consumerConnectionProvider = - Mockito.mock(ConsumerConnectionProvider.class); - final MessageLeftOverTracker messageLeftOverTracker = Mockito.mock(MessageLeftOverTracker.class); - final ConsumerConnector consumerConnector = Mockito.mock(ConsumerConnector.class); + @Test + @SuppressWarnings("unchecked") + public void testReceiveMessageAndVerifyMessageLeftoverFromPreviousPollAreTakenFirst() { + final ConsumerMetadata consumerMetadata = Mockito.mock(ConsumerMetadata.class); + final ConsumerConnectionProvider consumerConnectionProvider = + Mockito.mock(ConsumerConnectionProvider.class); + final MessageLeftOverTracker messageLeftOverTracker = Mockito.mock(MessageLeftOverTracker.class); + final ConsumerConnector consumerConnector = Mockito.mock(ConsumerConnector.class); - Mockito.when(messageLeftOverTracker.getCurrentCount()).thenReturn(3); - final MessageAndMetadata m1 = new MessageAndMetadata("key1", "value1", "topic1", 1, 1L); - final MessageAndMetadata m2 = new MessageAndMetadata("key2", "value2", "topic2", 1, 1L); - final MessageAndMetadata m3 = new MessageAndMetadata("key1", "value3", "topic3", 1, 1L); + Mockito.when(messageLeftOverTracker.getCurrentCount()).thenReturn(3); + final MessageAndMetadata m1 = new MessageAndMetadata("key1", "value1", "topic1", 1, 1L); + final MessageAndMetadata m2 = new MessageAndMetadata("key2", "value2", "topic2", 1, 1L); + final MessageAndMetadata m3 = new MessageAndMetadata("key1", "value3", "topic3", 1, 1L); - final List mList = new ArrayList(); - mList.add(m1); - mList.add(m2); - mList.add(m3); + final List mList = new ArrayList(); + mList.add(m1); + mList.add(m2); + mList.add(m3); - Mockito.when(messageLeftOverTracker.getMessageLeftOverFromPreviousPoll()).thenReturn(mList); + Mockito.when(messageLeftOverTracker.getMessageLeftOverFromPreviousPoll()).thenReturn(mList); - Mockito.when(consumerConnectionProvider.getConsumerConnector()).thenReturn(consumerConnector); + Mockito.when(consumerConnectionProvider.getConsumerConnector()).thenReturn(consumerConnector); - final ConsumerConfiguration consumerConfiguration = new ConsumerConfiguration(consumerMetadata, - consumerConnectionProvider, messageLeftOverTracker); - consumerConfiguration.setMaxMessages(5); + final ConsumerConfiguration consumerConfiguration = new ConsumerConfiguration(consumerMetadata, + consumerConnectionProvider, messageLeftOverTracker); + consumerConfiguration.setMaxMessages(5); - final KafkaStream stream = Mockito.mock(KafkaStream.class); - final List> streams = new ArrayList>(); - streams.add(stream); - final Map>> messageStreams = new HashMap>>(); - messageStreams.put("topic1", streams); + final KafkaStream stream = Mockito.mock(KafkaStream.class); + final List> streams = new ArrayList>(); + streams.add(stream); + final Map>> messageStreams = new HashMap>>(); + messageStreams.put("topic1", streams); - Mockito.when(consumerConfiguration.getConsumerMapWithMessageStreams()).thenReturn(messageStreams); - final ConsumerIterator iterator = Mockito.mock(ConsumerIterator.class); - Mockito.when(stream.iterator()).thenReturn(iterator); - final MessageAndMetadata messageAndMetadata = Mockito.mock(MessageAndMetadata.class); - Mockito.when(iterator.next()).thenReturn(messageAndMetadata); - Mockito.when(messageAndMetadata.message()).thenReturn("got message"); - Mockito.when(messageAndMetadata.topic()).thenReturn("topic1"); - Mockito.when(messageAndMetadata.partition()).thenReturn(1); + Mockito.when(consumerConfiguration.getConsumerMapWithMessageStreams()).thenReturn(messageStreams); + final ConsumerIterator iterator = Mockito.mock(ConsumerIterator.class); + Mockito.when(stream.iterator()).thenReturn(iterator); + final MessageAndMetadata messageAndMetadata = Mockito.mock(MessageAndMetadata.class); + Mockito.when(iterator.next()).thenReturn(messageAndMetadata); + Mockito.when(messageAndMetadata.message()).thenReturn("got message"); + Mockito.when(messageAndMetadata.topic()).thenReturn("topic1"); + Mockito.when(messageAndMetadata.partition()).thenReturn(1); - final Map>> messages = consumerConfiguration.receive(); - int sum = 0; + final Map>> messages = consumerConfiguration.receive(); + int sum = 0; - final Collection>> values = messages.values(); + final Collection>> values = messages.values(); - for (final Map> m : values) { - for (final List l : m.values()) { - sum += l.size(); - } + for (final Map> m : values) { + for (final List l : m.values()) { + sum += l.size(); + } - } - Assert.assertEquals(sum, 5); + } + Assert.assertEquals(sum, 5); - Assert.assertTrue(messages.containsKey("topic1")); - Assert.assertTrue(messages.containsKey("topic2")); - Assert.assertTrue(messages.containsKey("topic3")); + Assert.assertTrue(messages.containsKey("topic1")); + Assert.assertTrue(messages.containsKey("topic2")); + Assert.assertTrue(messages.containsKey("topic3")); - Assert.assertTrue(valueFound(messages.get("topic1").get(1), "value1")); - Assert.assertTrue(valueFound(messages.get("topic2").get(1), "value2")); - Assert.assertTrue(valueFound(messages.get("topic3").get(1), "value3")); - } + Assert.assertTrue(valueFound(messages.get("topic1").get(1), "value1")); + Assert.assertTrue(valueFound(messages.get("topic2").get(1), "value2")); + Assert.assertTrue(valueFound(messages.get("topic3").get(1), "value3")); + } - private boolean valueFound(final List l, final String value){ - for (final Object o : l){ - if (value.equals(o)){ - return true; - } - } + private boolean valueFound(final List l, final String value){ + for (final Object o : l){ + if (value.equals(o)){ + return true; + } + } - return false; - } + return false; + } } diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/KafkaConsumerContextTest.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/KafkaConsumerContextTest.java index 12bc189..456bca8 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/KafkaConsumerContextTest.java +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/KafkaConsumerContextTest.java @@ -28,53 +28,54 @@ import java.util.Map; /** * @author Soby Chacko + * @since 0.5 */ public class KafkaConsumerContextTest { - @Test - public void testMergeResultsFromMultipleConsumerConfiguration() { - final KafkaConsumerContext kafkaConsumerContext = new KafkaConsumerContext(); - final ListableBeanFactory beanFactory = Mockito.mock(ListableBeanFactory.class); - final ConsumerConfiguration consumerConfiguration1 = Mockito.mock(ConsumerConfiguration.class); - final ConsumerConfiguration consumerConfiguration2 = Mockito.mock(ConsumerConfiguration.class); + @Test + public void testMergeResultsFromMultipleConsumerConfiguration() { + final KafkaConsumerContext kafkaConsumerContext = new KafkaConsumerContext(); + final ListableBeanFactory beanFactory = Mockito.mock(ListableBeanFactory.class); + final ConsumerConfiguration consumerConfiguration1 = Mockito.mock(ConsumerConfiguration.class); + final ConsumerConfiguration consumerConfiguration2 = Mockito.mock(ConsumerConfiguration.class); - final Map map = new HashMap(); - map.put("config1", consumerConfiguration1); - map.put("config2", consumerConfiguration2); + final Map map = new HashMap(); + map.put("config1", consumerConfiguration1); + map.put("config2", consumerConfiguration2); - Mockito.when(beanFactory.getBeansOfType(ConsumerConfiguration.class)).thenReturn(map); - kafkaConsumerContext.setBeanFactory(beanFactory); + Mockito.when(beanFactory.getBeansOfType(ConsumerConfiguration.class)).thenReturn(map); + kafkaConsumerContext.setBeanFactory(beanFactory); - final Map>> result1 = new HashMap>>(); - final List l1 = new ArrayList(); - l1.add("got message1 - l1"); - l1.add("got message2 - l1"); - final Map> innerMap1 = new HashMap>(); - innerMap1.put(1, l1); - result1.put("topic1", innerMap1); + final Map>> result1 = new HashMap>>(); + final List l1 = new ArrayList(); + l1.add("got message1 - l1"); + l1.add("got message2 - l1"); + final Map> innerMap1 = new HashMap>(); + innerMap1.put(1, l1); + result1.put("topic1", innerMap1); - final Map>> result2 = new HashMap>>(); - final List l2 = new ArrayList(); - l2.add("got message1 - l2"); - l2.add("got message2 - l2"); - l2.add("got message3 - l2"); + final Map>> result2 = new HashMap>>(); + final List l2 = new ArrayList(); + l2.add("got message1 - l2"); + l2.add("got message2 - l2"); + l2.add("got message3 - l2"); - final Map> innerMap2 = new HashMap>(); - innerMap2.put(1, l2); - result1.put("topic2", innerMap2); + final Map> innerMap2 = new HashMap>(); + innerMap2.put(1, l2); + result1.put("topic2", innerMap2); - Mockito.when(consumerConfiguration1.receive()).thenReturn(result1); - Mockito.when(consumerConfiguration2.receive()).thenReturn(result2); + Mockito.when(consumerConfiguration1.receive()).thenReturn(result1); + Mockito.when(consumerConfiguration2.receive()).thenReturn(result2); - final Message>>> messages = kafkaConsumerContext.receive(); - Assert.assertEquals(messages.getPayload().size(), 2); - Assert.assertEquals(messages.getPayload().get("topic1").size(), 1); - Assert.assertEquals(messages.getPayload().get("topic1").get(1).get(0), "got message1 - l1"); - Assert.assertEquals(messages.getPayload().get("topic1").get(1).get(1), "got message2 - l1"); + final Message>>> messages = kafkaConsumerContext.receive(); + Assert.assertEquals(messages.getPayload().size(), 2); + Assert.assertEquals(messages.getPayload().get("topic1").size(), 1); + Assert.assertEquals(messages.getPayload().get("topic1").get(1).get(0), "got message1 - l1"); + Assert.assertEquals(messages.getPayload().get("topic1").get(1).get(1), "got message2 - l1"); - Assert.assertEquals(messages.getPayload().get("topic2").size(), 1); - Assert.assertEquals(messages.getPayload().get("topic2").get(1).get(0), "got message1 - l2"); - Assert.assertEquals(messages.getPayload().get("topic2").get(1).get(1), "got message2 - l2"); - Assert.assertEquals(messages.getPayload().get("topic2").get(1).get(2), "got message3 - l2"); - } + Assert.assertEquals(messages.getPayload().get("topic2").size(), 1); + Assert.assertEquals(messages.getPayload().get("topic2").get(1).get(0), "got message1 - l2"); + Assert.assertEquals(messages.getPayload().get("topic2").get(1).get(1), "got message2 - l2"); + Assert.assertEquals(messages.getPayload().get("topic2").get(1).get(2), "got message3 - l2"); + } } diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ProducerConfigurationTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ProducerConfigurationTests.java index 2fa2021..6feefe0 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ProducerConfigurationTests.java +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ProducerConfigurationTests.java @@ -37,267 +37,271 @@ import java.io.ObjectInputStream; /** * @author Soby Chacko + * @since 0.5 */ public class ProducerConfigurationTests { - @Test - @SuppressWarnings("unchecked") - public void testSendMessageWithNonDefaultKeyAndValueEncoders() throws Exception { - final ProducerMetadata producerMetadata = new ProducerMetadata("test"); - producerMetadata.setValueEncoder(new StringEncoder(null)); - producerMetadata.setKeyEncoder(new StringEncoder(null)); - producerMetadata.setKeyClassType(String.class); - producerMetadata.setValueClassType(String.class); - final Producer producer = Mockito.mock(Producer.class); + @Test + @SuppressWarnings("unchecked") + public void testSendMessageWithNonDefaultKeyAndValueEncoders() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + producerMetadata.setValueEncoder(new StringEncoder(null)); + producerMetadata.setKeyEncoder(new StringEncoder(null)); + producerMetadata.setKeyClassType(String.class); + producerMetadata.setValueClassType(String.class); + final Producer producer = Mockito.mock(Producer.class); - final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); + final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); - final Message message = MessageBuilder.withPayload("test message"). - setHeader("messageKey", "key") - .setHeader("topic", "test").build(); + final Message message = MessageBuilder.withPayload("test message") + .setHeader("messageKey", "key") + .setHeader("topic", "test") + .build(); - configuration.send(message); + configuration.send(message); - Mockito.verify(producer, Mockito.times(1)).send(Mockito.any(KeyedMessage.class)); + Mockito.verify(producer, Mockito.times(1)).send(Mockito.any(KeyedMessage.class)); - final ArgumentCaptor argument = ArgumentCaptor.forClass(KeyedMessage.class); - Mockito.verify(producer).send(argument.capture()); + final ArgumentCaptor argument = ArgumentCaptor.forClass(KeyedMessage.class); + Mockito.verify(producer).send(argument.capture()); - final KeyedMessage capturedKeyMessage = argument.getValue(); + final KeyedMessage capturedKeyMessage = argument.getValue(); - Assert.assertEquals(capturedKeyMessage.key(), "key"); - Assert.assertEquals(capturedKeyMessage.message(), "test message"); - Assert.assertEquals(capturedKeyMessage.topic(), "test"); - } + Assert.assertEquals(capturedKeyMessage.key(), "key"); + Assert.assertEquals(capturedKeyMessage.message(), "test message"); + Assert.assertEquals(capturedKeyMessage.topic(), "test"); + } - /** - * User does not set an explicit key/value encoder, but send a serializable object for both key/value - */ - @Test - @SuppressWarnings("unchecked") - public void testSendMessageWithDefaultKeyAndValueEncodersAndCustomSerializableKeyAndPayloadObject() throws Exception { - final ProducerMetadata producerMetadata = new ProducerMetadata("test"); - producerMetadata.setValueEncoder(new DefaultEncoder(null)); - producerMetadata.setKeyEncoder(new DefaultEncoder(null)); - final Producer producer = Mockito.mock(Producer.class); + /** + * User does not set an explicit key/value encoder, but send a serializable object for both key/value + */ + @Test + @SuppressWarnings("unchecked") + public void testSendMessageWithDefaultKeyAndValueEncodersAndCustomSerializableKeyAndPayloadObject() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + producerMetadata.setValueEncoder(new DefaultEncoder(null)); + producerMetadata.setKeyEncoder(new DefaultEncoder(null)); + final Producer producer = Mockito.mock(Producer.class); - final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); + final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); - final Message message = MessageBuilder.withPayload(new TestPayload("part1", "part2")). - setHeader("messageKey", new TestKey("compositePart1", "compositePart2")) - .setHeader("topic", "test").build(); + final Message message = MessageBuilder.withPayload(new TestPayload("part1", "part2")) + .setHeader("messageKey", new TestKey("compositePart1", "compositePart2")) + .setHeader("topic", "test") + .build(); - configuration.send(message); + configuration.send(message); - Mockito.verify(producer, Mockito.times(1)).send(Mockito.any(KeyedMessage.class)); + Mockito.verify(producer, Mockito.times(1)).send(Mockito.any(KeyedMessage.class)); - final ArgumentCaptor argument = ArgumentCaptor.forClass(KeyedMessage.class); - Mockito.verify(producer).send(argument.capture()); + final ArgumentCaptor argument = ArgumentCaptor.forClass(KeyedMessage.class); + Mockito.verify(producer).send(argument.capture()); - final KeyedMessage capturedKeyMessage = argument.getValue(); + final KeyedMessage capturedKeyMessage = argument.getValue(); - final byte[] keyBytes = (byte[])capturedKeyMessage.key(); + final byte[] keyBytes = (byte[])capturedKeyMessage.key(); - final ByteArrayInputStream keyInputStream = new ByteArrayInputStream (keyBytes); - final ObjectInputStream keyObjectInputStream = new ObjectInputStream (keyInputStream); - final Object keyObj = keyObjectInputStream.readObject(); + final ByteArrayInputStream keyInputStream = new ByteArrayInputStream (keyBytes); + final ObjectInputStream keyObjectInputStream = new ObjectInputStream (keyInputStream); + final Object keyObj = keyObjectInputStream.readObject(); - final TestKey tk = (TestKey)keyObj; + final TestKey tk = (TestKey)keyObj; - Assert.assertEquals(tk.getKeyPart1(), "compositePart1"); - Assert.assertEquals(tk.getKeyPart2(), "compositePart2"); + Assert.assertEquals(tk.getKeyPart1(), "compositePart1"); + Assert.assertEquals(tk.getKeyPart2(), "compositePart2"); - final byte[] messageBytes = (byte[])capturedKeyMessage.message(); + final byte[] messageBytes = (byte[])capturedKeyMessage.message(); - final ByteArrayInputStream messageInputStream = new ByteArrayInputStream (messageBytes); - final ObjectInputStream messageObjectInputStream = new ObjectInputStream (messageInputStream); - final Object messageObj = messageObjectInputStream.readObject(); + final ByteArrayInputStream messageInputStream = new ByteArrayInputStream (messageBytes); + final ObjectInputStream messageObjectInputStream = new ObjectInputStream (messageInputStream); + final Object messageObj = messageObjectInputStream.readObject(); - final TestPayload tp = (TestPayload)messageObj; + final TestPayload tp = (TestPayload)messageObj; - Assert.assertEquals(tp.getPart1(), "part1"); - Assert.assertEquals(tp.getPart2(), "part2"); + Assert.assertEquals(tp.getPart1(), "part1"); + Assert.assertEquals(tp.getPart2(), "part2"); - Assert.assertEquals(capturedKeyMessage.topic(), "test"); - } + Assert.assertEquals(capturedKeyMessage.topic(), "test"); + } - /** - * User does not set an explicit key encoder, but a value encoder, and sends the corresponding data - */ - @Test - @SuppressWarnings("unchecked") - public void testSendMessageWithDefaultKeyEncoderAndNonDefaultValueEncoderAndCorrespondingData() throws Exception { - final ProducerMetadata producerMetadata = new ProducerMetadata("test"); - final AvroBackedKafkaEncoder encoder = new AvroBackedKafkaEncoder(TestPayload.class); - producerMetadata.setValueEncoder(encoder); - producerMetadata.setKeyEncoder(new DefaultEncoder(null)); - producerMetadata.setValueClassType(TestPayload.class); - final Producer producer = Mockito.mock(Producer.class); + /** + * User does not set an explicit key encoder, but a value encoder, and sends the corresponding data + */ + @Test + @SuppressWarnings("unchecked") + public void testSendMessageWithDefaultKeyEncoderAndNonDefaultValueEncoderAndCorrespondingData() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + final AvroBackedKafkaEncoder encoder = new AvroBackedKafkaEncoder(TestPayload.class); + producerMetadata.setValueEncoder(encoder); + producerMetadata.setKeyEncoder(new DefaultEncoder(null)); + producerMetadata.setValueClassType(TestPayload.class); + final Producer producer = Mockito.mock(Producer.class); - final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); - final TestPayload tp = new TestPayload("part1", "part2"); - final Message message = MessageBuilder.withPayload(tp). - setHeader("messageKey", "key") - .setHeader("topic", "test").build(); + final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); + final TestPayload tp = new TestPayload("part1", "part2"); + final Message message = MessageBuilder.withPayload(tp) + .setHeader("messageKey", "key") + .setHeader("topic", "test") + .build(); - configuration.send(message); + configuration.send(message); - Mockito.verify(producer, Mockito.times(1)).send(Mockito.any(KeyedMessage.class)); + Mockito.verify(producer, Mockito.times(1)).send(Mockito.any(KeyedMessage.class)); - final ArgumentCaptor argument = ArgumentCaptor.forClass(KeyedMessage.class); - Mockito.verify(producer).send(argument.capture()); + final ArgumentCaptor argument = ArgumentCaptor.forClass(KeyedMessage.class); + Mockito.verify(producer).send(argument.capture()); - final KeyedMessage capturedKeyMessage = argument.getValue(); + final KeyedMessage capturedKeyMessage = argument.getValue(); - final byte[] keyBytes = (byte[])capturedKeyMessage.key(); + final byte[] keyBytes = (byte[])capturedKeyMessage.key(); - final ByteArrayInputStream keyInputStream = new ByteArrayInputStream (keyBytes); - final ObjectInputStream keyObjectInputStream = new ObjectInputStream (keyInputStream); - final Object keyObj = keyObjectInputStream.readObject(); + final ByteArrayInputStream keyInputStream = new ByteArrayInputStream (keyBytes); + final ObjectInputStream keyObjectInputStream = new ObjectInputStream (keyInputStream); + final Object keyObj = keyObjectInputStream.readObject(); - Assert.assertEquals("key", keyObj); - Assert.assertEquals(capturedKeyMessage.message(), tp); + Assert.assertEquals("key", keyObj); + Assert.assertEquals(capturedKeyMessage.message(), tp); - Assert.assertEquals(capturedKeyMessage.topic(), "test"); - } + Assert.assertEquals(capturedKeyMessage.topic(), "test"); + } - /** - * User does set an explicit key encoder, but not a value encoder, and sends the corresponding data - */ - @Test - @SuppressWarnings("unchecked") - public void testSendMessageWithNonDefaultKeyEncoderAndDefaultValueEncoderAndCorrespondingData() throws Exception { - final ProducerMetadata producerMetadata = new ProducerMetadata("test"); - final AvroBackedKafkaEncoder encoder = new AvroBackedKafkaEncoder(TestKey.class); - producerMetadata.setKeyEncoder(encoder); - producerMetadata.setValueEncoder(new DefaultEncoder(null)); - producerMetadata.setKeyClassType(TestKey.class); - final Producer producer = Mockito.mock(Producer.class); + /** + * User does set an explicit key encoder, but not a value encoder, and sends the corresponding data + */ + @Test + @SuppressWarnings("unchecked") + public void testSendMessageWithNonDefaultKeyEncoderAndDefaultValueEncoderAndCorrespondingData() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + final AvroBackedKafkaEncoder encoder = new AvroBackedKafkaEncoder(TestKey.class); + producerMetadata.setKeyEncoder(encoder); + producerMetadata.setValueEncoder(new DefaultEncoder(null)); + producerMetadata.setKeyClassType(TestKey.class); + final Producer producer = Mockito.mock(Producer.class); - final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); - final TestKey tk = new TestKey("part1", "part2"); - final Message message = MessageBuilder.withPayload("test message"). - setHeader("messageKey", tk) - .setHeader("topic", "test").build(); + final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); + final TestKey tk = new TestKey("part1", "part2"); + final Message message = MessageBuilder.withPayload("test message"). + setHeader("messageKey", tk) + .setHeader("topic", "test").build(); - configuration.send(message); + configuration.send(message); - Mockito.verify(producer, Mockito.times(1)).send(Mockito.any(KeyedMessage.class)); + Mockito.verify(producer, Mockito.times(1)).send(Mockito.any(KeyedMessage.class)); - final ArgumentCaptor argument = ArgumentCaptor.forClass(KeyedMessage.class); - Mockito.verify(producer).send(argument.capture()); + final ArgumentCaptor argument = ArgumentCaptor.forClass(KeyedMessage.class); + Mockito.verify(producer).send(argument.capture()); - final KeyedMessage capturedKeyMessage = argument.getValue(); + final KeyedMessage capturedKeyMessage = argument.getValue(); - Assert.assertEquals(capturedKeyMessage.key(), tk); + Assert.assertEquals(capturedKeyMessage.key(), tk); - final byte[] payloadBytes = (byte[])capturedKeyMessage.message(); + final byte[] payloadBytes = (byte[])capturedKeyMessage.message(); - final ByteArrayInputStream payloadBis = new ByteArrayInputStream (payloadBytes); - final ObjectInputStream payloadOis = new ObjectInputStream (payloadBis); - final Object payloadObj = payloadOis.readObject(); + final ByteArrayInputStream payloadBis = new ByteArrayInputStream (payloadBytes); + final ObjectInputStream payloadOis = new ObjectInputStream (payloadBis); + final Object payloadObj = payloadOis.readObject(); - Assert.assertEquals("test message", payloadObj); - - Assert.assertEquals(capturedKeyMessage.topic(), "test"); - } - - /** - * User does not set an explicit key/value encoder, but send a serializable String key/value pair - */ - @Test - @SuppressWarnings("unchecked") - public void testSendMessageWithDefaultKeyAndValueEncodersAndStringKeyAndValue() throws Exception { - final ProducerMetadata producerMetadata = new ProducerMetadata("test"); - producerMetadata.setValueEncoder(new DefaultEncoder(null)); - producerMetadata.setKeyEncoder(new DefaultEncoder(null)); - final Producer producer = Mockito.mock(Producer.class); - - final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); - - final Message message = MessageBuilder.withPayload("test message"). - setHeader("messageKey", "key") - .setHeader("topic", "test").build(); - - configuration.send(message); - - Mockito.verify(producer, Mockito.times(1)).send(Mockito.any(KeyedMessage.class)); - - final ArgumentCaptor argument = ArgumentCaptor.forClass(KeyedMessage.class); - Mockito.verify(producer).send(argument.capture()); - - final KeyedMessage capturedKeyMessage = argument.getValue(); - final byte[] keyBytes = (byte[])capturedKeyMessage.key(); - - final ByteArrayInputStream keyBis = new ByteArrayInputStream (keyBytes); - final ObjectInputStream keyOis = new ObjectInputStream (keyBis); - final Object keyObj = keyOis.readObject(); - - Assert.assertEquals("key", keyObj); - - final byte[] payloadBytes = (byte[])capturedKeyMessage.message(); - - final ByteArrayInputStream payloadBis = new ByteArrayInputStream (payloadBytes); - final ObjectInputStream payloadOis = new ObjectInputStream (payloadBis); - final Object payloadObj = payloadOis.readObject(); - - Assert.assertEquals("test message", payloadObj); - Assert.assertEquals(capturedKeyMessage.topic(), "test"); - } - - /** - * User does not set an explicit key/value encoder, but send non-serializable object for both key/value - */ - @Test(expected = NotSerializableException.class) - @SuppressWarnings("unchecked") - public void testSendMessageWithDefaultKeyAndValueEncodersButNonSerializableKeyAndValue() throws Exception { - final ProducerMetadata producerMetadata = new ProducerMetadata("test"); - producerMetadata.setValueEncoder(new DefaultEncoder(null)); - producerMetadata.setKeyEncoder(new DefaultEncoder(null)); - final Producer producer = Mockito.mock(Producer.class); - - final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); - - final Message message = MessageBuilder.withPayload(new NonSerializableTestPayload("part1", "part2")). - setHeader("messageKey", new NonSerializableTestKey("compositePart1", "compositePart2")) - .setHeader("topic", "test").build(); - configuration.send(message); - } - - /** - * User does not set an explicit key/value encoder, but send non-serializable key and serializable value - */ - @Test(expected = NotSerializableException.class) - @SuppressWarnings("unchecked") - public void testSendMessageWithDefaultKeyAndValueEncodersButNonSerializableKeyAndSerializableValue() throws Exception { - final ProducerMetadata producerMetadata = new ProducerMetadata("test"); - producerMetadata.setValueEncoder(new DefaultEncoder(null)); - producerMetadata.setKeyEncoder(new DefaultEncoder(null)); - final Producer producer = Mockito.mock(Producer.class); - - final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); - - final Message message = MessageBuilder.withPayload(new TestPayload("part1", "part2")). - setHeader("messageKey", new NonSerializableTestKey("compositePart1", "compositePart2")) - .setHeader("topic", "test").build(); - configuration.send(message); - } - - /** - * User does not set an explicit key/value encoder, but send serializable key and non-serializable value - */ - @Test(expected = NotSerializableException.class) - @SuppressWarnings("unchecked") - public void testSendMessageWithDefaultKeyAndValueEncodersButSerializableKeyAndNonSerializableValue() throws Exception { - final ProducerMetadata producerMetadata = new ProducerMetadata("test"); - producerMetadata.setValueEncoder(new DefaultEncoder(null)); - producerMetadata.setKeyEncoder(new DefaultEncoder(null)); - final Producer producer = Mockito.mock(Producer.class); - - final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); - - final Message message = MessageBuilder.withPayload(new NonSerializableTestPayload("part1", "part2")). - setHeader("messageKey", new TestKey("compositePart1", "compositePart2")) - .setHeader("topic", "test").build(); - configuration.send(message); - } + Assert.assertEquals("test message", payloadObj); + + Assert.assertEquals(capturedKeyMessage.topic(), "test"); + } + + /** + * User does not set an explicit key/value encoder, but send a serializable String key/value pair + */ + @Test + @SuppressWarnings("unchecked") + public void testSendMessageWithDefaultKeyAndValueEncodersAndStringKeyAndValue() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + producerMetadata.setValueEncoder(new DefaultEncoder(null)); + producerMetadata.setKeyEncoder(new DefaultEncoder(null)); + final Producer producer = Mockito.mock(Producer.class); + + final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); + + final Message message = MessageBuilder.withPayload("test message"). + setHeader("messageKey", "key") + .setHeader("topic", "test").build(); + + configuration.send(message); + + Mockito.verify(producer, Mockito.times(1)).send(Mockito.any(KeyedMessage.class)); + + final ArgumentCaptor argument = ArgumentCaptor.forClass(KeyedMessage.class); + Mockito.verify(producer).send(argument.capture()); + + final KeyedMessage capturedKeyMessage = argument.getValue(); + final byte[] keyBytes = (byte[])capturedKeyMessage.key(); + + final ByteArrayInputStream keyBis = new ByteArrayInputStream (keyBytes); + final ObjectInputStream keyOis = new ObjectInputStream (keyBis); + final Object keyObj = keyOis.readObject(); + + Assert.assertEquals("key", keyObj); + + final byte[] payloadBytes = (byte[])capturedKeyMessage.message(); + + final ByteArrayInputStream payloadBis = new ByteArrayInputStream (payloadBytes); + final ObjectInputStream payloadOis = new ObjectInputStream (payloadBis); + final Object payloadObj = payloadOis.readObject(); + + Assert.assertEquals("test message", payloadObj); + Assert.assertEquals(capturedKeyMessage.topic(), "test"); + } + + /** + * User does not set an explicit key/value encoder, but send non-serializable object for both key/value + */ + @Test(expected = NotSerializableException.class) + @SuppressWarnings("unchecked") + public void testSendMessageWithDefaultKeyAndValueEncodersButNonSerializableKeyAndValue() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + producerMetadata.setValueEncoder(new DefaultEncoder(null)); + producerMetadata.setKeyEncoder(new DefaultEncoder(null)); + final Producer producer = Mockito.mock(Producer.class); + + final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); + + final Message message = MessageBuilder.withPayload(new NonSerializableTestPayload("part1", "part2")). + setHeader("messageKey", new NonSerializableTestKey("compositePart1", "compositePart2")) + .setHeader("topic", "test").build(); + configuration.send(message); + } + + /** + * User does not set an explicit key/value encoder, but send non-serializable key and serializable value + */ + @Test(expected = NotSerializableException.class) + @SuppressWarnings("unchecked") + public void testSendMessageWithDefaultKeyAndValueEncodersButNonSerializableKeyAndSerializableValue() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + producerMetadata.setValueEncoder(new DefaultEncoder(null)); + producerMetadata.setKeyEncoder(new DefaultEncoder(null)); + final Producer producer = Mockito.mock(Producer.class); + + final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); + + final Message message = MessageBuilder.withPayload(new TestPayload("part1", "part2")). + setHeader("messageKey", new NonSerializableTestKey("compositePart1", "compositePart2")) + .setHeader("topic", "test").build(); + configuration.send(message); + } + + /** + * User does not set an explicit key/value encoder, but send serializable key and non-serializable value + */ + @Test(expected = NotSerializableException.class) + @SuppressWarnings("unchecked") + public void testSendMessageWithDefaultKeyAndValueEncodersButSerializableKeyAndNonSerializableValue() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + producerMetadata.setValueEncoder(new DefaultEncoder(null)); + producerMetadata.setKeyEncoder(new DefaultEncoder(null)); + final Producer producer = Mockito.mock(Producer.class); + + final ProducerConfiguration configuration = new ProducerConfiguration(producerMetadata, producer); + + final Message message = MessageBuilder.withPayload(new NonSerializableTestPayload("part1", "part2")). + setHeader("messageKey", new TestKey("compositePart1", "compositePart2")) + .setHeader("topic", "test").build(); + configuration.send(message); + } } diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ProducerFactoryBeanTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ProducerFactoryBeanTests.java index 0089e73..683a269 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ProducerFactoryBeanTests.java +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/support/ProducerFactoryBeanTests.java @@ -15,49 +15,50 @@ */ package org.springframework.integration.kafka.support; -import junit.framework.Assert; +import org.junit.Assert; import kafka.javaapi.producer.Producer; import org.junit.Test; import org.mockito.Mockito; /** * @author Soby Chacko + * @since 0.5 */ public class ProducerFactoryBeanTests { - @Test - public void createProducerWithDefaultMetadata() throws Exception { - final ProducerMetadata producerMetadata = new ProducerMetadata("test"); - final ProducerMetadata tm = Mockito.spy(producerMetadata); - final ProducerFactoryBean producerFactoryBean = new ProducerFactoryBean(tm, "localhost:9092"); - final Producer producer = producerFactoryBean.getObject(); + @Test + public void createProducerWithDefaultMetadata() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + final ProducerMetadata tm = Mockito.spy(producerMetadata); + final ProducerFactoryBean producerFactoryBean = new ProducerFactoryBean(tm, "localhost:9092"); + final Producer producer = producerFactoryBean.getObject(); - Assert.assertTrue(producer != null); + Assert.assertTrue(producer != null); - Mockito.verify(tm, Mockito.times(1)).getPartitioner(); - Mockito.verify(tm, Mockito.times(1)).getCompressionCodec(); - Mockito.verify(tm, Mockito.times(1)).getValueEncoder(); - Mockito.verify(tm, Mockito.times(1)).getKeyEncoder(); - Mockito.verify(tm, Mockito.times(1)).isAsync(); - Mockito.verify(tm, Mockito.times(0)).getBatchNumMessages(); - } + Mockito.verify(tm, Mockito.times(1)).getPartitioner(); + Mockito.verify(tm, Mockito.times(1)).getCompressionCodec(); + Mockito.verify(tm, Mockito.times(1)).getValueEncoder(); + Mockito.verify(tm, Mockito.times(1)).getKeyEncoder(); + Mockito.verify(tm, Mockito.times(1)).isAsync(); + Mockito.verify(tm, Mockito.times(0)).getBatchNumMessages(); + } - @Test - public void createProducerWithAsyncFeatures() throws Exception { - final ProducerMetadata producerMetadata = new ProducerMetadata("test"); - producerMetadata.setAsync(true); - producerMetadata.setBatchNumMessages("300"); - final ProducerMetadata tm = Mockito.spy(producerMetadata); - final ProducerFactoryBean producerFactoryBean = new ProducerFactoryBean(tm, "localhost:9092"); - final Producer producer = producerFactoryBean.getObject(); + @Test + public void createProducerWithAsyncFeatures() throws Exception { + final ProducerMetadata producerMetadata = new ProducerMetadata("test"); + producerMetadata.setAsync(true); + producerMetadata.setBatchNumMessages("300"); + final ProducerMetadata tm = Mockito.spy(producerMetadata); + final ProducerFactoryBean producerFactoryBean = new ProducerFactoryBean(tm, "localhost:9092"); + final Producer producer = producerFactoryBean.getObject(); - Assert.assertTrue(producer != null); + Assert.assertTrue(producer != null); - Mockito.verify(tm, Mockito.times(1)).getPartitioner(); - Mockito.verify(tm, Mockito.times(1)).getCompressionCodec(); - Mockito.verify(tm, Mockito.times(1)).getValueEncoder(); - Mockito.verify(tm, Mockito.times(1)).getKeyEncoder(); - Mockito.verify(tm, Mockito.times(1)).isAsync(); - Mockito.verify(tm, Mockito.times(2)).getBatchNumMessages(); - } + Mockito.verify(tm, Mockito.times(1)).getPartitioner(); + Mockito.verify(tm, Mockito.times(1)).getCompressionCodec(); + Mockito.verify(tm, Mockito.times(1)).getValueEncoder(); + Mockito.verify(tm, Mockito.times(1)).getKeyEncoder(); + Mockito.verify(tm, Mockito.times(1)).isAsync(); + Mockito.verify(tm, Mockito.times(2)).getBatchNumMessages(); + } } diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/NonSerializableTestKey.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/NonSerializableTestKey.java index eef22df..fab58fd 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/NonSerializableTestKey.java +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/NonSerializableTestKey.java @@ -15,21 +15,25 @@ */ package org.springframework.integration.kafka.test.utils; +/** + * @author Soby Chacko + * @since 0.5 + */ public class NonSerializableTestKey { - private final String keyPart1; - private final String keyPart2; + private final String keyPart1; + private final String keyPart2; - public NonSerializableTestKey(final String keyPart1, final String keyPart2) { - this.keyPart1 = keyPart1; - this.keyPart2 = keyPart2; - } + public NonSerializableTestKey(final String keyPart1, final String keyPart2) { + this.keyPart1 = keyPart1; + this.keyPart2 = keyPart2; + } - public String getKeyPart1() { - return keyPart1; - } + public String getKeyPart1() { + return keyPart1; + } - public String getKeyPart2() { - return keyPart2; - } + public String getKeyPart2() { + return keyPart2; + } } diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/NonSerializableTestPayload.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/NonSerializableTestPayload.java index 6b2bb2f..f18a10f 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/NonSerializableTestPayload.java +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/NonSerializableTestPayload.java @@ -15,20 +15,24 @@ */ package org.springframework.integration.kafka.test.utils; +/** + * @author Soby Chacko + * @since 0.5 + */ public class NonSerializableTestPayload { - private final String part1; - private final String part2; + private final String part1; + private final String part2; - public NonSerializableTestPayload(final String part1, final String part2) { - this.part1 = part1; - this.part2 = part2; - } + public NonSerializableTestPayload(final String part1, final String part2) { + this.part1 = part1; + this.part2 = part2; + } - public String getPart1() { - return part1; - } + public String getPart1() { + return part1; + } - public String getPart2() { - return part2; - } + public String getPart2() { + return part2; + } } diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestKey.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestKey.java index 10e0480..ee4314e 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestKey.java +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestKey.java @@ -17,22 +17,26 @@ package org.springframework.integration.kafka.test.utils; import java.io.Serializable; +/** + * @author Soby Chacko + * @since 0.5 + */ public class TestKey implements Serializable { - private static final long serialVersionUID = -6415387283545560656L; + private static final long serialVersionUID = -6415387283545560656L; - private final String keyPart1; - private final String keyPart2; + private final String keyPart1; + private final String keyPart2; - public TestKey(final String keyPart1, final String keyPart2) { - this.keyPart1 = keyPart1; - this.keyPart2 = keyPart2; - } + public TestKey(final String keyPart1, final String keyPart2) { + this.keyPart1 = keyPart1; + this.keyPart2 = keyPart2; + } - public String getKeyPart1() { - return keyPart1; - } + public String getKeyPart1() { + return keyPart1; + } - public String getKeyPart2() { - return keyPart2; - } + public String getKeyPart2() { + return keyPart2; + } } diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestObject.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestObject.java index 42c2ec1..4cb5302 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestObject.java +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestObject.java @@ -15,24 +15,28 @@ */ package org.springframework.integration.kafka.test.utils; +/** + * @author Soby Chacko + * @since 0.5 + */ public class TestObject { - public String testData1; - public int testData2; + public String testData1; + public int testData2; - public String getTestData1() { - return testData1; - } + public String getTestData1() { + return testData1; + } - public void setTestData1(final String testData1) { - this.testData1 = testData1; - } + public void setTestData1(final String testData1) { + this.testData1 = testData1; + } - public int getTestData2() { - return testData2; - } + public int getTestData2() { + return testData2; + } - public void setTestData2(final int testData2) { - this.testData2 = testData2; - } + public void setTestData2(final int testData2) { + this.testData2 = testData2; + } } diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestPayload.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestPayload.java index 3ac94f8..73e9bf4 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestPayload.java +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/test/utils/TestPayload.java @@ -17,22 +17,26 @@ package org.springframework.integration.kafka.test.utils; import java.io.Serializable; +/** + * @author Soby Chacko + * @since 0.5 + */ public class TestPayload implements Serializable { - private static final long serialVersionUID = -8560378224929007403L; + private static final long serialVersionUID = -8560378224929007403L; - private final String part1; - private final String part2; + private final String part1; + private final String part2; - public TestPayload(final String part1, final String part2){ - this.part1 = part1; - this.part2 = part2; - } + public TestPayload(final String part1, final String part2){ + this.part1 = part1; + this.part2 = part2; + } - public String getPart1() { - return part1; - } + public String getPart1() { + return part1; + } - public String getPart2() { - return part2; - } + public String getPart2() { + return part2; + } }