From 1d2a5b5f06f98e00bddee4f8ecb4a4eba4dc11b2 Mon Sep 17 00:00:00 2001 From: Lukasz Antoniak Date: Sun, 13 Jan 2013 11:54:25 -0800 Subject: [PATCH 1/3] INTEXT-36 - Add Voldemort Module --- spring-integration-voldemort/README.md | 56 ++++ spring-integration-voldemort/build.gradle | 266 +++++++++++++++ .../gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 39770 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + spring-integration-voldemort/gradlew | 164 +++++++++ spring-integration-voldemort/gradlew.bat | 90 +++++ .../publish-maven.gradle | 61 ++++ .../src/api/overview.html | 22 ++ .../src/dist/changelog.txt | 10 + .../src/dist/license.txt | 201 +++++++++++ .../src/dist/notice.txt | 21 ++ .../src/dist/readme.txt | 13 + .../VoldemortInboundChannelAdapterParser.java | 53 +++ .../config/xml/VoldemortNamespaceHandler.java | 31 ++ ...VoldemortOutboundChannelAdapterParser.java | 65 ++++ .../config/xml/VoldemortParserUtils.java | 47 +++ .../voldemort/config/xml/package-info.java | 4 + .../inbound/VoldemortMessageSource.java | 101 ++++++ .../voldemort/inbound/package-info.java | 4 + .../VoldemortStoringMessageHandler.java | 112 +++++++ .../voldemort/outbound/package-info.java | 4 + .../integration/voldemort/package-info.java | 4 + .../store/VoldemortMessageStore.java | 234 +++++++++++++ .../voldemort/support/PersistMode.java | 26 ++ .../voldemort/support/VoldemortHeaders.java | 36 ++ .../voldemort/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-voldemort-1.0.xsd | 187 +++++++++++ .../xml/spring-integration-voldemort.gif | Bin 0 -> 572 bytes .../src/reference/docbook/history.xml | 28 ++ .../src/reference/docbook/images/logo.png | Bin 0 -> 9627 bytes .../src/reference/docbook/index.xml | 63 ++++ .../src/reference/docbook/resources.xml | 35 ++ .../src/reference/docbook/voldemort.xml | 214 ++++++++++++ .../src/reference/docbook/whats-new.xml | 35 ++ .../test/BaseFunctionalTestCase.java | 101 ++++++ .../voldemort/test/domain/Car.java | 123 +++++++ .../voldemort/test/domain/Person.java | 89 +++++ .../voldemort/test/inbound/ObjectKeyTest.java | 66 ++++ .../inbound/VoldemortInboundAdapterTest.java | 101 ++++++ .../MessageUpdatingServiceActivator.java | 38 +++ .../test/outbound/ObjectKeyTest.java | 58 ++++ .../VoldemortOutboundAdapterTest.java | 177 ++++++++++ .../store/BaseStoreFunctionalTestCase.java | 44 +++ .../store/VoldemortMessageGroupStoreTest.java | 316 ++++++++++++++++++ .../VoldemortMessageStoreAggregationTest.java | 70 ++++ .../test/store/VoldemortMessageStoreTest.java | 187 +++++++++++ .../src/test/resources/cluster.xml | 12 + .../src/test/resources/log4j.properties | 8 + .../voldemort/test/common-test-context.xml | 16 + .../test/inbound/ObjectKeyTest-context.xml | 25 ++ .../VoldemortInboundAdapterTest-context.xml | 30 ++ .../test/outbound/ObjectKeyTest-context.xml | 19 ++ .../VoldemortOutboundAdapterTest-context.xml | 42 +++ ...ortMessageStoreAggregationTest-context.xml | 14 + .../test/store/store-test-context.xml | 25 ++ .../voldemort/test/store/stores.xml | 19 ++ .../src/test/resources/stores.xml | 34 ++ 61 files changed, 3819 insertions(+) create mode 100755 spring-integration-voldemort/README.md create mode 100755 spring-integration-voldemort/build.gradle create mode 100755 spring-integration-voldemort/gradle.properties create mode 100755 spring-integration-voldemort/gradle/wrapper/gradle-wrapper.jar create mode 100755 spring-integration-voldemort/gradle/wrapper/gradle-wrapper.properties create mode 100755 spring-integration-voldemort/gradlew create mode 100755 spring-integration-voldemort/gradlew.bat create mode 100755 spring-integration-voldemort/publish-maven.gradle create mode 100755 spring-integration-voldemort/src/api/overview.html create mode 100755 spring-integration-voldemort/src/dist/changelog.txt create mode 100755 spring-integration-voldemort/src/dist/license.txt create mode 100755 spring-integration-voldemort/src/dist/notice.txt create mode 100755 spring-integration-voldemort/src/dist/readme.txt create mode 100644 spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortInboundChannelAdapterParser.java create mode 100755 spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortNamespaceHandler.java create mode 100755 spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortOutboundChannelAdapterParser.java create mode 100755 spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortParserUtils.java create mode 100755 spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/package-info.java create mode 100644 spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/inbound/VoldemortMessageSource.java create mode 100755 spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/inbound/package-info.java create mode 100755 spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/outbound/VoldemortStoringMessageHandler.java create mode 100755 spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/outbound/package-info.java create mode 100755 spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/package-info.java create mode 100644 spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/store/VoldemortMessageStore.java create mode 100644 spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/support/PersistMode.java create mode 100644 spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/support/VoldemortHeaders.java create mode 100755 spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/support/package-info.java create mode 100755 spring-integration-voldemort/src/main/resources/META-INF/spring.handlers create mode 100755 spring-integration-voldemort/src/main/resources/META-INF/spring.schemas create mode 100755 spring-integration-voldemort/src/main/resources/META-INF/spring.tooling create mode 100755 spring-integration-voldemort/src/main/resources/org/springframework/integration/config/xml/spring-integration-voldemort-1.0.xsd create mode 100755 spring-integration-voldemort/src/main/resources/org/springframework/integration/config/xml/spring-integration-voldemort.gif create mode 100755 spring-integration-voldemort/src/reference/docbook/history.xml create mode 100755 spring-integration-voldemort/src/reference/docbook/images/logo.png create mode 100755 spring-integration-voldemort/src/reference/docbook/index.xml create mode 100755 spring-integration-voldemort/src/reference/docbook/resources.xml create mode 100755 spring-integration-voldemort/src/reference/docbook/voldemort.xml create mode 100755 spring-integration-voldemort/src/reference/docbook/whats-new.xml create mode 100755 spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/BaseFunctionalTestCase.java create mode 100644 spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/domain/Car.java create mode 100755 spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/domain/Person.java create mode 100644 spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/inbound/ObjectKeyTest.java create mode 100644 spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/inbound/VoldemortInboundAdapterTest.java create mode 100644 spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/outbound/MessageUpdatingServiceActivator.java create mode 100644 spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/outbound/ObjectKeyTest.java create mode 100755 spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/outbound/VoldemortOutboundAdapterTest.java create mode 100644 spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/BaseStoreFunctionalTestCase.java create mode 100644 spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/VoldemortMessageGroupStoreTest.java create mode 100644 spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreAggregationTest.java create mode 100644 spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreTest.java create mode 100755 spring-integration-voldemort/src/test/resources/cluster.xml create mode 100755 spring-integration-voldemort/src/test/resources/log4j.properties create mode 100755 spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/common-test-context.xml create mode 100644 spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/inbound/ObjectKeyTest-context.xml create mode 100644 spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/inbound/VoldemortInboundAdapterTest-context.xml create mode 100644 spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/outbound/ObjectKeyTest-context.xml create mode 100755 spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/outbound/VoldemortOutboundAdapterTest-context.xml create mode 100644 spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreAggregationTest-context.xml create mode 100644 spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/store-test-context.xml create mode 100644 spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/stores.xml create mode 100755 spring-integration-voldemort/src/test/resources/stores.xml diff --git a/spring-integration-voldemort/README.md b/spring-integration-voldemort/README.md new file mode 100755 index 0000000..965caee --- /dev/null +++ b/spring-integration-voldemort/README.md @@ -0,0 +1,56 @@ +Spring Integration Voldemort Adapter +==================================== + +The Voldemort extension for Spring Integration (SI) project includes inbound +and outbound channel adapters. + +Inbound channel adapter: +----------------------------------------------------------------------------- +Inbound channel adapter is used to retrieve data out of Voldemort database +and transfer objects into Spring Integration's channel. Component expects +user to provide Voldemort store client, message converter and desired object's +key. + +### Example: +~~~~~xml + + ... + + + + ... + +~~~~~ + +For more implementation details please review documentation and integration +test cases. + +Outbound channel adapter: +----------------------------------------------------------------------------- +Outbound channel adapter is used to insert data into Voldemort database +from Spring Integration's channel. Component expects user to provide +Voldemort store client and message converter. + +### Example: +~~~~~xml + + ... + + ... + +~~~~~ + +For more implementation details please review documentation and integration +test cases. + +Build +----------------------------------------------------------------------------- +For build instructions visit [Spring Integration on GitHub](https://github.com/SpringSource/spring-integration). \ No newline at end of file diff --git a/spring-integration-voldemort/build.gradle b/spring-integration-voldemort/build.gradle new file mode 100755 index 0000000..61618f8 --- /dev/null +++ b/spring-integration-voldemort/build.gradle @@ -0,0 +1,266 @@ +description = 'Spring Integration Voldemort 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.voldemort' + +repositories { + maven { url 'http://repo.springsource.org/libs-milestone' } + maven { url 'http://repo.springsource.org/plugins-release' } +} + +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' + voldemortVersion = '0.96' + + shortName = 'voldemort' +} + +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.integration:spring-integration-core:$springIntegrationVersion" + compile "voldemort:voldemort:$voldemortVersion" + testCompile "org.springframework.integration:spring-integration-test:$springIntegrationVersion" + testCompile "junit:junit-dep:$junitVersion" + testCompile "log4j:log4j:$log4jVersion" + testCompile "org.mockito:mockito-all:$mockitoVersion" + testCompile "org.springframework:spring-test:$springVersion" + 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) + // JVM settings from http://www.project-voldemort.com/voldemort/configuration.html + jvmArgs "-XX:+UseConcMarkSweepGC", "-XX:+UseParNewGC", "-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(); + + 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-voldemort/gradle.properties b/spring-integration-voldemort/gradle.properties new file mode 100755 index 0000000..bebfcbc --- /dev/null +++ b/spring-integration-voldemort/gradle.properties @@ -0,0 +1 @@ +version=1.0.0.BUILD-SNAPSHOT diff --git a/spring-integration-voldemort/gradle/wrapper/gradle-wrapper.jar b/spring-integration-voldemort/gradle/wrapper/gradle-wrapper.jar new file mode 100755 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-voldemort/gradlew.bat b/spring-integration-voldemort/gradlew.bat new file mode 100755 index 0000000..8a0b282 --- /dev/null +++ b/spring-integration-voldemort/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-voldemort/publish-maven.gradle b/spring-integration-voldemort/publish-maven.gradle new file mode 100755 index 0000000..0ba656a --- /dev/null +++ b/spring-integration-voldemort/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 = 'lukasz-antoniak' + name = 'Lukasz Antoniak' + email = 'Lukasz.Antoniak@gmail.com' + } + } + } + } +} diff --git a/spring-integration-voldemort/src/api/overview.html b/spring-integration-voldemort/src/api/overview.html new file mode 100755 index 0000000..fb0198b --- /dev/null +++ b/spring-integration-voldemort/src/api/overview.html @@ -0,0 +1,22 @@ + + +This document is the API specification for Spring Integration +
+
+

+ 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-voldemort/src/dist/changelog.txt b/spring-integration-voldemort/src/dist/changelog.txt new file mode 100755 index 0000000..5a45c4e --- /dev/null +++ b/spring-integration-voldemort/src/dist/changelog.txt @@ -0,0 +1,10 @@ +Spring Integration Voldemort Adapter Changelog +============================================== + +Changes in version 1.0.0 (13 Jan 2013) +------------------------------------- + +** New Feature + * Voldemort Inbound Adapter + * Voldemort Outbound Adapter + * Voldemort Message Store \ No newline at end of file diff --git a/spring-integration-voldemort/src/dist/license.txt b/spring-integration-voldemort/src/dist/license.txt new file mode 100755 index 0000000..261eeb9 --- /dev/null +++ b/spring-integration-voldemort/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 name) 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 name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name 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-voldemort/src/dist/notice.txt b/spring-integration-voldemort/src/dist/notice.txt new file mode 100755 index 0000000..f62045a --- /dev/null +++ b/spring-integration-voldemort/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-voldemort/src/dist/readme.txt b/spring-integration-voldemort/src/dist/readme.txt new file mode 100755 index 0000000..edccc0c --- /dev/null +++ b/spring-integration-voldemort/src/dist/readme.txt @@ -0,0 +1,13 @@ +Spring Integration Voldemort Adapter +------------------------------------ + +To find out what has changed since any earlier releases, see 'changelog.txt'. + +Please consult the documentation located within the 'docs/reference' directory +of this release and also visit the official Spring Integration home at +http://www.springsource.org/spring-integration + +There you will find links to the forum, issue tracker, and several other resources. + +See https://github.com/SpringSource/spring-integration#readme for additional +information including instructions on building from source. diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortInboundChannelAdapterParser.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortInboundChannelAdapterParser.java new file mode 100644 index 0000000..aa4fc98 --- /dev/null +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortInboundChannelAdapterParser.java @@ -0,0 +1,53 @@ +/* + * 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.voldemort.config.xml; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.RootBeanDefinition; +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.voldemort.inbound.VoldemortMessageSource; +import org.w3c.dom.Element; + +/** + * Parses Voldemort inbound adapter XML definition. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +public class VoldemortInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser { + /** + * Produces "int-voldemort:inbound-channel-adapter" bean definition. + *

