diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..5e56e040e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/bin diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..8b1409bdd --- /dev/null +++ b/gradle.properties @@ -0,0 +1,24 @@ +## Dependecies Version + +# Logging +log4jVersion = 1.2.17 +slf4jVersion = 1.6.6 + +# Common libraries +springVersion = 3.1.4.RELEASE +springDataVersion = 1.5.0.RELEASE +jacksonVersion = 1.8.8 + +# Testing +junitVersion = 4.8.1 +mockitoVersion = 1.8.5 + +# Drivers +datastaxVersion = 2.0.0-beta2 +cassandraVersion = 1.2.0 +nettyVersion = 3.6.2.Final + +# -------------------- +# Project wide version +# -------------------- +version=2.0.0.BUILD-SNAPSHOT \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..7b359d719 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..91b47e9b6 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Oct 10 20:59:12 EEST 2012 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=http\://services.gradle.org/distributions/gradle-1.2-bin.zip diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..3851082a8 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + 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 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/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..8a0b282aa --- /dev/null +++ b/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/maven.gradle b/maven.gradle new file mode 100644 index 000000000..bf7e46d3a --- /dev/null +++ b/maven.gradle @@ -0,0 +1,63 @@ +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 = 'http://github.com/shvid/spring-data-cassandra' + organization { + name = 'Mirantis' + url = 'http://www.mirantis.com/spring-data/cassnadra' + } + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' + } + } + scm { + url = 'http://github.com/shvid/spring-data-cassandra' + connection = 'scm:git:git://github.com/shvid/spring-data-cassandra' + developerConnection = 'scm:git:git://github.com/shvid/spring-data-cassandra' + } + developers { + developer { + id = 'shvid' + name = 'Alex Shvid' + email = 'aschwid@mirantis.com' + properties { + twitter = 'alexshvid' + } + } + } + } + } +} \ No newline at end of file diff --git a/readme.md b/readme.md index a68a30b15..e061e8db8 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,10 @@ Spring Data Cassandra ===================== +This is a Spring Data subproject for Cassandra that uses the binary CQL3 protocol via +the official DataStax 2.0 Java driver (https://github.com/datastax/java-driver). +Supports native CQL3 queries in Spring Repositories. + +For now, you can get and start a local Cassandra instance via the \*n\*x script `test-support/get-and-start-cassandra`. +It stores the cassandra process id in the file `.cassandra/dist/dsc-cassandra-x.y.z/cassandra.pid`, where `x.y.z` is the latest version of Cassandra. You can use this process id to stop Cassandra via ``kill `cat .cassandra/dist/dsc-cassandra-x.y.z/cassandra.pid` ``. \ No newline at end of file diff --git a/repo/com/datastax/cassandra/cassandra-driver-core/1.0.0-beta1/_maven.repositories b/repo/com/datastax/cassandra/cassandra-driver-core/1.0.0-beta1/_maven.repositories new file mode 100644 index 000000000..711fc0034 --- /dev/null +++ b/repo/com/datastax/cassandra/cassandra-driver-core/1.0.0-beta1/_maven.repositories @@ -0,0 +1,6 @@ +#NOTE: This is an internal implementation file, its format can be changed without prior notice. +#Sun Feb 24 13:09:33 AMT 2013 +cassandra-driver-core-1.0.0-beta1-javadoc.jar>= +cassandra-driver-core-1.0.0-beta1.jar>= +cassandra-driver-core-1.0.0-beta1.pom>= +cassandra-driver-core-1.0.0-beta1-sources.jar>= diff --git a/repo/com/datastax/cassandra/cassandra-driver-core/1.0.0-beta1/cassandra-driver-core-1.0.0-beta1-javadoc.jar b/repo/com/datastax/cassandra/cassandra-driver-core/1.0.0-beta1/cassandra-driver-core-1.0.0-beta1-javadoc.jar new file mode 100644 index 000000000..e56bc0442 Binary files /dev/null and b/repo/com/datastax/cassandra/cassandra-driver-core/1.0.0-beta1/cassandra-driver-core-1.0.0-beta1-javadoc.jar differ diff --git a/repo/com/datastax/cassandra/cassandra-driver-core/1.0.0-beta1/cassandra-driver-core-1.0.0-beta1-sources.jar b/repo/com/datastax/cassandra/cassandra-driver-core/1.0.0-beta1/cassandra-driver-core-1.0.0-beta1-sources.jar new file mode 100644 index 000000000..22fcf0db7 Binary files /dev/null and b/repo/com/datastax/cassandra/cassandra-driver-core/1.0.0-beta1/cassandra-driver-core-1.0.0-beta1-sources.jar differ diff --git a/repo/com/datastax/cassandra/cassandra-driver-core/1.0.0-beta1/cassandra-driver-core-1.0.0-beta1.jar b/repo/com/datastax/cassandra/cassandra-driver-core/1.0.0-beta1/cassandra-driver-core-1.0.0-beta1.jar new file mode 100644 index 000000000..5cd08ec05 Binary files /dev/null and b/repo/com/datastax/cassandra/cassandra-driver-core/1.0.0-beta1/cassandra-driver-core-1.0.0-beta1.jar differ diff --git a/repo/com/datastax/cassandra/cassandra-driver-core/1.0.0-beta1/cassandra-driver-core-1.0.0-beta1.pom b/repo/com/datastax/cassandra/cassandra-driver-core/1.0.0-beta1/cassandra-driver-core-1.0.0-beta1.pom new file mode 100644 index 000000000..823b45a21 --- /dev/null +++ b/repo/com/datastax/cassandra/cassandra-driver-core/1.0.0-beta1/cassandra-driver-core-1.0.0-beta1.pom @@ -0,0 +1,126 @@ + + + 4.0.0 + + com.datastax.cassandra + cassandra-driver-parent + 1.0.0-beta1 + + cassandra-driver-core + jar + DataStax Java Driver for Apache Cassandra - Core + A driver for Apache Cassandra 1.2+ that works exclusively with the Cassandra Query Language version 3 (CQL3) and Cassandra's binary protocol. + https://github.com/datastax/java-driver + + + + io.netty + netty + 3.6.2.Final + + + + com.google.guava + guava + 13.0.1 + + + + org.apache.cassandra + cassandra-thrift + 1.2.0 + + + + org.apache.thrift + libthrift + 0.7.0 + + + org.slf4j + slf4j-api + + + + + + org.codehaus.jackson + jackson-core-asl + 1.4.0 + + + + com.yammer.metrics + metrics-core + 2.2.0 + + + + + + + default + + default + 1.2.0 + + + true + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.5 + + false + + ${cassandra.version} + ${ipprefix} + + + + + + + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + Apache License Version 2.0 + + + + + scm:git:git@github.com:datastax/java-driver.git + scm:git:git@github.com:datastax/java-driver.git + https://github.com/datastax/java-driver + + + + + Various + DataStax + + + + + diff --git a/repo/com/datastax/cassandra/cassandra-driver-core/maven-metadata-local.xml b/repo/com/datastax/cassandra/cassandra-driver-core/maven-metadata-local.xml new file mode 100644 index 000000000..9b6dcc2ae --- /dev/null +++ b/repo/com/datastax/cassandra/cassandra-driver-core/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + com.datastax.cassandra + cassandra-driver-core + + 1.0.0-beta1 + + 1.0.0-beta1 + + 20130224090933 + + diff --git a/repo/com/datastax/cassandra/cassandra-driver-examples-parent/1.0.0-beta1/_maven.repositories b/repo/com/datastax/cassandra/cassandra-driver-examples-parent/1.0.0-beta1/_maven.repositories new file mode 100644 index 000000000..c672f7584 --- /dev/null +++ b/repo/com/datastax/cassandra/cassandra-driver-examples-parent/1.0.0-beta1/_maven.repositories @@ -0,0 +1,3 @@ +#NOTE: This is an internal implementation file, its format can be changed without prior notice. +#Sun Feb 24 13:09:33 AMT 2013 +cassandra-driver-examples-parent-1.0.0-beta1.pom>= diff --git a/repo/com/datastax/cassandra/cassandra-driver-examples-parent/1.0.0-beta1/cassandra-driver-examples-parent-1.0.0-beta1.pom b/repo/com/datastax/cassandra/cassandra-driver-examples-parent/1.0.0-beta1/cassandra-driver-examples-parent-1.0.0-beta1.pom new file mode 100644 index 000000000..6280142c8 --- /dev/null +++ b/repo/com/datastax/cassandra/cassandra-driver-examples-parent/1.0.0-beta1/cassandra-driver-examples-parent-1.0.0-beta1.pom @@ -0,0 +1,54 @@ + + + 4.0.0 + + com.datastax.cassandra + cassandra-driver-parent + 1.0.0-beta1 + + cassandra-driver-examples-parent + pom + DataStax Java Driver for Apache Cassandra Examples + A collection of examples to demonstrate DataStax Java Driver for Apache Cassandra. + https://github.com/datastax/java-driver + + + stress + + + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + Apache License Version 2.0 + + + + + scm:git:git@github.com:datastax/java-driver.git + scm:git:git@github.com:datastax/java-driver.git + https://github.com/datastax/java-driver + + + + + Various + DataStax + + + + diff --git a/repo/com/datastax/cassandra/cassandra-driver-examples-parent/maven-metadata-local.xml b/repo/com/datastax/cassandra/cassandra-driver-examples-parent/maven-metadata-local.xml new file mode 100644 index 000000000..73f033af9 --- /dev/null +++ b/repo/com/datastax/cassandra/cassandra-driver-examples-parent/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + com.datastax.cassandra + cassandra-driver-examples-parent + + 1.0.0-beta1 + + 1.0.0-beta1 + + 20130224090933 + + diff --git a/repo/com/datastax/cassandra/cassandra-driver-examples-stress/1.0.0-beta1/_maven.repositories b/repo/com/datastax/cassandra/cassandra-driver-examples-stress/1.0.0-beta1/_maven.repositories new file mode 100644 index 000000000..af054258e --- /dev/null +++ b/repo/com/datastax/cassandra/cassandra-driver-examples-stress/1.0.0-beta1/_maven.repositories @@ -0,0 +1,6 @@ +#NOTE: This is an internal implementation file, its format can be changed without prior notice. +#Sun Feb 24 13:09:34 AMT 2013 +cassandra-driver-examples-stress-1.0.0-beta1-javadoc.jar>= +cassandra-driver-examples-stress-1.0.0-beta1.jar>= +cassandra-driver-examples-stress-1.0.0-beta1.pom>= +cassandra-driver-examples-stress-1.0.0-beta1-sources.jar>= diff --git a/repo/com/datastax/cassandra/cassandra-driver-examples-stress/1.0.0-beta1/cassandra-driver-examples-stress-1.0.0-beta1-javadoc.jar b/repo/com/datastax/cassandra/cassandra-driver-examples-stress/1.0.0-beta1/cassandra-driver-examples-stress-1.0.0-beta1-javadoc.jar new file mode 100644 index 000000000..7284ab0b3 Binary files /dev/null and b/repo/com/datastax/cassandra/cassandra-driver-examples-stress/1.0.0-beta1/cassandra-driver-examples-stress-1.0.0-beta1-javadoc.jar differ diff --git a/repo/com/datastax/cassandra/cassandra-driver-examples-stress/1.0.0-beta1/cassandra-driver-examples-stress-1.0.0-beta1-sources.jar b/repo/com/datastax/cassandra/cassandra-driver-examples-stress/1.0.0-beta1/cassandra-driver-examples-stress-1.0.0-beta1-sources.jar new file mode 100644 index 000000000..a86701d94 Binary files /dev/null and b/repo/com/datastax/cassandra/cassandra-driver-examples-stress/1.0.0-beta1/cassandra-driver-examples-stress-1.0.0-beta1-sources.jar differ diff --git a/repo/com/datastax/cassandra/cassandra-driver-examples-stress/1.0.0-beta1/cassandra-driver-examples-stress-1.0.0-beta1.jar b/repo/com/datastax/cassandra/cassandra-driver-examples-stress/1.0.0-beta1/cassandra-driver-examples-stress-1.0.0-beta1.jar new file mode 100644 index 000000000..ba3ca9e80 Binary files /dev/null and b/repo/com/datastax/cassandra/cassandra-driver-examples-stress/1.0.0-beta1/cassandra-driver-examples-stress-1.0.0-beta1.jar differ diff --git a/repo/com/datastax/cassandra/cassandra-driver-examples-stress/1.0.0-beta1/cassandra-driver-examples-stress-1.0.0-beta1.pom b/repo/com/datastax/cassandra/cassandra-driver-examples-stress/1.0.0-beta1/cassandra-driver-examples-stress-1.0.0-beta1.pom new file mode 100644 index 000000000..b0de61ab2 --- /dev/null +++ b/repo/com/datastax/cassandra/cassandra-driver-examples-stress/1.0.0-beta1/cassandra-driver-examples-stress-1.0.0-beta1.pom @@ -0,0 +1,89 @@ + + + 4.0.0 + + com.datastax.cassandra + cassandra-driver-examples-parent + 1.0.0-beta1 + + cassandra-driver-examples-stress + jar + DataStax Java Driver for Apache Cassandra Examples - Stress + A stress test example for DataStax Java Driver for Apache Cassandra. + https://github.com/datastax/java-driver + + + + com.datastax.cassandra + cassandra-driver-core + 1.0.0-beta1 + + + + com.yammer.metrics + metrics-core + 2.2.0 + + + + net.sf.jopt-simple + jopt-simple + 4.3 + + + + + + + maven-assembly-plugin + + + + com.datastax.driver.stress.Stress + + + + jar-with-dependencies + + + + + + + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + Apache License Version 2.0 + + + + + scm:git:git@github.com:datastax/java-driver.git + scm:git:git@github.com:datastax/java-driver.git + https://github.com/datastax/java-driver + + + + + Various + DataStax + + + + + diff --git a/repo/com/datastax/cassandra/cassandra-driver-examples-stress/maven-metadata-local.xml b/repo/com/datastax/cassandra/cassandra-driver-examples-stress/maven-metadata-local.xml new file mode 100644 index 000000000..f1552536c --- /dev/null +++ b/repo/com/datastax/cassandra/cassandra-driver-examples-stress/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + com.datastax.cassandra + cassandra-driver-examples-stress + + 1.0.0-beta1 + + 1.0.0-beta1 + + 20130224090934 + + diff --git a/repo/com/datastax/cassandra/cassandra-driver-parent/1.0.0-beta1/_maven.repositories b/repo/com/datastax/cassandra/cassandra-driver-parent/1.0.0-beta1/_maven.repositories new file mode 100644 index 000000000..19b1cf1fa --- /dev/null +++ b/repo/com/datastax/cassandra/cassandra-driver-parent/1.0.0-beta1/_maven.repositories @@ -0,0 +1,3 @@ +#NOTE: This is an internal implementation file, its format can be changed without prior notice. +#Sun Feb 24 13:09:29 AMT 2013 +cassandra-driver-parent-1.0.0-beta1.pom>= diff --git a/repo/com/datastax/cassandra/cassandra-driver-parent/1.0.0-beta1/cassandra-driver-parent-1.0.0-beta1.pom b/repo/com/datastax/cassandra/cassandra-driver-parent/1.0.0-beta1/cassandra-driver-parent-1.0.0-beta1.pom new file mode 100644 index 000000000..7632d1dec --- /dev/null +++ b/repo/com/datastax/cassandra/cassandra-driver-parent/1.0.0-beta1/cassandra-driver-parent-1.0.0-beta1.pom @@ -0,0 +1,126 @@ + + + 4.0.0 + com.datastax.cassandra + cassandra-driver-parent + 1.0.0-beta1 + pom + DataStax Java Driver for Apache Cassandra + A driver for Apache Cassandra 1.2+ that works exclusively with the Cassandra Query Language version 3 (CQL3) and Cassandra's binary protocol. + https://github.com/datastax/java-driver + + + driver-core + driver-examples + + + + UTF-8 + + + + + org.apache.cassandra + cassandra-all + 1.2.0 + + + + log4j + log4j + 1.2.17 + test + + + + org.slf4j + slf4j-log4j12 + 1.6.6 + test + + + + junit + junit + 4.10 + test + + + + + + + maven-compiler-plugin + 2.5.1 + + 1.6 + 1.6 + true + true + true + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9 + + + attach-javadocs + + jar + + + + + + + + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + Apache License Version 2.0 + + + + + scm:git:git@github.com:datastax/java-driver.git + scm:git:git@github.com:datastax/java-driver.git + https://github.com/datastax/java-driver + + + + + Various + DataStax + + + diff --git a/repo/com/datastax/cassandra/cassandra-driver-parent/maven-metadata-local.xml b/repo/com/datastax/cassandra/cassandra-driver-parent/maven-metadata-local.xml new file mode 100644 index 000000000..0fea148ed --- /dev/null +++ b/repo/com/datastax/cassandra/cassandra-driver-parent/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + com.datastax.cassandra + cassandra-driver-parent + + 1.0.0-beta1 + + 1.0.0-beta1 + + 20130224090929 + + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..062310fab --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'spring-data-cassandra' \ No newline at end of file diff --git a/src/main/java/org/springframework/data/cassandra/config/AbstractCassandraConfiguration.java b/src/main/java/org/springframework/data/cassandra/config/AbstractCassandraConfiguration.java new file mode 100644 index 000000000..56a2ce9c8 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/config/AbstractCassandraConfiguration.java @@ -0,0 +1,172 @@ +/* + * Copyright 2011-2012 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.data.cassandra.config; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.data.annotation.Persistent; +import org.springframework.data.cassandra.convert.CassandraConverter; +import org.springframework.data.cassandra.convert.MappingCassandraConverter; +import org.springframework.data.cassandra.core.CassandraTemplate; +import org.springframework.data.cassandra.core.Keyspace; +import org.springframework.data.cassandra.mapping.CassandraMappingContext; +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.cassandra.mapping.Table; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.Session; + +/** + * Base class for Spring Data Cassandra configuration using JavaConfig. + * + * @author Alex Shvid + */ +@Configuration +public abstract class AbstractCassandraConfiguration { + + /** + * Return the name of the keyspace to connect to. + * + * @return must not be {@literal null}. + */ + protected abstract String getKeyspaceName(); + + /** + * Return the {@link Cluster} instance to connect to. + * + * @return + * @throws Exception + */ + @Bean + public abstract Cluster cluster() throws Exception; + + /** + * Creates a {@link Session} to be used by the {@link Keyspace}. Will use the {@link Cluster} instance + * configured in {@link #cluster()}. + * + * @see #cluster() + * @see #Keyspace() + * @return + * @throws Exception + */ + @Bean + public Session session() throws Exception { + String keyspace = getKeyspaceName(); + if (StringUtils.hasText(keyspace)) { + return cluster().connect(keyspace); + } + else { + return cluster().connect(); + } + } + + /** + * Creates a {@link Keyspace} to be used by the {@link CassandraTemplate}. Will use the {@link Session} instance + * configured in {@link #session()} and {@link CassandraConverter} configured in {@link #converter()}. + * + * @see #cluster() + * @see #Keyspace() + * @return + * @throws Exception + */ + @Bean + public Keyspace keyspace() throws Exception { + return new Keyspace(getKeyspaceName(), session(), converter()); + } + /** + * Return the base package to scan for mapped {@link Table}s. Will return the package name of the configuration + * class' (the concrete class, not this one here) by default. So if you have a {@code com.acme.AppConfig} extending + * {@link AbstractCassandraConfiguration} the base package will be considered {@code com.acme} unless the method is + * overriden to implement alternate behaviour. + * + * @return the base package to scan for mapped {@link Table} classes or {@literal null} to not enable scanning for + * entities. + */ + protected String getMappingBasePackage() { + return getClass().getPackage().getName(); + } + + /** + * Creates a {@link CassandraTemplate}. + * + * @return + * @throws Exception + */ + @Bean + public CassandraTemplate cassandraTemplate() throws Exception { + return new CassandraTemplate(keyspace()); + } + + /** + * Return the {@link MappingContext} instance to map Entities to properties. + * + * @return + * @throws Exception + */ + @Bean + public MappingContext, CassandraPersistentProperty> mappingContext() { + return new CassandraMappingContext(); + } + + /** + * Return the {@link CassandraConverter} instance to convert Rows to Objects. + * + * @return + * @throws Exception + */ + @Bean + public CassandraConverter converter() { + return new MappingCassandraConverter(mappingContext()); + } + + /** + * Scans the mapping base package for classes annotated with {@link Table}. + * + * @see #getMappingBasePackage() + * @return + * @throws ClassNotFoundException + */ + protected Set> getInitialEntitySet() throws ClassNotFoundException { + + String basePackage = getMappingBasePackage(); + Set> initialEntitySet = new HashSet>(); + + if (StringUtils.hasText(basePackage)) { + ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider( + false); + componentProvider.addIncludeFilter(new AnnotationTypeFilter(Table.class)); + componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class)); + + for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) { + initialEntitySet.add(ClassUtils.forName(candidate.getBeanClassName(), + AbstractCassandraConfiguration.class.getClassLoader())); + } + } + + return initialEntitySet; + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/config/BeanNames.java b/src/main/java/org/springframework/data/cassandra/config/BeanNames.java new file mode 100644 index 000000000..e4c0f9709 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/config/BeanNames.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * 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.data.cassandra.config; + +/** + * @author Alex Shvid + */ +public class BeanNames { + + static final String CASSANDRA_CLUSTER = "cassandra-cluster"; + static final String CASSANDRA_KEYSPACE = "cassandra-keyspace"; + +} diff --git a/src/main/java/org/springframework/data/cassandra/config/CassandraClusterParser.java b/src/main/java/org/springframework/data/cassandra/config/CassandraClusterParser.java new file mode 100644 index 000000000..246071fe8 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/config/CassandraClusterParser.java @@ -0,0 +1,121 @@ +/* + * Copyright 2011-2012 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.data.cassandra.config; + +import java.util.List; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.data.cassandra.core.CassandraClusterFactoryBean; +import org.springframework.data.config.ParsingUtils; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +/** + * Parser for <cluster;gt; definitions. + * + * @author Alex Shvid + */ + +public class CassandraClusterParser extends AbstractSimpleBeanDefinitionParser { + + @Override + protected Class getBeanClass(Element element) { + return CassandraClusterFactoryBean.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#resolveId(org.w3c.dom.Element, org.springframework.beans.factory.support.AbstractBeanDefinition, org.springframework.beans.factory.xml.ParserContext) + */ + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { + + String id = super.resolveId(element, definition, parserContext); + return StringUtils.hasText(id) ? id : BeanNames.CASSANDRA_CLUSTER; + } + + @Override + protected void doParse(Element element, ParserContext parserContext, + BeanDefinitionBuilder builder) { + + String contactPoints = element.getAttribute("contactPoints"); + if (StringUtils.hasText(contactPoints)) { + builder.addPropertyValue("contactPoints", contactPoints); + } + + String port = element.getAttribute("port"); + if (StringUtils.hasText(port)) { + builder.addPropertyValue("port", port); + } + + String compression = element.getAttribute("compression"); + if (StringUtils.hasText(compression)) { + builder.addPropertyValue("compressionType", CompressionType.valueOf(compression)); + } + + postProcess(builder, element); + } + + @Override + protected void postProcess(BeanDefinitionBuilder builder, Element element) { + List subElements = DomUtils.getChildElements(element); + + // parse nested elements + for (Element subElement : subElements) { + String name = subElement.getLocalName(); + + if ("local-pooling-options".equals(name)) { + builder.addPropertyValue("localPoolingOptions", parsePoolingOptions(subElement)); + } + else if ("remote-pooling-options".equals(name)) { + builder.addPropertyValue("remotePoolingOptions", parsePoolingOptions(subElement)); + } + else if ("socket-options".equals(name)) { + builder.addPropertyValue("socketOptions", parseSocketOptions(subElement)); + } + } + + } + + private BeanDefinition parsePoolingOptions(Element element) { + BeanDefinitionBuilder defBuilder = BeanDefinitionBuilder.genericBeanDefinition(PoolingOptionsConfig.class); + ParsingUtils.setPropertyValue(defBuilder, element, "min-simultaneous-requests", "minSimultaneousRequests"); + ParsingUtils.setPropertyValue(defBuilder, element, "max-simultaneous-requests", "maxSimultaneousRequests"); + ParsingUtils.setPropertyValue(defBuilder, element, "core-connections", "coreConnections"); + ParsingUtils.setPropertyValue(defBuilder, element, "max-connections", "maxConnections"); + return defBuilder.getBeanDefinition(); + } + + private BeanDefinition parseSocketOptions(Element element) { + BeanDefinitionBuilder defBuilder = BeanDefinitionBuilder.genericBeanDefinition(SocketOptionsConfig.class); + ParsingUtils.setPropertyValue(defBuilder, element, "connect-timeout-mls", "connectTimeoutMls"); + ParsingUtils.setPropertyValue(defBuilder, element, "keep-alive", "keepAlive"); + ParsingUtils.setPropertyValue(defBuilder, element, "reuse-address", "reuseAddress"); + ParsingUtils.setPropertyValue(defBuilder, element, "so-linger", "soLinger"); + ParsingUtils.setPropertyValue(defBuilder, element, "tcp-no-delay", "tcpNoDelay"); + ParsingUtils.setPropertyValue(defBuilder, element, "receive-buffer-size", "receiveBufferSize"); + ParsingUtils.setPropertyValue(defBuilder, element, "send-buffer-size", "sendBufferSize"); + return defBuilder.getBeanDefinition(); + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/config/CassandraKeyspaceParser.java b/src/main/java/org/springframework/data/cassandra/config/CassandraKeyspaceParser.java new file mode 100644 index 000000000..5b34d561d --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/config/CassandraKeyspaceParser.java @@ -0,0 +1,129 @@ +/* + * Copyright 2011-2012 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.data.cassandra.config; + +import java.util.List; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.ManagedList; +import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.data.cassandra.core.CassandraKeyspaceFactoryBean; +import org.springframework.data.config.ParsingUtils; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +/** + * Parser for <keyspace;gt; definitions. + * + * @author Alex Shvid + */ + + +public class CassandraKeyspaceParser extends AbstractSimpleBeanDefinitionParser { + + @Override + protected Class getBeanClass(Element element) { + return CassandraKeyspaceFactoryBean.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#resolveId(org.w3c.dom.Element, org.springframework.beans.factory.support.AbstractBeanDefinition, org.springframework.beans.factory.xml.ParserContext) + */ + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { + + String id = super.resolveId(element, definition, parserContext); + return StringUtils.hasText(id) ? id : BeanNames.CASSANDRA_KEYSPACE; + } + + @Override + protected void doParse(Element element, ParserContext parserContext, + BeanDefinitionBuilder builder) { + + String name = element.getAttribute("name"); + if (StringUtils.hasText(name)) { + builder.addPropertyValue("keyspace", name); + } + + String clusterRef = element.getAttribute("cassandra-cluster-ref"); + if (!StringUtils.hasText(clusterRef)) { + clusterRef = BeanNames.CASSANDRA_CLUSTER; + } + builder.addPropertyReference("cluster", clusterRef); + + String converterRef = element.getAttribute("cassandra-converter-ref"); + if (StringUtils.hasText(converterRef)) { + builder.addPropertyReference("converter", converterRef); + } + + postProcess(builder, element); + } + + @Override + protected void postProcess(BeanDefinitionBuilder builder, Element element) { + List subElements = DomUtils.getChildElements(element); + + // parse nested elements + for (Element subElement : subElements) { + String name = subElement.getLocalName(); + + if ("keyspace-attributes".equals(name)) { + builder.addPropertyValue("keyspaceAttributes", parseKeyspaceAttributes(subElement)); + } + } + + } + + private BeanDefinition parseKeyspaceAttributes(Element element) { + BeanDefinitionBuilder defBuilder = BeanDefinitionBuilder.genericBeanDefinition(KeyspaceAttributes.class); + ParsingUtils.setPropertyValue(defBuilder, element, "auto", "auto"); + ParsingUtils.setPropertyValue(defBuilder, element, "replication-strategy", "replicationStrategy"); + ParsingUtils.setPropertyValue(defBuilder, element, "replication-factor", "replicationFactor"); + ParsingUtils.setPropertyValue(defBuilder, element, "durable-writes", "durableWrites"); + + List subElements = DomUtils.getChildElements(element); + ManagedList tables = new ManagedList(subElements.size()); + + // parse nested elements + for (Element subElement : subElements) { + String name = subElement.getLocalName(); + + if ("table".equals(name)) { + tables.add(parseTable(subElement)); + } + } + if (!tables.isEmpty()) { + defBuilder.addPropertyValue("tables", tables); + } + + return defBuilder.getBeanDefinition(); + } + + private BeanDefinition parseTable(Element element) { + BeanDefinitionBuilder defBuilder = BeanDefinitionBuilder.genericBeanDefinition(TableAttributes.class); + ParsingUtils.setPropertyValue(defBuilder, element, "entity", "entity"); + ParsingUtils.setPropertyValue(defBuilder, element, "name", "name"); + return defBuilder.getBeanDefinition(); + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/config/CassandraNamespaceHandler.java b/src/main/java/org/springframework/data/cassandra/config/CassandraNamespaceHandler.java new file mode 100644 index 000000000..9d4bbea9d --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/config/CassandraNamespaceHandler.java @@ -0,0 +1,35 @@ +/* + * Copyright 2011-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.data.cassandra.config; + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + +/** + * Namespace handler for <cassandra;gt;. + * + * @author Alex Shvid + */ + +public class CassandraNamespaceHandler extends NamespaceHandlerSupport { + + public void init() { + + registerBeanDefinitionParser("cluster", new CassandraClusterParser()); + registerBeanDefinitionParser("keyspace", new CassandraKeyspaceParser()); + + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/config/CompressionType.java b/src/main/java/org/springframework/data/cassandra/config/CompressionType.java new file mode 100644 index 000000000..4f6092844 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/config/CompressionType.java @@ -0,0 +1,25 @@ +/* + * Copyright 2010-2012 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.data.cassandra.config; + +/** + * Simple enumeration for the various compression types. + * + * @author Alex Shvid + */ +public enum CompressionType { + none, snappy; +} diff --git a/src/main/java/org/springframework/data/cassandra/config/KeyspaceAttributes.java b/src/main/java/org/springframework/data/cassandra/config/KeyspaceAttributes.java new file mode 100644 index 000000000..f0765d886 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/config/KeyspaceAttributes.java @@ -0,0 +1,108 @@ +/* + * Copyright 2011-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.data.cassandra.config; + +import java.util.Collection; + +/** + * Keyspace attributes are used for manipulation around keyspace at the startup. + * Auto property defines the way how to do this. Other attributes used to + * ensure or update keyspace settings. + * + * @author Alex Shvid + */ +public class KeyspaceAttributes { + + public static final String DEFAULT_REPLICATION_STRATEGY = "SimpleStrategy"; + public static final int DEFAULT_REPLICATION_FACTOR = 1; + public static final boolean DEFAULT_DURABLE_WRITES = true; + + /* + * auto possible values: + * validate: validate the keyspace, makes no changes. + * update: update the keyspace. + * create: creates the keyspace, destroying previous data. + * create-drop: drop the keyspace at the end of the session. + */ + public static final String AUTO_VALIDATE = "validate"; + public static final String AUTO_UPDATE = "update"; + public static final String AUTO_CREATE = "create"; + public static final String AUTO_CREATE_DROP = "create-drop"; + + private String auto = AUTO_VALIDATE; + private String replicationStrategy = DEFAULT_REPLICATION_STRATEGY; + private int replicationFactor = DEFAULT_REPLICATION_FACTOR; + private boolean durableWrites = DEFAULT_DURABLE_WRITES; + + private Collection tables; + + public String getAuto() { + return auto; + } + + public void setAuto(String auto) { + this.auto = auto; + } + + public boolean isValidate() { + return AUTO_VALIDATE.equals(auto); + } + + public boolean isUpdate() { + return AUTO_UPDATE.equals(auto); + } + + public boolean isCreate() { + return AUTO_CREATE.equals(auto); + } + + public boolean isCreateDrop() { + return AUTO_CREATE_DROP.equals(auto); + } + + public String getReplicationStrategy() { + return replicationStrategy; + } + + public void setReplicationStrategy(String replicationStrategy) { + this.replicationStrategy = replicationStrategy; + } + + public int getReplicationFactor() { + return replicationFactor; + } + + public void setReplicationFactor(int replicationFactor) { + this.replicationFactor = replicationFactor; + } + + public boolean isDurableWrites() { + return durableWrites; + } + + public void setDurableWrites(boolean durableWrites) { + this.durableWrites = durableWrites; + } + + public Collection getTables() { + return tables; + } + + public void setTables(Collection tables) { + this.tables = tables; + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/config/PoolingOptionsConfig.java b/src/main/java/org/springframework/data/cassandra/config/PoolingOptionsConfig.java new file mode 100644 index 000000000..4ba96539e --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/config/PoolingOptionsConfig.java @@ -0,0 +1,62 @@ +/* + * Copyright 2011-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.data.cassandra.config; + +/** + * Pooling options POJO. Can be remote or local. + * + * @author Alex Shvid + */ +public class PoolingOptionsConfig { + + private Integer minSimultaneousRequests; + private Integer maxSimultaneousRequests; + private Integer coreConnections; + private Integer maxConnections; + + public Integer getMinSimultaneousRequests() { + return minSimultaneousRequests; + } + + public void setMinSimultaneousRequests(Integer minSimultaneousRequests) { + this.minSimultaneousRequests = minSimultaneousRequests; + } + + public Integer getMaxSimultaneousRequests() { + return maxSimultaneousRequests; + } + + public void setMaxSimultaneousRequests(Integer maxSimultaneousRequests) { + this.maxSimultaneousRequests = maxSimultaneousRequests; + } + + public Integer getCoreConnections() { + return coreConnections; + } + + public void setCoreConnections(Integer coreConnections) { + this.coreConnections = coreConnections; + } + + public Integer getMaxConnections() { + return maxConnections; + } + + public void setMaxConnections(Integer maxConnections) { + this.maxConnections = maxConnections; + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/config/SocketOptionsConfig.java b/src/main/java/org/springframework/data/cassandra/config/SocketOptionsConfig.java new file mode 100644 index 000000000..1e72c7742 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/config/SocketOptionsConfig.java @@ -0,0 +1,89 @@ +/* + * Copyright 2011-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.data.cassandra.config; + +/** + * Socket options POJO. Uses to configure Netty. + * + * @author Alex Shvid + */ +public class SocketOptionsConfig { + + private Integer connectTimeoutMls; + private Boolean keepAlive; + private Boolean reuseAddress; + private Integer soLinger; + private Boolean tcpNoDelay; + private Integer receiveBufferSize; + private Integer sendBufferSize; + + public Integer getConnectTimeoutMls() { + return connectTimeoutMls; + } + + public void setConnectTimeoutMls(Integer connectTimeoutMls) { + this.connectTimeoutMls = connectTimeoutMls; + } + + public Boolean getKeepAlive() { + return keepAlive; + } + + public void setKeepAlive(Boolean keepAlive) { + this.keepAlive = keepAlive; + } + + public Boolean getReuseAddress() { + return reuseAddress; + } + + public void setReuseAddress(Boolean reuseAddress) { + this.reuseAddress = reuseAddress; + } + + public Integer getSoLinger() { + return soLinger; + } + + public void setSoLinger(Integer soLinger) { + this.soLinger = soLinger; + } + + public Boolean getTcpNoDelay() { + return tcpNoDelay; + } + + public void setTcpNoDelay(Boolean tcpNoDelay) { + this.tcpNoDelay = tcpNoDelay; + } + + public Integer getReceiveBufferSize() { + return receiveBufferSize; + } + + public void setReceiveBufferSize(Integer receiveBufferSize) { + this.receiveBufferSize = receiveBufferSize; + } + + public Integer getSendBufferSize() { + return sendBufferSize; + } + + public void setSendBufferSize(Integer sendBufferSize) { + this.sendBufferSize = sendBufferSize; + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/config/TableAttributes.java b/src/main/java/org/springframework/data/cassandra/config/TableAttributes.java new file mode 100644 index 000000000..76f517365 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/config/TableAttributes.java @@ -0,0 +1,49 @@ +/* + * Copyright 2011-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.data.cassandra.config; + +/** + * Table attributes are used for manipulation around table at the startup (create/update/validate). + * + * @author Alex Shvid + */ +public class TableAttributes { + + private String entity; + private String name; + + public String getEntity() { + return entity; + } + + public void setEntity(String entity) { + this.entity = entity; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "TableAttributes [entity=" + entity + "]"; + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/convert/AbstractCassandraConverter.java b/src/main/java/org/springframework/data/cassandra/convert/AbstractCassandraConverter.java new file mode 100644 index 000000000..858a930bf --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/convert/AbstractCassandraConverter.java @@ -0,0 +1,67 @@ +/* + * Copyright 2011-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.data.cassandra.convert; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.data.convert.EntityInstantiators; + +/** + * Base class for {@link CassandraConverter} implementations. Sets up a {@link GenericConversionService} and populates basic + * converters. + * + * @author Alex Shvid + */ +public abstract class AbstractCassandraConverter implements CassandraConverter, InitializingBean { + + protected final GenericConversionService conversionService; + protected EntityInstantiators instantiators = new EntityInstantiators(); + + /** + * Creates a new {@link AbstractMongoConverter} using the given {@link GenericConversionService}. + * + * @param conversionService + */ + public AbstractCassandraConverter(GenericConversionService conversionService) { + this.conversionService = conversionService == null ? new DefaultConversionService() : conversionService; + } + + /** + * Registers {@link EntityInstantiators} to customize entity instantiation. + * + * @param instantiators + */ + public void setInstantiators(EntityInstantiators instantiators) { + this.instantiators = instantiators == null ? new EntityInstantiators() : instantiators; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.core.convert.MongoConverter#getConversionService() + */ + public ConversionService getConversionService() { + return conversionService; + } + + /* (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() { + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/convert/CassandraConverter.java b/src/main/java/org/springframework/data/cassandra/convert/CassandraConverter.java new file mode 100644 index 000000000..72266b0b3 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/convert/CassandraConverter.java @@ -0,0 +1,31 @@ +/* + * Copyright 2010-2011 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.data.cassandra.convert; + +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.convert.EntityConverter; + +import com.datastax.driver.core.Row; + +/** + * Central Cassandra specific converter interface from Object to Row. + * + * @author Alex Shvid + */ +public interface CassandraConverter extends EntityConverter, CassandraPersistentProperty, Object, Row> { + +} diff --git a/src/main/java/org/springframework/data/cassandra/convert/CassandraPropertyValueProvider.java b/src/main/java/org/springframework/data/cassandra/convert/CassandraPropertyValueProvider.java new file mode 100644 index 000000000..7801ed96a --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/convert/CassandraPropertyValueProvider.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012 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.data.cassandra.convert; + +import java.nio.ByteBuffer; + +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator; +import org.springframework.data.mapping.model.PropertyValueProvider; +import org.springframework.data.mapping.model.SpELExpressionEvaluator; +import org.springframework.util.Assert; + +import com.datastax.driver.core.DataType; +import com.datastax.driver.core.Row; + +/** + * {@link PropertyValueProvider} to read property values from a {@link Row}. + * + * @author Alex Shvid + */ +public class CassandraPropertyValueProvider implements PropertyValueProvider { + + private final Row source; + private final SpELExpressionEvaluator evaluator; + + /** + * Creates a new {@link CassandraPropertyValueProvider} with the given {@link Row} and {@link DefaultSpELExpressionEvaluator}. + * + * @param source must not be {@literal null}. + * @param evaluator must not be {@literal null}. + */ + public CassandraPropertyValueProvider(Row source, DefaultSpELExpressionEvaluator evaluator) { + Assert.notNull(source); + Assert.notNull(evaluator); + + this.source = source; + this.evaluator = evaluator; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.convert.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty) + */ + @SuppressWarnings("unchecked") + public T getPropertyValue(CassandraPersistentProperty property) { + + String expression = property.getSpelExpression(); + if (expression != null) { + return evaluator.evaluate(expression); + } + + String columnName = property.getColumnName(); + if (source.isNull(property.getColumnName())) { + return null; + } + DataType columnType = source.getColumnDefinitions().getType(columnName); + ByteBuffer bytes = source.getBytes(columnName); + return (T) columnType.deserialize(bytes); + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java b/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java new file mode 100644 index 000000000..659e24059 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java @@ -0,0 +1,145 @@ +/* + * Copyright 2011-2013 by the original author(s). + * + * 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.data.cassandra.convert; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.convert.EntityInstantiator; +import org.springframework.data.mapping.PropertyHandler; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.BeanWrapper; +import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator; +import org.springframework.data.mapping.model.MappingException; +import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider; +import org.springframework.data.mapping.model.PropertyValueProvider; +import org.springframework.data.mapping.model.SpELContext; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; + +import com.datastax.driver.core.Row; + +/** + * {@link CassandraConverter} that uses a {@link MappingContext} to do sophisticated mapping of domain objects to + * {@link Row}. + * + * @author Alex Shvid + */ +public class MappingCassandraConverter extends AbstractCassandraConverter implements ApplicationContextAware { + + protected static final Logger log = LoggerFactory.getLogger(MappingCassandraConverter.class); + + protected final MappingContext, CassandraPersistentProperty> mappingContext; + protected ApplicationContext applicationContext; + private SpELContext spELContext; + private boolean useFieldAccessOnly = true; + + /** + * Creates a new {@link MappingCassandraConverter} given the new {@link MappingContext}. + * + * @param mappingContext must not be {@literal null}. + */ + public MappingCassandraConverter(MappingContext, CassandraPersistentProperty> mappingContext) { + super(new DefaultConversionService()); + this.mappingContext = mappingContext; + this.spELContext = new SpELContext(RowReaderPropertyAccessor.INSTANCE); + } + + @SuppressWarnings("unchecked") + public R read(Class clazz, Row row) { + + TypeInformation type = ClassTypeInformation.from(clazz); + //TypeInformation typeToUse = typeMapper.readType(row, type); + TypeInformation typeToUse = type; + Class rawType = typeToUse.getType(); + + if (Row.class.isAssignableFrom(rawType)) { + return (R) row; + } + + CassandraPersistentEntity persistentEntity = (CassandraPersistentEntity) mappingContext.getPersistentEntity(typeToUse); + if (persistentEntity == null) { + throw new MappingException("No mapping metadata found for " + rawType.getName()); + } + + return read(persistentEntity, row); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.convert.EntityConverter#getMappingContext() + */ + public MappingContext, CassandraPersistentProperty> getMappingContext() { + return mappingContext; + } + + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + this.spELContext = new SpELContext(this.spELContext, applicationContext); + } + + private S read(final CassandraPersistentEntity entity, final Row row) { + + final DefaultSpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(row, spELContext); + + final PropertyValueProvider propertyProvider = new CassandraPropertyValueProvider(row, evaluator); + PersistentEntityParameterValueProvider parameterProvider = new PersistentEntityParameterValueProvider( + entity, propertyProvider, null); + + EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity); + S instance = instantiator.createInstance(entity, parameterProvider); + + final BeanWrapper, S> wrapper = BeanWrapper.create(instance, conversionService); + final S result = wrapper.getBean(); + + // Set properties not already set in the constructor + entity.doWithProperties(new PropertyHandler() { + public void doWithPersistentProperty(CassandraPersistentProperty prop) { + + boolean isConstructorProperty = entity.isConstructorArgument(prop); + boolean hasValueForProperty = row.getColumnDefinitions().contains(prop.getColumnName()); + + if (!hasValueForProperty || isConstructorProperty) { + return; + } + + Object obj = propertyProvider.getPropertyValue(prop); + wrapper.setProperty(prop, obj, useFieldAccessOnly); + } + }); + + return result; + } + + public void write(Object source, Row sink) { + // TODO Auto-generated method stub + + } + + public void setUseFieldAccessOnly(boolean useFieldAccessOnly) { + this.useFieldAccessOnly = useFieldAccessOnly; + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/convert/RowReaderPropertyAccessor.java b/src/main/java/org/springframework/data/cassandra/convert/RowReaderPropertyAccessor.java new file mode 100644 index 000000000..020b6afd2 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/convert/RowReaderPropertyAccessor.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012 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.data.cassandra.convert; + +import java.nio.ByteBuffer; + +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypedValue; + +import com.datastax.driver.core.DataType; +import com.datastax.driver.core.Row; + +/** + * {@link PropertyAccessor} to read values from a {@link Row}. + * + * @author Alex Shvid + */ +enum RowReaderPropertyAccessor implements PropertyAccessor { + + INSTANCE; + + /* + * (non-Javadoc) + * @see org.springframework.expression.PropertyAccessor#getSpecificTargetClasses() + */ + public Class[] getSpecificTargetClasses() { + return new Class[] { Row.class }; + } + + /* + * (non-Javadoc) + * @see org.springframework.expression.PropertyAccessor#canRead(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String) + */ + public boolean canRead(EvaluationContext context, Object target, String name) { + return ((Row) target).getColumnDefinitions().contains(name); + } + + /* + * (non-Javadoc) + * @see org.springframework.expression.PropertyAccessor#read(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String) + */ + public TypedValue read(EvaluationContext context, Object target, String name) { + Row row = (Row) target; + if (row.isNull(name)) { + return TypedValue.NULL; + } + DataType columnType = row.getColumnDefinitions().getType(name); + ByteBuffer bytes = row.getBytes(name); + Object object = columnType.deserialize(bytes); + return new TypedValue(object); + } + + /* + * (non-Javadoc) + * @see org.springframework.expression.PropertyAccessor#canWrite(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String) + */ + public boolean canWrite(EvaluationContext context, Object target, String name) { + return false; + } + + /* + * (non-Javadoc) + * @see org.springframework.expression.PropertyAccessor#write(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String, java.lang.Object) + */ + public void write(EvaluationContext context, Object target, String name, Object newValue) { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/core/CassandraClusterFactoryBean.java b/src/main/java/org/springframework/data/cassandra/core/CassandraClusterFactoryBean.java new file mode 100644 index 000000000..94a472292 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/core/CassandraClusterFactoryBean.java @@ -0,0 +1,261 @@ +/* + * Copyright 2011-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.data.cassandra.core; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.data.cassandra.config.CompressionType; +import org.springframework.data.cassandra.config.PoolingOptionsConfig; +import org.springframework.data.cassandra.config.SocketOptionsConfig; +import org.springframework.util.StringUtils; + +import com.datastax.driver.core.AuthProvider; +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.HostDistance; +import com.datastax.driver.core.PoolingOptions; +import com.datastax.driver.core.ProtocolOptions.Compression; +import com.datastax.driver.core.SocketOptions; +import com.datastax.driver.core.policies.LoadBalancingPolicy; +import com.datastax.driver.core.policies.ReconnectionPolicy; +import com.datastax.driver.core.policies.RetryPolicy; + +/** + * Convenient factory for configuring a Cassandra Cluster. + * + * @author Alex Shvid + */ + +public class CassandraClusterFactoryBean implements FactoryBean, + InitializingBean, DisposableBean, PersistenceExceptionTranslator { + + private static final int DEFAULT_PORT = 9042; + + private Cluster cluster; + + private String contactPoints; + private int port = DEFAULT_PORT; + private CompressionType compressionType; + + private PoolingOptionsConfig localPoolingOptions; + private PoolingOptionsConfig remotePoolingOptions; + private SocketOptionsConfig socketOptions; + + private AuthProvider authProvider; + private LoadBalancingPolicy loadBalancingPolicy; + private ReconnectionPolicy reconnectionPolicy; + private RetryPolicy retryPolicy; + + private boolean metricsEnabled = true; + + private final PersistenceExceptionTranslator exceptionTranslator = new CassandraExceptionTranslator(); + + public Cluster getObject() throws Exception { + return cluster; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + public Class getObjectType() { + return Cluster.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#isSingleton() + */ + public boolean isSingleton() { + return true; + } + + /* + * (non-Javadoc) + * @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException) + */ + public DataAccessException translateExceptionIfPossible(RuntimeException ex) { + return exceptionTranslator.translateExceptionIfPossible(ex); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception { + + if (!StringUtils.hasText(contactPoints)) { + throw new IllegalArgumentException( + "at least one server is required"); + } + + Cluster.Builder builder = Cluster.builder(); + + builder.addContactPoints(StringUtils.commaDelimitedListToStringArray(contactPoints)).withPort(port); + + if (compressionType != null) { + builder.withCompression(convertCompressionType(compressionType)); + } + + if (localPoolingOptions != null) { + builder.withPoolingOptions(configPoolingOptions(HostDistance.LOCAL, localPoolingOptions)); + } + + if (remotePoolingOptions != null) { + builder.withPoolingOptions(configPoolingOptions(HostDistance.REMOTE, remotePoolingOptions)); + } + + if (socketOptions != null) { + builder.withSocketOptions(configSocketOptions(socketOptions)); + } + + if (authProvider != null) { + builder.withAuthProvider(authProvider); + } + + if (loadBalancingPolicy != null) { + builder.withLoadBalancingPolicy(loadBalancingPolicy); + } + + if (reconnectionPolicy != null) { + builder.withReconnectionPolicy(reconnectionPolicy); + } + + if (retryPolicy != null) { + builder.withRetryPolicy(retryPolicy); + } + + if (!metricsEnabled) { + builder.withoutMetrics(); + } + + Cluster cluster = builder.build(); + + // initialize property + this.cluster = cluster; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.DisposableBean#destroy() + */ + public void destroy() throws Exception { + this.cluster.shutdown(); + } + + public void setContactPoints(String contactPoints) { + this.contactPoints = contactPoints; + } + + public void setPort(int port) { + this.port = port; + } + + public void setCompressionType(CompressionType compressionType) { + this.compressionType = compressionType; + } + + public void setLocalPoolingOptions(PoolingOptionsConfig localPoolingOptions) { + this.localPoolingOptions = localPoolingOptions; + } + + public void setRemotePoolingOptions(PoolingOptionsConfig remotePoolingOptions) { + this.remotePoolingOptions = remotePoolingOptions; + } + + public void setSocketOptions(SocketOptionsConfig socketOptions) { + this.socketOptions = socketOptions; + } + + public void setAuthProvider(AuthProvider authProvider) { + this.authProvider = authProvider; + } + + public void setLoadBalancingPolicy(LoadBalancingPolicy loadBalancingPolicy) { + this.loadBalancingPolicy = loadBalancingPolicy; + } + + public void setReconnectionPolicy(ReconnectionPolicy reconnectionPolicy) { + this.reconnectionPolicy = reconnectionPolicy; + } + + public void setRetryPolicy(RetryPolicy retryPolicy) { + this.retryPolicy = retryPolicy; + } + + public void setMetricsEnabled(boolean metricsEnabled) { + this.metricsEnabled = metricsEnabled; + } + + private static Compression convertCompressionType(CompressionType type) { + switch(type) { + case none: + return Compression.NONE; + case snappy: + return Compression.SNAPPY; + } + throw new IllegalArgumentException("unknown compression type " + type); + } + + private static PoolingOptions configPoolingOptions(HostDistance hostDistance, PoolingOptionsConfig config) { + PoolingOptions poolingOptions = new PoolingOptions(); + + if (config.getMinSimultaneousRequests() != null) { + poolingOptions.setMinSimultaneousRequestsPerConnectionThreshold(hostDistance, config.getMinSimultaneousRequests()); + } + if (config.getMaxSimultaneousRequests() != null) { + poolingOptions.setMaxSimultaneousRequestsPerConnectionThreshold(hostDistance, config.getMaxSimultaneousRequests()); + } + if (config.getCoreConnections() != null) { + poolingOptions.setCoreConnectionsPerHost(hostDistance, config.getCoreConnections()); + } + if (config.getMaxConnections() != null) { + poolingOptions.setMaxConnectionsPerHost(hostDistance, config.getMaxConnections()); + } + + return poolingOptions; + } + + private static SocketOptions configSocketOptions(SocketOptionsConfig config) { + SocketOptions socketOptions = new SocketOptions(); + + if (config.getConnectTimeoutMls() != null) { + socketOptions.setConnectTimeoutMillis(config.getConnectTimeoutMls()); + } + if (config.getKeepAlive() != null) { + socketOptions.setKeepAlive(config.getKeepAlive()); + } + if (config.getReuseAddress() != null) { + socketOptions.setReuseAddress(config.getReuseAddress()); + } + if (config.getSoLinger() != null) { + socketOptions.setSoLinger(config.getSoLinger()); + } + if (config.getTcpNoDelay() != null) { + socketOptions.setTcpNoDelay(config.getTcpNoDelay()); + } + if (config.getReceiveBufferSize() != null) { + socketOptions.setReceiveBufferSize(config.getReceiveBufferSize()); + } + if (config.getSendBufferSize() != null) { + socketOptions.setSendBufferSize(config.getSendBufferSize()); + } + + return socketOptions; + } +} diff --git a/src/main/java/org/springframework/data/cassandra/core/CassandraConnectionFailureException.java b/src/main/java/org/springframework/data/cassandra/core/CassandraConnectionFailureException.java new file mode 100644 index 000000000..194113f93 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/core/CassandraConnectionFailureException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2011-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.data.cassandra.core; + +import org.springframework.dao.DataAccessResourceFailureException; + +/** + * Cassandra connection exception. + * + * @author Alex Shvid + */ +public class CassandraConnectionFailureException extends DataAccessResourceFailureException { + + public CassandraConnectionFailureException(String msg) { + super(msg); + } + + public CassandraConnectionFailureException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/core/CassandraExceptionTranslator.java b/src/main/java/org/springframework/data/cassandra/core/CassandraExceptionTranslator.java new file mode 100644 index 000000000..9c08e05ac --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/core/CassandraExceptionTranslator.java @@ -0,0 +1,52 @@ +/* + * Copyright 2010-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.data.cassandra.core; + +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.dao.support.PersistenceExceptionTranslator; + +import com.datastax.driver.core.exceptions.InvalidQueryException; + +/** + * Simple {@link PersistenceExceptionTranslator} for Cassandra. Convert the given runtime exception to an appropriate + * exception from the {@code org.springframework.dao} hierarchy. Return {@literal null} if no translation is + * appropriate: any other exception may have resulted from user code, and should not be translated. + * + * @author Alex Shvid + */ + +public class CassandraExceptionTranslator implements PersistenceExceptionTranslator { + + /* + * (non-Javadoc) + * @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException) + */ + public DataAccessException translateExceptionIfPossible(RuntimeException ex) { + + // Check for well-known Cassandra subclasses. + + if (ex instanceof InvalidQueryException) { + return new DataAccessResourceFailureException(ex.getMessage(), ex); + } + + // If we get here, we have an exception that resulted from user code, + // rather than the persistence provider, so we return null to indicate + // that translation should not occur. + return null; + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/core/CassandraKeyspaceFactoryBean.java b/src/main/java/org/springframework/data/cassandra/core/CassandraKeyspaceFactoryBean.java new file mode 100644 index 000000000..11b63a8c1 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/core/CassandraKeyspaceFactoryBean.java @@ -0,0 +1,345 @@ +/* + * Copyright 2011-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.data.cassandra.core; + +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.data.cassandra.config.KeyspaceAttributes; +import org.springframework.data.cassandra.config.TableAttributes; +import org.springframework.data.cassandra.convert.CassandraConverter; +import org.springframework.data.cassandra.convert.MappingCassandraConverter; +import org.springframework.data.cassandra.mapping.CassandraMappingContext; +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.cassandra.util.CQLUtils; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.KeyspaceMetadata; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.TableMetadata; +import com.datastax.driver.core.exceptions.NoHostAvailableException; + +/** + * Convenient factory for configuring a Cassandra Session. + * Session is a thread safe singleton and created per a keyspace. + * So, it is enough to have one session per application. + * + * @author Alex Shvid + */ + +public class CassandraKeyspaceFactoryBean implements FactoryBean, +InitializingBean, DisposableBean, BeanClassLoaderAware, PersistenceExceptionTranslator { + + private static final Logger log = LoggerFactory.getLogger(CassandraKeyspaceFactoryBean.class); + + public static final String DEFAULT_REPLICATION_STRATEGY = "SimpleStrategy"; + public static final int DEFAULT_REPLICATION_FACTOR = 1; + + private ClassLoader beanClassLoader; + + private Cluster cluster; + private Session session; + private String keyspace; + + private CassandraConverter converter; + private MappingContext, CassandraPersistentProperty> mappingContext; + + private Keyspace keyspaceBean; + + private KeyspaceAttributes keyspaceAttributes; + + private final PersistenceExceptionTranslator exceptionTranslator = new CassandraExceptionTranslator(); + + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + + public Keyspace getObject() throws Exception { + return keyspaceBean; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + public Class getObjectType() { + return Session.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#isSingleton() + */ + public boolean isSingleton() { + return true; + } + + /* + * (non-Javadoc) + * @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException) + */ + public DataAccessException translateExceptionIfPossible(RuntimeException ex) { + return exceptionTranslator.translateExceptionIfPossible(ex); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception { + + if (this.converter == null) { + this.converter = getDefaultCassandraConverter(); + } + this.mappingContext = this.converter.getMappingContext(); + + + if (cluster == null) { + throw new IllegalArgumentException( + "at least one cluster is required"); + } + + Session session = null; + session = cluster.connect(); + + if (StringUtils.hasText(keyspace)) { + + KeyspaceMetadata keyspaceMetadata = cluster.getMetadata().getKeyspace(keyspace.toLowerCase()); + boolean keyspaceExists = keyspaceMetadata != null; + boolean keyspaceCreated = false; + + if (keyspaceExists) { + log.info("keyspace exists " + keyspaceMetadata.asCQLQuery()); + } + + if (keyspaceAttributes == null) { + keyspaceAttributes = new KeyspaceAttributes(); + } + + // drop the old keyspace if needed + if (keyspaceExists && (keyspaceAttributes.isCreate() || keyspaceAttributes.isCreateDrop())) { + log.info("Drop keyspace " + keyspace + " on afterPropertiesSet"); + session.execute("DROP KEYSPACE " + keyspace); + keyspaceExists = false; + } + + // create the new keyspace if needed + if (!keyspaceExists && (keyspaceAttributes.isCreate() || keyspaceAttributes.isCreateDrop() || keyspaceAttributes.isUpdate())) { + + String query = String.format("CREATE KEYSPACE %1$s WITH replication = { 'class' : '%2$s', 'replication_factor' : %3$d } AND DURABLE_WRITES = %4$b", + keyspace, + keyspaceAttributes.getReplicationStrategy(), + keyspaceAttributes.getReplicationFactor(), + keyspaceAttributes.isDurableWrites()); + + log.info("Create keyspace " + keyspace + " on afterPropertiesSet " + query); + + session.execute(query); + keyspaceCreated = true; + } + + // update keyspace if needed + if (keyspaceAttributes.isUpdate() && !keyspaceCreated) { + + if (compareKeyspaceAttributes(keyspaceAttributes, keyspaceMetadata) != null) { + + String query = String.format("ALTER KEYSPACE %1$s WITH replication = { 'class' : '%2$s', 'replication_factor' : %3$d } AND DURABLE_WRITES = %4$b", + keyspace, + keyspaceAttributes.getReplicationStrategy(), + keyspaceAttributes.getReplicationFactor(), + keyspaceAttributes.isDurableWrites()); + + log.info("Update keyspace " + keyspace + " on afterPropertiesSet " + query); + session.execute(query); + } + + } + + // validate keyspace if needed + if (keyspaceAttributes.isValidate()) { + + if (!keyspaceExists) { + throw new InvalidDataAccessApiUsageException("keyspace '" + keyspace + "' not found in the Cassandra"); + } + + String errorField = compareKeyspaceAttributes(keyspaceAttributes, keyspaceMetadata); + if (errorField != null) { + throw new InvalidDataAccessApiUsageException(errorField + " attribute is not much in the keyspace '" + keyspace + "'"); + } + + } + + session.execute("USE " + keyspace); + + if (!CollectionUtils.isEmpty(keyspaceAttributes.getTables())) { + + for (TableAttributes tableAttributes : keyspaceAttributes.getTables()) { + + String entityClassName = tableAttributes.getEntity(); + Class entityClass = ClassUtils.forName(entityClassName, this.beanClassLoader); + CassandraPersistentEntity entity = determineEntity(entityClass); + String useTableName = tableAttributes.getName() != null ? tableAttributes.getName() : entity.getTable(); + + if (keyspaceCreated) { + createNewTable(session, useTableName, entity); + } + else if (keyspaceAttributes.isUpdate()) { + TableMetadata table = keyspaceMetadata.getTable(useTableName.toLowerCase()); + if (table == null) { + createNewTable(session, useTableName, entity); + } + else { + // alter table columns + for (String cql : CQLUtils.alterTable(useTableName, entity, table)) { + log.info("Execute on keyspace " + keyspace + " CQL " + cql); + session.execute(cql); + } + } + } + else if (keyspaceAttributes.isValidate()) { + TableMetadata table = keyspaceMetadata.getTable(useTableName.toLowerCase()); + if (table == null) { + throw new InvalidDataAccessApiUsageException("not found table " + useTableName + " for entity " + entityClassName); + } + // validate columns + List alter = CQLUtils.alterTable(useTableName, entity, table); + if (!alter.isEmpty()) { + throw new InvalidDataAccessApiUsageException("invalid table " + useTableName + " for entity " + entityClassName + ". modify it by " + alter); + } + } + + //System.out.println("tableAttributes, entityClass=" + entityClass + ", table = " + entity.getTable()); + + } + } + + } + + // initialize property + this.session = session; + + this.keyspaceBean = new Keyspace(keyspace, session, converter); + } + + + private void createNewTable(Session session, String useTableName, + CassandraPersistentEntity entity) + throws NoHostAvailableException { + String cql = CQLUtils.createTable(useTableName, entity); + log.info("Execute on keyspace " + keyspace + " CQL " + cql); + session.execute(cql); + for (String indexCQL : CQLUtils.createIndexes(useTableName, entity)) { + log.info("Execute on keyspace " + keyspace + " CQL " + indexCQL); + session.execute(indexCQL); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.DisposableBean#destroy() + */ + public void destroy() throws Exception { + + if (StringUtils.hasText(keyspace) && keyspaceAttributes != null && keyspaceAttributes.isCreateDrop()) { + log.info("Drop keyspace " + keyspace + " on destroy"); + session.execute("USE system"); + session.execute("DROP KEYSPACE " + keyspace); + } + this.session.shutdown(); + } + + public void setKeyspace(String keyspace) { + this.keyspace = keyspace; + } + + public void setCluster(Cluster cluster) { + this.cluster = cluster; + } + + public void setKeyspaceAttributes(KeyspaceAttributes keyspaceAttributes) { + this.keyspaceAttributes = keyspaceAttributes; + } + + public void setConverter(CassandraConverter converter) { + this.converter = converter; + } + + private static String compareKeyspaceAttributes(KeyspaceAttributes keyspaceAttributes, KeyspaceMetadata keyspaceMetadata) { + if (keyspaceAttributes.isDurableWrites() != keyspaceMetadata.isDurableWrites()) { + return "durableWrites"; + } + Map replication = keyspaceMetadata.getReplication(); + String replicationFactorStr = replication.get("replication_factor"); + if (replicationFactorStr == null) { + return "replication_factor"; + } + try { + int replicationFactor = Integer.parseInt(replicationFactorStr); + if (keyspaceAttributes.getReplicationFactor() != replicationFactor) { + return "replication_factor"; + } + } + catch(NumberFormatException e) { + return "replication_factor"; + } + + String attributesStrategy = keyspaceAttributes.getReplicationStrategy(); + if (attributesStrategy.indexOf('.') == -1) { + attributesStrategy = "org.apache.cassandra.locator." + attributesStrategy; + } + String replicationStrategy = replication.get("class"); + if (!attributesStrategy.equals(replicationStrategy)) { + return "replication_class"; + } + return null; + } + + CassandraPersistentEntity determineEntity(Class entityClass) { + + if (entityClass == null) { + throw new InvalidDataAccessApiUsageException( + "No class parameter provided, entity table name can't be determined!"); + } + + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + if (entity == null) { + throw new InvalidDataAccessApiUsageException("No Persitent Entity information found for the class " + + entityClass.getName()); + } + return entity; + } + + private static final CassandraConverter getDefaultCassandraConverter() { + MappingCassandraConverter converter = new MappingCassandraConverter(new CassandraMappingContext()); + converter.afterPropertiesSet(); + return converter; + } +} diff --git a/src/main/java/org/springframework/data/cassandra/core/CassandraOperations.java b/src/main/java/org/springframework/data/cassandra/core/CassandraOperations.java new file mode 100644 index 000000000..6ec9cd2c5 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/core/CassandraOperations.java @@ -0,0 +1,143 @@ +/* + * Copyright 2011-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.data.cassandra.core; + +import java.util.List; + +import org.springframework.data.cassandra.convert.CassandraConverter; + +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.querybuilder.Update; + +/** + * @author Alex Shvid + */ +public interface CassandraOperations { + + /** + * The table name used for the specified class by this template. + * + * @param entityClass must not be {@literal null}. + * @return + */ + String getTableName(Class entityClass); + + /** + * Execute query and return Cassandra ResultSet + * + * @param query must not be {@literal null}. + * @return + */ + ResultSet executeQuery(String query); + + /** + * Execute query and convert ResultSet to the list of entities + * + * @param query must not be {@literal null}. + * @param selectClass must not be {@literal null}, mapped entity type. + * @return + */ + List select(String query, Class selectClass); + + /** + * Execute query and convert ResultSet to the entity + * + * @param query must not be {@literal null}. + * @param selectClass must not be {@literal null}, mapped entity type. + * @return + */ + T selectOne(String query, Class selectClass); + + /** + * Insert the given object to the table by id. + * + * @param object + */ + void insert(Object entity); + + /** + * Insert the given object to the table by id. + * + * @param object + */ + void insert(Object entity, String tableName); + + /** + * Remove the given object from the table by id. + * + * @param object + */ + void remove(Object object); + + /** + * Removes the given object from the given table. + * + * @param object + * @param table must not be {@literal null} or empty. + */ + void remove(Object object, String tableName); + + /** + * Create a table with the name and fields indicated by the entity class + * + * @param entityClass class that determines metadata of the table to create/drop. + */ + void createTable(Class entityClass); + + /** + * Create a table with the name and fields indicated by the entity class + * + * @param entityClass class that determines metadata of the table to create/drop. + * @param tableName explicit name of the table + */ + void createTable(Class entityClass, String tableName); + + /** + * Alter table with the name and fields indicated by the entity class + * + * @param entityClass class that determines metadata of the table to create/drop. + */ + void alterTable(Class entityClass); + + /** + * Alter table with the name and fields indicated by the entity class + * + * @param entityClass class that determines metadata of the table to create/drop. + * @param tableName explicit name of the table + */ + void alterTable(Class entityClass, String tableName); + + /** + * Alter table with the name and fields indicated by the entity class + * + * @param entityClass class that determines metadata of the table to create/drop. + */ + void dropTable(Class entityClass); + + /** + * Alter table with the name and fields indicated by the entity class + * + * @param tableName explicit name of the table. + */ + void dropTable(String tableName); + + /** + * Returns the underlying {@link CassandraConverter}. + * + * @return + */ + CassandraConverter getConverter(); +} diff --git a/src/main/java/org/springframework/data/cassandra/core/CassandraSystemException.java b/src/main/java/org/springframework/data/cassandra/core/CassandraSystemException.java new file mode 100644 index 000000000..cbbbfc0da --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/core/CassandraSystemException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2011-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.data.cassandra.core; + +import org.springframework.dao.UncategorizedDataAccessException; + +/** + * Exception thrown when we can't classify a Cassandra exception into one of Spring generic data access exceptions. + * + * @author Alex Shvid + */ + +public class CassandraSystemException extends UncategorizedDataAccessException { + + public CassandraSystemException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java b/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java new file mode 100644 index 000000000..8ecf6d216 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java @@ -0,0 +1,229 @@ +/* + * Copyright 2011-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.data.cassandra.core; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.springframework.dao.DuplicateKeyException; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.data.cassandra.convert.CassandraConverter; +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.convert.EntityReader; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.util.Assert; + +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Row; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.exceptions.NoHostAvailableException; + +/** + * @author Alex Shvid + */ +public class CassandraTemplate implements CassandraOperations { + + private final Session session; + private final CassandraConverter cassandraConverter; + private final MappingContext, CassandraPersistentProperty> mappingContext; + + private final PersistenceExceptionTranslator exceptionTranslator = new CassandraExceptionTranslator(); + + /** + * Constructor used for a basic template configuration + * + * @param keyspace must not be {@literal null}. + */ + public CassandraTemplate(Keyspace keyspace) { + this.session = keyspace.getSession(); + this.cassandraConverter = keyspace.getCassandraConverter(); + this.mappingContext = this.cassandraConverter.getMappingContext(); + } + + public String getTableName(Class entityClass) { + return determineTableName(entityClass); + } + + public ResultSet executeQuery(String query) { + try { + return session.execute(query); + } catch (NoHostAvailableException e) { + throw new CassandraConnectionFailureException("no host available", e); + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e); + } + } + + public List select(String query, Class selectClass) { + return selectInternal(query, new ReadRowCallback(cassandraConverter, selectClass)); + } + + public T selectOne(String query, Class selectClass) { + return selectOneInternal(query, new ReadRowCallback(cassandraConverter, selectClass)); + } + + + public void insert(Object entity) { + // TODO Auto-generated method stub + + } + + public void insert(Object entity, String tableName) { + // TODO Auto-generated method stub + + } + + public void remove(Object object) { + // TODO Auto-generated method stub + + } + + public void remove(Object object, String tableName) { + // TODO Auto-generated method stub + + } + + public void createTable(Class entityClass) { + // TODO Auto-generated method stub + + } + + public void createTable(Class entityClass, String tableName) { + // TODO Auto-generated method stub + + } + + public void alterTable(Class entityClass) { + // TODO Auto-generated method stub + + } + + public void alterTable(Class entityClass, String tableName) { + // TODO Auto-generated method stub + + } + + public void dropTable(Class entityClass) { + // TODO Auto-generated method stub + + } + + public void dropTable(String tableName) { + // TODO Auto-generated method stub + + } + + public CassandraConverter getConverter() { + return cassandraConverter; + } + + /** + * Simple internal callback to allow operations on a {@link Row}. + * + * @author Alex Shvid + */ + + private interface RowCallback { + + T doWith(Row object); + } + + /** + * Simple {@link RowCallback} that will transform {@link Row} into the given target type using the given + * {@link EntityReader}. + * + * @author Alex Shvid + */ + private static class ReadRowCallback implements RowCallback { + + private final EntityReader reader; + private final Class type; + + public ReadRowCallback(EntityReader reader, Class type) { + Assert.notNull(reader); + Assert.notNull(type); + this.reader = reader; + this.type = type; + } + + public T doWith(Row object) { + T source = reader.read(type, object); + return source; + } + } + + List selectInternal(String query, ReadRowCallback readRowCallback) { + try { + ResultSet resultSet = session.execute(query); + List result = new ArrayList(); + Iterator iterator = resultSet.iterator(); + while(iterator.hasNext()) { + Row row = iterator.next(); + result.add(readRowCallback.doWith(row)); + } + return result; + } catch (NoHostAvailableException e) { + throw new CassandraConnectionFailureException("no host available", e); + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e); + } + } + + T selectOneInternal(String query, ReadRowCallback readRowCallback) { + try { + ResultSet resultSet = session.execute(query); + Iterator iterator = resultSet.iterator(); + if (iterator.hasNext()) { + Row row = iterator.next(); + T result = readRowCallback.doWith(row); + if (iterator.hasNext()) { + throw new DuplicateKeyException("found two or more results in query " + query); + } + return result; + } + return null; + } catch (NoHostAvailableException e) { + throw new CassandraConnectionFailureException("no host available", e); + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e); + } + } + + String determineTableName(Class entityClass) { + + if (entityClass == null) { + throw new InvalidDataAccessApiUsageException( + "No class parameter provided, entity table name can't be determined!"); + } + + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + if (entity == null) { + throw new InvalidDataAccessApiUsageException("No Persitent Entity information found for the class " + + entityClass.getName()); + } + return entity.getTable(); + } + + private RuntimeException potentiallyConvertRuntimeException( + RuntimeException ex) { + RuntimeException resolved = this.exceptionTranslator.translateExceptionIfPossible(ex); + return resolved == null ? ex : resolved; + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/core/CassandraValue.java b/src/main/java/org/springframework/data/cassandra/core/CassandraValue.java new file mode 100644 index 000000000..489d6ed54 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/core/CassandraValue.java @@ -0,0 +1,45 @@ +/* + * Copyright 2011-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.data.cassandra.core; + +import java.nio.ByteBuffer; + +import com.datastax.driver.core.DataType; + +/** + * Simple Cassandra value of the ByteBuffer with DataType + * + * @author Alex Shvid + */ +public class CassandraValue { + + private final ByteBuffer value; + private final DataType type; + + public CassandraValue(ByteBuffer value, DataType type) { + this.value = value; + this.type = type; + } + + public ByteBuffer getValue() { + return value; + } + + public DataType getType() { + return type; + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/core/Keyspace.java b/src/main/java/org/springframework/data/cassandra/core/Keyspace.java new file mode 100644 index 000000000..b3b0be483 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/core/Keyspace.java @@ -0,0 +1,58 @@ +/* + * Copyright 2011-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.data.cassandra.core; + +import org.springframework.data.cassandra.convert.CassandraConverter; + +import com.datastax.driver.core.Session; + +/** + * Simple Cassandra Keyspace object + * + * @author Alex Shvid + */ +public class Keyspace { + + private final String keyspace; + private final Session session; + private final CassandraConverter cassandraConverter; + + /** + * Constructor used for a basic keyspace configuration + * + * @param keyspace, system if {@literal null}. + * @param session must not be {@literal null}. + * @param cassandraConverter must not be {@literal null}. + */ + public Keyspace(String keyspace, Session session, CassandraConverter cassandraConverter) { + this.keyspace = keyspace; + this.session = session; + this.cassandraConverter = cassandraConverter; + } + + public String getKeyspace() { + return keyspace; + } + + public Session getSession() { + return session; + } + + public CassandraConverter getCassandraConverter() { + return cassandraConverter; + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/cql/CQLBuilder.java b/src/main/java/org/springframework/data/cassandra/cql/CQLBuilder.java new file mode 100644 index 000000000..622b20805 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/CQLBuilder.java @@ -0,0 +1,9 @@ +package org.springframework.data.cassandra.cql; + +public abstract class CQLBuilder { + + public static CreateTable createTable(String tableName) { + return null; + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/cql/CreateTable.java b/src/main/java/org/springframework/data/cassandra/cql/CreateTable.java new file mode 100644 index 000000000..19f45a674 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/CreateTable.java @@ -0,0 +1,5 @@ +package org.springframework.data.cassandra.cql; + +public class CreateTable { + +} diff --git a/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntity.java b/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntity.java new file mode 100644 index 000000000..b296beee7 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntity.java @@ -0,0 +1,120 @@ +/* + * Copyright 2011-2012 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.data.cassandra.mapping; + +import java.util.Comparator; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.expression.BeanFactoryAccessor; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.data.cassandra.util.CassandraNamingUtils; +import org.springframework.data.mapping.model.BasicPersistentEntity; +import org.springframework.data.util.TypeInformation; +import org.springframework.expression.Expression; +import org.springframework.expression.ParserContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.util.StringUtils; + +/** + * Cassandra specific {@link BasicPersistentEntity} implementation that adds Cassandra specific meta-data such as the + * table name. + * + * @author Alex Shvid + */ +public class BasicCassandraPersistentEntity extends BasicPersistentEntity implements +CassandraPersistentEntity, ApplicationContextAware { + + private final String table; + private final SpelExpressionParser parser; + private final StandardEvaluationContext context; + + /** + * Creates a new {@link BasicCassandraPersistentEntity} with the given {@link TypeInformation}. Will default the + * table name to the entities simple type name. + * + * @param typeInformation + */ + public BasicCassandraPersistentEntity(TypeInformation typeInformation) { + + super(typeInformation, CassandraPersistentPropertyComparator.INSTANCE); + + this.parser = new SpelExpressionParser(); + this.context = new StandardEvaluationContext(); + + Class rawType = typeInformation.getType(); + String fallback = CassandraNamingUtils.getPreferredTableName(rawType); + + if (rawType.isAnnotationPresent(Table.class)) { + Table d = rawType.getAnnotation(Table.class); + this.table = StringUtils.hasText(d.name()) ? d.name() : fallback; + } else { + this.table = fallback; + } + } + + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + + context.addPropertyAccessor(new BeanFactoryAccessor()); + context.setBeanResolver(new BeanFactoryResolver(applicationContext)); + context.setRootObject(applicationContext); + } + + /** + * Returns the table the entity shall be persisted to. + * + * @return + */ + public String getTable() { + Expression expression = parser.parseExpression(table, ParserContext.TEMPLATE_EXPRESSION); + return expression.getValue(context, String.class); + } + + /** + * {@link Comparator} implementation inspecting the {@link CassandraPersistentProperty}'s order. + * + * @author Alex Shvid + */ + static enum CassandraPersistentPropertyComparator implements Comparator { + + INSTANCE; + + /* + * (non-Javadoc) + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + public int compare(CassandraPersistentProperty o1, CassandraPersistentProperty o2) { + + if (o1.isColumnId()) { + return 1; + } + + if (o2.isColumnId()) { + return -1; + } + + return o1.getColumnName().compareTo(o2.getColumnName()); + + } + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentProperty.java b/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentProperty.java new file mode 100644 index 000000000..d66959b5b --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentProperty.java @@ -0,0 +1,183 @@ +/* + * Copyright 2011-2012 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.data.cassandra.mapping; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Set; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.mapping.Association; +import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; +import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.util.TypeInformation; +import org.springframework.util.StringUtils; + +import com.datastax.driver.core.DataType; + +/** + * Cassandra specific {@link org.springframework.data.mapping.model.AnnotationBasedPersistentProperty} implementation. + * + * @author Alex Shvid + */ +public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentProperty implements +CassandraPersistentProperty { + + /** + * Creates a new {@link BasicCassandraPersistentProperty}. + * + * @param field + * @param propertyDescriptor + * @param owner + * @param simpleTypeHolder + */ + public BasicCassandraPersistentProperty(Field field, PropertyDescriptor propertyDescriptor, + CassandraPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { + super(field, propertyDescriptor, owner, simpleTypeHolder); + } + + /** + * Also considers fields that has a RowId annotation. + * + */ + @Override + public boolean isIdProperty() { + + if (super.isIdProperty()) { + return true; + } + + return getField().isAnnotationPresent(RowId.class); + } + + /** + * For dynamic tables returns true if property value is used as column name. + * + * @return + */ + public boolean isColumnId() { + return getField().isAnnotationPresent(ColumnId.class); + } + + /** + * Returns the column name to be used to store the value of the property inside the Cassandra. + * + * @return + */ + public String getColumnName() { + Column annotation = getField().getAnnotation(Column.class); + return annotation != null && StringUtils.hasText(annotation.value()) ? annotation.value() : field.getName(); + } + + /** + * Returns the data type information if exists. + * + * @return + */ + public DataType getDataType() { + Qualify annotation = getField().getAnnotation(Qualify.class); + if (annotation != null && annotation.type() != null) { + return qualifyAnnotatedType(annotation); + } + if (isMap()) { + List> args = getTypeInformation().getTypeArguments(); + ensureTypeArguments(args.size(), 2); + return DataType.map(autodetectPrimitiveType(args.get(0).getType()), autodetectPrimitiveType(args.get(1).getType())); + } + if (isCollectionLike()) { + List> args = getTypeInformation().getTypeArguments(); + ensureTypeArguments(args.size(), 1); + if (Set.class.isAssignableFrom(getType())) { + return DataType.set(autodetectPrimitiveType(args.get(0).getType())); + } + else if (List.class.isAssignableFrom(getType())) { + return DataType.list(autodetectPrimitiveType(args.get(0).getType())); + } + } + DataType dataType = CassandraSimpleTypes.autodetectPrimitive(this.getType()); + if (dataType == null) { + throw new InvalidDataAccessApiUsageException("only primitive types and Set,List,Map collections are allowed, unknown type for property '" + this.getName() + "' type is '" + this.getType() + "' in the entity " + this.getOwner().getName()); + } + return dataType; + } + + private DataType qualifyAnnotatedType(Qualify annotation) { + DataType.Name type = annotation.type(); + if (type.isCollection()) { + switch(type) { + case MAP: + ensureTypeArguments(annotation.typeArguments().length, 2); + return DataType.map(resolvePrimitiveType(annotation.typeArguments()[0]), + resolvePrimitiveType(annotation.typeArguments()[1])); + case LIST: + ensureTypeArguments(annotation.typeArguments().length, 1); + return DataType.list(resolvePrimitiveType(annotation.typeArguments()[0])); + case SET: + ensureTypeArguments(annotation.typeArguments().length, 1); + return DataType.set(resolvePrimitiveType(annotation.typeArguments()[0])); + default: + throw new InvalidDataAccessApiUsageException("unknown collection DataType for property '" + this.getName() + "' type is '" + this.getType() + "' in the entity " + this.getOwner().getName()); + } + } + else { + return CassandraSimpleTypes.resolvePrimitive(type); + } + } + + /** + * Returns true if the property has secondary index on this column. + * + * @return + */ + public boolean isIndexed() { + return getField().isAnnotationPresent(Index.class); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.AbstractPersistentProperty#createAssociation() + */ + @Override + protected Association createAssociation() { + return new Association(this, null); + } + + DataType resolvePrimitiveType(DataType.Name typeName) { + DataType dataType = CassandraSimpleTypes.resolvePrimitive(typeName); + if (dataType == null) { + throw new InvalidDataAccessApiUsageException("only primitive types are allowed inside collections for the property '" + this.getName() + "' type is '" + this.getType() + "' in the entity " + this.getOwner().getName()); + } + return dataType; + } + + DataType autodetectPrimitiveType(Class javaType) { + DataType dataType = CassandraSimpleTypes.autodetectPrimitive(javaType); + if (dataType == null) { + throw new InvalidDataAccessApiUsageException("only primitive types are allowed inside collections for the property '" + this.getName() + "' type is '" + this.getType() + "' in the entity " + this.getOwner().getName()); + } + return dataType; + } + + void ensureTypeArguments(int args, int expected) { + if (args != expected) { + throw new InvalidDataAccessApiUsageException("expected " + expected + " of typed arguments for the property '" + this.getName() + "' type is '" + this.getType() + "' in the entity " + this.getOwner().getName()); + } + } + +} + + \ No newline at end of file diff --git a/src/main/java/org/springframework/data/cassandra/mapping/CachingCassandraPersistentProperty.java b/src/main/java/org/springframework/data/cassandra/mapping/CachingCassandraPersistentProperty.java new file mode 100644 index 000000000..c46987e24 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/mapping/CachingCassandraPersistentProperty.java @@ -0,0 +1,104 @@ +/* + * Copyright 2011-2012 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.data.cassandra.mapping; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; + +import org.springframework.data.mapping.model.SimpleTypeHolder; + + +/** + * {@link CassandraPersistentProperty} caching access to {@link #isIdProperty()} and {@link #getColumnName()}. + * + * @author Alex Shvid + */ +public class CachingCassandraPersistentProperty extends BasicCassandraPersistentProperty { + + private Boolean isIdProperty; + private Boolean isColumnId; + private String columnName; + private Boolean isIndexed; + + /** + * Creates a new {@link CachingCassandraPersistentProperty}. + * + * @param field + * @param propertyDescriptor + * @param owner + * @param simpleTypeHolder + */ + public CachingCassandraPersistentProperty(Field field, PropertyDescriptor propertyDescriptor, + CassandraPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { + super(field, propertyDescriptor, owner, simpleTypeHolder); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.mapping.BasicCassandraPersistentProperty#isIdProperty() + */ + @Override + public boolean isIdProperty() { + + if (this.isIdProperty == null) { + this.isIdProperty = super.isIdProperty(); + } + + return this.isIdProperty; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.mapping.BasicCassandraPersistentProperty#isColumnId() + */ + @Override + public boolean isColumnId() { + + if (this.isColumnId == null) { + this.isColumnId = super.isColumnId(); + } + + return this.isColumnId; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.mapping.BasicCassandraPersistentProperty#getFieldName() + */ + @Override + public String getColumnName() { + + if (this.columnName == null) { + this.columnName = super.getColumnName(); + } + + return this.columnName; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.mapping.BasicCassandraPersistentProperty#isIndexed() + */ + @Override + public boolean isIndexed() { + + if (this.isIndexed == null) { + this.isIndexed = super.isIndexed(); + } + + return this.isIndexed; + } +} diff --git a/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java b/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java new file mode 100644 index 000000000..5c1b14ea6 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java @@ -0,0 +1,84 @@ +/* + * Copyright 2011-2012 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.data.cassandra.mapping; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.data.mapping.context.AbstractMappingContext; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.util.TypeInformation; + +/** + * Default implementation of a {@link MappingContext} for Cassandra using {@link BasicCassandraPersistentEntity} and + * {@link BasicCassandraPersistentProperty} as primary abstractions. + * + * @author Alex Shvid + */ +public class CassandraMappingContext extends AbstractMappingContext, CassandraPersistentProperty> +implements ApplicationContextAware { + + private ApplicationContext context; + + /** + * Creates a new {@link CassandraMappingContext}. + */ + public CassandraMappingContext() { + setSimpleTypeHolder(CassandraSimpleTypes.HOLDER); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.AbstractMappingContext#createPersistentProperty(java.lang.reflect.Field, java.beans.PropertyDescriptor, org.springframework.data.mapping.MutablePersistentEntity, org.springframework.data.mapping.SimpleTypeHolder) + */ + @Override + public CassandraPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, + BasicCassandraPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { + return new CachingCassandraPersistentProperty(field, descriptor, owner, simpleTypeHolder); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.BasicMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation, org.springframework.data.mapping.model.MappingContext) + */ + @Override + protected BasicCassandraPersistentEntity createPersistentEntity(TypeInformation typeInformation) { + + BasicCassandraPersistentEntity entity = new BasicCassandraPersistentEntity(typeInformation); + + if (context != null) { + entity.setApplicationContext(context); + } + + return entity; + } + + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + + this.context = applicationContext; + super.setApplicationContext(applicationContext); + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentEntity.java b/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentEntity.java new file mode 100644 index 000000000..a6f9f5fac --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentEntity.java @@ -0,0 +1,34 @@ +/* + * Copyright 2011-2012 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.data.cassandra.mapping; + +import org.springframework.data.mapping.PersistentEntity; + +/** + * Cassandra specific {@link PersistentEntity} abstraction. + * + * @author Alex Shvid + */ +public interface CassandraPersistentEntity extends PersistentEntity { + + /** + * Returns the table the entity shall be persisted to. + * + * @return + */ + String getTable(); + +} diff --git a/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentProperty.java b/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentProperty.java new file mode 100644 index 000000000..47b8993d6 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/mapping/CassandraPersistentProperty.java @@ -0,0 +1,57 @@ +/* + * Copyright 2011-2012 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.data.cassandra.mapping; + +import org.springframework.data.mapping.PersistentProperty; + +import com.datastax.driver.core.DataType; + +/** + * Cassandra specific {@link org.springframework.data.mapping.PersistentProperty} extension. + * + * @author Alex Shvid + */ +public interface CassandraPersistentProperty extends PersistentProperty { + + /** + * For dynamic tables returns true if property value is used as column name. + * + * @return + */ + boolean isColumnId(); + + /** + * Returns the name of the field a property is persisted to. + * + * @return + */ + String getColumnName(); + + /** + * Returns the data type. + * + * @return + */ + DataType getDataType(); + + /** + * Returns true if the property has secondary index on this column. + * + * @return + */ + boolean isIndexed(); + +} diff --git a/src/main/java/org/springframework/data/cassandra/mapping/CassandraSimpleTypes.java b/src/main/java/org/springframework/data/cassandra/mapping/CassandraSimpleTypes.java new file mode 100644 index 000000000..c62c15e7c --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/mapping/CassandraSimpleTypes.java @@ -0,0 +1,97 @@ +/* + * Copyright 2010-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.data.cassandra.mapping; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.util.TypeInformation; + +import com.datastax.driver.core.DataType; + +/** + * Simple constant holder for a {@link SimpleTypeHolder} enriched with Cassandra specific simple types. + * + * @author Alex Shvid + */ +public class CassandraSimpleTypes { + + private static final Map, Class> primitiveWrapperTypeMap = new HashMap, Class>(8); + + private static final Map, DataType> javaClassToDataType = new HashMap, DataType>(); + + private static final Map nameToDataType = new HashMap(); + + static { + + primitiveWrapperTypeMap.put(Boolean.class, boolean.class); + primitiveWrapperTypeMap.put(Byte.class, byte.class); + primitiveWrapperTypeMap.put(Character.class, char.class); + primitiveWrapperTypeMap.put(Double.class, double.class); + primitiveWrapperTypeMap.put(Float.class, float.class); + primitiveWrapperTypeMap.put(Integer.class, int.class); + primitiveWrapperTypeMap.put(Long.class, long.class); + primitiveWrapperTypeMap.put(Short.class, short.class); + + Set> simpleTypes = new HashSet>(); + for (DataType dataType : DataType.allPrimitiveTypes()) { + simpleTypes.add(dataType.asJavaClass()); + Class javaClass = dataType.asJavaClass(); + javaClassToDataType.put(javaClass, dataType); + Class primitiveJavaClass = primitiveWrapperTypeMap.get(javaClass); + if (primitiveJavaClass != null) { + javaClassToDataType.put(primitiveJavaClass, dataType); + } + nameToDataType.put(dataType.getName(), dataType); + } + javaClassToDataType.put(String.class, DataType.text()); + CASSANDRA_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes); + } + + private static final Set> CASSANDRA_SIMPLE_TYPES; + public static final SimpleTypeHolder HOLDER = new SimpleTypeHolder(CASSANDRA_SIMPLE_TYPES, true); + + private CassandraSimpleTypes() { + } + + public static DataType resolvePrimitive(DataType.Name name) { + return nameToDataType.get(name); + } + + public static DataType autodetectPrimitive(Class javaClass) { + return javaClassToDataType.get(javaClass); + } + + public static DataType.Name[] convertPrimitiveTypeArguments(List> arguments) { + DataType.Name[] result = new DataType.Name[arguments.size()]; + for (int i = 0; i != result.length; ++i) { + TypeInformation type = arguments.get(i); + DataType dataType = autodetectPrimitive(type.getType()); + if (dataType == null) { + throw new InvalidDataAccessApiUsageException("not found appropriate primitive DataType for type = '" + type.getType()); + } + result[i] = dataType.getName(); + } + return result; + } + +} diff --git a/src/main/java/org/springframework/data/cassandra/mapping/Column.java b/src/main/java/org/springframework/data/cassandra/mapping/Column.java new file mode 100644 index 000000000..a450afa36 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/mapping/Column.java @@ -0,0 +1,23 @@ +package org.springframework.data.cassandra.mapping; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Annotation to define custom metadata for document fields. + * + * @author Alex Shvid + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Column { + + /** + * The name of the column in the table. + * + * @return + */ + String value() default ""; + +} diff --git a/src/main/java/org/springframework/data/cassandra/mapping/ColumnId.java b/src/main/java/org/springframework/data/cassandra/mapping/ColumnId.java new file mode 100644 index 000000000..af58f2bc5 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/mapping/ColumnId.java @@ -0,0 +1,33 @@ +/* + * Copyright 2010-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.data.cassandra.mapping; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Uses in dynamic tables where column names are values of this field. + * Usually it is a Date/Time field or UUIDTime field. + * + * @author Alex Shvid + */ +@Retention(value=RetentionPolicy.RUNTIME) +@Target(value={ElementType.FIELD,ElementType.METHOD,ElementType.ANNOTATION_TYPE}) +public @interface ColumnId { + +} diff --git a/src/main/java/org/springframework/data/cassandra/mapping/DataTypeInformation.java b/src/main/java/org/springframework/data/cassandra/mapping/DataTypeInformation.java new file mode 100644 index 000000000..ca3c776f4 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/mapping/DataTypeInformation.java @@ -0,0 +1,75 @@ +/* + * Copyright 2010-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.data.cassandra.mapping; + +import com.datastax.driver.core.DataType; + +/** + * Uses to transfer DataType and attributes for the property. + * + * @author Alex Shvid + */ +public class DataTypeInformation { + + public static DataType.Name[] EMPTY_ATTRIBUTES = {}; + + private DataType.Name typeName; + private DataType.Name[] typeAttributes; + + public DataTypeInformation(DataType.Name typeName) { + this(typeName, EMPTY_ATTRIBUTES); + } + + public DataTypeInformation(DataType.Name typeName, DataType.Name[] typeAttributes) { + this.typeName = typeName; + this.typeAttributes = typeAttributes; + } + + public DataType.Name getTypeName() { + return typeName; + } + + public void setTypeName(DataType.Name typeName) { + this.typeName = typeName; + } + + public DataType.Name[] getTypeAttributes() { + return typeAttributes; + } + + public void setTypeAttributes(DataType.Name[] typeAttributes) { + this.typeAttributes = typeAttributes; + } + + public String toCQL() { + if (typeAttributes.length == 0) { + return typeName.name(); + } + else { + StringBuilder str = new StringBuilder(); + str.append(typeName.name()); + str.append('<'); + for (int i = 0; i != typeAttributes.length; ++i) { + if (i != 0) { + str.append(','); + } + str.append(typeAttributes[i].name()); + } + str.append('>'); + return str.toString(); + } + } +} diff --git a/src/main/java/org/springframework/data/cassandra/mapping/Index.java b/src/main/java/org/springframework/data/cassandra/mapping/Index.java new file mode 100644 index 000000000..b1571f7c2 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/mapping/Index.java @@ -0,0 +1,35 @@ +/* + * Copyright 2010-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.data.cassandra.mapping; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Identifies a secondary index in the table. Usually it is a field with common dublicate values + * for the hole table. such as city, place, educationType, state flags ant etc. + * + * Using unique fields is not common and has overhead, such as email, username and etc. + * + * @author Alex Shvid + */ +@Retention(value=RetentionPolicy.RUNTIME) +@Target(value={ElementType.FIELD,ElementType.METHOD,ElementType.ANNOTATION_TYPE}) +public @interface Index { + +} diff --git a/src/main/java/org/springframework/data/cassandra/mapping/Qualify.java b/src/main/java/org/springframework/data/cassandra/mapping/Qualify.java new file mode 100644 index 000000000..456bf4d99 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/mapping/Qualify.java @@ -0,0 +1,37 @@ +/* + * Copyright 2010-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.data.cassandra.mapping; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import com.datastax.driver.core.DataType; + +/** + * Qualifies data type as Cassandra type. + * + * @author Alex Shvid + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Qualify { + + DataType.Name type(); + + DataType.Name[] typeArguments() default {}; + +} diff --git a/src/main/java/org/springframework/data/cassandra/mapping/RowId.java b/src/main/java/org/springframework/data/cassandra/mapping/RowId.java new file mode 100644 index 000000000..0619add42 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/mapping/RowId.java @@ -0,0 +1,35 @@ +/* + * Copyright 2010-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.data.cassandra.mapping; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.data.annotation.Id; + +/** + * Identifies row ID in the Cassandra table. Same as @org.springframework.data.annotation.Id + * + * @author Alex Shvid + */ +@Retention(value=RetentionPolicy.RUNTIME) +@Target(value={ElementType.FIELD,ElementType.METHOD,ElementType.ANNOTATION_TYPE}) +@Id +public @interface RowId { + +} diff --git a/src/main/java/org/springframework/data/cassandra/mapping/Table.java b/src/main/java/org/springframework/data/cassandra/mapping/Table.java new file mode 100644 index 000000000..62a067935 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/mapping/Table.java @@ -0,0 +1,39 @@ +/* + * Copyright 2010-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.data.cassandra.mapping; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.data.annotation.Persistent; + +/** + * Identifies a domain object to be persisted to Cassandra as a table. + * + * @author Alex Shvid + */ +@Persistent +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE }) +public @interface Table { + + String name() default ""; + +} diff --git a/src/main/java/org/springframework/data/cassandra/util/CQLUtils.java b/src/main/java/org/springframework/data/cassandra/util/CQLUtils.java new file mode 100644 index 000000000..7146a4987 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/util/CQLUtils.java @@ -0,0 +1,178 @@ +package org.springframework.data.cassandra.util; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; +import org.springframework.data.mapping.PropertyHandler; + +import com.datastax.driver.core.ColumnMetadata; +import com.datastax.driver.core.DataType; +import com.datastax.driver.core.TableMetadata; + + +public abstract class CQLUtils { + + public static String createTable(String tableName, final CassandraPersistentEntity entity) { + + final StringBuilder str = new StringBuilder(); + str.append("CREATE TABLE "); + str.append(tableName); + str.append('('); + + final List ids = new ArrayList(); + final List idColumns = new ArrayList(); + + entity.doWithProperties(new PropertyHandler() { + public void doWithPersistentProperty(CassandraPersistentProperty prop) { + + if (str.charAt(str.length()-1) != '(') { + str.append(','); + } + + String columnName = prop.getColumnName(); + + str.append(columnName); + str.append(' '); + + DataType dataType = prop.getDataType(); + + str.append(toCQL(dataType)); + + if (prop.isIdProperty()) { + ids.add(prop.getColumnName()); + } + + if (prop.isColumnId()) { + idColumns.add(prop.getColumnName()); + } + + } + + }); + + if (ids.isEmpty()) { + throw new InvalidDataAccessApiUsageException("not found primary ID in the entity " + entity.getType()); + } + + str.append(",PRIMARY KEY("); + + if (ids.size() > 1) { + str.append('('); + } + + for (String id: ids) { + if (str.charAt(str.length()-1) != '(') { + str.append(','); + } + str.append(id); + } + + if (ids.size() > 1) { + str.append(')'); + } + + for (String id: idColumns) { + str.append(','); + str.append(id); + } + + str.append("));"); + + + return str.toString(); + } + + public static List createIndexes(final String tableName, final CassandraPersistentEntity entity) { + final List result = new ArrayList(); + + entity.doWithProperties(new PropertyHandler() { + public void doWithPersistentProperty(CassandraPersistentProperty prop) { + + if (prop.isIndexed()) { + + final StringBuilder str = new StringBuilder(); + str.append("CREATE INDEX ON "); + str.append(tableName); + str.append(" ("); + str.append(prop.getColumnName()); + str.append(");"); + + result.add(str.toString()); + } + + } + }); + + + return result; + } + + public static List alterTable(final String tableName, final CassandraPersistentEntity entity, final TableMetadata table) { + final List result = new ArrayList(); + + entity.doWithProperties(new PropertyHandler() { + public void doWithPersistentProperty(CassandraPersistentProperty prop) { + + String columnName = prop.getColumnName(); + DataType columnDataType = prop.getDataType(); + ColumnMetadata columnMetadata = table.getColumn(columnName.toLowerCase()); + + if (columnMetadata != null && columnDataType.equals(columnMetadata.getType())) { + return; + } + + final StringBuilder str = new StringBuilder(); + str.append("ALTER TABLE "); + str.append(tableName); + if (columnMetadata == null) { + str.append(" ADD "); + } + else { + str.append(" ALTER "); + } + + str.append(columnName); + str.append(' '); + + if (columnMetadata != null) { + str.append("TYPE "); + } + + str.append(toCQL(columnDataType)); + + str.append(';'); + result.add(str.toString()); + + } + }); + + + //System.out.println("CQL=" + table.asCQLQuery()); + + return result; + } + + public static String toCQL(DataType dataType) { + if (dataType.getTypeArguments().isEmpty()) { + return dataType.getName().name(); + } + else { + StringBuilder str = new StringBuilder(); + str.append(dataType.getName().name()); + str.append('<'); + for (DataType argDataType : dataType.getTypeArguments()) { + if (str.charAt(str.length()-1) != '<') { + str.append(','); + } + str.append(argDataType.getName().name()); + } + str.append('>'); + return str.toString(); + } + } + + +} diff --git a/src/main/java/org/springframework/data/cassandra/util/CassandraNamingUtils.java b/src/main/java/org/springframework/data/cassandra/util/CassandraNamingUtils.java new file mode 100644 index 000000000..e8ab0ce55 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/util/CassandraNamingUtils.java @@ -0,0 +1,43 @@ +/* + * Copyright 2011 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.data.cassandra.util; + + +/** + * Helper class featuring helper methods for working with Cassandra tables. + * Mainly intended for internal use within the framework. + * + * @author Alex Shvid + */ +public abstract class CassandraNamingUtils { + + /** + * Private constructor to prevent instantiation. + */ + private CassandraNamingUtils() { + } + + /** + * Obtains the table name to use for the provided class + * + * @param entityClass The class to determine the preferred table name for + * @return The preferred collection name + */ + public static String getPreferredTableName(Class entityClass) { + return entityClass.getSimpleName().toLowerCase(); + } + +} diff --git a/src/main/resources/META-INF/spring.handlers b/src/main/resources/META-INF/spring.handlers new file mode 100644 index 000000000..8a812d6ba --- /dev/null +++ b/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.springframework.org/schema/data/cassandra=org.springframework.data.cassandra.config.CassandraNamespaceHandler diff --git a/src/main/resources/META-INF/spring.schemas b/src/main/resources/META-INF/spring.schemas new file mode 100644 index 000000000..7cd18a56c --- /dev/null +++ b/src/main/resources/META-INF/spring.schemas @@ -0,0 +1,2 @@ +http\://www.springframework.org/schema/data/cassandra/spring-cassandra-1.0.xsd=org/springframework/data/cassandra/config/spring-cassandra-1.0.xsd +http\://www.springframework.org/schema/data/cassandra/spring-cassandra.xsd=org/springframework/data/cassandra/config/spring-cassandra-1.0.xsd \ No newline at end of file diff --git a/src/main/resources/META-INF/spring.tooling b/src/main/resources/META-INF/spring.tooling new file mode 100644 index 000000000..16f8213f1 --- /dev/null +++ b/src/main/resources/META-INF/spring.tooling @@ -0,0 +1,4 @@ +# Tooling related information for the jms namespace +http\://www.springframework.org/schema/data/cassnadra@name=Cassandra Namespace +http\://www.springframework.org/schema/data/cassandra@prefix=cassandra +http\://www.springframework.org/schema/data/cassnadra@icon=org/springframework/data/cassandra/config/spring-cassandra.gif diff --git a/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra-1.0.xsd b/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra-1.0.xsd new file mode 100644 index 000000000..334ce7274 --- /dev/null +++ b/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra-1.0.xsd @@ -0,0 +1,404 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The name of the Cassandra Cluster definition (by + default "cassandra-cluster") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The name of the Keyspace definition (by default + "cassandra-keyspace") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra.gif b/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra.gif new file mode 100644 index 000000000..20ed1f9a4 Binary files /dev/null and b/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra.gif differ diff --git a/src/test/java/org/springframework/data/cassandra/config/CassandraNamespaceTests.java b/src/test/java/org/springframework/data/cassandra/config/CassandraNamespaceTests.java new file mode 100644 index 000000000..339b9b300 --- /dev/null +++ b/src/test/java/org/springframework/data/cassandra/config/CassandraNamespaceTests.java @@ -0,0 +1,35 @@ +package org.springframework.data.cassandra.config; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.data.cassandra.core.Keyspace; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.Assert; + +import com.datastax.driver.core.Cluster; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class CassandraNamespaceTests { + + @Autowired + private ApplicationContext ctx; + + @Test + public void testSingleton() throws Exception { + Object cluster = ctx.getBean("cassandra-cluster"); + Assert.notNull(cluster); + Assert.isInstanceOf(Cluster.class, cluster); + Object ks = ctx.getBean("cassandra-keyspace"); + Assert.notNull(ks); + Assert.isInstanceOf(Keyspace.class, ks); + + + Cluster c = (Cluster) cluster; + System.out.println(org.apache.commons.beanutils.BeanUtils.describe(c.getConfiguration())); + } + +} diff --git a/src/test/java/org/springframework/data/cassandra/config/DriverTests.java b/src/test/java/org/springframework/data/cassandra/config/DriverTests.java new file mode 100644 index 000000000..ac513b805 --- /dev/null +++ b/src/test/java/org/springframework/data/cassandra/config/DriverTests.java @@ -0,0 +1,28 @@ +package org.springframework.data.cassandra.config; + +import org.junit.Test; + +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.Session; + +public class DriverTests { + + @Test + public void test() throws Exception { + + Cluster.Builder builder = Cluster.builder().addContactPoint("127.0.0.1"); + + //builder.withCompression(ProtocolOptions.Compression.SNAPPY); + + Cluster cluster = builder.build(); + + Session session = cluster.connect(); + + session.shutdown(); + + cluster.shutdown(); + + + } + +} diff --git a/src/test/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntityUnitTests.java b/src/test/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntityUnitTests.java new file mode 100644 index 000000000..b44ba4cba --- /dev/null +++ b/src/test/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntityUnitTests.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * 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.data.cassandra.mapping; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.when; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.context.ApplicationContext; +import org.springframework.data.util.ClassTypeInformation; + + +/** + * Unit tests for {@link BasicCassandraPersistentEntity}. + * + * @author Alex Shvid + */ +@RunWith(MockitoJUnitRunner.class) +public class BasicCassandraPersistentEntityUnitTests { + + + @Mock + ApplicationContext context; + + @Test + public void subclassInheritsAtDocumentAnnotation() { + + BasicCassandraPersistentEntity entity = new BasicCassandraPersistentEntity( + ClassTypeInformation.from(Notification.class)); + assertThat(entity.getTable(), is("messages")); + } + + @Test + public void evaluatesSpELExpression() { + + BasicCassandraPersistentEntity entity = new BasicCassandraPersistentEntity( + ClassTypeInformation.from(Area.class)); + assertThat(entity.getTable(), is("123")); + } + + @Test + public void collectionAllowsReferencingSpringBean() { + + MappingBean bean = new MappingBean(); + bean.userLine = "user_line"; + + when(context.getBean("mappingBean")).thenReturn(bean); + when(context.containsBean("mappingBean")).thenReturn(true); + + BasicCassandraPersistentEntity entity = new BasicCassandraPersistentEntity( + ClassTypeInformation.from(UserLine.class)); + entity.setApplicationContext(context); + + assertThat(entity.getTable(), is("user_line")); + } + + @Table(name = "messages") + class Message { + + } + + class Notification extends Message { + + } + + @Table(name = "#{123}") + class Area { + + } + + @Table(name = "#{mappingBean.userLine}") + class UserLine { + + } + + class MappingBean { + + String userLine; + + public String getUserLine() { + return userLine; + } + } + +} diff --git a/src/test/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentPropertyUnitTests.java b/src/test/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentPropertyUnitTests.java new file mode 100644 index 000000000..708c877fb --- /dev/null +++ b/src/test/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentPropertyUnitTests.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * 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.data.cassandra.mapping; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.lang.reflect.Field; +import java.util.Date; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.annotation.Id; +import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.util.ReflectionUtils; + + +/** + * Unit test for {@link BasicCassandraPersistentProperty}. + * + * @author Alex Shvid + */ +public class BasicCassandraPersistentPropertyUnitTests { + + CassandraPersistentEntity entity; + + @Before + public void setup() { + entity = new BasicCassandraPersistentEntity(ClassTypeInformation.from(Timeline.class)); + } + + @Test + public void usesAnnotatedColumnName() { + + Field field = ReflectionUtils.findField(Timeline.class, "text"); + assertThat(getPropertyFor(field).getColumnName(), is("message")); + } + + @Test + public void checksIdProperty() { + Field field = ReflectionUtils.findField(Timeline.class, "id"); + CassandraPersistentProperty property = getPropertyFor(field); + assertThat(property.isIdProperty(), is(true)); + } + + @Test + public void returnsPropertyNameForUnannotatedProperties() { + Field field = ReflectionUtils.findField(Timeline.class, "time"); + assertThat(getPropertyFor(field).getColumnName(), is("time")); + } + + @Test + public void checksColumnIdProperty() { + CassandraPersistentProperty property = getPropertyFor(ReflectionUtils.findField(Timeline.class, "time")); + assertThat(property.isColumnId(), is(true)); + } + + private CassandraPersistentProperty getPropertyFor(Field field) { + return new BasicCassandraPersistentProperty(field, null, entity, new SimpleTypeHolder()); + } + + class Timeline { + + @Id + String id; + + @ColumnId + Date time; + + @Column("message") + String text; + + } + +} diff --git a/src/test/java/org/springframework/data/cassandra/test/Comment.java b/src/test/java/org/springframework/data/cassandra/test/Comment.java new file mode 100644 index 000000000..7077baa4e --- /dev/null +++ b/src/test/java/org/springframework/data/cassandra/test/Comment.java @@ -0,0 +1,114 @@ +/* + * Copyright 2010-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.data.cassandra.test; + +import java.util.Date; +import java.util.Set; + +import org.springframework.data.annotation.Id; +import org.springframework.data.cassandra.mapping.ColumnId; +import org.springframework.data.cassandra.mapping.Qualify; +import org.springframework.data.cassandra.mapping.Table; + +import com.datastax.driver.core.DataType; + +/** + * This is an example of dynamic table that creates each time new column + * with Post timestamp annotated by @ColumnId. + * + * It is possible to use a static table for posts and identify them by PostId(UUID), + * but in this case we need to use MapReduce for Big Data to find posts for + * particular user, so it is better to have index (userId) -> index (post time) + * architecture. It helps a lot to build eventually a search index for the particular user. + * + * @author Alex Shvid + */ +@Table(name = "comments") +public class Comment { + + /* + * Primary Row ID + */ + @Id + private String author; + + /* + * Column ID + */ + @ColumnId + @Qualify(type=DataType.Name.TIMESTAMP) + private Date time; + + private String text; + + @Qualify(type=DataType.Name.SET, typeArguments={DataType.Name.TEXT}) + private Set likes; + + /* + * Reference to the Post + */ + private String postAuthor; + private Date postTime; + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public Date getTime() { + return time; + } + + public void setTime(Date time) { + this.time = time; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public Set getLikes() { + return likes; + } + + public void setLikes(Set likes) { + this.likes = likes; + } + + public String getPostAuthor() { + return postAuthor; + } + + public void setPostAuthor(String postAuthor) { + this.postAuthor = postAuthor; + } + + public Date getPostTime() { + return postTime; + } + + public void setPostTime(Date postTime) { + this.postTime = postTime; + } + +} diff --git a/src/test/java/org/springframework/data/cassandra/test/Notification.java b/src/test/java/org/springframework/data/cassandra/test/Notification.java new file mode 100644 index 000000000..d9bb678cd --- /dev/null +++ b/src/test/java/org/springframework/data/cassandra/test/Notification.java @@ -0,0 +1,109 @@ +/* + * Copyright 2010-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.data.cassandra.test; + +import java.util.Date; + +import org.springframework.data.annotation.Id; +import org.springframework.data.cassandra.mapping.ColumnId; +import org.springframework.data.cassandra.mapping.Index; +import org.springframework.data.cassandra.mapping.Table; + +/** + * This is an example of dynamic table that creates each time new column + * with Notification timestamp annotated by @ColumnId. + * + * By default it is active Notification until user deactivate it. + * This table uses index on the field active to access in WHERE cause only + * for active notifications. + * + * @author Alex Shvid + */ +@Table(name = "notifications") +public class Notification { + + /* + * Primary Row ID + */ + @Id + private String username; + + /* + * Column ID + */ + @ColumnId + private Date time; + + @Index + private boolean active; + + /* + * Reference data + */ + + private String type; // comment, post + private String refAuthor; + private Date refTime; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public Date getTime() { + return time; + } + + public void setTime(Date time) { + this.time = time; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getRefAuthor() { + return refAuthor; + } + + public void setRefAuthor(String refAuthor) { + this.refAuthor = refAuthor; + } + + public Date getRefTime() { + return refTime; + } + + public void setRefTime(Date refTime) { + this.refTime = refTime; + } + +} diff --git a/src/test/java/org/springframework/data/cassandra/test/Post.java b/src/test/java/org/springframework/data/cassandra/test/Post.java new file mode 100644 index 000000000..951aba196 --- /dev/null +++ b/src/test/java/org/springframework/data/cassandra/test/Post.java @@ -0,0 +1,124 @@ +/* + * Copyright 2010-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.data.cassandra.test; + +import java.util.Date; +import java.util.Map; +import java.util.Set; + +import org.springframework.data.annotation.Id; +import org.springframework.data.cassandra.mapping.ColumnId; +import org.springframework.data.cassandra.mapping.Table; + +/** + * This is an example of dynamic table that creates each time new column + * with Post timestamp annotated by @ColumnId. + * + * It is possible to use a static table for posts and identify them by PostId(UUID), + * but in this case we need to use MapReduce for Big Data to find posts for + * particular user, so it is better to have index (userId) -> index (post time) + * architecture. It helps a lot to build eventually a search index for the particular user. + * + * @author Alex Shvid + */ +@Table(name = "posts") +public class Post { + + /* + * Primary Row ID + */ + @Id + private String author; + + /* + * Column ID + */ + @ColumnId + private Date time; + + private String type; // status, share + + private String text; + private Set resources; + private Map comments; + private Set likes; + private Set followers; + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public Date getTime() { + return time; + } + + public void setTime(Date time) { + this.time = time; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public Set getResources() { + return resources; + } + + public void setResources(Set resources) { + this.resources = resources; + } + + public Map getComments() { + return comments; + } + + public void setComments(Map comments) { + this.comments = comments; + } + + public Set getLikes() { + return likes; + } + + public void setLikes(Set likes) { + this.likes = likes; + } + + public Set getFollowers() { + return followers; + } + + public void setFollowers(Set followers) { + this.followers = followers; + } + +} diff --git a/src/test/java/org/springframework/data/cassandra/test/Timeline.java b/src/test/java/org/springframework/data/cassandra/test/Timeline.java new file mode 100644 index 000000000..5ac7df462 --- /dev/null +++ b/src/test/java/org/springframework/data/cassandra/test/Timeline.java @@ -0,0 +1,89 @@ +/* + * Copyright 2010-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.data.cassandra.test; + +import java.util.Date; + +import org.springframework.data.annotation.Id; +import org.springframework.data.cassandra.mapping.ColumnId; +import org.springframework.data.cassandra.mapping.Table; + +/** + * This is an example of the users timeline dynamic table, where all columns + * are dynamically created by @ColumnId field value. The rest fields are places + * in Cassandra value. + * + * Timeline entity is used to store user's status updates that it follows in the site. + * Timeline always ordered by @ColumnId field and we can retrieve last top status + * updates by using limits. + * + * @author Alex Shvid + */ +@Table(name="timeline") +public class Timeline { + + /* + * Primary Row ID + */ + @Id + private String username; + + /* + * Column ID + */ + @ColumnId + private Date time; + + /* + * Reference to the post by author and postUID + */ + private String author; + private Date postTime; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public Date getTime() { + return time; + } + + public void setTime(Date time) { + this.time = time; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public Date getPostTime() { + return postTime; + } + + public void setPostTime(Date postTime) { + this.postTime = postTime; + } + + +} diff --git a/src/test/java/org/springframework/data/cassandra/test/User.java b/src/test/java/org/springframework/data/cassandra/test/User.java new file mode 100644 index 000000000..e20ed5131 --- /dev/null +++ b/src/test/java/org/springframework/data/cassandra/test/User.java @@ -0,0 +1,139 @@ +/* + * Copyright 2010-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.data.cassandra.test; + +import java.util.Set; + +import org.springframework.data.annotation.Id; +import org.springframework.data.cassandra.mapping.Index; +import org.springframework.data.cassandra.mapping.Table; + +/** + * This is an example of the Users statis table, where all fields are columns + * in Cassandra row. Some fields can be Set,List,Map like emails. + * + * User contains base information related for separate user, like + * names, additional information, emails, following users, friends. + * + * @author Alex Shvid + */ +@Table(name="users") +public class User { + + /* + * Primary Row ID + */ + @Id + private String username; + + /* + * Public information + */ + private String firstName; + private String lastName; + + /* + * Secondary index, used only on fields with common information, + * not effective on email, username + */ + @Index + private String place; + + /* + * User emails + */ + private Set emails; + + /* + * Password + */ + private String password; + + /* + * Following other users in userline + */ + private Set following; + + /* + * Friends of the user + */ + private Set friends; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + 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; + } + + public String getPlace() { + return place; + } + + public void setPlace(String place) { + this.place = place; + } + + public Set getEmails() { + return emails; + } + + public void setEmails(Set emails) { + this.emails = emails; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Set getFollowing() { + return following; + } + + public void setFollowing(Set following) { + this.following = following; + } + + public Set getFriends() { + return friends; + } + + public void setFriends(Set friends) { + this.friends = friends; + } + +} diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties new file mode 100644 index 000000000..6e2ec3286 --- /dev/null +++ b/src/test/resources/log4j.properties @@ -0,0 +1,6 @@ +log4j.rootLogger=WARN, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c - %m%n +log4j.logger.org.springframework.data.cassandra=INFO + diff --git a/src/test/resources/org/springframework/data/cassandra/config/CassandraNamespaceTests-context.xml b/src/test/resources/org/springframework/data/cassandra/config/CassandraNamespaceTests-context.xml new file mode 100644 index 000000000..5c8104201 --- /dev/null +++ b/src/test/resources/org/springframework/data/cassandra/config/CassandraNamespaceTests-context.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/springframework/data/cassandra/config/cassandra.properties b/src/test/resources/org/springframework/data/cassandra/config/cassandra.properties new file mode 100644 index 000000000..6a0dd3197 --- /dev/null +++ b/src/test/resources/org/springframework/data/cassandra/config/cassandra.properties @@ -0,0 +1,7 @@ +cassandra.contactPoints=localhost +cassandra.port=9042 +cassandra.keyspace=TestKS123 + + + + diff --git a/template.mf b/template.mf new file mode 100644 index 000000000..3cbe47034 --- /dev/null +++ b/template.mf @@ -0,0 +1,30 @@ +Bundle-SymbolicName: org.springframework.data.cassandra +Bundle-Name: Spring Data Cassandra +Bundle-Vendor: Mirantis +Bundle-ManifestVersion: 2 +Import-Package: + sun.reflect;version="0";resolution:=optional +Import-Template: + org.springframework.beans.*;version="[3.1.0, 4.0.0)", + org.springframework.cache.*;version="[3.1.0, 4.0.0)", + org.springframework.context.*;version="[3.1.0, 4.0.0)", + org.springframework.core.*;version="[3.1.0, 4.0.0)", + org.springframework.dao.*;version="[3.1.0, 4.0.0)", + org.springframework.scheduling.*;resolution:="optional";version="[3.1.0, 4.0.0)", + org.springframework.util.*;version="[3.1.0, 4.0.0)", + org.springframework.oxm.*;resolution:="optional";version="[3.1.0, 4.0.0)", + org.springframework.transaction.support.*;version="[3.1.0, 4.0.0)", + org.springframework.data.*;version="[1.5.0, 2.0.0)", + org.springframework.expression.*;version="[3.1.0, 4.0.0)", + org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional, + org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", + org.w3c.dom.*;version="0", + javax.xml.transform.*;resolution:="optional";version="0", + com.datastax.driver.core.*;resolution:="optional";version="[0.1.0, 1.0.0)", + org.apache.cassandra.db.marshal.*;version="[1.2.0, 1.3.0)", + org.slf4j.*;version="[1.5.0, 1.8.0)", + org.idevlab.rjc.*;resolution:="optional";version="[0.6.4, 0.6.4]", + org.apache.commons.pool.impl.*;resolution:="optional";version="[1.0.0, 3.0.0)", + org.codehaus.jackson.*;resolution:="optional";version="[1.6, 2.0.0)", + org.apache.commons.beanutils.*;resolution:="optional";version=1.8.5, + com.google.common.*;resolution:="optional";version="[11.0.0, 20.0.0)" \ No newline at end of file diff --git a/test-support/cassandra/conf/cassandra.yaml b/test-support/cassandra/conf/cassandra.yaml new file mode 100644 index 000000000..c6c96f715 --- /dev/null +++ b/test-support/cassandra/conf/cassandra.yaml @@ -0,0 +1,664 @@ +# Cassandra storage config YAML + +# NOTE: +# See http://wiki.apache.org/cassandra/StorageConfiguration for +# full explanations of configuration directives +# /NOTE + +# The name of the cluster. This is mainly used to prevent machines in +# one logical cluster from joining another. +cluster_name: 'Test Cluster' + +# This defines the number of tokens randomly assigned to this node on the ring +# The more tokens, relative to other nodes, the larger the proportion of data +# that this node will store. You probably want all nodes to have the same number +# of tokens assuming they have equal hardware capability. +# +# If you leave this unspecified, Cassandra will use the default of 1 token for legacy compatibility, +# and will use the initial_token as described below. +# +# Specifying initial_token will override this setting. +# +# If you already have a cluster with 1 token per node, and wish to migrate to +# multiple tokens per node, see http://wiki.apache.org/cassandra/Operations +num_tokens: 256 + +# initial_token allows you to specify tokens manually. While you can use # it with +# vnodes (num_tokens > 1, above) -- in which case you should provide a +# comma-separated list -- it's primarily used when adding nodes # to legacy clusters +# that do not have vnodes enabled. +# initial_token: + +# See http://wiki.apache.org/cassandra/HintedHandoff +hinted_handoff_enabled: true +# this defines the maximum amount of time a dead host will have hints +# generated. After it has been dead this long, new hints for it will not be +# created until it has been seen alive and gone down again. +max_hint_window_in_ms: 10800000 # 3 hours +# Maximum throttle in KBs per second, per delivery thread. This will be +# reduced proportionally to the number of nodes in the cluster. (If there +# are two nodes in the cluster, each delivery thread will use the maximum +# rate; if there are three, each will throttle to half of the maximum, +# since we expect two nodes to be delivering hints simultaneously.) +hinted_handoff_throttle_in_kb: 1024 +# Number of threads with which to deliver hints; +# Consider increasing this number when you have multi-dc deployments, since +# cross-dc handoff tends to be slower +max_hints_delivery_threads: 2 + +# The following setting populates the page cache on memtable flush and compaction +# WARNING: Enable this setting only when the whole node's data fits in memory. +# Defaults to: false +# populate_io_cache_on_flush: false + +# Authentication backend, implementing IAuthenticator; used to identify users +# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthenticator, +# PasswordAuthenticator}. +# +# - AllowAllAuthenticator performs no checks - set it to disable authentication. +# - PasswordAuthenticator relies on username/password pairs to authenticate +# users. It keeps usernames and hashed passwords in system_auth.credentials table. +# Please increase system_auth keyspace replication factor if you use this authenticator. +authenticator: AllowAllAuthenticator + +# Authorization backend, implementing IAuthorizer; used to limit access/provide permissions +# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthorizer, +# CassandraAuthorizer}. +# +# - AllowAllAuthorizer allows any action to any user - set it to disable authorization. +# - CassandraAuthorizer stores permissions in system_auth.permissions table. Please +# increase system_auth keyspace replication factor if you use this authorizer. +authorizer: AllowAllAuthorizer + +# Validity period for permissions cache (fetching permissions can be an +# expensive operation depending on the authorizer, CassandraAuthorizer is +# one example). Defaults to 2000, set to 0 to disable. +# Will be disabled automatically for AllowAllAuthorizer. +permissions_validity_in_ms: 2000 + +# The partitioner is responsible for distributing rows (by key) across +# nodes in the cluster. Any IPartitioner may be used, including your +# own as long as it is on the classpath. Out of the box, Cassandra +# provides org.apache.cassandra.dht.{Murmur3Partitioner, RandomPartitioner +# ByteOrderedPartitioner, OrderPreservingPartitioner (deprecated)}. +# +# - RandomPartitioner distributes rows across the cluster evenly by md5. +# This is the default prior to 1.2 and is retained for compatibility. +# - Murmur3Partitioner is similar to RandomPartioner but uses Murmur3_128 +# Hash Function instead of md5. When in doubt, this is the best option. +# - ByteOrderedPartitioner orders rows lexically by key bytes. BOP allows +# scanning rows in key order, but the ordering can generate hot spots +# for sequential insertion workloads. +# - OrderPreservingPartitioner is an obsolete form of BOP, that stores +# - keys in a less-efficient format and only works with keys that are +# UTF8-encoded Strings. +# - CollatingOPP collates according to EN,US rules rather than lexical byte +# ordering. Use this as an example if you need custom collation. +# +# See http://wiki.apache.org/cassandra/Operations for more on +# partitioners and token selection. +partitioner: org.apache.cassandra.dht.Murmur3Partitioner + +# Directories where Cassandra should store data on disk. Cassandra +# will spread data evenly across them, subject to the granularity of +# the configured compaction strategy. +data_file_directories: + - .cassandra/var/lib/cassandra/data + +# commit log +commitlog_directory: .cassandra/var/lib/cassandra/commitlog + +# policy for data disk failures: +# stop: shut down gossip and Thrift, leaving the node effectively dead, but +# can still be inspected via JMX. +# best_effort: stop using the failed disk and respond to requests based on +# remaining available sstables. This means you WILL see obsolete +# data at CL.ONE! +# ignore: ignore fatal errors and let requests fail, as in pre-1.2 Cassandra +disk_failure_policy: stop + +# Maximum size of the key cache in memory. +# +# Each key cache hit saves 1 seek and each row cache hit saves 2 seeks at the +# minimum, sometimes more. The key cache is fairly tiny for the amount of +# time it saves, so it's worthwhile to use it at large numbers. +# The row cache saves even more time, but must contain the entire row, +# so it is extremely space-intensive. It's best to only use the +# row cache if you have hot rows or static rows. +# +# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup. +# +# Default value is empty to make it "auto" (min(5% of Heap (in MB), 100MB)). Set to 0 to disable key cache. +key_cache_size_in_mb: + +# Duration in seconds after which Cassandra should +# save the key cache. Caches are saved to saved_caches_directory as +# specified in this configuration file. +# +# Saved caches greatly improve cold-start speeds, and is relatively cheap in +# terms of I/O for the key cache. Row cache saving is much more expensive and +# has limited use. +# +# Default is 14400 or 4 hours. +key_cache_save_period: 14400 + +# Number of keys from the key cache to save +# Disabled by default, meaning all keys are going to be saved +# key_cache_keys_to_save: 100 + +# Maximum size of the row cache in memory. +# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup. +# +# Default value is 0, to disable row caching. +row_cache_size_in_mb: 0 + +# Duration in seconds after which Cassandra should +# safe the row cache. Caches are saved to saved_caches_directory as specified +# in this configuration file. +# +# Saved caches greatly improve cold-start speeds, and is relatively cheap in +# terms of I/O for the key cache. Row cache saving is much more expensive and +# has limited use. +# +# Default is 0 to disable saving the row cache. +row_cache_save_period: 0 + +# Number of keys from the row cache to save +# Disabled by default, meaning all keys are going to be saved +# row_cache_keys_to_save: 100 + +# The off-heap memory allocator. Affects storage engine metadata as +# well as caches. Experiments show that JEMAlloc saves some memory +# than the native GCC allocator (i.e., JEMalloc is more +# fragmentation-resistant). +# +# Supported values are: NativeAllocator, JEMallocAllocator +# +# If you intend to use JEMallocAllocator you have to install JEMalloc as library and +# modify cassandra-env.sh as directed in the file. +# +# Defaults to NativeAllocator +# memory_allocator: NativeAllocator + +# saved caches +saved_caches_directory: .cassandra/var/lib/cassandra/saved_caches + +# commitlog_sync may be either "periodic" or "batch." +# When in batch mode, Cassandra won't ack writes until the commit log +# has been fsynced to disk. It will wait up to +# commitlog_sync_batch_window_in_ms milliseconds for other writes, before +# performing the sync. +# +# commitlog_sync: batch +# commitlog_sync_batch_window_in_ms: 50 +# +# the other option is "periodic" where writes may be acked immediately +# and the CommitLog is simply synced every commitlog_sync_period_in_ms +# milliseconds. By default this allows 1024*(CPU cores) pending +# entries on the commitlog queue. If you are writing very large blobs, +# you should reduce that; 16*cores works reasonably well for 1MB blobs. +# It should be at least as large as the concurrent_writes setting. +commitlog_sync: periodic +commitlog_sync_period_in_ms: 10000 +# commitlog_periodic_queue_size: + +# The size of the individual commitlog file segments. A commitlog +# segment may be archived, deleted, or recycled once all the data +# in it (potentially from each columnfamily in the system) has been +# flushed to sstables. +# +# The default size is 32, which is almost always fine, but if you are +# archiving commitlog segments (see commitlog_archiving.properties), +# then you probably want a finer granularity of archiving; 8 or 16 MB +# is reasonable. +commitlog_segment_size_in_mb: 32 + +# any class that implements the SeedProvider interface and has a +# constructor that takes a Map of parameters will do. +seed_provider: + # Addresses of hosts that are deemed contact points. + # Cassandra nodes use this list of hosts to find each other and learn + # the topology of the ring. You must change this if you are running + # multiple nodes! + - class_name: org.apache.cassandra.locator.SimpleSeedProvider + parameters: + # seeds is actually a comma-delimited list of addresses. + # Ex: ",," + - seeds: "127.0.0.1" + +# For workloads with more data than can fit in memory, Cassandra's +# bottleneck will be reads that need to fetch data from +# disk. "concurrent_reads" should be set to (16 * number_of_drives) in +# order to allow the operations to enqueue low enough in the stack +# that the OS and drives can reorder them. +# +# On the other hand, since writes are almost never IO bound, the ideal +# number of "concurrent_writes" is dependent on the number of cores in +# your system; (8 * number_of_cores) is a good rule of thumb. +concurrent_reads: 32 +concurrent_writes: 32 + +# Total memory to use for sstable-reading buffers. Defaults to +# the smaller of 1/4 of heap or 512MB. +# file_cache_size_in_mb: 512 + +# Total memory to use for memtables. Cassandra will flush the largest +# memtable when this much memory is used. +# If omitted, Cassandra will set it to 1/3 of the heap. +# memtable_total_space_in_mb: 2048 + +# Total space to use for commitlogs. Since commitlog segments are +# mmapped, and hence use up address space, the default size is 32 +# on 32-bit JVMs, and 1024 on 64-bit JVMs. +# +# If space gets above this value (it will round up to the next nearest +# segment multiple), Cassandra will flush every dirty CF in the oldest +# segment and remove it. So a small total commitlog space will tend +# to cause more flush activity on less-active columnfamilies. +# commitlog_total_space_in_mb: 4096 + +# This sets the amount of memtable flush writer threads. These will +# be blocked by disk io, and each one will hold a memtable in memory +# while blocked. If you have a large heap and many data directories, +# you can increase this value for better flush performance. +# By default this will be set to the amount of data directories defined. +#memtable_flush_writers: 1 + +# the number of full memtables to allow pending flush, that is, +# waiting for a writer thread. At a minimum, this should be set to +# the maximum number of secondary indexes created on a single CF. +memtable_flush_queue_size: 4 + +# Whether to, when doing sequential writing, fsync() at intervals in +# order to force the operating system to flush the dirty +# buffers. Enable this to avoid sudden dirty buffer flushing from +# impacting read latencies. Almost always a good idea on SSDs; not +# necessarily on platters. +trickle_fsync: false +trickle_fsync_interval_in_kb: 10240 + +# TCP port, for commands and data +storage_port: 7000 + +# SSL port, for encrypted communication. Unused unless enabled in +# encryption_options +ssl_storage_port: 7001 + +# Address to bind to and tell other Cassandra nodes to connect to. You +# _must_ change this if you want multiple nodes to be able to +# communicate! +# +# Leaving it blank leaves it up to InetAddress.getLocalHost(). This +# will always do the Right Thing _if_ the node is properly configured +# (hostname, name resolution, etc), and the Right Thing is to use the +# address associated with the hostname (it might not be). +# +# Setting this to 0.0.0.0 is always wrong. +listen_address: localhost + +# Address to broadcast to other Cassandra nodes +# Leaving this blank will set it to the same value as listen_address +# broadcast_address: 1.2.3.4 + +# Internode authentication backend, implementing IInternodeAuthenticator; +# used to allow/disallow connections from peer nodes. +# internode_authenticator: org.apache.cassandra.auth.AllowAllInternodeAuthenticator + +# Whether to start the native transport server. +# Please note that the address on which the native transport is bound is the +# same as the rpc_address. The port however is different and specified below. +start_native_transport: true +# port for the CQL native transport to listen for clients on +native_transport_port: 9042 +# The maximum threads for handling requests when the native transport is used. +# This is similar to rpc_max_threads though the default differs slightly (and +# there is no native_transport_min_threads, idle threads will always be stopped +# after 30 seconds). +# native_transport_max_threads: 128 + +# Whether to start the thrift rpc server. +start_rpc: true + +# The address to bind the Thrift RPC service and native transport +# server -- clients connect here. +# +# Leaving this blank has the same effect it does for ListenAddress, +# (i.e. it will be based on the configured hostname of the node). +# +# Note that unlike ListenAddress above, it is allowed to specify 0.0.0.0 +# here if you want to listen on all interfaces but is not best practice +# as it is known to confuse the node auto-discovery features of some +# client drivers. +rpc_address: localhost +# port for Thrift to listen for clients on +rpc_port: 9160 + +# enable or disable keepalive on rpc connections +rpc_keepalive: true + +# Cassandra provides three out-of-the-box options for the RPC Server: +# +# sync -> One thread per thrift connection. For a very large number of clients, memory +# will be your limiting factor. On a 64 bit JVM, 180KB is the minimum stack size +# per thread, and that will correspond to your use of virtual memory (but physical memory +# may be limited depending on use of stack space). +# +# hsha -> Stands for "half synchronous, half asynchronous." All thrift clients are handled +# asynchronously using a small number of threads that does not vary with the amount +# of thrift clients (and thus scales well to many clients). The rpc requests are still +# synchronous (one thread per active request). +# +# The default is sync because on Windows hsha is about 30% slower. On Linux, +# sync/hsha performance is about the same, with hsha of course using less memory. +# +# Alternatively, can provide your own RPC server by providing the fully-qualified class name +# of an o.a.c.t.TServerFactory that can create an instance of it. +rpc_server_type: sync + +# Uncomment rpc_min|max_thread to set request pool size limits. +# +# Regardless of your choice of RPC server (see above), the number of maximum requests in the +# RPC thread pool dictates how many concurrent requests are possible (but if you are using the sync +# RPC server, it also dictates the number of clients that can be connected at all). +# +# The default is unlimited and thus provides no protection against clients overwhelming the server. You are +# encouraged to set a maximum that makes sense for you in production, but do keep in mind that +# rpc_max_threads represents the maximum number of client requests this server may execute concurrently. +# +# rpc_min_threads: 16 +# rpc_max_threads: 2048 + +# uncomment to set socket buffer sizes on rpc connections +# rpc_send_buff_size_in_bytes: +# rpc_recv_buff_size_in_bytes: + +# Uncomment to set socket buffer size for internode communication +# Note that when setting this, the buffer size is limited by net.core.wmem_max +# and when not setting it it is defined by net.ipv4.tcp_wmem +# See: +# /proc/sys/net/core/wmem_max +# /proc/sys/net/core/rmem_max +# /proc/sys/net/ipv4/tcp_wmem +# /proc/sys/net/ipv4/tcp_wmem +# and: man tcp +# internode_send_buff_size_in_bytes: +# internode_recv_buff_size_in_bytes: + +# Frame size for thrift (maximum message length). +thrift_framed_transport_size_in_mb: 15 + +# Set to true to have Cassandra create a hard link to each sstable +# flushed or streamed locally in a backups/ subdirectory of the +# keyspace data. Removing these links is the operator's +# responsibility. +incremental_backups: false + +# Whether or not to take a snapshot before each compaction. Be +# careful using this option, since Cassandra won't clean up the +# snapshots for you. Mostly useful if you're paranoid when there +# is a data format change. +snapshot_before_compaction: false + +# Whether or not a snapshot is taken of the data before keyspace truncation +# or dropping of column families. The STRONGLY advised default of true +# should be used to provide data safety. If you set this flag to false, you will +# lose data on truncation or drop. +auto_snapshot: true + +# Add column indexes to a row after its contents reach this size. +# Increase if your column values are large, or if you have a very large +# number of columns. The competing causes are, Cassandra has to +# deserialize this much of the row to read a single column, so you want +# it to be small - at least if you do many partial-row reads - but all +# the index data is read for each access, so you don't want to generate +# that wastefully either. +column_index_size_in_kb: 64 + +# Size limit for rows being compacted in memory. Larger rows will spill +# over to disk and use a slower two-pass compaction process. A message +# will be logged specifying the row key. +in_memory_compaction_limit_in_mb: 64 + +# Number of simultaneous compactions to allow, NOT including +# validation "compactions" for anti-entropy repair. Simultaneous +# compactions can help preserve read performance in a mixed read/write +# workload, by mitigating the tendency of small sstables to accumulate +# during a single long running compactions. The default is usually +# fine and if you experience problems with compaction running too +# slowly or too fast, you should look at +# compaction_throughput_mb_per_sec first. +# +# concurrent_compactors defaults to the number of cores. +# Uncomment to make compaction mono-threaded, the pre-0.8 default. +#concurrent_compactors: 1 + +# Multi-threaded compaction. When enabled, each compaction will use +# up to one thread per core, plus one thread per sstable being merged. +# This is usually only useful for SSD-based hardware: otherwise, +# your concern is usually to get compaction to do LESS i/o (see: +# compaction_throughput_mb_per_sec), not more. +multithreaded_compaction: false + +# Throttles compaction to the given total throughput across the entire +# system. The faster you insert data, the faster you need to compact in +# order to keep the sstable count down, but in general, setting this to +# 16 to 32 times the rate you are inserting data is more than sufficient. +# Setting this to 0 disables throttling. Note that this account for all types +# of compaction, including validation compaction. +compaction_throughput_mb_per_sec: 16 + +# Track cached row keys during compaction, and re-cache their new +# positions in the compacted sstable. Disable if you use really large +# key caches. +compaction_preheat_key_cache: true + +# Throttles all outbound streaming file transfers on this node to the +# given total throughput in Mbps. This is necessary because Cassandra does +# mostly sequential IO when streaming data during bootstrap or repair, which +# can lead to saturating the network connection and degrading rpc performance. +# When unset, the default is 200 Mbps or 25 MB/s. +# stream_throughput_outbound_megabits_per_sec: 200 + +# How long the coordinator should wait for read operations to complete +read_request_timeout_in_ms: 5000 +# How long the coordinator should wait for seq or index scans to complete +range_request_timeout_in_ms: 10000 +# How long the coordinator should wait for writes to complete +write_request_timeout_in_ms: 2000 +# How long a coordinator should continue to retry a CAS operation +# that contends with other proposals for the same row +cas_contention_timeout_in_ms: 1000 +# How long the coordinator should wait for truncates to complete +# (This can be much longer, because unless auto_snapshot is disabled +# we need to flush first so we can snapshot before removing the data.) +truncate_request_timeout_in_ms: 60000 +# The default timeout for other, miscellaneous operations +request_timeout_in_ms: 10000 + +# Enable operation timeout information exchange between nodes to accurately +# measure request timeouts. If disabled, replicas will assume that requests +# were forwarded to them instantly by the coordinator, which means that +# under overload conditions we will waste that much extra time processing +# already-timed-out requests. +# +# Warning: before enabling this property make sure to ntp is installed +# and the times are synchronized between the nodes. +cross_node_timeout: false + +# Enable socket timeout for streaming operation. +# When a timeout occurs during streaming, streaming is retried from the start +# of the current file. This _can_ involve re-streaming an important amount of +# data, so you should avoid setting the value too low. +# Default value is 0, which never timeout streams. +# streaming_socket_timeout_in_ms: 0 + +# phi value that must be reached for a host to be marked down. +# most users should never need to adjust this. +# phi_convict_threshold: 8 + +# endpoint_snitch -- Set this to a class that implements +# IEndpointSnitch. The snitch has two functions: +# - it teaches Cassandra enough about your network topology to route +# requests efficiently +# - it allows Cassandra to spread replicas around your cluster to avoid +# correlated failures. It does this by grouping machines into +# "datacenters" and "racks." Cassandra will do its best not to have +# more than one replica on the same "rack" (which may not actually +# be a physical location) +# +# IF YOU CHANGE THE SNITCH AFTER DATA IS INSERTED INTO THE CLUSTER, +# YOU MUST RUN A FULL REPAIR, SINCE THE SNITCH AFFECTS WHERE REPLICAS +# ARE PLACED. +# +# Out of the box, Cassandra provides +# - SimpleSnitch: +# Treats Strategy order as proximity. This improves cache locality +# when disabling read repair, which can further improve throughput. +# Only appropriate for single-datacenter deployments. +# - PropertyFileSnitch: +# Proximity is determined by rack and data center, which are +# explicitly configured in cassandra-topology.properties. +# - GossipingPropertyFileSnitch +# The rack and datacenter for the local node are defined in +# cassandra-rackdc.properties and propagated to other nodes via gossip. If +# cassandra-topology.properties exists, it is used as a fallback, allowing +# migration from the PropertyFileSnitch. +# - RackInferringSnitch: +# Proximity is determined by rack and data center, which are +# assumed to correspond to the 3rd and 2nd octet of each node's +# IP address, respectively. Unless this happens to match your +# deployment conventions (as it did Facebook's), this is best used +# as an example of writing a custom Snitch class. +# - Ec2Snitch: +# Appropriate for EC2 deployments in a single Region. Loads Region +# and Availability Zone information from the EC2 API. The Region is +# treated as the datacenter, and the Availability Zone as the rack. +# Only private IPs are used, so this will not work across multiple +# Regions. +# - Ec2MultiRegionSnitch: +# Uses public IPs as broadcast_address to allow cross-region +# connectivity. (Thus, you should set seed addresses to the public +# IP as well.) You will need to open the storage_port or +# ssl_storage_port on the public IP firewall. (For intra-Region +# traffic, Cassandra will switch to the private IP after +# establishing a connection.) +# +# You can use a custom Snitch by setting this to the full class name +# of the snitch, which will be assumed to be on your classpath. +endpoint_snitch: SimpleSnitch + +# controls how often to perform the more expensive part of host score +# calculation +dynamic_snitch_update_interval_in_ms: 100 +# controls how often to reset all host scores, allowing a bad host to +# possibly recover +dynamic_snitch_reset_interval_in_ms: 600000 +# if set greater than zero and read_repair_chance is < 1.0, this will allow +# 'pinning' of replicas to hosts in order to increase cache capacity. +# The badness threshold will control how much worse the pinned host has to be +# before the dynamic snitch will prefer other replicas over it. This is +# expressed as a double which represents a percentage. Thus, a value of +# 0.2 means Cassandra would continue to prefer the static snitch values +# until the pinned host was 20% worse than the fastest. +dynamic_snitch_badness_threshold: 0.1 + +# request_scheduler -- Set this to a class that implements +# RequestScheduler, which will schedule incoming client requests +# according to the specific policy. This is useful for multi-tenancy +# with a single Cassandra cluster. +# NOTE: This is specifically for requests from the client and does +# not affect inter node communication. +# org.apache.cassandra.scheduler.NoScheduler - No scheduling takes place +# org.apache.cassandra.scheduler.RoundRobinScheduler - Round robin of +# client requests to a node with a separate queue for each +# request_scheduler_id. The scheduler is further customized by +# request_scheduler_options as described below. +request_scheduler: org.apache.cassandra.scheduler.NoScheduler + +# Scheduler Options vary based on the type of scheduler +# NoScheduler - Has no options +# RoundRobin +# - throttle_limit -- The throttle_limit is the number of in-flight +# requests per client. Requests beyond +# that limit are queued up until +# running requests can complete. +# The value of 80 here is twice the number of +# concurrent_reads + concurrent_writes. +# - default_weight -- default_weight is optional and allows for +# overriding the default which is 1. +# - weights -- Weights are optional and will default to 1 or the +# overridden default_weight. The weight translates into how +# many requests are handled during each turn of the +# RoundRobin, based on the scheduler id. +# +# request_scheduler_options: +# throttle_limit: 80 +# default_weight: 5 +# weights: +# Keyspace1: 1 +# Keyspace2: 5 + +# request_scheduler_id -- An identifier based on which to perform +# the request scheduling. Currently the only valid option is keyspace. +# request_scheduler_id: keyspace + +# Enable or disable inter-node encryption +# Default settings are TLS v1, RSA 1024-bit keys (it is imperative that +# users generate their own keys) TLS_RSA_WITH_AES_128_CBC_SHA as the cipher +# suite for authentication, key exchange and encryption of the actual data transfers. +# NOTE: No custom encryption options are enabled at the moment +# The available internode options are : all, none, dc, rack +# +# If set to dc cassandra will encrypt the traffic between the DCs +# If set to rack cassandra will encrypt the traffic between the racks +# +# The passwords used in these options must match the passwords used when generating +# the keystore and truststore. For instructions on generating these files, see: +# http://download.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#CreateKeystore +# +server_encryption_options: + internode_encryption: none + keystore: conf/.keystore + keystore_password: cassandra + truststore: conf/.truststore + truststore_password: cassandra + # More advanced defaults below: + # protocol: TLS + # algorithm: SunX509 + # store_type: JKS + # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA] + # require_client_auth: false + +# enable or disable client/server encryption. +client_encryption_options: + enabled: false + keystore: conf/.keystore + keystore_password: cassandra + # require_client_auth: false + # Set trustore and truststore_password if require_client_auth is true + # truststore: conf/.truststore + # truststore_password: cassandra + # More advanced defaults below: + # protocol: TLS + # algorithm: SunX509 + # store_type: JKS + # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA] + +# internode_compression controls whether traffic between nodes is +# compressed. +# can be: all - all traffic is compressed +# dc - traffic between different datacenters is compressed +# none - nothing is compressed. +internode_compression: all + +# Enable or disable tcp_nodelay for inter-dc communication. +# Disabling it will result in larger (but fewer) network packets being sent, +# reducing overhead from the TCP protocol itself, at the cost of increasing +# latency if you block for cross-datacenter responses. +inter_dc_tcp_nodelay: false + +# Enable or disable kernel page cache preheating from contents of the key cache after compaction. +# When enabled it would preheat only first "page" (4KB) of each row to optimize +# for sequential access. Note: This could be harmful for fat rows, see CASSANDRA-4937 +# for further details on that topic. +preheat_kernel_page_cache: false diff --git a/test-support/get-and-start-cassandra b/test-support/get-and-start-cassandra new file mode 100755 index 000000000..2c045e19e --- /dev/null +++ b/test-support/get-and-start-cassandra @@ -0,0 +1,23 @@ +CASSANDRA_DIST=.cassandra/dist +mkdir -p $CASSANDRA_DIST + +curl -sL http://downloads.datastax.com/community/dsc.tar.gz > $CASSANDRA_DIST/dist.tgz +tar -xzf $CASSANDRA_DIST/dist.tgz -C $CASSANDRA_DIST + +CASSANDRA_HOME=`find $CASSANDRA_DIST -name 'dsc-cassandra-*' -print` +if [ -z "$CASSANDRA_HOME" ]; then + echo "Couldn't determine CASSANDRA_HOME" + exit 1 +fi +echo "CASSANDRA_HOME is $CASSANDRA_HOME" + +# these directories must match what's in test-support/cassandra/conf/cassandra.yaml +mkdir -p .cassandra/var/lib/cassandra +mkdir -p .cassandra/var/log/cassandra + +mv $CASSANDRA_HOME/conf/cassandra.yaml $CASSANDRA_HOME/conf/cassandra.yaml.original +cp test-support/cassandra/conf/cassandra.yaml $CASSANDRA_HOME/conf/cassandra.yaml + +$CASSANDRA_HOME/bin/cassandra -p $CASSANDRA_HOME/cassandra.pid + +sleep 5