+ * {@inheritDoc} + */ + @Override + protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) { + final BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition( VoldemortMessageSource.class ); + VoldemortParserUtils.processCommonAttributes( element, builder ); + final RootBeanDefinition queryExpressionDef = + IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression( + VoldemortParserUtils.SEARCH_KEY, VoldemortParserUtils.SEARCH_KEY_EXPRESSION, + parserContext, element, true + ); + builder.addPropertyValue( VoldemortParserUtils.KEY_EXPRESSION_PROPERTY, queryExpressionDef ); + IntegrationNamespaceUtils.setValueIfAttributeDefined( builder, element, VoldemortParserUtils.DELETE_AFTER_POLL ); + IntegrationNamespaceUtils.setValueIfAttributeDefined( builder, element, VoldemortParserUtils.EXTRACT_VALUE ); + return builder.getBeanDefinition(); + } +} diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortNamespaceHandler.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortNamespaceHandler.java new file mode 100755 index 0000000..85ab0fe --- /dev/null +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortNamespaceHandler.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.voldemort.config.xml; + +import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler; + +/** + * The handler for Voldemort extension namespace. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +public class VoldemortNamespaceHandler extends AbstractIntegrationNamespaceHandler { + public void init() { + registerBeanDefinitionParser( "inbound-channel-adapter", new VoldemortInboundChannelAdapterParser() ); + registerBeanDefinitionParser( "outbound-channel-adapter", new VoldemortOutboundChannelAdapterParser() ); + } +} diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortOutboundChannelAdapterParser.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortOutboundChannelAdapterParser.java new file mode 100755 index 0000000..3ee51aa --- /dev/null +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortOutboundChannelAdapterParser.java @@ -0,0 +1,65 @@ +/* + * 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.voldemort.config.xml; + +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.ExpressionFactoryBean; +import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.voldemort.outbound.VoldemortStoringMessageHandler; +import org.w3c.dom.Element; + +/** + * Parses Voldemort outbound adapter XML definition. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +public class VoldemortOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { + /** + * Produces "int-voldemort:outbound-channel-adapter" bean definition. + *

+ * {@inheritDoc} + */ + @Override + protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { + final BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition( VoldemortStoringMessageHandler.class ); + VoldemortParserUtils.processCommonAttributes( element, builder ); + final boolean hasKey = element.hasAttribute( VoldemortParserUtils.STORE_KEY ); + final boolean hasKeyExpression = element.hasAttribute( VoldemortParserUtils.STORE_KEY_EXPRESSION ); + if ( hasKey && hasKeyExpression ) { + parserContext.getReaderContext().error( "At most one of '" + VoldemortParserUtils.STORE_KEY + + "' or '" + VoldemortParserUtils.STORE_KEY_EXPRESSION + "' is allowed.", element ); + } + if ( hasKey ) { + builder.addPropertyValue( + VoldemortParserUtils.KEY_PROPERTY, + new TypedStringValue( element.getAttribute( VoldemortParserUtils.STORE_KEY ) ) + ); + } + if ( hasKeyExpression ) { + RootBeanDefinition expressionDef = new RootBeanDefinition( ExpressionFactoryBean.class ); + expressionDef.getConstructorArgumentValues().addGenericArgumentValue( element.getAttribute( VoldemortParserUtils.STORE_KEY_EXPRESSION ) ); + builder.addPropertyValue( VoldemortParserUtils.KEY_EXPRESSION_PROPERTY, expressionDef ); + } + IntegrationNamespaceUtils.setValueIfAttributeDefined( builder, element, VoldemortParserUtils.PERSIST_MODE ); + return builder.getBeanDefinition(); + } +} diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortParserUtils.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortParserUtils.java new file mode 100755 index 0000000..1762115 --- /dev/null +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortParserUtils.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.voldemort.config.xml; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.w3c.dom.Element; + +/** + * Contains various utility methods for parsing Voldemort Adapter specific namesspace elements. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +abstract class VoldemortParserUtils { + static final String STORE_CLIENT = "store-client"; + static final String PERSIST_MODE = "persist-mode"; + static final String SEARCH_KEY = "search-key"; + static final String SEARCH_KEY_EXPRESSION = "search-key-expression"; + static final String STORE_KEY = "store-key"; + static final String STORE_KEY_EXPRESSION = "store-key-expression"; + static final String DELETE_AFTER_POLL = "delete-after-poll"; + static final String EXTRACT_VALUE = "extract-value"; + + static final String KEY_PROPERTY = "key"; + static final String KEY_EXPRESSION_PROPERTY = "keyExpression"; + + /** + * Handles "store-client" and "message-converter" attributes. + */ + static void processCommonAttributes(Element element, BeanDefinitionBuilder builder) { + final String storeClient = element.getAttribute( VoldemortParserUtils.STORE_CLIENT ); + builder.addConstructorArgReference( storeClient ); + } +} diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/package-info.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/package-info.java new file mode 100755 index 0000000..8c9202d --- /dev/null +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides parser classes to manage XML namespace for the Voldemort components. + */ +package org.springframework.integration.voldemort.config.xml; diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/inbound/VoldemortMessageSource.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/inbound/VoldemortMessageSource.java new file mode 100644 index 0000000..d6524e3 --- /dev/null +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/inbound/VoldemortMessageSource.java @@ -0,0 +1,101 @@ +/* + * 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.voldemort.inbound; + +import org.springframework.expression.Expression; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.integration.Message; +import org.springframework.integration.context.IntegrationObjectSupport; +import org.springframework.integration.core.MessageSource; +import org.springframework.integration.expression.ExpressionUtils; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.voldemort.support.VoldemortHeaders; +import voldemort.client.StoreClient; +import voldemort.versioning.Versioned; + +/** + * Voldemort polling inbound adapter implementation. Regularly tries to retrieve object with a given key. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +public class VoldemortMessageSource extends IntegrationObjectSupport implements MessageSource { + private final StoreClient client; + + /** + * Key expression which will be evaluated on every call to the {@link #receive()} method. + */ + private volatile Expression keyExpression; + private volatile StandardEvaluationContext evaluationContext; + private volatile boolean extractValue = true; + private volatile boolean deleteAfterPoll = false; + + /** + * Creates new message source. + * + * @param client Voldemort store client. + */ + public VoldemortMessageSource(StoreClient client) { + this.client = client; + } + + @Override + protected void onInit() throws Exception { + super.onInit(); + if ( getBeanFactory() != null ) { + evaluationContext = ExpressionUtils.createStandardEvaluationContext( getBeanFactory() ); + } + else { + evaluationContext = ExpressionUtils.createStandardEvaluationContext(); + } + } + + @Override + @SuppressWarnings("unchecked") + public Message receive() { + final Object key = keyExpression.getValue( evaluationContext, Object.class ); + final Versioned value = client.get( key ); + if ( value != null ) { + if ( deleteAfterPoll ) { + client.delete( key ); + } + return MessageBuilder.withPayload( extractValue ? value.getValue() : value ) + .setHeader( VoldemortHeaders.KEY, key ).build(); + } + return null; + } + + @Override + public String getComponentType() { + return "voldemort:inbound-channel-adapter"; + } + + public void setDeleteAfterPoll(boolean deleteAfterPoll) { + this.deleteAfterPoll = deleteAfterPoll; + } + + public Expression getKeyExpression() { + return keyExpression; + } + + public void setKeyExpression(Expression keyExpression) { + this.keyExpression = keyExpression; + } + + public void setExtractValue(boolean extractValue) { + this.extractValue = extractValue; + } +} diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/inbound/package-info.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/inbound/package-info.java new file mode 100755 index 0000000..5c18a3c --- /dev/null +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/inbound/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides inbound Spring Integration Voldemort components. + */ +package org.springframework.integration.voldemort.inbound; diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/outbound/VoldemortStoringMessageHandler.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/outbound/VoldemortStoringMessageHandler.java new file mode 100755 index 0000000..8704746 --- /dev/null +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/outbound/VoldemortStoringMessageHandler.java @@ -0,0 +1,112 @@ +/* + * 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.voldemort.outbound; + +import org.springframework.expression.Expression; +import org.springframework.expression.common.LiteralExpression; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.integration.Message; +import org.springframework.integration.expression.ExpressionUtils; +import org.springframework.integration.handler.AbstractMessageHandler; +import org.springframework.integration.voldemort.support.PersistMode; +import org.springframework.integration.voldemort.support.VoldemortHeaders; +import voldemort.client.StoreClient; + +/** + * Voldemort outbound adapter implementation. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +public class VoldemortStoringMessageHandler extends AbstractMessageHandler { + private final StoreClient client; + + private volatile StandardEvaluationContext evaluationContext; + private volatile Expression keyExpression = new SpelExpressionParser().parseExpression( "headers." + VoldemortHeaders.KEY ); + + private volatile PersistMode persistMode = PersistMode.PUT; + + /** + * Creates new message sender. + * + * @param client Voldemort store client. + */ + public VoldemortStoringMessageHandler(StoreClient client) { + this.client = client; + } + + @Override + protected void onInit() throws Exception { + super.onInit(); + if ( getBeanFactory() != null ) { + evaluationContext = ExpressionUtils.createStandardEvaluationContext( getBeanFactory() ); + } + else { + evaluationContext = ExpressionUtils.createStandardEvaluationContext(); + } + } + + @Override + @SuppressWarnings("unchecked") + protected void handleMessageInternal(Message message) throws Exception { + final Object key = keyExpression.getValue( evaluationContext, message, Object.class ); + switch ( determinePersistMode( message ) ) { + case PUT: + client.put( key, message.getPayload() ); + break; + case DELETE: + client.delete( key ); + break; + } + } + + /** + * Computes desired persist mode for a given message. Default output adapter's configuration + * can be overridden with {@link VoldemortHeaders#PERSIST_MODE} message header which supports + * direct or text representation of {@link PersistMode} enumeration. + * + * @param message Spring Integration message. + * @return Persist mode. + */ + private PersistMode determinePersistMode(Message message) { + final Object confValue = message.getHeaders().get( VoldemortHeaders.PERSIST_MODE ); + if ( confValue instanceof PersistMode ) { + return (PersistMode) confValue; + } + else if ( confValue instanceof String ) { + return PersistMode.valueOf( (String) confValue ); + } + return persistMode; + } + + @Override + public String getComponentType() { + return "voldemort:outbound-channel-adapter"; + } + + public void setKey(String key) { + setKeyExpression( new LiteralExpression( key ) ); + } + + public void setKeyExpression(Expression keyExpression) { + this.keyExpression = keyExpression; + } + + public void setPersistMode(PersistMode persistMode) { + this.persistMode = persistMode; + } +} diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/outbound/package-info.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/outbound/package-info.java new file mode 100755 index 0000000..a77b939 --- /dev/null +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/outbound/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides Spring Integration Voldemort components for doing outbound operations. + */ +package org.springframework.integration.voldemort.outbound; diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/package-info.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/package-info.java new file mode 100755 index 0000000..c4eebeb --- /dev/null +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/package-info.java @@ -0,0 +1,4 @@ +/** + * Root package of the Voldemort Module. + */ +package org.springframework.integration.voldemort; diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/store/VoldemortMessageStore.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/store/VoldemortMessageStore.java new file mode 100644 index 0000000..6caf424 --- /dev/null +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/store/VoldemortMessageStore.java @@ -0,0 +1,234 @@ +/* + * 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.voldemort.store; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.locks.Lock; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.integration.store.AbstractKeyValueMessageStore; +import org.springframework.integration.store.MessageGroupStore; +import org.springframework.integration.store.MessageStore; +import org.springframework.integration.util.DefaultLockRegistry; +import org.springframework.integration.util.LockRegistry; +import org.springframework.integration.util.UUIDConverter; +import voldemort.client.StoreClient; +import voldemort.client.UpdateAction; +import voldemort.serialization.SerializationException; +import voldemort.versioning.Versioned; + +/** + * Voldemort implementation of the key-value style {@link MessageStore} and {@link MessageGroupStore}. + * Implementation note: message identifiers are persisted as {@link String}s. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +@SuppressWarnings("unchecked") +public class VoldemortMessageStore extends AbstractKeyValueMessageStore implements InitializingBean { + /** + * Key under which message store tracks all currently saved message identifiers. + */ + protected final static String MESSAGE_KEY_LIST = "MESSAGE_KEY_LIST"; + + /** + * Key under which message store tracks all currently saved message group identifiers. + */ + protected final static String MESSAGE_GROUP_KEY_LIST = "MESSAGE_GROUP_KEY_LIST"; + + private final static LockRegistry LOCK_REGISTRY = new DefaultLockRegistry(); + + private final StoreClient client; + + public VoldemortMessageStore(StoreClient client) { + this.client = client; + } + + @Override + public void afterPropertiesSet() throws Exception { + // Initialize empty set of message and message group identifiers if needed. + synchronized ( getClass() ) { + if ( client.get( MESSAGE_KEY_LIST ) == null ) { + client.put( MESSAGE_KEY_LIST, new HashSet() ); + } + if ( client.get( MESSAGE_GROUP_KEY_LIST ) == null ) { + client.put( MESSAGE_GROUP_KEY_LIST, new HashSet() ); + } + } + } + + @Override + protected Object doRetrieve(Object id) { + final Versioned version = client.get( id ); + if ( version != null ) { + return version.getValue(); + } + return null; + } + + @Override + protected void doStore(final Object id, final Object objectToStore) { + final Lock messageLock = doLock( id ); + try { + client.put( id, objectToStore ); + // Keeping track of stored message identifiers for efficient implementation of #doListKeys(String) method. + // Voldemort does not provide API to list all keys matching specified pattern. + final String keyListKey = computeKeyListKey( id ); + final Lock keyListLock = doLock( keyListKey ); + try { + client.applyUpdate( new AddKeyUpdateAction( id, keyListKey ) ); + } + finally { + keyListLock.unlock(); + } + } + catch ( SerializationException e ) { + throw new IllegalArgumentException( "Voldemort failed to serialize message with id: " + id + "." ); + } + finally { + messageLock.unlock(); + } + } + + @Override + protected Object doRemove(final Object id) { + final Object message = doRetrieve( id ); + final Lock messageLock = doLock( id ); + try { + client.delete( id ); + // Keeping track of stored message identifiers for efficient implementation of #doListKeys(String) method. + // Voldemort does not provide API to list all keys matching specified pattern. + final String keyListKey = computeKeyListKey( id ); + final Lock keyListLock = doLock( keyListKey ); + try { + client.applyUpdate( new RemoveKeyUpdateAction( id, keyListKey ) ); + } + finally { + keyListLock.unlock(); + } + return message; + } + finally { + messageLock.unlock(); + } + } + + @Override + protected Collection doListKeys(String keyPattern) { + return Collections.unmodifiableSet( (Set) client.get( computeKeyListKey( keyPattern ) ).getValue() ); + } + + /** + * @param id Message or message group identifier. {@link String} type required. + * @return Identifier under which set of all currently persisted message or message group keys is saved. + */ + private String computeKeyListKey(Object id) { + final String key = (String) id; + if ( isMessageGroupKey( key ) ) { + return MESSAGE_GROUP_KEY_LIST; + } + else if ( isMessageKey( key ) ) { + return MESSAGE_KEY_LIST; + } + else { + throw new IllegalArgumentException("Unsupported identifier: " + key + "."); + } + } + + /** + * @param key Message or message group identifier. + * @return {@code true} in case of message identifier, {@code false} otherwise. + */ + private boolean isMessageKey(String key) { + return key.startsWith( AbstractKeyValueMessageStore.MESSAGE_KEY_PREFIX ); + } + + /** + * @param key Message or message group identifier. + * @return {@code true} in case of message group identifier, {@code false} otherwise. + */ + private boolean isMessageGroupKey(String key) { + return key.startsWith( AbstractKeyValueMessageStore.MESSAGE_GROUP_KEY_PREFIX ); + } + + /** + * Acquire JVM wide lock on the given object. + * @param obj Object. + * @return Lock. + */ + private Lock doLock(Object obj) { + final Lock lock = LOCK_REGISTRY.obtain( UUIDConverter.getUUID( obj ).toString() ); + lock.lock(); + return lock; + } + + /** + * Voldemort update action that adds given key to the list of currently saved identifiers. + */ + private static class AddKeyUpdateAction extends UpdateAction { + private final Object id; + private final String keyListKey; + + /** + * The only constructor. + * + * @param id Message or message group identifier. + * @param keyListKey Key under which Voldemort stores set of all currently persisted identifiers. + */ + private AddKeyUpdateAction(Object id, String keyListKey) { + this.id = id; + this.keyListKey = keyListKey; + } + + @Override + public void update(StoreClient storeClient) { + final Set keys = (Set) storeClient.get( keyListKey ).getValue(); + if ( keys.add( id ) ) { + storeClient.put( keyListKey, keys ); + } + } + } + + /** + * Voldemort update action that removes given key from the list of currently saved identifiers. + */ + private static class RemoveKeyUpdateAction extends UpdateAction { + private final Object id; + private final String keyListKey; + + /** + * The only constructor. + * + * @param id Message or message group identifier. + * @param keyListKey Key under which Voldemort stores set of all currently persisted identifiers. + */ + private RemoveKeyUpdateAction(Object id, String keyListKey) { + this.id = id; + this.keyListKey = keyListKey; + } + + @Override + public void update(StoreClient storeClient) { + final Set keys = (Set) storeClient.get( keyListKey ).getValue(); + if ( keys.remove( id ) ) { + storeClient.put( keyListKey, keys ); + } + } + } +} diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/support/PersistMode.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/support/PersistMode.java new file mode 100644 index 0000000..c4583c7 --- /dev/null +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/support/PersistMode.java @@ -0,0 +1,26 @@ +/* + * 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.voldemort.support; + +/** + * Indicates whether records shall be added or removed from Voldemort database. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +public enum PersistMode { + PUT, DELETE +} diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/support/VoldemortHeaders.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/support/VoldemortHeaders.java new file mode 100644 index 0000000..ca92796 --- /dev/null +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/support/VoldemortHeaders.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.voldemort.support; + +/** + * Voldemort adapter specific message headers. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +public abstract class VoldemortHeaders { + private static final String PREFIX = "voldemort_"; + + /** + * Header representing key under which message payload should be stored. + */ + public static final String KEY = PREFIX + "_key"; + + /** + * Overrides default output adapter's persist mode for a given message. + */ + public static final String PERSIST_MODE = PREFIX + "persistMode"; +} diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/support/package-info.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/support/package-info.java new file mode 100755 index 0000000..2cf0e46 --- /dev/null +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/support/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides various support classes used across Spring Integration Voldemort components. + */ +package org.springframework.integration.voldemort.support; diff --git a/spring-integration-voldemort/src/main/resources/META-INF/spring.handlers b/spring-integration-voldemort/src/main/resources/META-INF/spring.handlers new file mode 100755 index 0000000..74e9341 --- /dev/null +++ b/spring-integration-voldemort/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.springframework.org/schema/integration/voldemort=org.springframework.integration.voldemort.config.xml.VoldemortNamespaceHandler diff --git a/spring-integration-voldemort/src/main/resources/META-INF/spring.schemas b/spring-integration-voldemort/src/main/resources/META-INF/spring.schemas new file mode 100755 index 0000000..e2e301d --- /dev/null +++ b/spring-integration-voldemort/src/main/resources/META-INF/spring.schemas @@ -0,0 +1,2 @@ +http\://www.springframework.org/schema/integration/voldemort/spring-integration-voldemort-1.0.xsd=org/springframework/integration/config/xml/spring-integration-voldemort-1.0.xsd +http\://www.springframework.org/schema/integration/voldemort/spring-integration-voldemort.xsd=org/springframework/integration/config/xml/spring-integration-voldemort-1.0.xsd diff --git a/spring-integration-voldemort/src/main/resources/META-INF/spring.tooling b/spring-integration-voldemort/src/main/resources/META-INF/spring.tooling new file mode 100755 index 0000000..c93fd86 --- /dev/null +++ b/spring-integration-voldemort/src/main/resources/META-INF/spring.tooling @@ -0,0 +1,4 @@ +# Tooling related information for the integration Voldemort namespace +http\://www.springframework.org/schema/integration/voldemort@name=integration Voldemort Namespace +http\://www.springframework.org/schema/integration/voldemort@prefix=int-voldemort +http\://www.springframework.org/schema/integration/voldemort@icon=org/springframework/integration/config/xml/spring-integration-voldemort.gif diff --git a/spring-integration-voldemort/src/main/resources/org/springframework/integration/config/xml/spring-integration-voldemort-1.0.xsd b/spring-integration-voldemort/src/main/resources/org/springframework/integration/config/xml/spring-integration-voldemort-1.0.xsd new file mode 100755 index 0000000..9b053db --- /dev/null +++ b/spring-integration-voldemort/src/main/resources/org/springframework/integration/config/xml/spring-integration-voldemort-1.0.xsd @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + Defines an Outbound Channel Adapter that writes messages to + Voldemort key-value store. + + + + + + + + Specifies the key for Voldemort store. If an expression + is needed, use 'store-key-expression' attribute. + Both options are mutually exclusive. + + + + + + + Spring Expression Language statement used to determine + desired entry key in Voldemort store. + Default expression equals 'headers.voldemort_key'. + This attribute is mutually exclusive with 'store-key' attribute. + + + + + + + Defines default persist mode for outbound-channel-adapter. + By default values are added or updated in the underlying + Voldemort database. Hereby behavior can be overridden using + VoldemortHeaders.PERSIST_MODE message header. + + + + + + + + + + Specifies the order for invocation when this endpoint is connected as a + subscriber to SubscribableChannel. + + + + + + + + + + Defines an Inbound Channel Adapter that reads records from Voldemort + key-value store. + + + + + + + + + + + String key that is being searched. + This attribute is mutually exclusive + with 'search-key-expression' attribute. + + + + + + + Spring Expression Language statement that returns + desired entry key. + This attribute is mutually exclusive with 'search-key' attribute. + + + + + + + Indicates whether returned object should be removed + from Voldemort database after polling into Spring + Integration channel. + + + + + + + + + + Flag controlling value extraction from Voldemort's + Versioned object. + + + + + + + + + + + + + Common configuration for Voldemort adapters. + + + + + + Identifies the underlying Spring bean definition. + + + + + + + + Reference to a MessageChannel that will be utilized to + "receive from" or "send to" messages depending on the + adapter type (e.g. inbound or outbound). + + + + + + + + + + + + Native Voldemort store client. + + + + + + + + + + + Flag to indicate that the component should start automatically + on startup (default true). + + + + + + + + + + + + + + + diff --git a/spring-integration-voldemort/src/main/resources/org/springframework/integration/config/xml/spring-integration-voldemort.gif b/spring-integration-voldemort/src/main/resources/org/springframework/integration/config/xml/spring-integration-voldemort.gif new file mode 100755 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-voldemort/src/reference/docbook/history.xml b/spring-integration-voldemort/src/reference/docbook/history.xml new file mode 100755 index 0000000..4e9aaf7 --- /dev/null +++ b/spring-integration-voldemort/src/reference/docbook/history.xml @@ -0,0 +1,28 @@ + + + Change History + + Change History + + + + Release + Date + Changes + + + + + 1.0.0 + 1 March 2013 + + Initial release providing basic capabilities for message store, + inbound and outbound adapters. + + + + +
+
diff --git a/spring-integration-voldemort/src/reference/docbook/images/logo.png b/spring-integration-voldemort/src/reference/docbook/images/logo.png new file mode 100755 index 0000000000000000000000000000000000000000..4f2804f849424ee722de5062adc04a4022539c5d GIT binary patch literal 9627 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 NsOTtHDcVF3{|~1+q%Qyf literal 0 HcmV?d00001 diff --git a/spring-integration-voldemort/src/reference/docbook/index.xml b/spring-integration-voldemort/src/reference/docbook/index.xml new file mode 100755 index 0000000..238b413 --- /dev/null +++ b/spring-integration-voldemort/src/reference/docbook/index.xml @@ -0,0 +1,63 @@ + + + + Spring Integration Voldemort Adapter + Voldemort Adapter ${version} + Spring Integration + ${version} + + + + + + + + + + + + + + + Lukasz Antoniak + + + + © SpringSource Inc., 2013 + + + + + + + What's new? + + + If you are interested in the changes and features, that were introduced in + earlier versions, please take a look at chapter . + + + + + + + Integration Adapters + + + Spring Integration adapter for Voldemort enables users to persist data + transferred through message channel in the destination key-value store, + as well as to poll records from the Voldemort database and process them + inside Spring Integration flow. Hereby extension provides also the + implementation of a message store. + + + + + + Appendices + + + + diff --git a/spring-integration-voldemort/src/reference/docbook/resources.xml b/spring-integration-voldemort/src/reference/docbook/resources.xml new file mode 100755 index 0000000..9369e3a --- /dev/null +++ b/spring-integration-voldemort/src/reference/docbook/resources.xml @@ -0,0 +1,35 @@ + + + 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. + +
+ +
+ Project Voldemort Home + + If you are not familiar with Voldemort distributed key-value store, please visit + Project Voldemort homepage. Hereby site contains + complete design overview, list of available configuration options and documentation of client API. Links + to source code, bug tracker and wiki can be found as well. + +
+ +
+ Voldemort Adapter Integration Tests + + Integration tests of Voldemort adapter are available together with the entire source code on GitHub + (review content of src/test/java and src/test/resources packages). + +
+ +
diff --git a/spring-integration-voldemort/src/reference/docbook/voldemort.xml b/spring-integration-voldemort/src/reference/docbook/voldemort.xml new file mode 100755 index 0000000..167cf27 --- /dev/null +++ b/spring-integration-voldemort/src/reference/docbook/voldemort.xml @@ -0,0 +1,214 @@ + + + Voldemort Adapter + + The Voldemort extension for Spring Integration provides: + + + + + + Outbound Channel Adapter + + + + + + + Inbound Channel Adapter + + + + + + + Message Store + + + + + + + Each one of the components listed above requires to pass an implementation of + voldemort.client.StoreClient as constructor parameter. This way + Voldemort Adapter does not need to introduce all configuration options required to build native store client + and enables developers to choose the concrete implementation of hereby interface. + + + + + + + + + + + + +]]> + +
+ Common Configuration Attributes + + Certain configuration parameters that are shared amongst inbound and + outbound adapters have been described below. + + + + Common Adapter Attributes + + + + + + Attribute Name + Description + + + + + id + + 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. + Optional. + + + + channel + + Reference to a message channel that will be utilized to "receive from" or "send to" + messages depending on the adapter type (e.g. inbound or outbound). + Required. + + + + store-client + + Native Voldemort store client (implementation of + voldemort.client.StoreClient interface). + Required. + + + + auto-startup + + Lifecycle attribute signaling if this component should be started during application + context startup. Defaults to true. Optional. + + + + +
+
+ +
+ Outbound Channel Adapter + + The Voldemort Outbound channel adapter allows to persist data transferred through message channel in + the underlying key-value store. + + <int-voldemort:outbound-channel-adapter id="voldemortOutbound" + channel="voldemortOutboundChannel" + store-client="storeClient" + store-key="constant-key" + store-key-expression="payload.id" + persist-mode="DELETE" + order="1" + auto-startup="true" /> + + + + Specifies the key for Voldemort store. If an expression is needed, use + store-key-expression attribute. Both options are mutually exclusive. + Required. + + + + + Spring Expression Language statement used to determine desired entry key in Voldemort store. + Default expression equals headers.voldemort_key + (see VoldemortHeaders.KEY). This option is mutually exclusive with + store-key attribute. Required. + + + + + Defines default persist mode for outbound channel adapter. Normally values are added + or updated in the Voldemort database. Hereby behavior can be overridden on a message level using + VoldemortHeaders.PERSIST_MODE header (see PersistMode + enumeration). Supported options: PUT or DELETE. + Optional. + + + + + Specifies the order for invocation when this endpoint is connected as a subscriber to + SubscribableChannel. Optional. + + + +
+
+ Inbound Channel Adapter + + An inbound channel adapter allows to poll records from the Voldemort database and process them inside + Spring Integration flow. + + <int-voldemort:inbound-channel-adapter id="voldemortInbound" + channel="voldemortInboundChannel" + store-client="storeClient" + message-converter="messageConverter" + search-key="lukasz" + search-key-expression="@keyProducer.value" + delete-after-poll="true" + extract-value="true" + auto-startup="true" /> + + + + String key that is being searched. This option is mutually exclusive with + search-key-expression attribute. Required. + + + + + Spring Expression Language statement that returns desired entry key. Useful for keys of non string + type. This option is mutually exclusive with search-key attribute. + Required. + + + + + Indicates whether returned object should be removed from Voldemort database after polling into + Spring Integration channel. Defaults to false. Optional. + + + + + Flag controlling value extraction from Voldemort's + voldemort.versioning.Versioned object. + Defaults to true. Optional. + + + +
+
+ Message Store + + Message Store allows to persist Spring Integration messages in Voldemort database. Hereby module provides + VoldemortMessageStore which is an implementation of both the + MessageStore strategy (mainly used by QueueChannel and + ClaimCheck patterns) and the MessageGroupStore strategy + (mainly used by Aggregator and Resequencer patterns). + + + + + +]]> +
+
diff --git a/spring-integration-voldemort/src/reference/docbook/whats-new.xml b/spring-integration-voldemort/src/reference/docbook/whats-new.xml new file mode 100755 index 0000000..eab323f --- /dev/null +++ b/spring-integration-voldemort/src/reference/docbook/whats-new.xml @@ -0,0 +1,35 @@ + + + Changes in 1.0.0 + + This chapter provides an overview of new features and improvements + that have been added to the Voldemort Adapter. + + + + Inbound Channel Adapter allows to poll data from Voldemort key-value store. + Supported features: + + Periodically poll value persisted under given key. + Specify search key directly or as a Spring Expression Language statement. + Automatically remove data from Voldemort store after putting message into + Spring Integration channel. + Control adapter startup time. + + + + Outbound Channel Adapter allows to insert, update and delete data from + Voldemort key-value store. Supported features: + + Define operation type (e.g. put or delete) on adapter and message level. + Order execution when chaining with other adapters. + Control adapter startup time. + + + + Message store allows to persist Spring Integration messages in Voldemort database. + + + diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/BaseFunctionalTestCase.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/BaseFunctionalTestCase.java new file mode 100755 index 0000000..4e30167 --- /dev/null +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/BaseFunctionalTestCase.java @@ -0,0 +1,101 @@ +/* + * 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.voldemort.test; + +import java.io.File; +import java.util.Properties; + +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import voldemort.server.VoldemortConfig; +import voldemort.server.VoldemortServer; + +/** + * Base class for functional test cases. Handles embedded Voldemort server startup and shutdown. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +public abstract class BaseFunctionalTestCase { + protected VoldemortServer server = null; + + /** + * Starts Voldemort embedded server. + */ + @Before + public void setUp() throws Exception { + final VoldemortConfig config = configureServer(); + server = new VoldemortServer( config ); + server.start(); + } + + /** + * Stops Voldemort embedded server. + */ + @After + public void tearDown() throws Exception { + if ( server != null && server.isStarted() ) { + server.stop(); + } + } + + /** + * Review cluster.xml and stores.xml configuration files. + * + * @return Voldemort embedded server configuration. + */ + protected VoldemortConfig configureServer() throws Exception { + final File voldemortHome = new File( System.getProperty( "java.io.tmpdir" ), "voldemort" ); + FileUtils.deleteDirectory( voldemortHome ); + + final Properties properties = new Properties(); + properties.put( "node.id", "0" ); + properties.put( "voldemort.home", voldemortHome.getAbsolutePath() ); + addConfigOptions( properties ); + final VoldemortConfig config = new VoldemortConfig( properties ); + + final File metadata = new File( config.getMetadataDirectory() ); + FileUtils.forceMkdir( metadata ); + + FileUtils.copyFileToDirectory( getClusterConfiguration(), metadata ); + FileUtils.copyFileToDirectory( getStoreConfiguration(), metadata ); + + return config; + } + + /** + * @return Voldemort cluster configuration descriptor. + */ + protected File getClusterConfiguration() { + return new File( "src/test/resources/cluster.xml" ); + } + + /** + * @return Voldemort store configuration descriptor. + */ + protected File getStoreConfiguration() { + return new File( "src/test/resources/stores.xml" ); + } + + /** + * Subclasses may want to setup specific server configuration parameters. + * + * @param properties Voldemort server configuration properties. + */ + protected void addConfigOptions(Properties properties) { + } +} diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/domain/Car.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/domain/Car.java new file mode 100644 index 0000000..0b81119 --- /dev/null +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/domain/Car.java @@ -0,0 +1,123 @@ +/* + * 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.voldemort.test.domain; + +import java.io.Serializable; + +/** + * Sample object with composite key persisted in Voldemort database. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +public class Car implements Serializable { + private static final long serialVersionUID = -891019943116582242L; + + private CarId id; + private String model; + + public Car(CarId id, String model) { + this.id = id; + this.model = model; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( ! ( o instanceof Car ) ) return false; + + Car car = (Car) o; + + if ( id != null ? !id.equals( car.id ) : car.id != null ) return false; + if ( model != null ? !model.equals( car.model ) : car.model != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + ( model != null ? model.hashCode() : 0 ); + return result; + } + + @Override + public String toString() { + return "Car(id = " + id + ", model = " + model + ")"; + } + + public CarId getId() { + return id; + } + + public void setId(CarId id) { + this.id = id; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + /** + * Car identifier wrapper. + * + * @author Lukasz Antoniak + * @since 1.0 + */ + public static class CarId implements Serializable { + private static final long serialVersionUID = -5586075844887213095L; + + private Integer id; + + public CarId(Integer id) { + this.id = id; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( ! ( o instanceof CarId ) ) return false; + + CarId carId = (CarId) o; + + if ( id != null ? !id.equals( carId.id ) : carId.id != null ) return false; + + return true; + } + + @Override + public int hashCode() { + return id != null ? id.hashCode() : 0; + } + + @Override + public String toString() { + return "CarId(id = " + id + ")"; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + } +} diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/domain/Person.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/domain/Person.java new file mode 100755 index 0000000..071279d --- /dev/null +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/domain/Person.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.voldemort.test.domain; + +import java.io.Serializable; + +/** + * Sample object persisted in Voldemort database. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +public class Person implements Serializable { + private static final long serialVersionUID = -9092199331950213292L; + + private String id; + private String firstName; + private String lastName; + + public Person(String id, String firstName, String lastName) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof Person ) ) return false; + + Person person = (Person) o; + + if ( id != null ? !id.equals( person.id ) : person.id != null ) return false; + if ( firstName != null ? !firstName.equals( person.firstName ) : person.firstName != null ) return false; + if ( lastName != null ? !lastName.equals( person.lastName ) : person.lastName != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + ( firstName != null ? firstName.hashCode() : 0 ); + result = 31 * result + ( lastName != null ? lastName.hashCode() : 0 ); + return result; + } + + @Override + public String toString() { + return "Person(id = " + id + ", firstName = " + firstName + ", lastName = " + lastName + ")"; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } +} diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/inbound/ObjectKeyTest.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/inbound/ObjectKeyTest.java new file mode 100644 index 0000000..ad6471e --- /dev/null +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/inbound/ObjectKeyTest.java @@ -0,0 +1,66 @@ +/* + * 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.voldemort.test.inbound; + +import junit.framework.Assert; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.Message; +import org.springframework.integration.core.PollableChannel; +import org.springframework.integration.voldemort.test.BaseFunctionalTestCase; +import org.springframework.integration.voldemort.test.domain.Car; +import voldemort.client.StoreClient; + +/** + * Test key of object type. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +@SuppressWarnings("unchecked") +public class ObjectKeyTest extends BaseFunctionalTestCase { + @Test + public void testReceiveMessageByObjectKey() { + final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "ObjectKeyTest-context.xml", getClass() ); + final StoreClient storeClient = context.getBean( "objectStoreClient", StoreClient.class ); + final PollableChannel inboundChannel = context.getBean( "voldemortInboundChannel", PollableChannel.class ); + + // given + final Car.CarId carId = new Car.CarId( 1 ); + final Car car = new Car( carId, "Ford Mustang" ); + storeClient.put( carId, car ); + + // when + final Message received = (Message) inboundChannel.receive(); + + // then + Assert.assertEquals( car, received.getPayload() ); + + context.close(); + } + + /** + * Produces key of object type. + * + * @author Lukasz Antoniak + * @since 1.0 + */ + public static class ObjectKeyProducer { + public Object getValue() { + return new Car.CarId( 1 ); + } + } +} diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/inbound/VoldemortInboundAdapterTest.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/inbound/VoldemortInboundAdapterTest.java new file mode 100644 index 0000000..e52617e --- /dev/null +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/inbound/VoldemortInboundAdapterTest.java @@ -0,0 +1,101 @@ +/* + * 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.voldemort.test.inbound; + +import junit.framework.Assert; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.Message; +import org.springframework.integration.core.PollableChannel; +import org.springframework.integration.voldemort.test.BaseFunctionalTestCase; +import org.springframework.integration.voldemort.test.domain.Person; +import voldemort.client.StoreClient; +import voldemort.versioning.Versioned; + +/** + * Voldemort basic inbound adapter tests. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +@SuppressWarnings("unchecked") +public class VoldemortInboundAdapterTest extends BaseFunctionalTestCase { + /** + * Tests inbound adapter configured with "search-key" attribute. + */ + @Test + public void testReceiveMessageKey() { + final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortInboundAdapterTest-context.xml", getClass() ); + final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); + final PollableChannel inboundChannel = context.getBean( "voldemortInboundChannel", PollableChannel.class ); + + // given + final Person lukasz = new Person( "lukasz", "Lukasz", "Antoniak" ); + storeClient.put( lukasz.getId(), lukasz ); + + // when + final Message received = (Message) inboundChannel.receive(); + + // then + Assert.assertEquals( lukasz, received.getPayload() ); + + context.close(); + } + + /** + * Tests inbound adapter configured with "search-key-expression" attribute. + */ + @Test + public void testReceiveMessageExpr() { + final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortInboundAdapterTest-context.xml", getClass() ); + final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); + final PollableChannel inboundChannel = context.getBean( "voldemortInboundChannel", PollableChannel.class ); + + // given + final Person kinga = new Person( "kinga", "Kinga", "Mroz" ); + storeClient.put( kinga.getId(), kinga ); + + // when + final Message> received = (Message>) inboundChannel.receive(); + + // then + final Versioned found = storeClient.get( kinga.getId() ); + Assert.assertEquals( found, received.getPayload() ); + + context.close(); + } + + @Test + public void testDeleteAfterPoll() { + final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortInboundAdapterTest-context.xml", getClass() ); + final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); + final PollableChannel inboundChannel = context.getBean( "voldemortInboundChannel", PollableChannel.class ); + + // given + final Person robert = new Person( "robert", "Robert", "Antoniak" ); + storeClient.put( robert.getId(), robert ); + + // when + final Message received = (Message) inboundChannel.receive(); + + // then + Assert.assertEquals( robert, received.getPayload() ); + final Versioned found = storeClient.get( robert.getId() ); + Assert.assertNull( found ); + + context.close(); + } +} diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/outbound/MessageUpdatingServiceActivator.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/outbound/MessageUpdatingServiceActivator.java new file mode 100644 index 0000000..520940c --- /dev/null +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/outbound/MessageUpdatingServiceActivator.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.voldemort.test.outbound; + +import org.springframework.integration.Message; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.voldemort.test.domain.Person; + +/** + * Sample service activator used to verify order of output adapters execution. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +public class MessageUpdatingServiceActivator { + @ServiceActivator + public Message updateMessage(Message message) { + updatePerson( message.getPayload() ); + return message; + } + + protected void updatePerson(Person person) { + person.setFirstName( "Robert" ); + } +} diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/outbound/ObjectKeyTest.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/outbound/ObjectKeyTest.java new file mode 100644 index 0000000..bdec184 --- /dev/null +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/outbound/ObjectKeyTest.java @@ -0,0 +1,58 @@ +/* + * 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.voldemort.test.outbound; + +import junit.framework.Assert; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.voldemort.support.VoldemortHeaders; +import org.springframework.integration.voldemort.test.BaseFunctionalTestCase; +import org.springframework.integration.voldemort.test.domain.Car; +import voldemort.client.StoreClient; +import voldemort.versioning.Versioned; + +/** + * Test key of object type. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +@SuppressWarnings("unchecked") +public class ObjectKeyTest extends BaseFunctionalTestCase { + @Test + public void testCompositeKey() { + final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "ObjectKeyTest-context.xml", getClass() ); + final StoreClient storeClient = context.getBean( "objectStoreClient", StoreClient.class ); + final MessageChannel voldemortOutboundPutChannel = context.getBean( "voldemortOutboundPutChannel", MessageChannel.class ); + + // given + final Car.CarId carId = new Car.CarId( 1 ); + final Car car = new Car( carId, "Ford Mustang" ); + + // when + final Message message = MessageBuilder.withPayload( car ).setHeader( VoldemortHeaders.KEY, carId ).build(); + voldemortOutboundPutChannel.send( message ); + + // then + final Versioned found = storeClient.get( carId ); + Assert.assertEquals( car, found.getValue() ); + + context.close(); + } +} diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/outbound/VoldemortOutboundAdapterTest.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/outbound/VoldemortOutboundAdapterTest.java new file mode 100755 index 0000000..588cba8 --- /dev/null +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/outbound/VoldemortOutboundAdapterTest.java @@ -0,0 +1,177 @@ +/* + * 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.voldemort.test.outbound; + +import junit.framework.Assert; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.MessageDeliveryException; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.voldemort.support.PersistMode; +import org.springframework.integration.voldemort.support.VoldemortHeaders; +import org.springframework.integration.voldemort.test.BaseFunctionalTestCase; +import org.springframework.integration.voldemort.test.domain.Person; +import voldemort.client.StoreClient; +import voldemort.versioning.Versioned; + +/** + * Voldemort basic outbound adapter tests. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +@SuppressWarnings("unchecked") +public class VoldemortOutboundAdapterTest extends BaseFunctionalTestCase { + @Test + public void testPutObject() { + final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); + final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); + final MessageChannel voldemortOutboundPutChannel = context.getBean( "voldemortOutboundPutChannel", MessageChannel.class ); + + // given + final Person lukasz = new Person( "1", "Lukasz", "Antoniak" ); + + // when + final Message message = MessageBuilder.withPayload( lukasz ).setHeader( VoldemortHeaders.KEY, lukasz.getId() ).build(); + voldemortOutboundPutChannel.send( message ); + + // then + final Versioned found = storeClient.get( lukasz.getId() ); + Assert.assertEquals( lukasz, found.getValue() ); + + context.close(); + } + + @Test + public void testPutObjectsConstantKey() { + final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); + final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); + final MessageChannel voldemortOutboundChannel = context.getBean( "voldemortOutboundPutConstantKeyChannel", MessageChannel.class ); + + // given + final Person lukasz = new Person( "1", "Lukasz", "Antoniak" ); + + // when + final Message firstMessage = MessageBuilder.withPayload( lukasz ).build(); + voldemortOutboundChannel.send( firstMessage ); + + // then + Assert.assertEquals( lukasz, storeClient.get( "constant-key" ).getValue() ); + + // given + final Person tomasz = new Person( "2", "Tomasz", "Antoniak" ); + + // when + final Message secondMessage = MessageBuilder.withPayload( tomasz ).build(); + voldemortOutboundChannel.send( secondMessage ); + + // then + Assert.assertEquals( tomasz, storeClient.get( "constant-key" ).getValue() ); + + context.close(); + } + + @Test + public void testDeleteObject() { + final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); + final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); + final MessageChannel voldemortOutboundDeleteChannel = context.getBean( "voldemortOutboundDeleteChannel", MessageChannel.class ); + + // given + final Person lukasz = new Person( "1", "Lukasz", "Antoniak" ); + storeClient.put( lukasz.getId(), lukasz ); + + // when + final Message message = MessageBuilder.withPayload( lukasz ).build(); + voldemortOutboundDeleteChannel.send( message ); + + // then + final Versioned found = storeClient.get( lukasz.getId() ); + Assert.assertNull( found ); + + context.close(); + } + + @Test + public void testOverridePersistMode() { + final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); + final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); + final MessageChannel voldemortOutboundDeleteChannel = context.getBean( "voldemortOutboundDeleteChannel", MessageChannel.class ); + + // given + final Person lukasz = new Person( "1", "Lukasz", "Antoniak" ); + + // when + // Overriding output adapter's persist mode. + final Message message = MessageBuilder.withPayload( lukasz ) + .setHeader( VoldemortHeaders.PERSIST_MODE, PersistMode.PUT ).build(); + voldemortOutboundDeleteChannel.send( message ); + + // then + final Versioned found = storeClient.get( lukasz.getId() ); + Assert.assertEquals( lukasz, found.getValue() ); + + context.close(); + } + + @Test + public void testStoppedAdapter() { + final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); + final MessageChannel voldemortStoppedChannel = context.getBean( "voldemortStoppedChannel", MessageChannel.class ); + + // given + final Person lukasz = new Person( "1", "Lukasz", "Antoniak" ); + + // when + final Message message = MessageBuilder.withPayload( lukasz ).build(); + try { + voldemortStoppedChannel.send( message ); + } + catch ( MessageDeliveryException e ) { + return; + } + finally { + context.close(); + } + + Assert.fail(); + } + + @Test + public void testOrder() { + final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); + final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); + final MessageUpdatingServiceActivator messageUpdater = context.getBean( "messageUpdater", MessageUpdatingServiceActivator.class ); + final MessageChannel voldemortOrderChannel = context.getBean( "voldemortOrderChannel", MessageChannel.class ); + + // given + final Person lukasz = new Person( "lukasz", "Lukasz", "Antoniak" ); + final Person copy = new Person( "lukasz", "Lukasz", "Antoniak" ); + + // when + final Message message = MessageBuilder.withPayload( lukasz ).build(); + voldemortOrderChannel.send( message ); + + // then + messageUpdater.updatePerson( copy ); + final Versioned found = storeClient.get( lukasz.getId() ); + Assert.assertEquals( copy, found.getValue() ); + + context.close(); + } +} diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/BaseStoreFunctionalTestCase.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/BaseStoreFunctionalTestCase.java new file mode 100644 index 0000000..8fc8641 --- /dev/null +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/BaseStoreFunctionalTestCase.java @@ -0,0 +1,44 @@ +package org.springframework.integration.voldemort.test.store; + +import java.io.File; + +import org.junit.After; +import org.junit.Before; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.voldemort.store.VoldemortMessageStore; +import org.springframework.integration.voldemort.test.BaseFunctionalTestCase; + +/** + * Base class for message store test cases. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +public abstract class BaseStoreFunctionalTestCase extends BaseFunctionalTestCase { + protected ClassPathXmlApplicationContext context = null; + protected VoldemortMessageStore store = null; + + @Override + protected File getStoreConfiguration() { + return new File( "src/test/resources/org/springframework/integration/voldemort/test/store/stores.xml" ); + } + + @Before + public void contextSetup() { + context = new ClassPathXmlApplicationContext( "store-test-context.xml", getClass() ); + store = context.getBean( "voldemortMessageStore", VoldemortMessageStore.class ); + } + + @After + public void contextDestroy() { + store = null; + if ( context != null ) { + context.close(); + context = null; + } + } + + protected VoldemortMessageStore createNewStoreClient() { + return context.getBean( "voldemortMessageStorePrototype", VoldemortMessageStore.class ); + } +} diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/VoldemortMessageGroupStoreTest.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/VoldemortMessageGroupStoreTest.java new file mode 100644 index 0000000..40e1f0f --- /dev/null +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/VoldemortMessageGroupStoreTest.java @@ -0,0 +1,316 @@ +/* + * 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.voldemort.test.store; + +import java.util.Iterator; +import java.util.Properties; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.integration.Message; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.history.MessageHistory; +import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.store.MessageGroup; +import org.springframework.integration.store.SimpleMessageGroup; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.voldemort.store.VoldemortMessageStore; + +/** + * Voldemort message store tests based on Redis module. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +@SuppressWarnings("unchecked") +public class VoldemortMessageGroupStoreTest extends BaseStoreFunctionalTestCase { + @Test + public void testNonExistingEmptyMessageGroup() { + final MessageGroup messageGroup = store.getMessageGroup( 1 ); + + Assert.assertNotNull( messageGroup ); + Assert.assertTrue( messageGroup instanceof SimpleMessageGroup ); + Assert.assertEquals( 0, messageGroup.size() ); + } + + @Test + public void testUpdatedDateChangesWithEachAddedMessage() throws InterruptedException { + MessageGroup messageGroup = store.getMessageGroup( 1 ); + Message message = new GenericMessage( "Hello" ); + messageGroup = store.addMessageToGroup( 1, message ); + Assert.assertEquals( 1, messageGroup.size() ); + + long createdTimestamp = messageGroup.getTimestamp(); + long updatedTimestamp = messageGroup.getLastModified(); + Assert.assertEquals( createdTimestamp, updatedTimestamp ); + Thread.sleep( 1000 ); + + message = new GenericMessage( "Hello" ); + messageGroup = store.addMessageToGroup( 1, message ); + createdTimestamp = messageGroup.getTimestamp(); + updatedTimestamp = messageGroup.getLastModified(); + Assert.assertTrue( updatedTimestamp > createdTimestamp ); + + // use another message store instance + VoldemortMessageStore newStore = createNewStoreClient(); + + messageGroup = newStore.getMessageGroup( 1 ); + Assert.assertEquals( 2, messageGroup.size() ); + } + + @Test + public void testMessageGroupAddOperation() { + MessageGroup messageGroup = store.getMessageGroup( 1 ); + Message message = new GenericMessage( "Hello" ); + messageGroup = store.addMessageToGroup( 1, message ); + Assert.assertEquals( 1, messageGroup.size() ); + + // use another message store instance + VoldemortMessageStore newStore = createNewStoreClient(); + + messageGroup = newStore.getMessageGroup( 1 ); + Assert.assertEquals( 1, messageGroup.size() ); + } + + @Test + public void testMessageGroupWithAddedMessageUUIDGroupIdAndUUIDHeader() { + final Object id = UUID.randomUUID(); + MessageGroup messageGroup = store.getMessageGroup( id ); + final UUID uuidA = UUID.randomUUID(); + Message messageA = MessageBuilder.withPayload( "A" ).setHeader( "foo", uuidA ).build(); + final UUID uuidB = UUID.randomUUID(); + Message messageB = MessageBuilder.withPayload( "B" ).setHeader( "foo", uuidB ).build(); + store.addMessageToGroup( id, messageA ); + messageGroup = store.addMessageToGroup( id, messageB ); + Assert.assertEquals( 2, messageGroup.size() ); + Message retrievedMessage = store.getMessage( messageA.getHeaders().getId() ); + Assert.assertNotNull( retrievedMessage ); + Assert.assertEquals( retrievedMessage.getHeaders().getId(), messageA.getHeaders().getId() ); + Object fooHeader = retrievedMessage.getHeaders().get( "foo" ); + Assert.assertTrue( fooHeader instanceof UUID ); + Assert.assertEquals( uuidA, fooHeader ); + } + + @Test + public void testCountMessagesInGroup() { + Message messageA = new GenericMessage( "A" ); + Message messageB = new GenericMessage( "B" ); + store.addMessageToGroup( 1, messageA ); + store.addMessageToGroup( 1, messageB ); + Assert.assertEquals( 2, store.messageGroupSize( 1 ) ); + } + + @Test + public void testRemoveMessageGroup() { + MessageGroup messageGroup = store.getMessageGroup( 1 ); + Message message = new GenericMessage( "Hello" ); + messageGroup = store.addMessageToGroup( messageGroup.getGroupId(), message ); + Assert.assertEquals( 1, messageGroup.size() ); + + store.removeMessageGroup( 1 ); + + MessageGroup messageGroupA = store.getMessageGroup( 1 ); + Assert.assertNotSame( messageGroup, messageGroupA ); + Assert.assertEquals( 0, messageGroupA.getMessages().size() ); + Assert.assertEquals( 0, messageGroupA.size() ); + + // use another message store instance + VoldemortMessageStore newStore = createNewStoreClient(); + + messageGroup = newStore.getMessageGroup( 1 ); + + Assert.assertEquals( 0, messageGroup.getMessages().size() ); + Assert.assertEquals( 0, messageGroup.size() ); + } + + @Test + public void testCompleteMessageGroup() { + MessageGroup messageGroup = store.getMessageGroup( 1 ); + Message message = new GenericMessage( "Hello" ); + messageGroup = store.addMessageToGroup( messageGroup.getGroupId(), message ); + store.completeGroup( messageGroup.getGroupId() ); + messageGroup = store.getMessageGroup( 1 ); + Assert.assertTrue( messageGroup.isComplete() ); + } + + @Test + public void testLastReleasedSequenceNumber() { + MessageGroup messageGroup = store.getMessageGroup( 1 ); + Message message = new GenericMessage( "Hello" ); + messageGroup = store.addMessageToGroup( messageGroup.getGroupId(), message ); + store.setLastReleasedSequenceNumberForGroup( messageGroup.getGroupId(), 5 ); + messageGroup = store.getMessageGroup( 1 ); + Assert.assertEquals( 5, messageGroup.getLastReleasedMessageSequenceNumber() ); + } + + @Test + public void testRemoveMessageFromTheGroup() { + MessageGroup messageGroup = store.getMessageGroup( 1 ); + Message message = new GenericMessage( "2" ); + store.addMessageToGroup( messageGroup.getGroupId(), new GenericMessage( "1" ) ); + store.addMessageToGroup( messageGroup.getGroupId(), message ); + messageGroup = store.addMessageToGroup( messageGroup.getGroupId(), new GenericMessage( "3" ) ); + Assert.assertEquals( 3, messageGroup.size() ); + + messageGroup = store.removeMessageFromGroup( 1, message ); + Assert.assertEquals( 2, messageGroup.size() ); + + // use another message store instance + VoldemortMessageStore newStore = createNewStoreClient(); + + messageGroup = newStore.getMessageGroup( 1 ); + Assert.assertEquals( 2, messageGroup.size() ); + } + + @Test + public void testWithMessageHistory() { + MessageGroup messageGroup = store.getMessageGroup( 1 ); + Message message = new GenericMessage( "Hello" ); + DirectChannel fooChannel = new DirectChannel(); + fooChannel.setBeanName( "fooChannel" ); + DirectChannel barChannel = new DirectChannel(); + barChannel.setBeanName( "barChannel" ); + + message = MessageHistory.write( message, fooChannel ); + message = MessageHistory.write( message, barChannel ); + store.addMessageToGroup( 1, message ); + message = store.getMessageGroup( 1 ).getMessages().iterator().next(); + MessageHistory messageHistory = MessageHistory.read( message ); + + Assert.assertNotNull( messageHistory ); + Assert.assertEquals( 2, messageHistory.size() ); + + Properties fooChannelHistory = messageHistory.get( 0 ); + + Assert.assertEquals( "fooChannel", fooChannelHistory.get( "name" ) ); + Assert.assertEquals( "channel", fooChannelHistory.get( "type" ) ); + } + + @Test + public void testRemoveNonExistingMessageFromGroup() { + MessageGroup messageGroup = store.getMessageGroup( 1 ); + store.addMessageToGroup( messageGroup.getGroupId(), new GenericMessage( "1" ) ); + store.removeMessageFromGroup( messageGroup.getGroupId(), new GenericMessage( "2" ) ); + } + + @Test + public void testRemoveNonExistingMessageFromNonExistingGroup() { + store.removeMessageFromGroup( 1, new GenericMessage( "2" ) ); + } + + @Test + public void testMultipleInstancesOfGroupStore() { + VoldemortMessageStore store1 = createNewStoreClient(); + VoldemortMessageStore store2 = createNewStoreClient(); + + Message message = new GenericMessage( "1" ); + store1.addMessageToGroup( 1, message ); + MessageGroup messageGroup = store2.addMessageToGroup( 1, new GenericMessage( "2" ) ); + Assert.assertEquals( 2, messageGroup.getMessages().size() ); + + VoldemortMessageStore store3 = createNewStoreClient(); + messageGroup = store3.removeMessageFromGroup( 1, message ); + Assert.assertEquals( 1, messageGroup.getMessages().size() ); + } + + @Test + public void testIteratorOfMessageGroups() { + VoldemortMessageStore store1 = createNewStoreClient(); + VoldemortMessageStore store2 = createNewStoreClient(); + + store1.addMessageToGroup( 1, new GenericMessage( "1" ) ); + store2.addMessageToGroup( 2, new GenericMessage( "2" ) ); + store1.addMessageToGroup( 3, new GenericMessage( "3" ) ); + store2.addMessageToGroup( 3, new GenericMessage( "3A" ) ); + + Iterator messageGroups = store1.iterator(); + int counter = 0; + while ( messageGroups.hasNext() ) { + final MessageGroup group = messageGroups.next(); + final String groupId = (String) group.getGroupId(); + if ( "1".equals( groupId ) ) { + Assert.assertEquals( 1, group.getMessages().size() ); + } + else if ( "2".equals( groupId ) ) { + Assert.assertEquals( 1, group.getMessages().size() ); + } + else if ( "3".equals( groupId ) ) { + Assert.assertEquals( 2, group.getMessages().size() ); + } + ++counter; + } + Assert.assertEquals( 3, counter ); + + store2.removeMessageGroup( 3 ); + + messageGroups = store1.iterator(); + counter = 0; + while ( messageGroups.hasNext() ) { + messageGroups.next(); + ++counter; + } + Assert.assertEquals( 2, counter ); + } + + @Test + public void testConcurrentModifications() throws InterruptedException { + final VoldemortMessageStore store1 = createNewStoreClient(); + final VoldemortMessageStore store2 = createNewStoreClient(); + + final ExecutorService executor = Executors.newCachedThreadPool(); + final Counter errorCounter = new Counter(); + final Random randomGenerator = new Random(); + for ( int i = 0; i < 10; ++i ) { + executor.execute( new Runnable() { + public void run() { + try { + final Message message = new GenericMessage( UUID.randomUUID() ); + MessageGroup group = store1.addMessageToGroup( 1, message ); + + Thread.sleep( randomGenerator.nextInt( 100 ) ); + + group = store2.removeMessageFromGroup( 1, message ); + } + catch ( Exception e ) { + errorCounter.increment(); + } + } + }); + } + executor.shutdown(); + executor.awaitTermination( 10, TimeUnit.SECONDS ); + + Assert.assertEquals( 0, errorCounter.getValue() ); + Assert.assertEquals( 0, store1.getMessageCount() ); + } + + private static class Counter { + private int value = 0; + + public synchronized int increment() { + return ++value; + } + + public int getValue() { + return value; + } + } +} diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreAggregationTest.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreAggregationTest.java new file mode 100644 index 0000000..c38016e --- /dev/null +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreAggregationTest.java @@ -0,0 +1,70 @@ +/* + * 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.voldemort.test.store; + +import java.io.File; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.voldemort.test.BaseFunctionalTestCase; + +/** + * Voldemort message store tests based on Redis module. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +@SuppressWarnings("unchecked") +public class VoldemortMessageStoreAggregationTest extends BaseFunctionalTestCase { + @Override + protected File getStoreConfiguration() { + return new File( "src/test/resources/org/springframework/integration/voldemort/test/store/stores.xml" ); + } + + @Test + public void testAggregatorWithShutdown() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortMessageStoreAggregationTest-context.xml", getClass() ); + MessageChannel input = context.getBean( "inputChannel", MessageChannel.class ); + QueueChannel output = context.getBean( "outputChannel", QueueChannel.class ); + + Message message1 = MessageBuilder.withPayload( "1" ) + .setSequenceNumber( 1 ).setSequenceSize( 3 ).setCorrelationId( 1 ).build(); + Message message2 = MessageBuilder.withPayload( "2" ) + .setSequenceNumber( 2 ).setSequenceSize( 3 ).setCorrelationId( 1 ).build(); + input.send( message1 ); + Assert.assertNull( output.receive( 1000 ) ); + input.send( message2 ); + Assert.assertNull( output.receive( 1000 ) ); + + context.close(); + + context = new ClassPathXmlApplicationContext( "VoldemortMessageStoreAggregationTest-context.xml", getClass() ); + input = context.getBean( "inputChannel", MessageChannel.class ); + output = context.getBean( "outputChannel", QueueChannel.class ); + + Message message3 = MessageBuilder.withPayload( "3" ) + .setSequenceNumber( 3 ).setSequenceSize( 3 ).setCorrelationId( 1 ).build(); + input.send( message3 ); + Assert.assertNotNull( output.receive( 1000 ) ); + + context.close(); + } +} diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreTest.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreTest.java new file mode 100644 index 0000000..415e604 --- /dev/null +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreTest.java @@ -0,0 +1,187 @@ +/* + * 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.voldemort.test.store; + +import java.io.Serializable; +import java.util.Properties; +import java.util.UUID; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.integration.Message; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.history.MessageHistory; +import org.springframework.integration.message.GenericMessage; + +/** + * Voldemort message store tests based on Redis module. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +@SuppressWarnings("unchecked") +public class VoldemortMessageStoreTest extends BaseStoreFunctionalTestCase { + @Test + public void testGetNonExistingMessage() { + final Message message = store.getMessage( UUID.randomUUID() ); + + Assert.assertNull( message ); + } + + @Test + public void testGetMessageCountWhenEmpty() { + Assert.assertEquals( 0, store.getMessageCount() ); + } + + @Test + public void testAddStringMessage() { + final Message stringMessage = new GenericMessage( "Hello Voldemort" ); + + final Message storedMessage = store.addMessage( stringMessage ); + + Assert.assertNotSame( stringMessage, storedMessage ); + Assert.assertEquals( stringMessage.getPayload(), storedMessage.getPayload() ); + } + + @Test + public void testAddSerializableObjectMessage() { + final Address address = new Address( "1600 Pennsylvania Av, Washington, DC" ); + final Person person = new Person( "Barack Obama", address ); + Message objectMessage = new GenericMessage( person ); + + Message storedMessage = store.addMessage( objectMessage ); + + Assert.assertNotSame( objectMessage, storedMessage ); + Assert.assertEquals( person.getName(), storedMessage.getPayload().getName() ); + Assert.assertEquals( person.getAddress().getAddress(), storedMessage.getPayload().getAddress().getAddress() ); + } + + @Test(expected = IllegalArgumentException.class) + public void testAddNonSerializableObjectMessage() { + Message objectMessage = new GenericMessage( new Foo() ); + store.addMessage( objectMessage ); + } + + @Test + public void testAddAndGetStringMessage() { + final Message stringMessage = new GenericMessage( "Hello Voldemort" ); + + store.addMessage( stringMessage ); + final Message retrievedMessage = (Message) store.getMessage( stringMessage.getHeaders().getId() ); + + Assert.assertNotNull( retrievedMessage ); + Assert.assertEquals( stringMessage.getPayload(), retrievedMessage.getPayload() ); + } + + @Test + public void testAddAndRemoveStringMessage() { + final Message stringMessage = new GenericMessage( "Hello Voldemort" ); + + store.addMessage(stringMessage); + Message retrievedMessage = (Message) store.removeMessage( stringMessage.getHeaders().getId() ); + + Assert.assertNotNull( retrievedMessage ); + Assert.assertEquals( stringMessage.getPayload(), retrievedMessage.getPayload() ); + Assert.assertNull( store.getMessage( stringMessage.getHeaders().getId() ) ); + } + + @Test + public void testMessageCount() { + final Message stringMessage1 = new GenericMessage( "Hello Voldemort" ); + final Message stringMessage2 = new GenericMessage( "Hello World" ); + + store.addMessage( stringMessage1 ); + Assert.assertEquals( 1, store.getMessageCount() ); + + store.addMessage( stringMessage2 ); + Assert.assertEquals( 2, store.getMessageCount() ); + + store.removeMessage( stringMessage1.getHeaders().getId() ); + Assert.assertEquals( 1, store.getMessageCount() ); + } + + @Test + public void testWithMessageHistory() { + Message message = new GenericMessage( "Hello" ); + final DirectChannel fooChannel = new DirectChannel(); + fooChannel.setBeanName( "fooChannel" ); + final DirectChannel barChannel = new DirectChannel(); + barChannel.setBeanName( "barChannel" ); + + message = MessageHistory.write( message, fooChannel ); + message = MessageHistory.write( message, barChannel ); + store.addMessage( message ); + message = store.getMessage( message.getHeaders().getId() ); + MessageHistory messageHistory = MessageHistory.read( message ); + + Assert.assertNotNull( messageHistory ); + Assert.assertEquals( 2, messageHistory.size() ); + + Properties fooChannelHistory = messageHistory.get( 0 ); + + Assert.assertEquals( "fooChannel", fooChannelHistory.get( "name" ) ); + Assert.assertEquals( "channel", fooChannelHistory.get( "type" ) ); + } + + public static class Person implements Serializable { + private static final long serialVersionUID = 6109955909562732898L; + + private String name; + private Address address; + + public Person(String name, Address address) { + this.name = name; + this.address = address; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public static class Address implements Serializable { + private static final long serialVersionUID = 2382619388682259472L; + + private String address; + + public Address(String address) { + this.address = address; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + } + + public static class Foo { + } +} diff --git a/spring-integration-voldemort/src/test/resources/cluster.xml b/spring-integration-voldemort/src/test/resources/cluster.xml new file mode 100755 index 0000000..69f4a64 --- /dev/null +++ b/spring-integration-voldemort/src/test/resources/cluster.xml @@ -0,0 +1,12 @@ + + + test-cluster + + 0 + localhost + 8080 + 6666 + 6667 + 0, 1 + + \ No newline at end of file diff --git a/spring-integration-voldemort/src/test/resources/log4j.properties b/spring-integration-voldemort/src/test/resources/log4j.properties new file mode 100755 index 0000000..4130143 --- /dev/null +++ b/spring-integration-voldemort/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.voldemort=INFO diff --git a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/common-test-context.xml b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/common-test-context.xml new file mode 100755 index 0000000..2995ee6 --- /dev/null +++ b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/common-test-context.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/inbound/ObjectKeyTest-context.xml b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/inbound/ObjectKeyTest-context.xml new file mode 100644 index 0000000..bda59a3 --- /dev/null +++ b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/inbound/ObjectKeyTest-context.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/inbound/VoldemortInboundAdapterTest-context.xml b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/inbound/VoldemortInboundAdapterTest-context.xml new file mode 100644 index 0000000..f5ac708 --- /dev/null +++ b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/inbound/VoldemortInboundAdapterTest-context.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/outbound/ObjectKeyTest-context.xml b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/outbound/ObjectKeyTest-context.xml new file mode 100644 index 0000000..a0e7eb6 --- /dev/null +++ b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/outbound/ObjectKeyTest-context.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/outbound/VoldemortOutboundAdapterTest-context.xml b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/outbound/VoldemortOutboundAdapterTest-context.xml new file mode 100755 index 0000000..1c0b337 --- /dev/null +++ b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/outbound/VoldemortOutboundAdapterTest-context.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreAggregationTest-context.xml b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreAggregationTest-context.xml new file mode 100644 index 0000000..c861068 --- /dev/null +++ b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreAggregationTest-context.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/store-test-context.xml b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/store-test-context.xml new file mode 100644 index 0000000..83f1ba9 --- /dev/null +++ b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/store-test-context.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/stores.xml b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/stores.xml new file mode 100644 index 0000000..afa6fbf --- /dev/null +++ b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/stores.xml @@ -0,0 +1,19 @@ + + + + test-message-store + lukasz.antoniak@gmail.com + bdb + client + 1 + 1 + 1 + + string + UTF-8 + + + java-serialization + + + \ No newline at end of file diff --git a/spring-integration-voldemort/src/test/resources/stores.xml b/spring-integration-voldemort/src/test/resources/stores.xml new file mode 100755 index 0000000..013c15f --- /dev/null +++ b/spring-integration-voldemort/src/test/resources/stores.xml @@ -0,0 +1,34 @@ + + + + test-store + lukasz.antoniak@gmail.com + bdb + client + 1 + 1 + 1 + + string + UTF-8 + + + java-serialization + + + + test-object-store + lukasz.antoniak@gmail.com + bdb + client + 1 + 1 + 1 + + java-serialization + + + java-serialization + + + \ No newline at end of file From 28759a6116a002277b2d6a9bb63d605bd1b49eec Mon Sep 17 00:00:00 2001 From: Gunnar Hillert Date: Tue, 2 Apr 2013 16:14:23 -0400 Subject: [PATCH 2/3] INTEXT-36 - Cleanup * Convert spaces to tabs * Upgrade to Gradle 1.5 * Change Group Id to 'org.springframework.integration' * Add Sonar Runner plugin * Reduce Sonar warnings * Ensure ASL license header is present in all source files * Fix XSD documentation not showing up in STS --- spring-integration-voldemort/build.gradle | 7 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 39770 -> 46742 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- spring-integration-voldemort/gradlew | 8 +- spring-integration-voldemort/gradlew.bat | 180 ++++----- ...VoldemortOutboundChannelAdapterParser.java | 130 +++---- .../config/xml/VoldemortParserUtils.java | 7 +- .../inbound/VoldemortMessageSource.java | 10 +- .../VoldemortStoringMessageHandler.java | 224 +++++------ .../store/VoldemortMessageStore.java | 12 +- .../voldemort/support/VoldemortHeaders.java | 2 +- .../xml/spring-integration-voldemort-1.0.xsd | 363 +++++++++--------- .../test/BaseFunctionalTestCase.java | 202 +++++----- .../voldemort/test/domain/Car.java | 2 +- .../voldemort/test/domain/Person.java | 178 ++++----- .../VoldemortOutboundAdapterTest.java | 354 ++++++++--------- .../store/BaseStoreFunctionalTestCase.java | 15 + .../VoldemortMessageStoreAggregationTest.java | 1 - .../src/test/resources/cluster.xml | 24 +- .../voldemort/test/common-test-context.xml | 33 +- .../test/inbound/ObjectKeyTest-context.xml | 39 +- .../VoldemortInboundAdapterTest-context.xml | 49 +-- .../test/outbound/ObjectKeyTest-context.xml | 29 +- .../VoldemortOutboundAdapterTest-context.xml | 85 ++-- ...ortMessageStoreAggregationTest-context.xml | 21 +- .../test/store/store-test-context.xml | 39 +- .../voldemort/test/store/stores.xml | 34 +- .../src/test/resources/stores.xml | 68 ++-- 28 files changed, 1079 insertions(+), 1041 deletions(-) diff --git a/spring-integration-voldemort/build.gradle b/spring-integration-voldemort/build.gradle index 61618f8..adc7080 100755 --- a/spring-integration-voldemort/build.gradle +++ b/spring-integration-voldemort/build.gradle @@ -1,4 +1,4 @@ -description = 'Spring Integration Voldemort Adapter' +description = 'Spring Integration Voldemort Support' buildscript { repositories { @@ -14,7 +14,7 @@ apply from: "${rootProject.projectDir}/publish-maven.gradle" apply plugin: 'eclipse' apply plugin: 'idea' -group = 'org.springframework.integration.voldemort' +group = 'org.springframework.integration' repositories { maven { url 'http://repo.springsource.org/libs-milestone' } @@ -100,6 +100,7 @@ reference { } apply plugin: 'sonar' +apply plugin: 'sonar-runner' sonar { @@ -262,5 +263,5 @@ task dist(dependsOn: assemble) { task wrapper(type: Wrapper) { description = 'Generates gradlew[.bat] scripts' - gradleVersion = '1.3' + gradleVersion = '1.5' } diff --git a/spring-integration-voldemort/gradle/wrapper/gradle-wrapper.jar b/spring-integration-voldemort/gradle/wrapper/gradle-wrapper.jar index 81dcde627d5cfbc12c96dc5113aac79b66a15118..faa569a9a0eedc9ff37450fed24a7efd77a86729 100755 GIT binary patch delta 41889 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 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-voldemort/gradlew.bat b/spring-integration-voldemort/gradlew.bat index 8a0b282..aec9973 100755 --- a/spring-integration-voldemort/gradlew.bat +++ b/spring-integration-voldemort/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-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortOutboundChannelAdapterParser.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortOutboundChannelAdapterParser.java index 3ee51aa..b3a3df4 100755 --- a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortOutboundChannelAdapterParser.java +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortOutboundChannelAdapterParser.java @@ -1,65 +1,65 @@ -/* - * 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.voldemort.config.xml; - -import org.springframework.beans.factory.config.TypedStringValue; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.ExpressionFactoryBean; -import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; -import org.springframework.integration.voldemort.outbound.VoldemortStoringMessageHandler; -import org.w3c.dom.Element; - -/** - * Parses Voldemort outbound adapter XML definition. - * - * @author Lukasz Antoniak - * @since 1.0 - */ -public class VoldemortOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { - /** - * Produces "int-voldemort:outbound-channel-adapter" bean definition. - *

- * {@inheritDoc} - */ - @Override - protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { - final BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition( VoldemortStoringMessageHandler.class ); - VoldemortParserUtils.processCommonAttributes( element, builder ); - final boolean hasKey = element.hasAttribute( VoldemortParserUtils.STORE_KEY ); - final boolean hasKeyExpression = element.hasAttribute( VoldemortParserUtils.STORE_KEY_EXPRESSION ); - if ( hasKey && hasKeyExpression ) { - parserContext.getReaderContext().error( "At most one of '" + VoldemortParserUtils.STORE_KEY - + "' or '" + VoldemortParserUtils.STORE_KEY_EXPRESSION + "' is allowed.", element ); - } - if ( hasKey ) { - builder.addPropertyValue( - VoldemortParserUtils.KEY_PROPERTY, - new TypedStringValue( element.getAttribute( VoldemortParserUtils.STORE_KEY ) ) - ); - } - if ( hasKeyExpression ) { - RootBeanDefinition expressionDef = new RootBeanDefinition( ExpressionFactoryBean.class ); - expressionDef.getConstructorArgumentValues().addGenericArgumentValue( element.getAttribute( VoldemortParserUtils.STORE_KEY_EXPRESSION ) ); - builder.addPropertyValue( VoldemortParserUtils.KEY_EXPRESSION_PROPERTY, expressionDef ); - } - IntegrationNamespaceUtils.setValueIfAttributeDefined( builder, element, VoldemortParserUtils.PERSIST_MODE ); - return builder.getBeanDefinition(); - } -} +/* + * 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.voldemort.config.xml; + +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.ExpressionFactoryBean; +import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.voldemort.outbound.VoldemortStoringMessageHandler; +import org.w3c.dom.Element; + +/** + * Parses Voldemort outbound adapter XML definition. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +public class VoldemortOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { + /** + * Produces "int-voldemort:outbound-channel-adapter" bean definition. + *

+ * {@inheritDoc} + */ + @Override + protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { + final BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition( VoldemortStoringMessageHandler.class ); + VoldemortParserUtils.processCommonAttributes( element, builder ); + final boolean hasKey = element.hasAttribute( VoldemortParserUtils.STORE_KEY ); + final boolean hasKeyExpression = element.hasAttribute( VoldemortParserUtils.STORE_KEY_EXPRESSION ); + if ( hasKey && hasKeyExpression ) { + parserContext.getReaderContext().error( "At most one of '" + VoldemortParserUtils.STORE_KEY + + "' or '" + VoldemortParserUtils.STORE_KEY_EXPRESSION + "' is allowed.", element ); + } + if ( hasKey ) { + builder.addPropertyValue( + VoldemortParserUtils.KEY_PROPERTY, + new TypedStringValue( element.getAttribute( VoldemortParserUtils.STORE_KEY ) ) + ); + } + if ( hasKeyExpression ) { + RootBeanDefinition expressionDef = new RootBeanDefinition( ExpressionFactoryBean.class ); + expressionDef.getConstructorArgumentValues().addGenericArgumentValue( element.getAttribute( VoldemortParserUtils.STORE_KEY_EXPRESSION ) ); + builder.addPropertyValue( VoldemortParserUtils.KEY_EXPRESSION_PROPERTY, expressionDef ); + } + IntegrationNamespaceUtils.setValueIfAttributeDefined( builder, element, VoldemortParserUtils.PERSIST_MODE ); + return builder.getBeanDefinition(); + } +} diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortParserUtils.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortParserUtils.java index 1762115..d416b0e 100755 --- a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortParserUtils.java +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/config/xml/VoldemortParserUtils.java @@ -24,7 +24,7 @@ import org.w3c.dom.Element; * @author Lukasz Antoniak * @since 1.0 */ -abstract class VoldemortParserUtils { +public final class VoldemortParserUtils { static final String STORE_CLIENT = "store-client"; static final String PERSIST_MODE = "persist-mode"; static final String SEARCH_KEY = "search-key"; @@ -37,6 +37,11 @@ abstract class VoldemortParserUtils { static final String KEY_PROPERTY = "key"; static final String KEY_EXPRESSION_PROPERTY = "keyExpression"; + /** Prevent instantiation. */ + private VoldemortParserUtils() { + throw new AssertionError(); + } + /** * Handles "store-client" and "message-converter" attributes. */ diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/inbound/VoldemortMessageSource.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/inbound/VoldemortMessageSource.java index d6524e3..2c046c5 100644 --- a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/inbound/VoldemortMessageSource.java +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/inbound/VoldemortMessageSource.java @@ -32,7 +32,9 @@ import voldemort.versioning.Versioned; * @author Lukasz Antoniak * @since 1.0 */ -public class VoldemortMessageSource extends IntegrationObjectSupport implements MessageSource { +public class VoldemortMessageSource extends IntegrationObjectSupport + implements MessageSource { + private final StoreClient client; /** @@ -65,15 +67,15 @@ public class VoldemortMessageSource extends IntegrationObjectSupport implements @Override @SuppressWarnings("unchecked") - public Message receive() { + public Message receive() { final Object key = keyExpression.getValue( evaluationContext, Object.class ); - final Versioned value = client.get( key ); + final Versioned value = client.get( key ); if ( value != null ) { if ( deleteAfterPoll ) { client.delete( key ); } return MessageBuilder.withPayload( extractValue ? value.getValue() : value ) - .setHeader( VoldemortHeaders.KEY, key ).build(); + .setHeader(VoldemortHeaders.KEY, key ).build(); } return null; } diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/outbound/VoldemortStoringMessageHandler.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/outbound/VoldemortStoringMessageHandler.java index 8704746..edf38e7 100755 --- a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/outbound/VoldemortStoringMessageHandler.java +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/outbound/VoldemortStoringMessageHandler.java @@ -1,112 +1,112 @@ -/* - * 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.voldemort.outbound; - -import org.springframework.expression.Expression; -import org.springframework.expression.common.LiteralExpression; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.integration.Message; -import org.springframework.integration.expression.ExpressionUtils; -import org.springframework.integration.handler.AbstractMessageHandler; -import org.springframework.integration.voldemort.support.PersistMode; -import org.springframework.integration.voldemort.support.VoldemortHeaders; -import voldemort.client.StoreClient; - -/** - * Voldemort outbound adapter implementation. - * - * @author Lukasz Antoniak - * @since 1.0 - */ -public class VoldemortStoringMessageHandler extends AbstractMessageHandler { - private final StoreClient client; - - private volatile StandardEvaluationContext evaluationContext; - private volatile Expression keyExpression = new SpelExpressionParser().parseExpression( "headers." + VoldemortHeaders.KEY ); - - private volatile PersistMode persistMode = PersistMode.PUT; - - /** - * Creates new message sender. - * - * @param client Voldemort store client. - */ - public VoldemortStoringMessageHandler(StoreClient client) { - this.client = client; - } - - @Override - protected void onInit() throws Exception { - super.onInit(); - if ( getBeanFactory() != null ) { - evaluationContext = ExpressionUtils.createStandardEvaluationContext( getBeanFactory() ); - } - else { - evaluationContext = ExpressionUtils.createStandardEvaluationContext(); - } - } - - @Override - @SuppressWarnings("unchecked") - protected void handleMessageInternal(Message message) throws Exception { - final Object key = keyExpression.getValue( evaluationContext, message, Object.class ); - switch ( determinePersistMode( message ) ) { - case PUT: - client.put( key, message.getPayload() ); - break; - case DELETE: - client.delete( key ); - break; - } - } - - /** - * Computes desired persist mode for a given message. Default output adapter's configuration - * can be overridden with {@link VoldemortHeaders#PERSIST_MODE} message header which supports - * direct or text representation of {@link PersistMode} enumeration. - * - * @param message Spring Integration message. - * @return Persist mode. - */ - private PersistMode determinePersistMode(Message message) { - final Object confValue = message.getHeaders().get( VoldemortHeaders.PERSIST_MODE ); - if ( confValue instanceof PersistMode ) { - return (PersistMode) confValue; - } - else if ( confValue instanceof String ) { - return PersistMode.valueOf( (String) confValue ); - } - return persistMode; - } - - @Override - public String getComponentType() { - return "voldemort:outbound-channel-adapter"; - } - - public void setKey(String key) { - setKeyExpression( new LiteralExpression( key ) ); - } - - public void setKeyExpression(Expression keyExpression) { - this.keyExpression = keyExpression; - } - - public void setPersistMode(PersistMode persistMode) { - this.persistMode = persistMode; - } -} +/* + * 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.voldemort.outbound; + +import org.springframework.expression.Expression; +import org.springframework.expression.common.LiteralExpression; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.integration.Message; +import org.springframework.integration.expression.ExpressionUtils; +import org.springframework.integration.handler.AbstractMessageHandler; +import org.springframework.integration.voldemort.support.PersistMode; +import org.springframework.integration.voldemort.support.VoldemortHeaders; +import voldemort.client.StoreClient; + +/** + * Voldemort outbound adapter implementation. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +public class VoldemortStoringMessageHandler extends AbstractMessageHandler { + private final StoreClient client; + + private volatile StandardEvaluationContext evaluationContext; + private volatile Expression keyExpression = new SpelExpressionParser().parseExpression( "headers." + VoldemortHeaders.KEY ); + + private volatile PersistMode persistMode = PersistMode.PUT; + + /** + * Creates new message sender. + * + * @param client Voldemort store client. + */ + public VoldemortStoringMessageHandler(StoreClient client) { + this.client = client; + } + + @Override + protected void onInit() throws Exception { + super.onInit(); + if ( getBeanFactory() != null ) { + evaluationContext = ExpressionUtils.createStandardEvaluationContext( getBeanFactory() ); + } + else { + evaluationContext = ExpressionUtils.createStandardEvaluationContext(); + } + } + + @Override + @SuppressWarnings("unchecked") + protected void handleMessageInternal(Message message) throws Exception { + final Object key = keyExpression.getValue( evaluationContext, message, Object.class ); + switch ( determinePersistMode( message ) ) { + case PUT: + client.put( key, message.getPayload() ); + break; + case DELETE: + client.delete( key ); + break; + } + } + + /** + * Computes desired persist mode for a given message. Default output adapter's configuration + * can be overridden with {@link VoldemortHeaders#PERSIST_MODE} message header which supports + * direct or text representation of {@link PersistMode} enumeration. + * + * @param message Spring Integration message. + * @return Persist mode. + */ + private PersistMode determinePersistMode(Message message) { + final Object confValue = message.getHeaders().get( VoldemortHeaders.PERSIST_MODE ); + if ( confValue instanceof PersistMode ) { + return (PersistMode) confValue; + } + else if ( confValue instanceof String ) { + return PersistMode.valueOf( (String) confValue ); + } + return persistMode; + } + + @Override + public String getComponentType() { + return "voldemort:outbound-channel-adapter"; + } + + public void setKey(String key) { + setKeyExpression( new LiteralExpression( key ) ); + } + + public void setKeyExpression(Expression keyExpression) { + this.keyExpression = keyExpression; + } + + public void setPersistMode(PersistMode persistMode) { + this.persistMode = persistMode; + } +} diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/store/VoldemortMessageStore.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/store/VoldemortMessageStore.java index 6caf424..c141b06 100644 --- a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/store/VoldemortMessageStore.java +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/store/VoldemortMessageStore.java @@ -45,14 +45,14 @@ public class VoldemortMessageStore extends AbstractKeyValueMessageStore implemen /** * Key under which message store tracks all currently saved message identifiers. */ - protected final static String MESSAGE_KEY_LIST = "MESSAGE_KEY_LIST"; + protected static final String MESSAGE_KEY_LIST = "MESSAGE_KEY_LIST"; /** * Key under which message store tracks all currently saved message group identifiers. */ - protected final static String MESSAGE_GROUP_KEY_LIST = "MESSAGE_GROUP_KEY_LIST"; + protected static final String MESSAGE_GROUP_KEY_LIST = "MESSAGE_GROUP_KEY_LIST"; - private final static LockRegistry LOCK_REGISTRY = new DefaultLockRegistry(); + private static final LockRegistry LOCK_REGISTRY = new DefaultLockRegistry(); private final StoreClient client; @@ -99,7 +99,7 @@ public class VoldemortMessageStore extends AbstractKeyValueMessageStore implemen } } catch ( SerializationException e ) { - throw new IllegalArgumentException( "Voldemort failed to serialize message with id: " + id + "." ); + throw new IllegalArgumentException( "Voldemort failed to serialize message with id: " + id + ".", e ); } finally { messageLock.unlock(); @@ -181,7 +181,7 @@ public class VoldemortMessageStore extends AbstractKeyValueMessageStore implemen /** * Voldemort update action that adds given key to the list of currently saved identifiers. */ - private static class AddKeyUpdateAction extends UpdateAction { + private static final class AddKeyUpdateAction extends UpdateAction { private final Object id; private final String keyListKey; @@ -208,7 +208,7 @@ public class VoldemortMessageStore extends AbstractKeyValueMessageStore implemen /** * Voldemort update action that removes given key from the list of currently saved identifiers. */ - private static class RemoveKeyUpdateAction extends UpdateAction { + private static final class RemoveKeyUpdateAction extends UpdateAction { private final Object id; private final String keyListKey; diff --git a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/support/VoldemortHeaders.java b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/support/VoldemortHeaders.java index ca92796..f973f20 100644 --- a/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/support/VoldemortHeaders.java +++ b/spring-integration-voldemort/src/main/java/org/springframework/integration/voldemort/support/VoldemortHeaders.java @@ -27,7 +27,7 @@ public abstract class VoldemortHeaders { /** * Header representing key under which message payload should be stored. */ - public static final String KEY = PREFIX + "_key"; + public static final String KEY = PREFIX + "key"; /** * Overrides default output adapter's persist mode for a given message. diff --git a/spring-integration-voldemort/src/main/resources/org/springframework/integration/config/xml/spring-integration-voldemort-1.0.xsd b/spring-integration-voldemort/src/main/resources/org/springframework/integration/config/xml/spring-integration-voldemort-1.0.xsd index 9b053db..0d329f0 100755 --- a/spring-integration-voldemort/src/main/resources/org/springframework/integration/config/xml/spring-integration-voldemort-1.0.xsd +++ b/spring-integration-voldemort/src/main/resources/org/springframework/integration/config/xml/spring-integration-voldemort-1.0.xsd @@ -1,187 +1,196 @@ + xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans" + xmlns:tool="http://www.springframework.org/schema/tool" + xmlns:integration="http://www.springframework.org/schema/integration" + targetNamespace="http://www.springframework.org/schema/integration/voldemort" + elementFormDefault="qualified" attributeFormDefault="unqualified"> - - - + + + - - + - + - - - - Defines an Outbound Channel Adapter that writes messages to - Voldemort key-value store. - - - - - - - - Specifies the key for Voldemort store. If an expression - is needed, use 'store-key-expression' attribute. - Both options are mutually exclusive. - - - - - - - Spring Expression Language statement used to determine - desired entry key in Voldemort store. - Default expression equals 'headers.voldemort_key'. - This attribute is mutually exclusive with 'store-key' attribute. - - - - - - - Defines default persist mode for outbound-channel-adapter. - By default values are added or updated in the underlying - Voldemort database. Hereby behavior can be overridden using - VoldemortHeaders.PERSIST_MODE message header. - - - - - - - - - - Specifies the order for invocation when this endpoint is connected as a - subscriber to SubscribableChannel. - - - - - + + + + Defines an Outbound Channel Adapter that writes messages to + Voldemort key-value store. + + + + + + + + Specifies the key for the Voldemort store. If an expression + is needed, use the 'store-key-expression' attribute. + Both options are mutually exclusive. + + + + + + + Spring Expression Language statement used to determine + the desired entry key into the Voldemort store. + Default expression equals 'headers.voldemort_key'. + This attribute is mutually exclusive with the 'store-key' + attribute. + + + + + + + Defines the default persist mode for the outbound-channel-adapter. + Available modes are: - - - - Defines an Inbound Channel Adapter that reads records from Voldemort - key-value store. - - - - - - - - - - - String key that is being searched. - This attribute is mutually exclusive - with 'search-key-expression' attribute. - - - - - - - Spring Expression Language statement that returns - desired entry key. - This attribute is mutually exclusive with 'search-key' attribute. - - - - - - - Indicates whether returned object should be removed - from Voldemort database after polling into Spring - Integration channel. - - - - - - - - - - Flag controlling value extraction from Voldemort's - Versioned object. - - - - - - - - + - PUT + - DELETE - - - - Common configuration for Voldemort adapters. - - - - - - Identifies the underlying Spring bean definition. - - - - - - - - Reference to a MessageChannel that will be utilized to - "receive from" or "send to" messages depending on the - adapter type (e.g. inbound or outbound). - - - - - - - - - - - - Native Voldemort store client. - - - - - - - - - - - Flag to indicate that the component should start automatically - on startup (default true). - - - - - - - + By default values are added or updated in the underlying + Voldemort database (PUT). The specified behavior can be + overridden using the VoldemortHeaders.PERSIST_MODE message header. + + + + + + + + + + Specifies the order for invocation when this endpoint is + connected as a subscriber to SubscribableChannel. + + + + + - - - - - - + + + + Defines an Inbound Channel Adapter that reads records from + the Voldemort key-value store. + + + + + + + + + + + String key that is being searched for. + This attribute is mutually exclusive + with the 'search-key-expression' attribute. + + + + + + + Spring Expression Language (SpEL) statement that + returns the desired entry key. + + This attribute is mutually exclusive with the + 'search-key' attribute. + + + + + + + Indicates whether returned objects should be removed + from the Voldemort database after polling. Defaults to + 'false'. + + + + + + + + + + Flag controlling the value extraction from Voldemort's + Versioned object. Defaults to 'true'. + + + + + + + + + + + + + Common configuration for Voldemort adapters. + + + + + + Identifies the underlying Spring bean definition. + + + + + + + Reference to a MessageChannel that will be utilized to + "receive from" or "send to" messages depending on the + adapter type (e.g. inbound or outbound). + + + + + + + + + + + + Native Voldemort store client. Needs to be an implementation + of 'voldemort.client.StoreClient'. + + + + + + + + + + + + Flag indicating that the component should start automatically + on startup (default is 'true'). + + + + + + + + + + + + + + diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/BaseFunctionalTestCase.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/BaseFunctionalTestCase.java index 4e30167..15b573e 100755 --- a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/BaseFunctionalTestCase.java +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/BaseFunctionalTestCase.java @@ -1,101 +1,101 @@ -/* - * 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.voldemort.test; - -import java.io.File; -import java.util.Properties; - -import org.apache.commons.io.FileUtils; -import org.junit.After; -import org.junit.Before; -import voldemort.server.VoldemortConfig; -import voldemort.server.VoldemortServer; - -/** - * Base class for functional test cases. Handles embedded Voldemort server startup and shutdown. - * - * @author Lukasz Antoniak - * @since 1.0 - */ -public abstract class BaseFunctionalTestCase { - protected VoldemortServer server = null; - - /** - * Starts Voldemort embedded server. - */ - @Before - public void setUp() throws Exception { - final VoldemortConfig config = configureServer(); - server = new VoldemortServer( config ); - server.start(); - } - - /** - * Stops Voldemort embedded server. - */ - @After - public void tearDown() throws Exception { - if ( server != null && server.isStarted() ) { - server.stop(); - } - } - - /** - * Review cluster.xml and stores.xml configuration files. - * - * @return Voldemort embedded server configuration. - */ - protected VoldemortConfig configureServer() throws Exception { - final File voldemortHome = new File( System.getProperty( "java.io.tmpdir" ), "voldemort" ); - FileUtils.deleteDirectory( voldemortHome ); - - final Properties properties = new Properties(); - properties.put( "node.id", "0" ); - properties.put( "voldemort.home", voldemortHome.getAbsolutePath() ); - addConfigOptions( properties ); - final VoldemortConfig config = new VoldemortConfig( properties ); - - final File metadata = new File( config.getMetadataDirectory() ); - FileUtils.forceMkdir( metadata ); - - FileUtils.copyFileToDirectory( getClusterConfiguration(), metadata ); - FileUtils.copyFileToDirectory( getStoreConfiguration(), metadata ); - - return config; - } - - /** - * @return Voldemort cluster configuration descriptor. - */ - protected File getClusterConfiguration() { - return new File( "src/test/resources/cluster.xml" ); - } - - /** - * @return Voldemort store configuration descriptor. - */ - protected File getStoreConfiguration() { - return new File( "src/test/resources/stores.xml" ); - } - - /** - * Subclasses may want to setup specific server configuration parameters. - * - * @param properties Voldemort server configuration properties. - */ - protected void addConfigOptions(Properties properties) { - } -} +/* + * 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.voldemort.test; + +import java.io.File; +import java.util.Properties; + +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import voldemort.server.VoldemortConfig; +import voldemort.server.VoldemortServer; + +/** + * Base class for functional test cases. Handles embedded Voldemort server startup and shutdown. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +public abstract class BaseFunctionalTestCase { + protected VoldemortServer server = null; + + /** + * Starts Voldemort embedded server. + */ + @Before + public void setUp() throws Exception { + final VoldemortConfig config = configureServer(); + server = new VoldemortServer( config ); + server.start(); + } + + /** + * Stops Voldemort embedded server. + */ + @After + public void tearDown() throws Exception { + if ( server != null && server.isStarted() ) { + server.stop(); + } + } + + /** + * Review cluster.xml and stores.xml configuration files. + * + * @return Voldemort embedded server configuration. + */ + protected VoldemortConfig configureServer() throws Exception { + final File voldemortHome = new File( System.getProperty( "java.io.tmpdir" ), "voldemort" ); + FileUtils.deleteDirectory( voldemortHome ); + + final Properties properties = new Properties(); + properties.put( "node.id", "0" ); + properties.put( "voldemort.home", voldemortHome.getAbsolutePath() ); + addConfigOptions( properties ); + final VoldemortConfig config = new VoldemortConfig( properties ); + + final File metadata = new File( config.getMetadataDirectory() ); + FileUtils.forceMkdir( metadata ); + + FileUtils.copyFileToDirectory( getClusterConfiguration(), metadata ); + FileUtils.copyFileToDirectory( getStoreConfiguration(), metadata ); + + return config; + } + + /** + * @return Voldemort cluster configuration descriptor. + */ + protected File getClusterConfiguration() { + return new File( "src/test/resources/cluster.xml" ); + } + + /** + * @return Voldemort store configuration descriptor. + */ + protected File getStoreConfiguration() { + return new File( "src/test/resources/stores.xml" ); + } + + /** + * Subclasses may want to setup specific server configuration parameters. + * + * @param properties Voldemort server configuration properties. + */ + protected void addConfigOptions(Properties properties) { + } +} diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/domain/Car.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/domain/Car.java index 0b81119..e5fd953 100644 --- a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/domain/Car.java +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/domain/Car.java @@ -18,7 +18,7 @@ package org.springframework.integration.voldemort.test.domain; import java.io.Serializable; /** - * Sample object with composite key persisted in Voldemort database. + * Sample object with composite key persisted to Voldemort database. * * @author Lukasz Antoniak * @since 1.0 diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/domain/Person.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/domain/Person.java index 071279d..6b9227a 100755 --- a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/domain/Person.java +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/domain/Person.java @@ -1,89 +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.voldemort.test.domain; - -import java.io.Serializable; - -/** - * Sample object persisted in Voldemort database. - * - * @author Lukasz Antoniak - * @since 1.0 - */ -public class Person implements Serializable { - private static final long serialVersionUID = -9092199331950213292L; - - private String id; - private String firstName; - private String lastName; - - public Person(String id, String firstName, String lastName) { - this.id = id; - this.firstName = firstName; - this.lastName = lastName; - } - - @Override - public boolean equals(Object o) { - if ( this == o ) return true; - if ( !( o instanceof Person ) ) return false; - - Person person = (Person) o; - - if ( id != null ? !id.equals( person.id ) : person.id != null ) return false; - if ( firstName != null ? !firstName.equals( person.firstName ) : person.firstName != null ) return false; - if ( lastName != null ? !lastName.equals( person.lastName ) : person.lastName != null ) return false; - - return true; - } - - @Override - public int hashCode() { - int result = id != null ? id.hashCode() : 0; - result = 31 * result + ( firstName != null ? firstName.hashCode() : 0 ); - result = 31 * result + ( lastName != null ? lastName.hashCode() : 0 ); - return result; - } - - @Override - public String toString() { - return "Person(id = " + id + ", firstName = " + firstName + ", lastName = " + lastName + ")"; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } -} +/* + * 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.voldemort.test.domain; + +import java.io.Serializable; + +/** + * Sample object persisted in Voldemort database. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +public class Person implements Serializable { + private static final long serialVersionUID = -9092199331950213292L; + + private String id; + private String firstName; + private String lastName; + + public Person(String id, String firstName, String lastName) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof Person ) ) return false; + + Person person = (Person) o; + + if ( id != null ? !id.equals( person.id ) : person.id != null ) return false; + if ( firstName != null ? !firstName.equals( person.firstName ) : person.firstName != null ) return false; + if ( lastName != null ? !lastName.equals( person.lastName ) : person.lastName != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + ( firstName != null ? firstName.hashCode() : 0 ); + result = 31 * result + ( lastName != null ? lastName.hashCode() : 0 ); + return result; + } + + @Override + public String toString() { + return "Person(id = " + id + ", firstName = " + firstName + ", lastName = " + lastName + ")"; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } +} diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/outbound/VoldemortOutboundAdapterTest.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/outbound/VoldemortOutboundAdapterTest.java index 588cba8..1275863 100755 --- a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/outbound/VoldemortOutboundAdapterTest.java +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/outbound/VoldemortOutboundAdapterTest.java @@ -1,177 +1,177 @@ -/* - * 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.voldemort.test.outbound; - -import junit.framework.Assert; -import org.junit.Test; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.integration.Message; -import org.springframework.integration.MessageChannel; -import org.springframework.integration.MessageDeliveryException; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.voldemort.support.PersistMode; -import org.springframework.integration.voldemort.support.VoldemortHeaders; -import org.springframework.integration.voldemort.test.BaseFunctionalTestCase; -import org.springframework.integration.voldemort.test.domain.Person; -import voldemort.client.StoreClient; -import voldemort.versioning.Versioned; - -/** - * Voldemort basic outbound adapter tests. - * - * @author Lukasz Antoniak - * @since 1.0 - */ -@SuppressWarnings("unchecked") -public class VoldemortOutboundAdapterTest extends BaseFunctionalTestCase { - @Test - public void testPutObject() { - final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); - final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); - final MessageChannel voldemortOutboundPutChannel = context.getBean( "voldemortOutboundPutChannel", MessageChannel.class ); - - // given - final Person lukasz = new Person( "1", "Lukasz", "Antoniak" ); - - // when - final Message message = MessageBuilder.withPayload( lukasz ).setHeader( VoldemortHeaders.KEY, lukasz.getId() ).build(); - voldemortOutboundPutChannel.send( message ); - - // then - final Versioned found = storeClient.get( lukasz.getId() ); - Assert.assertEquals( lukasz, found.getValue() ); - - context.close(); - } - - @Test - public void testPutObjectsConstantKey() { - final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); - final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); - final MessageChannel voldemortOutboundChannel = context.getBean( "voldemortOutboundPutConstantKeyChannel", MessageChannel.class ); - - // given - final Person lukasz = new Person( "1", "Lukasz", "Antoniak" ); - - // when - final Message firstMessage = MessageBuilder.withPayload( lukasz ).build(); - voldemortOutboundChannel.send( firstMessage ); - - // then - Assert.assertEquals( lukasz, storeClient.get( "constant-key" ).getValue() ); - - // given - final Person tomasz = new Person( "2", "Tomasz", "Antoniak" ); - - // when - final Message secondMessage = MessageBuilder.withPayload( tomasz ).build(); - voldemortOutboundChannel.send( secondMessage ); - - // then - Assert.assertEquals( tomasz, storeClient.get( "constant-key" ).getValue() ); - - context.close(); - } - - @Test - public void testDeleteObject() { - final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); - final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); - final MessageChannel voldemortOutboundDeleteChannel = context.getBean( "voldemortOutboundDeleteChannel", MessageChannel.class ); - - // given - final Person lukasz = new Person( "1", "Lukasz", "Antoniak" ); - storeClient.put( lukasz.getId(), lukasz ); - - // when - final Message message = MessageBuilder.withPayload( lukasz ).build(); - voldemortOutboundDeleteChannel.send( message ); - - // then - final Versioned found = storeClient.get( lukasz.getId() ); - Assert.assertNull( found ); - - context.close(); - } - - @Test - public void testOverridePersistMode() { - final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); - final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); - final MessageChannel voldemortOutboundDeleteChannel = context.getBean( "voldemortOutboundDeleteChannel", MessageChannel.class ); - - // given - final Person lukasz = new Person( "1", "Lukasz", "Antoniak" ); - - // when - // Overriding output adapter's persist mode. - final Message message = MessageBuilder.withPayload( lukasz ) - .setHeader( VoldemortHeaders.PERSIST_MODE, PersistMode.PUT ).build(); - voldemortOutboundDeleteChannel.send( message ); - - // then - final Versioned found = storeClient.get( lukasz.getId() ); - Assert.assertEquals( lukasz, found.getValue() ); - - context.close(); - } - - @Test - public void testStoppedAdapter() { - final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); - final MessageChannel voldemortStoppedChannel = context.getBean( "voldemortStoppedChannel", MessageChannel.class ); - - // given - final Person lukasz = new Person( "1", "Lukasz", "Antoniak" ); - - // when - final Message message = MessageBuilder.withPayload( lukasz ).build(); - try { - voldemortStoppedChannel.send( message ); - } - catch ( MessageDeliveryException e ) { - return; - } - finally { - context.close(); - } - - Assert.fail(); - } - - @Test - public void testOrder() { - final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); - final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); - final MessageUpdatingServiceActivator messageUpdater = context.getBean( "messageUpdater", MessageUpdatingServiceActivator.class ); - final MessageChannel voldemortOrderChannel = context.getBean( "voldemortOrderChannel", MessageChannel.class ); - - // given - final Person lukasz = new Person( "lukasz", "Lukasz", "Antoniak" ); - final Person copy = new Person( "lukasz", "Lukasz", "Antoniak" ); - - // when - final Message message = MessageBuilder.withPayload( lukasz ).build(); - voldemortOrderChannel.send( message ); - - // then - messageUpdater.updatePerson( copy ); - final Versioned found = storeClient.get( lukasz.getId() ); - Assert.assertEquals( copy, found.getValue() ); - - context.close(); - } -} +/* + * 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.voldemort.test.outbound; + +import junit.framework.Assert; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.MessageDeliveryException; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.voldemort.support.PersistMode; +import org.springframework.integration.voldemort.support.VoldemortHeaders; +import org.springframework.integration.voldemort.test.BaseFunctionalTestCase; +import org.springframework.integration.voldemort.test.domain.Person; +import voldemort.client.StoreClient; +import voldemort.versioning.Versioned; + +/** + * Voldemort basic outbound adapter tests. + * + * @author Lukasz Antoniak + * @since 1.0 + */ +@SuppressWarnings("unchecked") +public class VoldemortOutboundAdapterTest extends BaseFunctionalTestCase { + @Test + public void testPutObject() { + final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); + final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); + final MessageChannel voldemortOutboundPutChannel = context.getBean( "voldemortOutboundPutChannel", MessageChannel.class ); + + // given + final Person lukasz = new Person( "1", "Lukasz", "Antoniak" ); + + // when + final Message message = MessageBuilder.withPayload( lukasz ).setHeader( VoldemortHeaders.KEY, lukasz.getId() ).build(); + voldemortOutboundPutChannel.send( message ); + + // then + final Versioned found = storeClient.get( lukasz.getId() ); + Assert.assertEquals( lukasz, found.getValue() ); + + context.close(); + } + + @Test + public void testPutObjectsConstantKey() { + final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); + final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); + final MessageChannel voldemortOutboundChannel = context.getBean( "voldemortOutboundPutConstantKeyChannel", MessageChannel.class ); + + // given + final Person lukasz = new Person( "1", "Lukasz", "Antoniak" ); + + // when + final Message firstMessage = MessageBuilder.withPayload( lukasz ).build(); + voldemortOutboundChannel.send( firstMessage ); + + // then + Assert.assertEquals( lukasz, storeClient.get( "constant-key" ).getValue() ); + + // given + final Person tomasz = new Person( "2", "Tomasz", "Antoniak" ); + + // when + final Message secondMessage = MessageBuilder.withPayload( tomasz ).build(); + voldemortOutboundChannel.send( secondMessage ); + + // then + Assert.assertEquals( tomasz, storeClient.get( "constant-key" ).getValue() ); + + context.close(); + } + + @Test + public void testDeleteObject() { + final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); + final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); + final MessageChannel voldemortOutboundDeleteChannel = context.getBean( "voldemortOutboundDeleteChannel", MessageChannel.class ); + + // given + final Person lukasz = new Person( "1", "Lukasz", "Antoniak" ); + storeClient.put( lukasz.getId(), lukasz ); + + // when + final Message message = MessageBuilder.withPayload( lukasz ).build(); + voldemortOutboundDeleteChannel.send( message ); + + // then + final Versioned found = storeClient.get( lukasz.getId() ); + Assert.assertNull( found ); + + context.close(); + } + + @Test + public void testOverridePersistMode() { + final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); + final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); + final MessageChannel voldemortOutboundDeleteChannel = context.getBean( "voldemortOutboundDeleteChannel", MessageChannel.class ); + + // given + final Person lukasz = new Person( "1", "Lukasz", "Antoniak" ); + + // when + // Overriding output adapter's persist mode. + final Message message = MessageBuilder.withPayload( lukasz ) + .setHeader( VoldemortHeaders.PERSIST_MODE, PersistMode.PUT ).build(); + voldemortOutboundDeleteChannel.send( message ); + + // then + final Versioned found = storeClient.get( lukasz.getId() ); + Assert.assertEquals( lukasz, found.getValue() ); + + context.close(); + } + + @Test + public void testStoppedAdapter() { + final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); + final MessageChannel voldemortStoppedChannel = context.getBean( "voldemortStoppedChannel", MessageChannel.class ); + + // given + final Person lukasz = new Person( "1", "Lukasz", "Antoniak" ); + + // when + final Message message = MessageBuilder.withPayload( lukasz ).build(); + try { + voldemortStoppedChannel.send( message ); + } + catch ( MessageDeliveryException e ) { + return; + } + finally { + context.close(); + } + + Assert.fail(); + } + + @Test + public void testOrder() { + final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "VoldemortOutboundAdapterTest-context.xml", getClass() ); + final StoreClient storeClient = context.getBean( "storeClient", StoreClient.class ); + final MessageUpdatingServiceActivator messageUpdater = context.getBean( "messageUpdater", MessageUpdatingServiceActivator.class ); + final MessageChannel voldemortOrderChannel = context.getBean( "voldemortOrderChannel", MessageChannel.class ); + + // given + final Person lukasz = new Person( "lukasz", "Lukasz", "Antoniak" ); + final Person copy = new Person( "lukasz", "Lukasz", "Antoniak" ); + + // when + final Message message = MessageBuilder.withPayload( lukasz ).build(); + voldemortOrderChannel.send( message ); + + // then + messageUpdater.updatePerson( copy ); + final Versioned found = storeClient.get( lukasz.getId() ); + Assert.assertEquals( copy, found.getValue() ); + + context.close(); + } +} diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/BaseStoreFunctionalTestCase.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/BaseStoreFunctionalTestCase.java index 8fc8641..8843123 100644 --- a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/BaseStoreFunctionalTestCase.java +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/BaseStoreFunctionalTestCase.java @@ -1,3 +1,18 @@ +/* + * 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.voldemort.test.store; import java.io.File; diff --git a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreAggregationTest.java b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreAggregationTest.java index c38016e..4bb8bd1 100644 --- a/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreAggregationTest.java +++ b/spring-integration-voldemort/src/test/java/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreAggregationTest.java @@ -32,7 +32,6 @@ import org.springframework.integration.voldemort.test.BaseFunctionalTestCase; * @author Lukasz Antoniak * @since 1.0 */ -@SuppressWarnings("unchecked") public class VoldemortMessageStoreAggregationTest extends BaseFunctionalTestCase { @Override protected File getStoreConfiguration() { diff --git a/spring-integration-voldemort/src/test/resources/cluster.xml b/spring-integration-voldemort/src/test/resources/cluster.xml index 69f4a64..eb4c3eb 100755 --- a/spring-integration-voldemort/src/test/resources/cluster.xml +++ b/spring-integration-voldemort/src/test/resources/cluster.xml @@ -1,12 +1,12 @@ - - - test-cluster - - 0 - localhost - 8080 - 6666 - 6667 - 0, 1 - - \ No newline at end of file + + + test-cluster + + 0 + localhost + 8080 + 6666 + 6667 + 0, 1 + + diff --git a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/common-test-context.xml b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/common-test-context.xml index 2995ee6..7d7d056 100755 --- a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/common-test-context.xml +++ b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/common-test-context.xml @@ -1,16 +1,17 @@ - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + diff --git a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/inbound/ObjectKeyTest-context.xml b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/inbound/ObjectKeyTest-context.xml index bda59a3..17361ab 100644 --- a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/inbound/ObjectKeyTest-context.xml +++ b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/inbound/ObjectKeyTest-context.xml @@ -1,25 +1,26 @@ - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:int="http://www.springframework.org/schema/integration" + xmlns:int-voldemort="http://www.springframework.org/schema/integration/voldemort" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd + http://www.springframework.org/schema/integration/voldemort http://www.springframework.org/schema/integration/voldemort/spring-integration-voldemort.xsd"> - - - + - - - + + + - + + + - - - - \ No newline at end of file + + + + + + diff --git a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/inbound/VoldemortInboundAdapterTest-context.xml b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/inbound/VoldemortInboundAdapterTest-context.xml index f5ac708..cc84a4c 100644 --- a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/inbound/VoldemortInboundAdapterTest-context.xml +++ b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/inbound/VoldemortInboundAdapterTest-context.xml @@ -1,30 +1,31 @@ - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:int="http://www.springframework.org/schema/integration" + xmlns:int-voldemort="http://www.springframework.org/schema/integration/voldemort" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd + http://www.springframework.org/schema/integration/voldemort http://www.springframework.org/schema/integration/voldemort/spring-integration-voldemort.xsd"> - - - + - - - + + + - - - + + + - - - - \ No newline at end of file + + + + + + + + diff --git a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/outbound/ObjectKeyTest-context.xml b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/outbound/ObjectKeyTest-context.xml index a0e7eb6..ac6020e 100644 --- a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/outbound/ObjectKeyTest-context.xml +++ b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/outbound/ObjectKeyTest-context.xml @@ -1,19 +1,20 @@ - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:int="http://www.springframework.org/schema/integration" + xmlns:int-voldemort="http://www.springframework.org/schema/integration/voldemort" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd + http://www.springframework.org/schema/integration/voldemort http://www.springframework.org/schema/integration/voldemort/spring-integration-voldemort.xsd"> - - - + - + + + - - \ No newline at end of file + + + + diff --git a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/outbound/VoldemortOutboundAdapterTest-context.xml b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/outbound/VoldemortOutboundAdapterTest-context.xml index 1c0b337..55524fa 100755 --- a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/outbound/VoldemortOutboundAdapterTest-context.xml +++ b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/outbound/VoldemortOutboundAdapterTest-context.xml @@ -1,42 +1,43 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreAggregationTest-context.xml b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreAggregationTest-context.xml index c861068..9598434 100644 --- a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreAggregationTest-context.xml +++ b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/VoldemortMessageStoreAggregationTest-context.xml @@ -1,14 +1,15 @@ - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:int="http://www.springframework.org/schema/integration" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd"> - + - - - - \ No newline at end of file + + + + + + diff --git a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/store-test-context.xml b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/store-test-context.xml index 83f1ba9..b595472 100644 --- a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/store-test-context.xml +++ b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/store-test-context.xml @@ -1,25 +1,26 @@ - - - + xsi:schemaLocation=" + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> - - - + + + - - - + + + - - - + + + - - - - \ No newline at end of file + + + + + + + + diff --git a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/stores.xml b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/stores.xml index afa6fbf..90b80ec 100644 --- a/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/stores.xml +++ b/spring-integration-voldemort/src/test/resources/org/springframework/integration/voldemort/test/store/stores.xml @@ -1,19 +1,19 @@ - - test-message-store - lukasz.antoniak@gmail.com - bdb - client - 1 - 1 - 1 - - string - UTF-8 - - - java-serialization - - - \ No newline at end of file + + test-message-store + lukasz.antoniak@gmail.com + bdb + client + 1 + 1 + 1 + + string + UTF-8 + + + java-serialization + + + diff --git a/spring-integration-voldemort/src/test/resources/stores.xml b/spring-integration-voldemort/src/test/resources/stores.xml index 013c15f..404ead9 100755 --- a/spring-integration-voldemort/src/test/resources/stores.xml +++ b/spring-integration-voldemort/src/test/resources/stores.xml @@ -1,34 +1,34 @@ - - - - test-store - lukasz.antoniak@gmail.com - bdb - client - 1 - 1 - 1 - - string - UTF-8 - - - java-serialization - - - - test-object-store - lukasz.antoniak@gmail.com - bdb - client - 1 - 1 - 1 - - java-serialization - - - java-serialization - - - \ No newline at end of file + + + + test-store + lukasz.antoniak@gmail.com + bdb + client + 1 + 1 + 1 + + string + UTF-8 + + + java-serialization + + + + test-object-store + lukasz.antoniak@gmail.com + bdb + client + 1 + 1 + 1 + + java-serialization + + + java-serialization + + + From 14995ec2f94f85139de0f106c929f6466a340d3b Mon Sep 17 00:00:00 2001 From: Gunnar Hillert Date: Tue, 2 Apr 2013 16:24:59 -0400 Subject: [PATCH 3/3] INTEXT-61 - Add Voldemort Sample --- samples/voldemort/README.md | 63 +++++++++++ samples/voldemort/pom.xml | 99 ++++++++++++++++ .../integration/samples/voldemort/Main.java | 107 ++++++++++++++++++ .../voldemort/service/BusinessService.java | 30 +++++ .../spring-integration-context.xml | 44 +++++++ .../voldemort/src/main/resources/log4j.xml | 28 +++++ 6 files changed, 371 insertions(+) create mode 100644 samples/voldemort/README.md create mode 100644 samples/voldemort/pom.xml create mode 100644 samples/voldemort/src/main/java/org/springframework/integration/samples/voldemort/Main.java create mode 100644 samples/voldemort/src/main/java/org/springframework/integration/samples/voldemort/service/BusinessService.java create mode 100644 samples/voldemort/src/main/resources/META-INF/spring/integration/spring-integration-context.xml create mode 100644 samples/voldemort/src/main/resources/log4j.xml diff --git a/samples/voldemort/README.md b/samples/voldemort/README.md new file mode 100644 index 0000000..7580576 --- /dev/null +++ b/samples/voldemort/README.md @@ -0,0 +1,63 @@ +Spring Integration - Project Voldemort Sample +============================================= + +## Overview + +This sample illustrates the usage of the [Voldemort][] adapters provided by the [Spring Integration Extensions][] project. The samples provides 2 simple use-cases: + +1. A user-entered String is persisted to [Voldemort][] using the *Voldemort Outbound Channel Adapter*. +2. [Voldemort][] is polled every 5 seconds using the *Voldemort Inbound Channel Adapter* and the retrieved values are printed to the console using the *Logging Channel Adapter*. + +## Requirements + +This sample requires a [Voldemort][] instance to be running locally on port 6666. In case you need to customize, please modify the respective values in `src/main/resources/META-INF/spring/integration/spring-integration-context.xml`. + +```xml + + + +``` + +## How to Run the Sample + +You can run the Voldemort sample by either: + +* running the "Main" class from within STS (Right-click on Main class --> Run As --> Java Application) +* or from the command line: + - mvn package + - mvn exec:java + +Once the application is started, you will be asked to entered a String: + + 13:24:54.275 INFO [org.springframework.integration.samples.voldemort.Main.main()][org.springframework.integration.samples.voldemort.Main] + ========================================================= + + Welcome to the Spring Integration Voldemort Sample! + + For more information please visit: + http://www.springsource.org/spring-integration + + ========================================================= + 13:24:55.460 INFO [org.springframework.integration.samples.voldemort.Main.main()][org.springframework.integration.samples.voldemort.Main] + ========================================================= + + Please press 'q + Enter' to quit the application. + + ========================================================= + Please enter a string and press : myString + Persisting String: 'myString' with key 'hello'. + +Every time the Voldemort database is polled, you will the following output: + + 13:27:40.449 INFO [task-scheduler-10][org.springframework.integration.samples.voldemort] [Payload=myString][Headers={timestamp=1364923660449, id=6c861b02-5084-4606-b7bb-b1910e14598f, voldemort_key=hello}] + 13:27:45.449 INFO [task-scheduler-2][org.springframework.integration.samples.voldemort] [Payload=myString][Headers={timestamp=1364923665449, id=8f6eaecc-08ec-41eb-815b-7140d2b660a0, voldemort_key=hello}] + +-------------------------------------------------------------------------------- + +For help please take a look at the Spring Integration documentation: + +http://www.springsource.org/spring-integration + +[Spring Integration Extensions]: https://github.com/SpringSource/spring-integration-extensions +[Voldemort]: http://www.project-voldemort.com/ + diff --git a/samples/voldemort/pom.xml b/samples/voldemort/pom.xml new file mode 100644 index 0000000..151fda3 --- /dev/null +++ b/samples/voldemort/pom.xml @@ -0,0 +1,99 @@ + + 4.0.0 + + org.springframework.integration.samples + voldemort + 1.0.0.BUILD-SNAPSHOT + jar + + voldemort-sample + http://www.springsource.org/spring-integration + + + 2.2.1 + + + + UTF-8 + 1.0.0.BUILD-SNAPSHOT + 1.2.17 + 4.11 + + + + + repo.springsource.org.milestone + Spring Framework Maven Milestone Repository + https://repo.springsource.org/libs-milestone + + + + + + + maven-eclipse-plugin + 2.9 + + + org.springframework.ide.eclipse.core.springnature + + + org.springframework.ide.eclipse.core.springbuilder + + true + true + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.0 + + 1.6 + 1.6 + -Xlint:all + true + true + + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + org.springframework.integration.samples.voldemort.Main + + + + + + + + + + + junit + junit + ${junit.version} + test + + + + + + org.springframework.integration + spring-integration-voldemort + ${spring.integration.voldemort.version} + + + + + + log4j + log4j + ${log4j.version} + + + + diff --git a/samples/voldemort/src/main/java/org/springframework/integration/samples/voldemort/Main.java b/samples/voldemort/src/main/java/org/springframework/integration/samples/voldemort/Main.java new file mode 100644 index 0000000..82cfe98 --- /dev/null +++ b/samples/voldemort/src/main/java/org/springframework/integration/samples/voldemort/Main.java @@ -0,0 +1,107 @@ +/* + * 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.voldemort; + +import java.util.Scanner; + +import org.apache.log4j.Logger; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import org.springframework.integration.samples.voldemort.service.BusinessService; + + +/** + * Starts the Spring Context and will initialize the Spring Integration routes. + * + * @author Gunnar Hillert + * @since 1.0 + * + */ +public final class Main { + + private static final Logger LOGGER = Logger.getLogger(Main.class); + + private Main() { } + + /** + * Load the Spring Integration Application Context + * + * @param args - command line arguments + */ + public static void main(final String... args) { + + if (LOGGER.isInfoEnabled()) { + LOGGER.info("\n=========================================================" + + "\n " + + "\n Welcome to Spring Integration! " + + "\n " + + "\n For more information please visit: " + + "\n http://www.springsource.org/spring-integration " + + "\n " + + "\n=========================================================" ); + } + + final AbstractApplicationContext context = + new ClassPathXmlApplicationContext("classpath:META-INF/spring/integration/*-context.xml"); + + context.registerShutdownHook(); + + final Scanner scanner = new Scanner(System.in); + + final BusinessService service = context.getBean(BusinessService.class); + + if (LOGGER.isInfoEnabled()) { + LOGGER.info("\n=========================================================" + + "\n " + + "\n Please press 'q + Enter' to quit the application. " + + "\n " + + "\n=========================================================" ); + } + + System.out.print("Please enter a string and press : "); + + final String key = "hello"; + + while (true) { + + final String data = scanner.nextLine(); + + if("q".equals(data.trim())) { + break; + } + + try { + + System.out.println(String.format("Persisting String: '%s' with key '%s'.", data, key)); + service.saveData(key, data); + + } catch (Exception e) { + LOGGER.error("An exception was caught: " + e); + } + + System.out.print("Please enter a string and press :"); + + } + + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Exiting application...bye."); + } + + System.exit(0); + + } +} diff --git a/samples/voldemort/src/main/java/org/springframework/integration/samples/voldemort/service/BusinessService.java b/samples/voldemort/src/main/java/org/springframework/integration/samples/voldemort/service/BusinessService.java new file mode 100644 index 0000000..8c78a67 --- /dev/null +++ b/samples/voldemort/src/main/java/org/springframework/integration/samples/voldemort/service/BusinessService.java @@ -0,0 +1,30 @@ +/* + * 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.voldemort.service; + +import org.springframework.integration.annotation.Header; + +/** + * + * @author Gunnar Hillert + * @since 1.0 + * + */ +public interface BusinessService { + + String saveData(@Header("voldemort_key") String key, String stringToPersist); + +} diff --git a/samples/voldemort/src/main/resources/META-INF/spring/integration/spring-integration-context.xml b/samples/voldemort/src/main/resources/META-INF/spring/integration/spring-integration-context.xml new file mode 100644 index 0000000..fbe1027 --- /dev/null +++ b/samples/voldemort/src/main/resources/META-INF/spring/integration/spring-integration-context.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/voldemort/src/main/resources/log4j.xml b/samples/voldemort/src/main/resources/log4j.xml new file mode 100644 index 0000000..dfe6fcf --- /dev/null +++ b/samples/voldemort/src/main/resources/log4j.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +