├── .editorconfig ├── .gitattributes ├── .gitignore ├── .mvn ├── settings.xml └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .travis.yml ├── mvnw ├── mvnw.bat ├── pom.xml ├── readme.md ├── rook-api ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── github │ └── shyiko │ └── rook │ └── api │ ├── ReplicationEventExceptionHandler.java │ ├── ReplicationEventListener.java │ ├── ReplicationStream.java │ └── event │ ├── DeleteRowsReplicationEvent.java │ ├── InsertRowsReplicationEvent.java │ ├── ReplicationEvent.java │ ├── RowsMutationReplicationEvent.java │ ├── TXReplicationEvent.java │ └── UpdateRowsReplicationEvent.java ├── rook-source-mysql ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── github │ └── shyiko │ └── rook │ └── source │ └── mysql │ └── MySQLReplicationStream.java ├── rook-target-hibernate4-cache ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── github │ │ └── shyiko │ │ └── rook │ │ └── target │ │ └── hibernate4 │ │ └── cache │ │ ├── AbstractCacheSynchronizer.java │ │ ├── EvictionTarget.java │ │ ├── HibernateCacheSynchronizer.java │ │ ├── PrimaryKey.java │ │ ├── QueryCacheSynchronizer.java │ │ ├── SecondLevelCacheSynchronizer.java │ │ └── SynchronizationContext.java │ └── test │ ├── java │ └── com │ │ └── github │ │ └── shyiko │ │ └── rook │ │ └── target │ │ └── hibernate │ │ └── cache │ │ ├── AbstractHibernateTest.java │ │ ├── SecondLevelCacheSynchronizerTest.java │ │ ├── SynchronizationContextTest.java │ │ └── model │ │ ├── Entity.java │ │ ├── EntityProperty.java │ │ ├── EntityWithCompositeKey.java │ │ └── NonCacheableEntity.java │ └── resources │ ├── ehcache.xml │ └── hibernate.cfg.xml ├── rook-target-hibernate4-fulltextindex ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── github │ └── shyiko │ └── rook │ └── target │ └── hibernate4 │ └── fulltextindex │ ├── DefaultRowsMutationIndexer.java │ ├── FullTextIndexSynchronizer.java │ ├── IndexingDirective.java │ ├── PrimaryKey.java │ ├── Reference.java │ ├── RowsMutation.java │ ├── RowsMutationIndexer.java │ └── SynchronizationContext.java └── supplement ├── codequality ├── checkstyle.xml ├── license.header └── readme.md ├── integration-testing ├── hibernate4-cache-over-mysql │ ├── pom.xml │ ├── readme.md │ └── src │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── shyiko │ │ │ └── rook │ │ │ └── it │ │ │ └── h4com │ │ │ ├── CountDownReplicationListener.java │ │ │ ├── IntegrationTest.java │ │ │ └── model │ │ │ ├── CompositeKeyEntity.java │ │ │ ├── IgnoredEntity.java │ │ │ ├── JoinedOneToManyEntity.java │ │ │ ├── OneToManyEntity.java │ │ │ ├── OneToOneEntity.java │ │ │ └── RootEntity.java │ │ └── resources │ │ ├── hibernate.cfg.xml │ │ ├── log4j.properties │ │ ├── master-ehcache.xml │ │ ├── master-sdb-ehcache.xml │ │ ├── master-sdb.properties │ │ ├── master.properties │ │ ├── slave-ehcache.xml │ │ ├── slave-sdb-ehcache.xml │ │ ├── slave-sdb.properties │ │ └── slave.properties └── hibernate4-fulltextindex-over-mysql │ ├── pom.xml │ ├── readme.md │ └── src │ └── test │ ├── java │ └── com │ │ └── github │ │ └── shyiko │ │ └── rook │ │ └── it │ │ └── h4ftiom │ │ ├── CountDownReplicationListener.java │ │ ├── IntegrationTest.java │ │ └── model │ │ ├── JoinedOneToManyEntity.java │ │ ├── ManyToManyEntity.java │ │ └── RootEntity.java │ └── resources │ ├── hibernate.cfg.xml │ ├── log4j.properties │ ├── master.properties │ └── slave.properties └── vagrant └── mysql-5.5.27-sandbox-prepackaged └── vagrantfile /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.{java,kt,kts,scala,rs,xml}] 14 | indent_size = 4 15 | 16 | [{Makefile,*.go}] 17 | indent_style = tab 18 | indent_size = 4 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.bat eol=crlf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .iml 3 | .DS_Store 4 | target 5 | -------------------------------------------------------------------------------- /.mvn/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | maven-central 9 | ${env.OSS_SONATYPE_ORG_USERNAME} 10 | ${env.OSS_SONATYPE_ORG_PASSWORD} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shyiko/rook/b7aec251eed1189ffb4c4adf33f1293ce822a3ab/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repository.apache.org/content/repositories/releases/org/apache/maven/apache-maven/3.2.5/apache-maven-3.2.5-bin.zip 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | script: ./mvnw clean verify 3 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # The Maven Wrapper 0.1.0 (https://github.com/shyiko/mvnw). 4 | # Based on https://github.com/gradle/gradle/blob/62925785791e2487c43d19d234c2fced6d750412/gradlew. 5 | # 6 | 7 | # Add default JVM options here. You can also use JAVA_OPTS and MAVEN_OPTS to pass JVM options to this script. 8 | DEFAULT_JVM_OPTS="-Dfile.encoding=UTF-8" 9 | 10 | APP_BASE_NAME=`basename "$0"` 11 | 12 | # Use the maximum available, or set MAX_FD != -1 to use that value. 13 | MAX_FD="maximum" 14 | 15 | warn ( ) { 16 | echo "$*" 17 | } 18 | 19 | die ( ) { 20 | echo 21 | echo "$*" 22 | echo 23 | exit 1 24 | } 25 | 26 | # OS specific support (must be 'true' or 'false'). 27 | cygwin=false 28 | msys=false 29 | darwin=false 30 | case "`uname`" in 31 | CYGWIN* ) 32 | cygwin=true 33 | ;; 34 | Darwin* ) 35 | darwin=true 36 | ;; 37 | MINGW* ) 38 | msys=true 39 | ;; 40 | esac 41 | 42 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 43 | if $cygwin ; then 44 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 45 | fi 46 | 47 | # Attempt to set APP_HOME 48 | # Resolve links: $0 may be a link 49 | PRG="$0" 50 | # Need this for relative symlinks. 51 | while [ -h "$PRG" ] ; do 52 | ls=`ls -ld "$PRG"` 53 | link=`expr "$ls" : '.*-> \(.*\)$'` 54 | if expr "$link" : '/.*' > /dev/null; then 55 | PRG="$link" 56 | else 57 | PRG=`dirname "$PRG"`"/$link" 58 | fi 59 | done 60 | SAVED="`pwd`" 61 | cd "`dirname \"$PRG\"`/" 62 | APP_HOME="`pwd -P`" 63 | cd "$SAVED" 64 | 65 | CLASSPATH=$APP_HOME/.mvn/wrapper/maven-wrapper.jar 66 | 67 | # Determine the Java command to use to start the JVM. 68 | if [ -n "$JAVA_HOME" ] ; then 69 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 70 | # IBM's JDK on AIX uses strange locations for the executables 71 | JAVACMD="$JAVA_HOME/jre/sh/java" 72 | else 73 | JAVACMD="$JAVA_HOME/bin/java" 74 | fi 75 | if [ ! -x "$JAVACMD" ] ; then 76 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 77 | 78 | Please set the JAVA_HOME variable in your environment to match the 79 | location of your Java installation." 80 | fi 81 | else 82 | JAVACMD="java" 83 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 84 | 85 | Please set the JAVA_HOME variable in your environment to match the 86 | location of your Java installation." 87 | fi 88 | 89 | # Increase the maximum file descriptors if we can. 90 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 91 | MAX_FD_LIMIT=`ulimit -H -n` 92 | if [ $? -eq 0 ] ; then 93 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 94 | MAX_FD="$MAX_FD_LIMIT" 95 | fi 96 | ulimit -n $MAX_FD 97 | if [ $? -ne 0 ] ; then 98 | warn "Could not set maximum file descriptor limit: $MAX_FD" 99 | fi 100 | else 101 | warn "Could not query businessSystem maximum file descriptor limit: $MAX_FD_LIMIT" 102 | fi 103 | fi 104 | 105 | # For Cygwin, switch paths to Windows format before running java 106 | if $cygwin ; then 107 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 108 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 109 | 110 | # We build the pattern for arguments to be converted via cygpath 111 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 112 | SEP="" 113 | for dir in $ROOTDIRSRAW ; do 114 | ROOTDIRS="$ROOTDIRS$SEP$dir" 115 | SEP="|" 116 | done 117 | OURCYGPATTERN="(^($ROOTDIRS))" 118 | # Add a user-defined pattern to the cygpath arguments 119 | if [ "$MAVEN_CYGPATTERN" != "" ] ; then 120 | OURCYGPATTERN="$OURCYGPATTERN|($MAVEN_CYGPATTERN)" 121 | fi 122 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 123 | i=0 124 | for arg in "$@" ; do 125 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 126 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 127 | 128 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 129 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 130 | else 131 | eval `echo args$i`="\"$arg\"" 132 | fi 133 | i=$((i+1)) 134 | done 135 | case $i in 136 | (0) set -- ;; 137 | (1) set -- "$args0" ;; 138 | (2) set -- "$args0" "$args1" ;; 139 | (3) set -- "$args0" "$args1" "$args2" ;; 140 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 141 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 142 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 143 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 144 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 145 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 146 | esac 147 | fi 148 | 149 | # taken from https://github.com/takari/maven-wrapper/blob/69f3c6dd1b07620f28c1fc8cb20e392afcd9e95b/mvnw 150 | ld() { if [ -f "$1" ]; then echo "$(tr -s '\n' ' ' < "$1")"; fi } 151 | 152 | JVM_CONFIG="$(ld "$APP_HOME/.mvn/jvm.config")" 153 | MAVEN_CONFIG="$(ld "$APP_HOME/.mvn/maven.config")" 154 | 155 | exec "$JAVACMD" $DEFAULT_JVM_OPTS $JVM_CONFIG $JAVA_OPTS $MAVEN_OPTS -classpath "$CLASSPATH" -Dmaven.multiModuleProjectDirectory="$APP_HOME" org.apache.maven.wrapper.MavenWrapperMain $MAVEN_CONFIG "$@" -------------------------------------------------------------------------------- /mvnw.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem 3 | @rem The Maven Wrapper 0.1.0 (https://github.com/shyiko/mvnw). 4 | @rem Based on https://github.com/gradle/gradle/blob/62925785791e2487c43d19d234c2fced6d750412/gradlew.bat. 5 | @rem 6 | 7 | @rem Set local scope for the variables with windows NT shell 8 | if "%OS%"=="Windows_NT" setlocal 9 | 10 | @rem Add default JVM options here. You can also use JAVA_OPTS and MAVEN_OPTS to pass JVM options to this script. 11 | set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 12 | 13 | set DIRNAME=%~dp0 14 | if "%DIRNAME%" == "" set DIRNAME=. 15 | set APP_BASE_NAME=%~n0 16 | set APP_HOME=%DIRNAME% 17 | 18 | @rem Find java.exe 19 | if defined JAVA_HOME goto findJavaFromJavaHome 20 | 21 | set JAVA_EXE=java.exe 22 | %JAVA_EXE% -version >NUL 2>&1 23 | if "%ERRORLEVEL%" == "0" goto init 24 | 25 | echo. 26 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 27 | echo. 28 | echo Please set the JAVA_HOME variable in your environment to match the 29 | echo location of your Java installation. 30 | 31 | goto fail 32 | 33 | :findJavaFromJavaHome 34 | set JAVA_HOME=%JAVA_HOME:"=% 35 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 36 | 37 | if exist "%JAVA_EXE%" goto init 38 | 39 | echo. 40 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 41 | echo. 42 | echo Please set the JAVA_HOME variable in your environment to match the 43 | echo location of your Java installation. 44 | 45 | goto fail 46 | 47 | :init 48 | @rem Get command-line arguments, handling Windowz variants 49 | 50 | if not "%OS%" == "Windows_NT" goto win9xME_args 51 | if "%@eval[2+2]" == "4" goto 4NT_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | goto execute 63 | 64 | :4NT_args 65 | @rem Get arguments from the 4NT Shell from JP Software 66 | set CMD_LINE_ARGS=%$ 67 | 68 | :execute 69 | 70 | @rem taken from https://github.com/takari/maven-wrapper/blob/69f3c6dd1b07620f28c1fc8cb20e392afcd9e95b/mvnw 71 | 72 | IF NOT EXIST "%APP_HOME%\.mvn\jvm.config" goto endReadJvmConfig 73 | 74 | @setlocal EnableExtensions EnableDelayedExpansion 75 | for /F "usebackq delims=" %%a in ("%APP_HOME%\.mvn\jvm.config") do set JVM_CONFIG=!JVM_CONFIG! %%a 76 | @endlocal & set JVM_CONFIG=%JVM_CONFIG% 77 | 78 | :endReadJvmConfig 79 | 80 | IF NOT EXIST "%APP_HOME%\.mvn\maven.config" goto endReadMavenConfig 81 | 82 | @setlocal EnableExtensions EnableDelayedExpansion 83 | for /F "usebackq delims=" %%a in ("%APP_HOME%\.mvn\maven.config") do set MAVEN_CONFIG=!MAVEN_CONFIG! %%a 84 | @endlocal & set MAVEN_CONFIG=%MAVEN_CONFIG% 85 | 86 | :endReadMavenConfig 87 | 88 | @rem Setup the command line 89 | 90 | set CLASSPATH=%APP_HOME%\.mvn\wrapper\maven-wrapper.jar 91 | 92 | @rem Execute Maven 93 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JVM_CONFIG% %JAVA_OPTS% %MAVEN_OPTS% -classpath "%CLASSPATH%" -Dmaven.multiModuleProjectDirectory="%APP_HOME%" org.apache.maven.wrapper.MavenWrapperMain %MAVEN_CONFIG% %CMD_LINE_ARGS% 94 | 95 | :end 96 | @rem End local scope for the variables with windows NT shell 97 | if "%ERRORLEVEL%"=="0" goto mainEnd 98 | 99 | :fail 100 | rem Set variable MAVEN_EXIT_CONSOLE if you need the _script_ return code instead of 101 | rem the _cmd.exe /c_ return code! 102 | if not "" == "%MAVEN_EXIT_CONSOLE%" exit 1 103 | exit /b 1 104 | 105 | :mainEnd 106 | if "%OS%"=="Windows_NT" endlocal 107 | 108 | :omega 109 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # rook [![Build Status](https://travis-ci.org/shyiko/rook.png?branch=master)](https://travis-ci.org/shyiko/rook) [![Coverage Status](https://coveralls.io/repos/shyiko/rook/badge.png?branch=master)](https://coveralls.io/r/shyiko/rook?branch=master) 2 | 3 | Change Data Capture (CDC) toolkit for keeping system layers in sync with the database. 4 | 5 | Out-of-box rook includes support for MySQL as a source and Hibernate 4 cache (2nd Level & Query), 6 | FullText index backed by [Hibernate Search](http://www.hibernate.org/subprojects/search.html) as targets. 7 | 8 | ## Usage 9 | 10 | The latest release version available through [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.github.shyiko.rook%22%20AND%20a%3A%22rook%22). 11 | 12 | ### Eviction of Hibernate 4 Second Level/Query cache records in response to the replication events (on MySQL) 13 | 14 | ```xml 15 | 16 | 17 | com.github.shyiko.rook 18 | rook-source-mysql 19 | 0.1.3 20 | 21 | 22 | com.github.shyiko.rook 23 | rook-target-hibernate4-cache 24 | 0.1.3 25 | 26 | 27 | ``` 28 | 29 | ```java 30 | org.hibernate.cfg.Configuration configuration = ... 31 | org.hibernate.SessionFactory sessionFactory = ... 32 | new MySQLReplicationStream("hostname", 3306, "username", "password"). 33 | registerListener(new HibernateCacheSynchronizer(configuration, sessionFactory)). 34 | connect(); 35 | ``` 36 | 37 | > Integration tests available at [supplement/integration-testing/hibernate4-cache-over-mysql](https://github.com/shyiko/rook/tree/master/supplement/integration-testing/hibernate4-cache-over-mysql) 38 | 39 | ### Update of Hibernate 4 Search controlled FT index in response to the replication events (on MySQL) 40 | 41 | > Keep in mind that default indexer, which is used by FullTextIndexSynchronizer, relies on 42 | @org.hibernate.search.annotations.ContainedIn for propagation of indexing events to the container entity(ies). 43 | As a result, either each @IndexedEmbedded-annotated field/method MUST have corresponding @ContainedIn member 44 | (inside target entity) or a different indexer is required. 45 | 46 | ```xml 47 | 48 | 49 | com.github.shyiko.rook 50 | rook-source-mysql 51 | 0.1.3 52 | 53 | 54 | com.github.shyiko.rook 55 | rook-target-hibernate4-fulltextindex 56 | 0.1.3 57 | 58 | 59 | ``` 60 | 61 | ```java 62 | org.hibernate.cfg.Configuration configuration = ... 63 | org.hibernate.SessionFactory sessionFactory = ... 64 | new MySQLReplicationStream("hostname", 3306, "username", "password"). 65 | registerListener(new FullTextIndexSynchronizer(configuration, sessionFactory)). 66 | connect(); 67 | ``` 68 | 69 | > Integration tests available at [supplement/integration-testing/hibernate4-fulltextindex-over-mysql](https://github.com/shyiko/rook/tree/master/supplement/integration-testing/hibernate4-fulltextindex-over-mysql) 70 | 71 | ## License 72 | 73 | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 74 | -------------------------------------------------------------------------------- /rook-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.github.shyiko.rook 7 | rook 8 | 0.1.4-SNAPSHOT 9 | 10 | 11 | rook-api 12 | 13 | -------------------------------------------------------------------------------- /rook-api/src/main/java/com/github/shyiko/rook/api/ReplicationEventExceptionHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Igor Grunskiy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.api; 17 | 18 | /** 19 | * @author Igor Grunskiy 20 | */ 21 | public interface ReplicationEventExceptionHandler { 22 | 23 | void handle(Exception e); 24 | } 25 | -------------------------------------------------------------------------------- /rook-api/src/main/java/com/github/shyiko/rook/api/ReplicationEventListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.api; 17 | 18 | import com.github.shyiko.rook.api.event.ReplicationEvent; 19 | 20 | /** 21 | * @author Stanley Shyiko 22 | */ 23 | public interface ReplicationEventListener { 24 | 25 | void onEvent(ReplicationEvent event); 26 | } 27 | -------------------------------------------------------------------------------- /rook-api/src/main/java/com/github/shyiko/rook/api/ReplicationStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.api; 17 | 18 | import java.io.IOException; 19 | import java.util.concurrent.TimeoutException; 20 | 21 | /** 22 | * @author Stanley Shyiko 23 | */ 24 | public interface ReplicationStream { 25 | 26 | void connect() throws IOException; 27 | void connect(long timeoutInMilliseconds) throws IOException, TimeoutException; 28 | boolean isConnected(); 29 | void registerListener(ReplicationEventListener listener); 30 | void unregisterListener(ReplicationEventListener listener); 31 | void unregisterListener(Class listenerClass); 32 | void disconnect() throws IOException; 33 | } 34 | -------------------------------------------------------------------------------- /rook-api/src/main/java/com/github/shyiko/rook/api/event/DeleteRowsReplicationEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.api.event; 17 | 18 | import java.io.Serializable; 19 | import java.util.Arrays; 20 | import java.util.List; 21 | 22 | /** 23 | * @author Stanley Shyiko 24 | */ 25 | public class DeleteRowsReplicationEvent extends RowsMutationReplicationEvent> { 26 | 27 | public DeleteRowsReplicationEvent(long serverId, String schema, String table, List rows) { 28 | super(serverId, schema, table, rows); 29 | } 30 | 31 | public DeleteRowsReplicationEvent(long serverId, String schema, String table, Serializable[] row) { 32 | super(serverId, schema, table, Arrays.asList(new Serializable[][]{row})); 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | final StringBuilder sb = new StringBuilder(); 38 | sb.append("DeleteRowsReplicationEvent"); 39 | sb.append("{serverId=").append(serverId); 40 | sb.append(", schema='").append(schema).append('\''); 41 | sb.append(", table='").append(table).append('\''); 42 | sb.append(", rows=["); 43 | if (!rows.isEmpty()) { 44 | for (Serializable[] row : rows) { 45 | sb.append(Arrays.toString(row)).append(", "); 46 | } 47 | int length = sb.length(); 48 | sb.replace(length - 2, length, ""); 49 | } 50 | sb.append("]}"); 51 | return sb.toString(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /rook-api/src/main/java/com/github/shyiko/rook/api/event/InsertRowsReplicationEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.api.event; 17 | 18 | import java.io.Serializable; 19 | import java.util.Arrays; 20 | import java.util.List; 21 | 22 | /** 23 | * @author Stanley Shyiko 24 | */ 25 | public class InsertRowsReplicationEvent extends RowsMutationReplicationEvent> { 26 | 27 | public InsertRowsReplicationEvent(long serverId, String schema, String table, List rows) { 28 | super(serverId, schema, table, rows); 29 | } 30 | 31 | public InsertRowsReplicationEvent(long serverId, String schema, String table, Serializable[] row) { 32 | super(serverId, schema, table, Arrays.asList(new Serializable[][]{row})); 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | final StringBuilder sb = new StringBuilder(); 38 | sb.append("InsertRowsReplicationEvent"); 39 | sb.append("{serverId=").append(serverId); 40 | sb.append(", schema='").append(schema).append('\''); 41 | sb.append(", table='").append(table).append('\''); 42 | sb.append(", rows=["); 43 | if (!rows.isEmpty()) { 44 | for (Serializable[] row : rows) { 45 | sb.append(Arrays.toString(row)).append(", "); 46 | } 47 | int length = sb.length(); 48 | sb.replace(length - 2, length, ""); 49 | } 50 | sb.append("]}"); 51 | return sb.toString(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /rook-api/src/main/java/com/github/shyiko/rook/api/event/ReplicationEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.api.event; 17 | 18 | /** 19 | * @author Stanley Shyiko 20 | */ 21 | public interface ReplicationEvent { 22 | 23 | } 24 | -------------------------------------------------------------------------------- /rook-api/src/main/java/com/github/shyiko/rook/api/event/RowsMutationReplicationEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.api.event; 17 | 18 | import java.util.Collection; 19 | 20 | /** 21 | * @param type of collection for storing rows 22 | * @author Stanley Shyiko 23 | */ 24 | public abstract class RowsMutationReplicationEvent implements ReplicationEvent { 25 | 26 | protected final long serverId; 27 | protected final String schema; 28 | protected final String table; 29 | protected final T rows; 30 | 31 | protected RowsMutationReplicationEvent(long serverId, String schema, String table, T rows) { 32 | this.serverId = serverId; 33 | this.schema = schema; 34 | this.table = table; 35 | this.rows = rows; 36 | } 37 | 38 | public long getServerId() { 39 | return serverId; 40 | } 41 | 42 | public String getSchema() { 43 | return schema; 44 | } 45 | 46 | public String getTable() { 47 | return table; 48 | } 49 | 50 | public T getRows() { 51 | return rows; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /rook-api/src/main/java/com/github/shyiko/rook/api/event/TXReplicationEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.api.event; 17 | 18 | import java.util.List; 19 | 20 | /** 21 | * @author Stanley Shyiko 22 | */ 23 | public class TXReplicationEvent implements ReplicationEvent { 24 | 25 | private List events; 26 | 27 | public TXReplicationEvent(List events) { 28 | this.events = events; 29 | } 30 | 31 | public List getEvents() { 32 | return events; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | final StringBuilder sb = new StringBuilder(); 38 | sb.append("TXReplicationEvent"); 39 | sb.append("{events=["); 40 | for (ReplicationEvent event : events) { 41 | sb.append("\n ").append(event).append(","); 42 | } 43 | if (!events.isEmpty()) { 44 | sb.replace(sb.length() - 1, sb.length(), "\n"); 45 | } 46 | sb.append("]}"); 47 | return sb.toString(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /rook-api/src/main/java/com/github/shyiko/rook/api/event/UpdateRowsReplicationEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.api.event; 17 | 18 | import java.io.Serializable; 19 | import java.util.AbstractMap; 20 | import java.util.Arrays; 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | /** 25 | * @author Stanley Shyiko 26 | */ 27 | public class UpdateRowsReplicationEvent extends RowsMutationReplicationEvent>> { 29 | 30 | public UpdateRowsReplicationEvent(long serverId, String schema, String table, List> rows) { 32 | super(serverId, schema, table, rows); 33 | } 34 | 35 | @SuppressWarnings("unchecked") 36 | public UpdateRowsReplicationEvent(long serverId, String schema, String table, Serializable[] previousValues, 37 | Serializable[] values) { 38 | super(serverId, schema, table, Arrays.>asList( 39 | new AbstractMap.SimpleEntry(previousValues, values))); 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | final StringBuilder sb = new StringBuilder(); 45 | sb.append("UpdateRowsReplicationEvent"); 46 | sb.append("{serverId=").append(serverId); 47 | sb.append(", schema='").append(schema).append('\''); 48 | sb.append(", table='").append(table).append('\''); 49 | sb.append(", rows=["); 50 | if (!rows.isEmpty()) { 51 | for (Map.Entry row : rows) { 52 | sb.append("{"). 53 | append(Arrays.toString(row.getKey())). 54 | append("->"). 55 | append(Arrays.toString(row.getValue())). 56 | append("}, "); 57 | } 58 | int length = sb.length(); 59 | sb.replace(length - 2, length, ""); 60 | } 61 | sb.append("]}"); 62 | return sb.toString(); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /rook-source-mysql/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.github.shyiko.rook 7 | rook 8 | 0.1.4-SNAPSHOT 9 | 10 | 11 | rook-source-mysql 12 | 13 | 14 | 15 | com.github.shyiko.rook 16 | rook-api 17 | 0.1.4-SNAPSHOT 18 | 19 | 20 | com.github.shyiko 21 | mysql-binlog-connector-java 22 | 0.2.2 23 | 24 | 25 | org.slf4j 26 | slf4j-api 27 | 1.6.1 28 | 29 | 30 | 31 | 32 | 33 | sonatype-snapshots 34 | https://oss.sonatype.org/content/repositories/snapshots 35 | 36 | true 37 | 38 | 39 | false 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /rook-source-mysql/src/main/java/com/github/shyiko/rook/source/mysql/MySQLReplicationStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.source.mysql; 17 | 18 | import com.github.shyiko.mysql.binlog.BinaryLogClient; 19 | import com.github.shyiko.mysql.binlog.event.DeleteRowsEventData; 20 | import com.github.shyiko.mysql.binlog.event.Event; 21 | import com.github.shyiko.mysql.binlog.event.EventType; 22 | import com.github.shyiko.mysql.binlog.event.QueryEventData; 23 | import com.github.shyiko.mysql.binlog.event.TableMapEventData; 24 | import com.github.shyiko.mysql.binlog.event.UpdateRowsEventData; 25 | import com.github.shyiko.mysql.binlog.event.WriteRowsEventData; 26 | import com.github.shyiko.rook.api.ReplicationEventExceptionHandler; 27 | import com.github.shyiko.rook.api.ReplicationEventListener; 28 | import com.github.shyiko.rook.api.ReplicationStream; 29 | import com.github.shyiko.rook.api.event.RowsMutationReplicationEvent; 30 | import com.github.shyiko.rook.api.event.DeleteRowsReplicationEvent; 31 | import com.github.shyiko.rook.api.event.InsertRowsReplicationEvent; 32 | import com.github.shyiko.rook.api.event.ReplicationEvent; 33 | import com.github.shyiko.rook.api.event.TXReplicationEvent; 34 | import com.github.shyiko.rook.api.event.UpdateRowsReplicationEvent; 35 | import org.slf4j.Logger; 36 | import org.slf4j.LoggerFactory; 37 | 38 | import java.io.IOException; 39 | import java.util.ArrayList; 40 | import java.util.HashMap; 41 | import java.util.Iterator; 42 | import java.util.LinkedList; 43 | import java.util.List; 44 | import java.util.Map; 45 | import java.util.Set; 46 | import java.util.HashSet; 47 | import java.util.concurrent.TimeoutException; 48 | 49 | /** 50 | * @author Stanley Shyiko 51 | */ 52 | public class MySQLReplicationStream implements ReplicationStream { 53 | 54 | private Logger logger = LoggerFactory.getLogger(getClass()); 55 | 56 | private final String hostname; 57 | private final int port; 58 | private final String username; 59 | private final String password; 60 | 61 | private BinaryLogClient binaryLogClient; 62 | 63 | private final List listeners = new LinkedList(); 64 | private ReplicationEventExceptionHandler exceptionHandler; 65 | 66 | private volatile boolean groupEventsByTX = true; 67 | 68 | private Set ignoredServerIds = new HashSet(); 69 | private Set ignoredTables = new HashSet(); 70 | 71 | public MySQLReplicationStream(String username, String password) { 72 | this("localhost", 3306, username, password); 73 | } 74 | 75 | public MySQLReplicationStream(String hostname, int port, String username, String password) { 76 | this.hostname = hostname; 77 | this.port = port; 78 | this.username = username; 79 | this.password = password; 80 | } 81 | 82 | public void setGroupEventsByTX(boolean groupEventsByTX) { 83 | this.groupEventsByTX = groupEventsByTX; 84 | } 85 | 86 | public void setExceptionHandler(ReplicationEventExceptionHandler exceptionHandler) { 87 | this.exceptionHandler = exceptionHandler; 88 | } 89 | 90 | public void setIgnoredHostsIds(Set ignoredServerIds) { 91 | this.ignoredServerIds = ignoredServerIds; 92 | } 93 | 94 | public void setIgnoredTables(Set ignoredTables) { 95 | this.ignoredTables = ignoredTables; 96 | } 97 | 98 | @Override 99 | public void connect() throws IOException { 100 | allocateBinaryLogClient().connect(); 101 | } 102 | 103 | @Override 104 | public void connect(long timeoutInMilliseconds) throws IOException, TimeoutException { 105 | allocateBinaryLogClient().connect(timeoutInMilliseconds); 106 | } 107 | 108 | private synchronized BinaryLogClient allocateBinaryLogClient() { 109 | if (isConnected()) { 110 | throw new IllegalStateException("MySQL replication stream is already open"); 111 | } 112 | binaryLogClient = new BinaryLogClient(hostname, port, username, password); 113 | binaryLogClient.registerEventListener(new DelegatingEventListener()); 114 | configureBinaryLogClient(binaryLogClient); 115 | return binaryLogClient; 116 | } 117 | 118 | protected void configureBinaryLogClient(BinaryLogClient binaryLogClient) { 119 | // template method 120 | } 121 | 122 | @Override 123 | public synchronized boolean isConnected() { 124 | return binaryLogClient != null && binaryLogClient.isConnected(); 125 | } 126 | 127 | @Override 128 | public void registerListener(ReplicationEventListener listener) { 129 | synchronized (listeners) { 130 | listeners.add(listener); 131 | } 132 | } 133 | 134 | @Override 135 | public void unregisterListener(ReplicationEventListener listener) { 136 | synchronized (listeners) { 137 | listeners.remove(listener); 138 | } 139 | } 140 | 141 | public void unregisterListener(Class listenerClass) { 142 | synchronized (listeners) { 143 | Iterator iterator = listeners.iterator(); 144 | while (iterator.hasNext()) { 145 | ReplicationEventListener replicationListener = iterator.next(); 146 | if (listenerClass.isInstance(replicationListener)) { 147 | iterator.remove(); 148 | } 149 | } 150 | } 151 | } 152 | 153 | @Override 154 | public synchronized void disconnect() throws IOException { 155 | if (binaryLogClient != null) { 156 | binaryLogClient.disconnect(); 157 | binaryLogClient = null; 158 | } 159 | } 160 | 161 | private void notifyListeners(ReplicationEvent event) { 162 | if ((event = filterEvent(event)) == null) { 163 | return; 164 | } 165 | synchronized (listeners) { 166 | for (ReplicationEventListener listener : listeners) { 167 | try { 168 | listener.onEvent(event); 169 | } catch (Exception e) { 170 | if (logger.isWarnEnabled()) { 171 | logger.warn(listener + " choked on " + event, e); 172 | } 173 | if (exceptionHandler != null) { 174 | exceptionHandler.handle(e); 175 | } 176 | } 177 | } 178 | } 179 | } 180 | 181 | private ReplicationEvent filterEvent(ReplicationEvent event) { 182 | if (event instanceof TXReplicationEvent) { 183 | List filteredEvents = new ArrayList(); 184 | List txEvents = ((TXReplicationEvent) event).getEvents(); 185 | for (ReplicationEvent e : txEvents) { 186 | ReplicationEvent fe = filterOutTxEvent(e); 187 | if (fe != null) { 188 | filteredEvents.add(fe); 189 | } 190 | } 191 | return filteredEvents.isEmpty() ? null : new TXReplicationEvent(filteredEvents); 192 | } 193 | return event; 194 | } 195 | 196 | private ReplicationEvent filterOutTxEvent(ReplicationEvent e) { 197 | if (e instanceof RowsMutationReplicationEvent) { 198 | RowsMutationReplicationEvent re = (RowsMutationReplicationEvent) e; 199 | return !ignoredServerIds.contains(re.getServerId()) && !ignoredTables.contains(re.getTable()) ? e : null; 200 | } 201 | return e; 202 | } 203 | 204 | private final class DelegatingEventListener implements BinaryLogClient.EventListener { 205 | 206 | private final Map tablesById = new HashMap(); 207 | private final List txQueue = new LinkedList(); 208 | private boolean transactionInProgress; 209 | 210 | @Override 211 | public void onEvent(Event event) { 212 | // todo: do something about schema changes 213 | EventType eventType = event.getHeader().getEventType(); 214 | switch (eventType) { 215 | case TABLE_MAP: 216 | TableMapEventData tableMapEventData = event.getData(); 217 | tablesById.put(tableMapEventData.getTableId(), tableMapEventData); 218 | break; 219 | case PRE_GA_WRITE_ROWS: 220 | case WRITE_ROWS: 221 | case EXT_WRITE_ROWS: 222 | handleWriteRowsEvent(event); 223 | break; 224 | case PRE_GA_UPDATE_ROWS: 225 | case UPDATE_ROWS: 226 | case EXT_UPDATE_ROWS: 227 | handleUpdateRowsEvent(event); 228 | break; 229 | case PRE_GA_DELETE_ROWS: 230 | case DELETE_ROWS: 231 | case EXT_DELETE_ROWS: 232 | handleDeleteRowsEvent(event); 233 | break; 234 | case QUERY: 235 | if (groupEventsByTX) { 236 | QueryEventData queryEventData = event.getData(); 237 | String query = queryEventData.getSql(); 238 | if ("BEGIN".equals(query)) { 239 | transactionInProgress = true; 240 | } 241 | } 242 | break; 243 | case XID: 244 | if (groupEventsByTX) { 245 | notifyListeners(new TXReplicationEvent(new ArrayList(txQueue))); 246 | txQueue.clear(); 247 | transactionInProgress = false; 248 | } 249 | break; 250 | default: 251 | // ignore 252 | } 253 | } 254 | 255 | private void handleWriteRowsEvent(Event event) { 256 | WriteRowsEventData eventData = event.getData(); 257 | TableMapEventData tableMapEvent = tablesById.get(eventData.getTableId()); 258 | enqueue(new InsertRowsReplicationEvent(event.getHeader().getServerId(), tableMapEvent.getDatabase(), 259 | tableMapEvent.getTable(), eventData.getRows())); 260 | } 261 | 262 | private void handleUpdateRowsEvent(Event event) { 263 | UpdateRowsEventData eventData = event.getData(); 264 | TableMapEventData tableMapEvent = tablesById.get(eventData.getTableId()); 265 | enqueue(new UpdateRowsReplicationEvent(event.getHeader().getServerId(), tableMapEvent.getDatabase(), 266 | tableMapEvent.getTable(), eventData.getRows())); 267 | } 268 | 269 | private void handleDeleteRowsEvent(Event event) { 270 | DeleteRowsEventData eventData = event.getData(); 271 | TableMapEventData tableMapEvent = tablesById.get(eventData.getTableId()); 272 | enqueue(new DeleteRowsReplicationEvent(event.getHeader().getServerId(), tableMapEvent.getDatabase(), 273 | tableMapEvent.getTable(), eventData.getRows())); 274 | } 275 | 276 | private void enqueue(ReplicationEvent event) { 277 | if (groupEventsByTX && transactionInProgress) { 278 | txQueue.add(event); 279 | } else { 280 | notifyListeners(event); 281 | } 282 | } 283 | 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /rook-target-hibernate4-cache/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.github.shyiko.rook 7 | rook 8 | 0.1.4-SNAPSHOT 9 | 10 | 11 | rook-target-hibernate4-cache 12 | 13 | 14 | 15 | com.github.shyiko.rook 16 | rook-api 17 | 0.1.4-SNAPSHOT 18 | 19 | 20 | org.hibernate 21 | hibernate-core 22 | 4.2.2.Final 23 | 24 | 25 | org.hibernate 26 | hibernate-ehcache 27 | 4.2.2.Final 28 | 29 | 30 | net.sf.ehcache 31 | ehcache-core 32 | 33 | 34 | 35 | 36 | net.sf.ehcache 37 | ehcache-core 38 | 2.6.6 39 | 40 | 41 | org.testng 42 | testng 43 | 6.8 44 | test 45 | 46 | 47 | com.h2database 48 | h2 49 | 1.3.168 50 | test 51 | 52 | 53 | commons-lang 54 | commons-lang 55 | 2.6 56 | test 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /rook-target-hibernate4-cache/src/main/java/com/github/shyiko/rook/target/hibernate4/cache/AbstractCacheSynchronizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Igor Grunskiy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate4.cache; 17 | 18 | import com.github.shyiko.rook.api.ReplicationEventListener; 19 | import com.github.shyiko.rook.api.event.InsertRowsReplicationEvent; 20 | import com.github.shyiko.rook.api.event.ReplicationEvent; 21 | import com.github.shyiko.rook.api.event.RowsMutationReplicationEvent; 22 | import com.github.shyiko.rook.api.event.TXReplicationEvent; 23 | import com.github.shyiko.rook.api.event.UpdateRowsReplicationEvent; 24 | import com.github.shyiko.rook.api.event.DeleteRowsReplicationEvent; 25 | import java.io.Serializable; 26 | import java.util.ArrayList; 27 | import java.util.Collection; 28 | import java.util.LinkedList; 29 | import java.util.List; 30 | import java.util.Map; 31 | 32 | /** 33 | * @author Igor Grunskiy 34 | */ 35 | public abstract class AbstractCacheSynchronizer implements ReplicationEventListener { 36 | 37 | protected final SynchronizationContext synchronizationContext; 38 | 39 | protected AbstractCacheSynchronizer(SynchronizationContext synchronizationContext) { 40 | this.synchronizationContext = synchronizationContext; 41 | } 42 | 43 | @Override 44 | public void onEvent(ReplicationEvent event) { 45 | Collection events = null; 46 | if (event instanceof TXReplicationEvent) { 47 | Collection replicationEvents = ((TXReplicationEvent) event).getEvents(); 48 | events = new ArrayList(replicationEvents.size()); 49 | for (ReplicationEvent replicationEvent : replicationEvents) { 50 | if (replicationEvent instanceof RowsMutationReplicationEvent) { 51 | events.add((RowsMutationReplicationEvent) replicationEvent); 52 | } 53 | } 54 | } else if (event instanceof RowsMutationReplicationEvent) { 55 | events = new LinkedList(); 56 | events.add((RowsMutationReplicationEvent) event); 57 | } 58 | if (events != null && !events.isEmpty()) { 59 | processTX(events); 60 | } 61 | } 62 | 63 | protected List resolveAffectedRows(RowsMutationReplicationEvent event) { 64 | if (event instanceof InsertRowsReplicationEvent) { 65 | return ((InsertRowsReplicationEvent) event).getRows(); 66 | } 67 | if (event instanceof UpdateRowsReplicationEvent) { 68 | List> rows = ((UpdateRowsReplicationEvent) event).getRows(); 69 | List result = new ArrayList(rows.size()); 70 | for (Map.Entry row : rows) { 71 | result.add(row.getKey()); 72 | } 73 | return result; 74 | } 75 | if (event instanceof DeleteRowsReplicationEvent) { 76 | return ((DeleteRowsReplicationEvent) event).getRows(); 77 | } 78 | throw new UnsupportedOperationException("Unexpected " + event.getClass()); 79 | } 80 | 81 | protected abstract void processTX(Collection txEvents); 82 | 83 | } 84 | -------------------------------------------------------------------------------- /rook-target-hibernate4-cache/src/main/java/com/github/shyiko/rook/target/hibernate4/cache/EvictionTarget.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate4.cache; 17 | 18 | /** 19 | * @author Stanley Shyiko 20 | */ 21 | public class EvictionTarget { 22 | 23 | /** 24 | * className or role depending whether target is an entity or a collection 25 | */ 26 | private final String name; 27 | private final PrimaryKey primaryKey; 28 | private final boolean collection; 29 | 30 | public EvictionTarget(String name, PrimaryKey primaryKey, boolean collection) { 31 | this.name = name; 32 | this.primaryKey = primaryKey; 33 | this.collection = collection; 34 | } 35 | 36 | public String getName() { 37 | return name; 38 | } 39 | 40 | public PrimaryKey getPrimaryKey() { 41 | return primaryKey; 42 | } 43 | 44 | public boolean isCollection() { 45 | return collection; 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return "EvictionTarget{" + 51 | "collection=" + collection + 52 | ", name='" + name + '\'' + 53 | '}'; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /rook-target-hibernate4-cache/src/main/java/com/github/shyiko/rook/target/hibernate4/cache/HibernateCacheSynchronizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate4.cache; 17 | 18 | import com.github.shyiko.rook.api.ReplicationEventListener; 19 | import com.github.shyiko.rook.api.event.ReplicationEvent; 20 | import org.hibernate.SessionFactory; 21 | import org.hibernate.cfg.Configuration; 22 | 23 | import java.sql.SQLException; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | /** 28 | * @author Stanley Shyiko 29 | */ 30 | public class HibernateCacheSynchronizer implements ReplicationEventListener { 31 | 32 | private final List listeners; 33 | 34 | public HibernateCacheSynchronizer(Configuration configuration, SessionFactory sessionFactory) throws SQLException { 35 | SynchronizationContext synchronizationContext = new SynchronizationContext(configuration, sessionFactory); 36 | listeners = new ArrayList(); 37 | listeners.add(new SecondLevelCacheSynchronizer(synchronizationContext)); 38 | if (synchronizationContext.getSessionFactory().getSettings().isQueryCacheEnabled()) { 39 | listeners.add(new QueryCacheSynchronizer(synchronizationContext)); 40 | } 41 | } 42 | 43 | @Override 44 | public void onEvent(ReplicationEvent event) { 45 | for (ReplicationEventListener listener : listeners) { 46 | listener.onEvent(event); 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /rook-target-hibernate4-cache/src/main/java/com/github/shyiko/rook/target/hibernate4/cache/PrimaryKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate4.cache; 17 | 18 | import org.hibernate.mapping.Collection; 19 | import org.hibernate.mapping.Column; 20 | import org.hibernate.mapping.Component; 21 | import org.hibernate.mapping.KeyValue; 22 | import org.hibernate.mapping.PersistentClass; 23 | import org.hibernate.mapping.Property; 24 | import org.hibernate.mapping.Table; 25 | import org.hibernate.type.EmbeddedComponentType; 26 | import org.hibernate.type.Type; 27 | 28 | import java.io.Serializable; 29 | import java.lang.reflect.Field; 30 | import java.util.Iterator; 31 | import java.util.Map; 32 | 33 | /** 34 | * @author Stanley Shyiko 35 | */ 36 | public class PrimaryKey { 37 | 38 | private Class entityClass; 39 | private final KeyColumn[] positionWithinRow; 40 | 41 | public PrimaryKey(Collection collection, Map columnIndexByNameMap) { 42 | this(collection.getKey(), collection.getCollectionTable(), columnIndexByNameMap); 43 | if (positionWithinRow.length != 1) { 44 | throw new IllegalStateException("Unexpected PK length " + positionWithinRow.length); 45 | } 46 | } 47 | 48 | public PrimaryKey(PersistentClass persistentClass, Map columnIndexByNameMap) { 49 | this(persistentClass.getKey(), persistentClass.getTable(), columnIndexByNameMap); 50 | final Type type = persistentClass.getIdentifier().getType(); 51 | if (type instanceof EmbeddedComponentType) { 52 | entityClass = type.getReturnedClass(); 53 | } else { 54 | entityClass = persistentClass.getMappedClass(); 55 | } 56 | } 57 | 58 | private PrimaryKey(KeyValue keyValue, Table table, Map columnIndexByNameMap) { 59 | KeyColumn[] positionWithinRow = new KeyColumn[keyValue.getColumnSpan()]; 60 | int index = 0; 61 | if (keyValue instanceof Component) { 62 | Iterator propertyIterator = ((Component) keyValue).getPropertyIterator(); 63 | while (propertyIterator.hasNext()) { 64 | Property property = (Property) propertyIterator.next(); 65 | String columnName = ((Column) property.getColumnIterator().next()).getName(); 66 | positionWithinRow[index++] = new KeyColumn(property.getName(), columnIndexByNameMap.get(columnName)); 67 | } 68 | } else { 69 | Iterator columnIterator = keyValue.getColumnIterator(); 70 | while (columnIterator.hasNext()) { 71 | String columnName = ((Column) columnIterator.next()).getName(); 72 | positionWithinRow[index++] = new KeyColumn(columnName, columnIndexByNameMap.get(columnName)); 73 | } 74 | } 75 | if (positionWithinRow.length == 0) { 76 | throw new IllegalStateException("Unable to determine PK for " + table.getName()); 77 | } 78 | this.positionWithinRow = positionWithinRow; 79 | } 80 | 81 | public Serializable getIdentifier(Serializable[] row) { 82 | if (positionWithinRow.length == 1) { 83 | return row[positionWithinRow[0].index]; 84 | } 85 | try { 86 | Serializable identifier = (Serializable) entityClass.newInstance(); 87 | for (KeyColumn keyColumn : positionWithinRow) { 88 | Field field = entityClass.getDeclaredField(keyColumn.name); 89 | field.setAccessible(true); 90 | field.set(identifier, row[keyColumn.index]); 91 | } 92 | return identifier; 93 | } catch (Throwable e) { 94 | throw new IllegalStateException("Unable to instantiate entity key", e); 95 | } 96 | } 97 | 98 | /** 99 | * Class that contains single key column data. 100 | */ 101 | private static final class KeyColumn { 102 | 103 | private final String name; 104 | private final int index; 105 | 106 | private KeyColumn(String name, int index) { 107 | this.name = name; 108 | this.index = index; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /rook-target-hibernate4-cache/src/main/java/com/github/shyiko/rook/target/hibernate4/cache/QueryCacheSynchronizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate4.cache; 17 | 18 | import com.github.shyiko.rook.api.event.RowsMutationReplicationEvent; 19 | import org.hibernate.engine.spi.SessionFactoryImplementor; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import java.io.Serializable; 24 | import java.util.Collection; 25 | import java.util.Collections; 26 | import java.util.HashSet; 27 | import java.util.Set; 28 | 29 | /** 30 | * @author Stanley Shyiko 31 | */ 32 | public class QueryCacheSynchronizer extends AbstractCacheSynchronizer { 33 | 34 | private static final String[] EMPTY_STRING_ARRAY = new String[0]; 35 | 36 | private final Logger logger = LoggerFactory.getLogger(getClass()); 37 | 38 | public QueryCacheSynchronizer(SynchronizationContext synchronizationContext) { 39 | super(synchronizationContext); 40 | if (!synchronizationContext.getSessionFactory().getSettings().isQueryCacheEnabled()) { 41 | throw new IllegalStateException( 42 | "Query Cache (controlled by hibernate.cache.use_query_cache property) is disabled"); 43 | } 44 | } 45 | 46 | @Override 47 | protected void processTX(Collection events) { 48 | Set spacesToInvalidate = new HashSet(); 49 | for (RowsMutationReplicationEvent event : events) { 50 | Collection evictionTargets = synchronizationContext.getEvictionTargets( 51 | event.getSchema().toLowerCase() + "." + event.getTable().toLowerCase()); 52 | for (EvictionTarget evictionTarget : evictionTargets) { 53 | Collections.addAll(spacesToInvalidate, resolveQuerySpaces(evictionTarget)); 54 | } 55 | } 56 | if (!spacesToInvalidate.isEmpty()) { 57 | SessionFactoryImplementor factory = synchronizationContext.getSessionFactory(); 58 | if (logger.isDebugEnabled()) { 59 | logger.debug("Invalidating spaces: " + spacesToInvalidate); 60 | } 61 | factory.getUpdateTimestampsCache().invalidate(spacesToInvalidate.toArray( 62 | new Serializable[spacesToInvalidate.size()])); 63 | } 64 | } 65 | 66 | private String[] resolveQuerySpaces(EvictionTarget evictionTarget) { 67 | String role = evictionTarget.getName(); 68 | SessionFactoryImplementor factory = synchronizationContext.getSessionFactory(); 69 | Serializable[] spaces; 70 | if (evictionTarget.isCollection()) { 71 | spaces = factory.getCollectionPersister(role).getCollectionSpaces(); 72 | } else { 73 | // todo(shyiko): how about querySpaces? 74 | spaces = factory.getEntityPersister(role).getPropertySpaces(); 75 | } 76 | return spaces == null ? EMPTY_STRING_ARRAY : (String[]) spaces; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /rook-target-hibernate4-cache/src/main/java/com/github/shyiko/rook/target/hibernate4/cache/SecondLevelCacheSynchronizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate4.cache; 17 | 18 | import com.github.shyiko.rook.api.event.RowsMutationReplicationEvent; 19 | import org.hibernate.Cache; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import java.io.Serializable; 24 | import java.util.Collection; 25 | 26 | /** 27 | * @author Stanley Shyiko 28 | */ 29 | public class SecondLevelCacheSynchronizer extends AbstractCacheSynchronizer { 30 | 31 | private final Logger logger = LoggerFactory.getLogger(getClass()); 32 | 33 | public SecondLevelCacheSynchronizer(SynchronizationContext synchronizationContext) { 34 | super(synchronizationContext); 35 | if (!synchronizationContext.getSessionFactory().getSettings().isSecondLevelCacheEnabled()) { 36 | throw new IllegalStateException( 37 | "Second Level Cache (controlled by hibernate.cache.use_second_level_cache property) is disabled"); 38 | } 39 | } 40 | 41 | protected void processTX(Collection txEvents) { 42 | for (RowsMutationReplicationEvent event : txEvents) { 43 | Cache cache = synchronizationContext.getSessionFactory().getCache(); 44 | String qualifiedName = event.getSchema().toLowerCase() + "." + event.getTable().toLowerCase(); 45 | 46 | for (EvictionTarget evictionTarget : synchronizationContext.getEvictionTargets(qualifiedName)) { 47 | for (Serializable[] row : resolveAffectedRows(event)) { 48 | Serializable key = evictionTarget.getPrimaryKey().getIdentifier(row); 49 | if (logger.isDebugEnabled()) { 50 | logger.debug("Evicting " + evictionTarget.getName() + "#" + key); 51 | } 52 | // todo(shyiko): do we need a lock here? 53 | if (evictionTarget.isCollection()) { 54 | if (key == null) { 55 | continue; // that's ok, there is no mapped collection for this row 56 | } 57 | cache.evictCollection(evictionTarget.getName(), key); 58 | } else { 59 | if (key == null) { 60 | throw new IllegalStateException("Failed to extract primary key from " + evictionTarget); 61 | } 62 | cache.evictEntity(evictionTarget.getName(), key); 63 | } 64 | } 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /rook-target-hibernate4-cache/src/main/java/com/github/shyiko/rook/target/hibernate4/cache/SynchronizationContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate4.cache; 17 | 18 | import org.hibernate.SessionFactory; 19 | import org.hibernate.cfg.Configuration; 20 | import org.hibernate.engine.spi.SessionFactoryImplementor; 21 | import org.hibernate.mapping.PersistentClass; 22 | import org.hibernate.mapping.Table; 23 | import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; 24 | 25 | import java.sql.Connection; 26 | import java.sql.DatabaseMetaData; 27 | import java.sql.ResultSet; 28 | import java.sql.SQLException; 29 | import java.util.Collection; 30 | import java.util.Collections; 31 | import java.util.HashMap; 32 | import java.util.Iterator; 33 | import java.util.LinkedList; 34 | import java.util.Map; 35 | 36 | /** 37 | * @author Stanley Shyiko 38 | */ 39 | public class SynchronizationContext { 40 | 41 | private final String schema; 42 | private final SessionFactory sessionFactory; 43 | private final Map> targetsByTable = 44 | new HashMap>(); 45 | private final Map> columnMappingsByTable = 46 | new HashMap>(); 47 | 48 | public SynchronizationContext(Configuration configuration, SessionFactory sessionFactory) 49 | throws SQLException { 50 | this.schema = ((SessionFactoryImplementor) sessionFactory).getJdbcServices(). 51 | getExtractedMetaDataSupport().getConnectionCatalogName().toLowerCase(); 52 | this.sessionFactory = sessionFactory; 53 | loadClassMappings(configuration); 54 | loadCollectionMappings(configuration); 55 | } 56 | 57 | public SessionFactoryImplementor getSessionFactory() { 58 | return (SessionFactoryImplementor) sessionFactory; 59 | } 60 | 61 | public Collection getEvictionTargets(String table) { 62 | Collection evictionTargets = targetsByTable.get(table.toLowerCase()); 63 | return evictionTargets == null ? Collections.emptyList() : evictionTargets; 64 | } 65 | 66 | private void loadClassMappings(Configuration configuration) throws SQLException { 67 | for (Iterator iterator = configuration.getClassMappings(); iterator.hasNext(); ) { 68 | PersistentClass persistentClass = iterator.next(); 69 | String entityName = persistentClass.getEntityName(); 70 | boolean isCacheable = ((SessionFactoryImplementor) sessionFactory). 71 | getEntityPersister(entityName).hasCache(); 72 | if (isCacheable) { 73 | Table table = persistentClass.getTable(); 74 | PrimaryKey primaryKey = new PrimaryKey(persistentClass, getColumnIndexByNameMap(table)); 75 | evictionTargetsOf(table).add(new EvictionTarget(entityName, primaryKey, false)); 76 | } 77 | } 78 | } 79 | 80 | private Map getColumnIndexByNameMap(Table table) throws SQLException { 81 | String tableName = table.getName(); 82 | if (!columnMappingsByTable.containsKey(tableName)) { 83 | Map columnIndexByName = extractColumnMapping(table); 84 | columnMappingsByTable.put(tableName, Collections.unmodifiableMap(columnIndexByName)); 85 | } 86 | return columnMappingsByTable.get(tableName); 87 | } 88 | 89 | private Map extractColumnMapping(Table table) throws SQLException { 90 | Map result = new HashMap(); 91 | Connection connection = null; 92 | ConnectionProvider connectionProvider = getSessionFactory().getServiceRegistry(). 93 | getService(ConnectionProvider.class); 94 | try { 95 | connection = connectionProvider.getConnection(); 96 | DatabaseMetaData meta = connection.getMetaData(); 97 | ResultSet rs = meta.getColumns(table.getCatalog(), table.getSchema(), table.getName(), "%"); 98 | try { 99 | int index = 0; 100 | while (rs.next()) { 101 | String column = rs.getString("COLUMN_NAME"); 102 | if (column != null) { 103 | result.put(column, index++); 104 | } 105 | } 106 | } finally { 107 | rs.close(); 108 | } 109 | } finally { 110 | connectionProvider.closeConnection(connection); 111 | } 112 | return result; 113 | } 114 | 115 | private void loadCollectionMappings(Configuration configuration) throws SQLException { 116 | @SuppressWarnings("unchecked") 117 | Iterator iterator = configuration.getCollectionMappings(); 118 | while (iterator.hasNext()) { 119 | org.hibernate.mapping.Collection collection = iterator.next(); 120 | String role = collection.getRole(); 121 | boolean isCacheable = ((SessionFactoryImplementor) sessionFactory). 122 | getCollectionPersister(role).hasCache(); 123 | if (isCacheable) { 124 | Table table = collection.getCollectionTable(); 125 | PrimaryKey primaryKey = new PrimaryKey(collection, getColumnIndexByNameMap(table)); 126 | evictionTargetsOf(table).add(new EvictionTarget(role, primaryKey, true)); 127 | } 128 | } 129 | } 130 | 131 | private Collection evictionTargetsOf(Table table) { 132 | String key = schema + "." + table.getName().toLowerCase(); 133 | Collection evictionTargets = targetsByTable.get(key); 134 | if (evictionTargets == null) { 135 | targetsByTable.put(key, evictionTargets = new LinkedList()); 136 | } 137 | return evictionTargets; 138 | } 139 | 140 | public Map getColumnMappingsByTable(String tableName) { 141 | return columnMappingsByTable.get(tableName); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /rook-target-hibernate4-cache/src/test/java/com/github/shyiko/rook/target/hibernate/cache/AbstractHibernateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate.cache; 17 | 18 | import com.github.shyiko.rook.target.hibernate4.cache.SynchronizationContext; 19 | import org.hibernate.SessionFactory; 20 | import org.hibernate.cfg.Configuration; 21 | import org.hibernate.service.ServiceRegistry; 22 | import org.hibernate.service.ServiceRegistryBuilder; 23 | import org.testng.annotations.AfterClass; 24 | import org.testng.annotations.BeforeClass; 25 | 26 | import java.sql.SQLException; 27 | 28 | /** 29 | * @author Stanley Shyiko 30 | */ 31 | public abstract class AbstractHibernateTest { 32 | 33 | protected SynchronizationContext synchronizationContext; 34 | 35 | @BeforeClass 36 | public void setUp() throws SQLException { 37 | Configuration configuration = new Configuration().configure("hibernate.cfg.xml"); 38 | ServiceRegistry serviceRegistry = new ServiceRegistryBuilder(). 39 | applySettings(configuration.getProperties()).buildServiceRegistry(); 40 | SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry); 41 | synchronizationContext = new SynchronizationContext(configuration, sessionFactory); 42 | } 43 | 44 | @AfterClass 45 | public void tearDown() { 46 | synchronizationContext.getSessionFactory().close(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /rook-target-hibernate4-cache/src/test/java/com/github/shyiko/rook/target/hibernate/cache/SecondLevelCacheSynchronizerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Ivan Zaytsev 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate.cache; 17 | 18 | import com.github.shyiko.rook.api.event.DeleteRowsReplicationEvent; 19 | import com.github.shyiko.rook.target.hibernate.cache.model.Entity; 20 | import com.github.shyiko.rook.target.hibernate.cache.model.EntityProperty; 21 | import com.github.shyiko.rook.target.hibernate.cache.model.EntityWithCompositeKey; 22 | import com.github.shyiko.rook.target.hibernate4.cache.SecondLevelCacheSynchronizer; 23 | import org.hibernate.Cache; 24 | import org.hibernate.Session; 25 | import org.hibernate.SessionFactory; 26 | import org.testng.annotations.Test; 27 | 28 | import java.io.Serializable; 29 | import java.util.concurrent.atomic.AtomicLong; 30 | 31 | import static org.testng.Assert.assertFalse; 32 | import static org.testng.Assert.assertNotNull; 33 | import static org.testng.Assert.assertTrue; 34 | 35 | /** 36 | * @author Ivan Zaytsev 37 | */ 38 | public class SecondLevelCacheSynchronizerTest extends AbstractHibernateTest { 39 | 40 | @Test 41 | public void testEvictionOfEntityWithCompositeKey() throws Exception { 42 | final Cache cache = synchronizationContext.getSessionFactory().getCache(); 43 | final EntityWithCompositeKey firstEntityKey = new EntityWithCompositeKey(1, 2), 44 | secondEntityKey = new EntityWithCompositeKey(3, 4); 45 | executeInTransaction(new Callback() { 46 | 47 | @Override 48 | public void execute(Session session) { 49 | session.save(new EntityWithCompositeKey(1, 2, "name_12")); 50 | session.save(new EntityWithCompositeKey(3, 4, "name_34")); 51 | } 52 | }); 53 | executeInTransaction(new Callback() { 54 | 55 | @Override 56 | public void execute(Session session) { 57 | session.get(EntityWithCompositeKey.class, firstEntityKey); 58 | session.get(EntityWithCompositeKey.class, secondEntityKey); 59 | } 60 | }); 61 | executeInTransaction(new Callback() { 62 | 63 | @Override 64 | public void execute(Session obj) { 65 | assertTrue(cache.containsEntity(EntityWithCompositeKey.class, firstEntityKey)); 66 | SecondLevelCacheSynchronizer secondLevelCacheSynchronizer = 67 | new SecondLevelCacheSynchronizer(synchronizationContext); 68 | secondLevelCacheSynchronizer.onEvent(new DeleteRowsReplicationEvent(0, "rook", "entity_with_cpk", 69 | new Serializable[] {2L, 1L})); 70 | assertFalse(cache.containsEntity(EntityWithCompositeKey.class, firstEntityKey)); 71 | assertTrue(cache.containsEntity(EntityWithCompositeKey.class, secondEntityKey)); 72 | } 73 | }); 74 | } 75 | 76 | @Test 77 | public void testEvictionOfEntityCollection() throws Exception { 78 | final Cache cache = synchronizationContext.getSessionFactory().getCache(); 79 | final AtomicLong entityId = new AtomicLong(); 80 | executeInTransaction(new Callback() { 81 | 82 | @Override 83 | public void execute(Session session) { 84 | Entity entity = new Entity(); 85 | entity.setName("Name"); 86 | 87 | EntityProperty entityProperty = new EntityProperty(); 88 | entityProperty.setName("name"); 89 | entityProperty.setValue("value"); 90 | entityProperty.setEnclosingEntity(entity); 91 | entity.getProperties().add(entityProperty); 92 | 93 | entityId.set((Long) session.save(entity)); 94 | } 95 | }); 96 | executeInTransaction(new Callback() { 97 | 98 | @Override 99 | public void execute(Session session) { 100 | assertNotNull(session.get(Entity.class, entityId.get())); 101 | } 102 | }); 103 | executeInTransaction(new Callback() { 104 | 105 | @Override 106 | public void execute(Session obj) { 107 | assertTrue(cache.containsEntity(Entity.class, entityId.get())); 108 | SecondLevelCacheSynchronizer secondLevelCacheSynchronizer = 109 | new SecondLevelCacheSynchronizer(synchronizationContext); 110 | secondLevelCacheSynchronizer.onEvent(new DeleteRowsReplicationEvent(0, "rook", "entity", 111 | new Serializable[] {entityId.get()})); 112 | assertFalse(cache.containsEntity(Entity.class, entityId.get())); 113 | 114 | assertTrue(cache.containsCollection(Entity.class.getName() + ".properties", entityId.get())); 115 | 116 | // entity_property table structure [id, name, value, entity_id] 117 | secondLevelCacheSynchronizer.onEvent(new DeleteRowsReplicationEvent(0, "rook", "entity_property", 118 | new Serializable[]{null, null, null, entityId.get()})); 119 | 120 | assertFalse(cache.containsCollection(Entity.class.getName() + ".properties", entityId.get())); 121 | } 122 | }); 123 | } 124 | 125 | private void executeInTransaction(Callback callback) { 126 | SessionFactory sessionFactory = synchronizationContext.getSessionFactory(); 127 | Session session = sessionFactory.openSession(); 128 | try { 129 | session.beginTransaction(); 130 | callback.execute(session); 131 | session.getTransaction().commit(); 132 | } finally { 133 | session.close(); 134 | } 135 | } 136 | 137 | private interface Callback { 138 | 139 | void execute(T obj); 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /rook-target-hibernate4-cache/src/test/java/com/github/shyiko/rook/target/hibernate/cache/SynchronizationContextTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Ivan Zaytsev 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate.cache; 17 | 18 | import com.github.shyiko.rook.target.hibernate.cache.model.EntityWithCompositeKey; 19 | import com.github.shyiko.rook.target.hibernate4.cache.EvictionTarget; 20 | import com.github.shyiko.rook.target.hibernate4.cache.PrimaryKey; 21 | import org.apache.commons.lang.builder.EqualsBuilder; 22 | import org.testng.Assert; 23 | import org.testng.annotations.Test; 24 | 25 | import java.io.Serializable; 26 | import java.util.Collection; 27 | import java.util.Map; 28 | 29 | import static org.testng.Assert.assertEquals; 30 | 31 | /** 32 | * @author Ivan Zaytsev 33 | */ 34 | public class SynchronizationContextTest extends AbstractHibernateTest { 35 | 36 | @Test 37 | public void testSimpleKeyMapping() throws Exception { 38 | PrimaryKey primaryKey = synchronizationContext.getEvictionTargets("rook.entity"). 39 | iterator().next().getPrimaryKey(); 40 | Serializable[] allFieldsFofDummy = new Serializable[] {1L, "name"}; 41 | assertEquals(primaryKey.getIdentifier(allFieldsFofDummy), (Long) 1L); 42 | } 43 | 44 | @Test 45 | public void testCollectionMapping() throws Exception { 46 | Collection evictionTargets = synchronizationContext.getEvictionTargets("rook.entity_property"); 47 | assertEquals(1, evictionTargets.size()); 48 | EvictionTarget collectionEvictionTarget = evictionTargets.iterator().next(); 49 | 50 | // simulating correct binlog column order 51 | Map mappingsByName = synchronizationContext.getColumnMappingsByTable("entity_property"); 52 | Serializable[] collectionfields = new Serializable[mappingsByName.size()]; 53 | collectionfields[mappingsByName.get("id")] = 2L; 54 | collectionfields[mappingsByName.get("entity_id")] = 1L; 55 | collectionfields[mappingsByName.get("name")] = 56 | "Answer to the Ultimate Question of Life, the Universe, and Everything"; 57 | collectionfields[mappingsByName.get("value")] = "42"; 58 | 59 | assertEquals(collectionEvictionTarget.getPrimaryKey().getIdentifier(collectionfields), (Long) 1L); 60 | } 61 | 62 | @Test 63 | public void testCompositeKeyMapping() throws Exception { 64 | PrimaryKey primaryKey = synchronizationContext.getEvictionTargets("rook.entity_with_cpk"). 65 | iterator().next().getPrimaryKey(); 66 | EntityWithCompositeKey expectedKey = new EntityWithCompositeKey(1L, 2L); 67 | Assert.assertTrue(EqualsBuilder.reflectionEquals( 68 | primaryKey.getIdentifier(new Serializable[]{2L, 1L, "name"}), expectedKey)); 69 | } 70 | 71 | @Test 72 | public void testNonCacheableEntityMapping() throws Exception { 73 | Assert.assertTrue(synchronizationContext.getEvictionTargets("rook.non_cacheable_entity").isEmpty()); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /rook-target-hibernate4-cache/src/test/java/com/github/shyiko/rook/target/hibernate/cache/model/Entity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Ivan Zaytsev 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate.cache.model; 17 | 18 | import org.hibernate.annotations.Cache; 19 | import org.hibernate.annotations.CacheConcurrencyStrategy; 20 | 21 | import javax.persistence.CascadeType; 22 | import javax.persistence.Column; 23 | import javax.persistence.FetchType; 24 | import javax.persistence.GeneratedValue; 25 | import javax.persistence.Id; 26 | import javax.persistence.OneToMany; 27 | import javax.persistence.Table; 28 | import java.io.Serializable; 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | 32 | /** 33 | * @author Ivan Zaytsev 34 | */ 35 | @javax.persistence.Entity 36 | @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "CORE_REGION") 37 | @Table(name = "entity") 38 | public class Entity implements Serializable { 39 | 40 | @Id 41 | @GeneratedValue 42 | @Column(name = "_id") 43 | private long id; 44 | 45 | @Column 46 | private String name; 47 | 48 | @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "CORE_REGION") 49 | @OneToMany(mappedBy = "enclosingEntity", fetch = FetchType.EAGER, targetEntity = EntityProperty.class, 50 | cascade = CascadeType.ALL, orphanRemoval = true) 51 | private List properties = new ArrayList(); 52 | 53 | public long getId() { 54 | return id; 55 | } 56 | 57 | public void setId(long id) { 58 | this.id = id; 59 | } 60 | 61 | public String getName() { 62 | return name; 63 | } 64 | 65 | public void setName(String name) { 66 | this.name = name; 67 | } 68 | 69 | public List getProperties() { 70 | return properties; 71 | } 72 | 73 | public void setProperties(List properties) { 74 | this.properties = properties; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /rook-target-hibernate4-cache/src/test/java/com/github/shyiko/rook/target/hibernate/cache/model/EntityProperty.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Igor Grunskiy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate.cache.model; 17 | 18 | import javax.persistence.Column; 19 | import javax.persistence.GeneratedValue; 20 | import javax.persistence.Id; 21 | import javax.persistence.JoinColumn; 22 | import javax.persistence.ManyToOne; 23 | import javax.persistence.Table; 24 | import java.io.Serializable; 25 | 26 | /** 27 | * @author Igor Grunskiy 28 | */ 29 | @javax.persistence.Entity 30 | @Table(name = "entity_property") 31 | public class EntityProperty implements Serializable { 32 | 33 | @Id 34 | @GeneratedValue 35 | private long id; 36 | 37 | @ManyToOne 38 | @JoinColumn(name = "entity_id") 39 | private Entity enclosingEntity; 40 | 41 | @Column(name = "name", nullable = false) 42 | private String name; 43 | 44 | @Column(columnDefinition = "mediumtext", name = "value") 45 | private String value; 46 | 47 | public Entity getEnclosingEntity() { 48 | return enclosingEntity; 49 | } 50 | 51 | public void setEnclosingEntity(Entity enclosingEntity) { 52 | this.enclosingEntity = enclosingEntity; 53 | } 54 | 55 | public long getId() { 56 | return id; 57 | } 58 | 59 | public void setId(long id) { 60 | this.id = id; 61 | } 62 | 63 | public String getName() { 64 | return name; 65 | } 66 | 67 | public void setName(String name) { 68 | this.name = name; 69 | } 70 | 71 | public String getValue() { 72 | return value; 73 | } 74 | 75 | public void setValue(String value) { 76 | this.value = value; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /rook-target-hibernate4-cache/src/test/java/com/github/shyiko/rook/target/hibernate/cache/model/EntityWithCompositeKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Ivan Zaytsev 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate.cache.model; 17 | 18 | import org.hibernate.annotations.Cache; 19 | import org.hibernate.annotations.CacheConcurrencyStrategy; 20 | 21 | import javax.persistence.Column; 22 | import javax.persistence.Entity; 23 | import javax.persistence.Id; 24 | import javax.persistence.Table; 25 | import java.io.Serializable; 26 | 27 | /** 28 | * @author Ivan Zaytsev 29 | */ 30 | @Entity 31 | @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "CORE_REGION") 32 | @Table(name = "entity_with_cpk") 33 | public class EntityWithCompositeKey implements Serializable { 34 | 35 | @Id 36 | private long id1; 37 | @Id 38 | @Column(name = "_id2") 39 | private long id2; 40 | @Column 41 | private String name; 42 | 43 | public EntityWithCompositeKey() { 44 | } 45 | 46 | public EntityWithCompositeKey(long id1, long id2) { 47 | this.id1 = id1; 48 | this.id2 = id2; 49 | } 50 | 51 | public EntityWithCompositeKey(long id1, long id2, String name) { 52 | this.id1 = id1; 53 | this.id2 = id2; 54 | this.name = name; 55 | } 56 | 57 | public long getId1() { 58 | return id1; 59 | } 60 | 61 | public void setId1(long id1) { 62 | this.id1 = id1; 63 | } 64 | 65 | public Long getId2() { 66 | return id2; 67 | } 68 | 69 | public void setId2(Long id2) { 70 | this.id2 = id2; 71 | } 72 | 73 | public String getName() { 74 | return name; 75 | } 76 | 77 | public void setName(String name) { 78 | this.name = name; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /rook-target-hibernate4-cache/src/test/java/com/github/shyiko/rook/target/hibernate/cache/model/NonCacheableEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate.cache.model; 17 | 18 | import javax.persistence.Id; 19 | import javax.persistence.Table; 20 | 21 | /** 22 | * @author Stanley Shyiko 23 | */ 24 | @javax.persistence.Entity 25 | @Table(name = "non_cacheable_entity") 26 | public class NonCacheableEntity { 27 | 28 | @Id 29 | private long id; 30 | 31 | public long getId() { 32 | return id; 33 | } 34 | 35 | public void setId(long id) { 36 | this.id = id; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /rook-target-hibernate4-cache/src/test/resources/ehcache.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 9 | 10 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /rook-target-hibernate4-cache/src/test/resources/hibernate.cfg.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | org.h2.Driver 9 | jdbc:h2:mem:rook;MODE=MySQL;DB_CLOSE_DELAY=-1 10 | sa 11 | 12 | org.hibernate.dialect.H2Dialect 13 | create-drop 14 | org.hibernate.cache.ehcache.EhCacheRegionFactory 15 | true 16 | true 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /rook-target-hibernate4-fulltextindex/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.github.shyiko.rook 7 | rook 8 | 0.1.4-SNAPSHOT 9 | 10 | 11 | rook-target-hibernate4-fulltextindex 12 | 13 | 14 | 15 | com.github.shyiko.rook 16 | rook-api 17 | 0.1.4-SNAPSHOT 18 | 19 | 20 | org.hibernate 21 | hibernate-search 22 | 4.3.0.Final 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /rook-target-hibernate4-fulltextindex/src/main/java/com/github/shyiko/rook/target/hibernate4/fulltextindex/DefaultRowsMutationIndexer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate4.fulltextindex; 17 | 18 | import org.hibernate.Session; 19 | import org.hibernate.Transaction; 20 | import org.hibernate.search.FullTextSession; 21 | import org.hibernate.search.Search; 22 | import org.hibernate.search.indexes.interceptor.EntityIndexingInterceptor; 23 | import org.hibernate.search.indexes.interceptor.IndexingOverride; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | 27 | import java.io.Serializable; 28 | import java.util.Collection; 29 | import java.util.HashMap; 30 | import java.util.List; 31 | import java.util.Map; 32 | import java.util.TreeSet; 33 | 34 | /** 35 | * @author Stanley Shyiko 36 | */ 37 | public class DefaultRowsMutationIndexer implements RowsMutationIndexer { 38 | 39 | private final Logger logger = LoggerFactory.getLogger(getClass()); 40 | 41 | @Override 42 | public void index(List rowsMutations, SynchronizationContext synchronizationContext) { 43 | IndexingLog indexingLog = new IndexingLog(); 44 | Session session = synchronizationContext.getSessionFactory().openSession(); 45 | try { 46 | FullTextSession fullTextSession = Search.getFullTextSession(session); 47 | Transaction tx = fullTextSession.beginTransaction(); 48 | try { 49 | for (RowsMutation entity : rowsMutations) { 50 | indexRowsMutation(fullTextSession, entity, indexingLog, synchronizationContext); 51 | } 52 | tx.commit(); 53 | } catch (RuntimeException e) { 54 | tx.rollback(); 55 | } 56 | } finally { 57 | session.close(); 58 | } 59 | if (logger.isDebugEnabled()) { 60 | logger.debug("Indexed " + indexingLog.toString()); 61 | } 62 | } 63 | 64 | @SuppressWarnings("unchecked") 65 | private void indexRowsMutation(FullTextSession session, RowsMutation rowsMutation, IndexingLog indexingLog, 66 | SynchronizationContext synchronizationContext) { 67 | IndexingDirective indexingDirective = rowsMutation.getIndexingDirective(); 68 | PrimaryKey primaryKey = indexingDirective.getPrimaryKey(); 69 | Class entityClass = primaryKey.getEntityClass(); 70 | for (Serializable[] row : rowsMutation.getRows()) { 71 | Serializable id = primaryKey.getIdentifier(row); 72 | if (indexingLog.isIndexed(entityClass, id)) { 73 | continue; 74 | } 75 | Object entity = loadEntity(session, entityClass, id); 76 | if (!indexingDirective.isSuppressSelfIndexing()) { 77 | if (entity != null) { 78 | indexEntity(session, entity, indexingDirective); 79 | } else { 80 | purgeEntity(session, entityClass, id); 81 | } 82 | } 83 | indexingLog.markIndexed(entityClass, id); 84 | if (entity != null) { 85 | indexContainers(session, entity, indexingDirective, indexingLog, synchronizationContext); 86 | } 87 | } 88 | } 89 | 90 | private void indexContainers(FullTextSession session, Object entity, IndexingDirective indexingDirective, 91 | IndexingLog indexingLog, SynchronizationContext synchronizationContext) { 92 | for (Reference containerReference : indexingDirective.getContainerReferences()) { 93 | Object container = containerReference.navigateFrom(entity); 94 | if (container != null) { 95 | IndexingDirective containerIndexingDirective = 96 | synchronizationContext.getIndexingDirective(containerReference.getTargetEntityClass()); 97 | if (container instanceof Collection) { 98 | for (Object containerEntity : (Collection) container) { 99 | indexContainer(session, containerEntity, containerIndexingDirective, indexingLog, 100 | synchronizationContext); 101 | } 102 | } else { 103 | indexContainer(session, container, containerIndexingDirective, indexingLog, 104 | synchronizationContext); 105 | } 106 | } 107 | } 108 | } 109 | 110 | private void indexContainer(FullTextSession session, Object entity, IndexingDirective indexingDirective, 111 | IndexingLog indexingLog, SynchronizationContext synchronizationContext) { 112 | PrimaryKey primaryKey = indexingDirective.getPrimaryKey(); 113 | Class entityClass = primaryKey.getEntityClass(); 114 | Serializable id = primaryKey.getIdentifier(entity); 115 | if (indexingLog.isIndexed(entityClass, id)) { 116 | return; 117 | } 118 | if (!indexingDirective.isSuppressSelfIndexing()) { 119 | indexEntity(session, entity, indexingDirective); 120 | } 121 | indexingLog.markIndexed(entityClass, id); 122 | indexContainers(session, entity, indexingDirective, indexingLog, synchronizationContext); 123 | } 124 | 125 | protected Object loadEntity(Session session, Class entityClass, Serializable id) { 126 | return session.get(entityClass, id); 127 | } 128 | 129 | @SuppressWarnings("unchecked") 130 | protected void indexEntity(FullTextSession session, Object entity, IndexingDirective indexingDirective) { 131 | EntityIndexingInterceptor interceptor = indexingDirective.getEntityIndexingInterceptor(); 132 | if (interceptor != null) { 133 | IndexingOverride indexingOverride = interceptor.onUpdate(entity); 134 | if (indexingOverride == IndexingOverride.SKIP) { 135 | return; 136 | } else 137 | if (indexingOverride == IndexingOverride.REMOVE) { 138 | PrimaryKey primaryKey = indexingDirective.getPrimaryKey(); 139 | session.purge(primaryKey.getEntityClass(), primaryKey.getIdentifier(entity)); 140 | return; 141 | } 142 | } 143 | session.index(entity); 144 | } 145 | 146 | @SuppressWarnings("unchecked") 147 | protected void purgeEntity(FullTextSession session, Class entityClass, Serializable id) { 148 | session.purge(entityClass, id); 149 | } 150 | 151 | private static class IndexingLog { 152 | 153 | private Map> indexedEntities = new HashMap>(); 154 | 155 | public void markIndexed(Class entityClass, Serializable id) { 156 | Collection ids = indexedEntities.get(entityClass); 157 | if (ids == null) { 158 | indexedEntities.put(entityClass, ids = new TreeSet()); 159 | } 160 | ids.add(id); 161 | } 162 | 163 | public boolean isIndexed(Class entityClass, Serializable id) { 164 | Collection ids = indexedEntities.get(entityClass); 165 | return ids != null && ids.contains(id); 166 | } 167 | 168 | @Override 169 | public String toString() { 170 | StringBuilder sb = new StringBuilder("["); 171 | if (!indexedEntities.isEmpty()) { 172 | for (Map.Entry> entry : indexedEntities.entrySet()) { 173 | sb.append(entry.getKey().getSimpleName()).append("#").append(entry.getValue()).append(", "); 174 | } 175 | sb.replace(sb.length() - 2, sb.length(), ""); 176 | } 177 | sb.append("]"); 178 | return sb.toString(); 179 | } 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /rook-target-hibernate4-fulltextindex/src/main/java/com/github/shyiko/rook/target/hibernate4/fulltextindex/FullTextIndexSynchronizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate4.fulltextindex; 17 | 18 | import com.github.shyiko.rook.api.ReplicationEventListener; 19 | import com.github.shyiko.rook.api.event.DeleteRowsReplicationEvent; 20 | import com.github.shyiko.rook.api.event.InsertRowsReplicationEvent; 21 | import com.github.shyiko.rook.api.event.ReplicationEvent; 22 | import com.github.shyiko.rook.api.event.RowsMutationReplicationEvent; 23 | import com.github.shyiko.rook.api.event.TXReplicationEvent; 24 | import com.github.shyiko.rook.api.event.UpdateRowsReplicationEvent; 25 | import org.hibernate.SessionFactory; 26 | import org.hibernate.cfg.Configuration; 27 | 28 | import java.io.Serializable; 29 | import java.util.ArrayList; 30 | import java.util.Collection; 31 | import java.util.LinkedList; 32 | import java.util.List; 33 | import java.util.Map; 34 | 35 | /** 36 | * @author Stanley Shyiko 37 | */ 38 | public class FullTextIndexSynchronizer implements ReplicationEventListener { 39 | 40 | private final SynchronizationContext synchronizationContext; 41 | private final RowsMutationIndexer indexer; 42 | 43 | public FullTextIndexSynchronizer(Configuration configuration, SessionFactory sessionFactory) { 44 | this(configuration, sessionFactory, new DefaultRowsMutationIndexer()); 45 | } 46 | 47 | public FullTextIndexSynchronizer(Configuration configuration, SessionFactory sessionFactory, 48 | RowsMutationIndexer rowChangeIndexer) { 49 | this.synchronizationContext = new SynchronizationContext(configuration, sessionFactory); 50 | this.indexer = rowChangeIndexer; 51 | } 52 | 53 | @Override 54 | public void onEvent(ReplicationEvent event) { 55 | Collection events = null; 56 | if (event instanceof TXReplicationEvent) { 57 | Collection replicationEvents = ((TXReplicationEvent) event).getEvents(); 58 | events = new ArrayList(replicationEvents.size()); 59 | for (ReplicationEvent replicationEvent : replicationEvents) { 60 | if (replicationEvent instanceof RowsMutationReplicationEvent) { 61 | events.add((RowsMutationReplicationEvent) replicationEvent); 62 | } 63 | } 64 | } else 65 | if (event instanceof RowsMutationReplicationEvent) { 66 | events = new LinkedList(); 67 | events.add((RowsMutationReplicationEvent) event); 68 | } 69 | if (events != null && !events.isEmpty()) { 70 | updateIndex(events); 71 | } 72 | } 73 | 74 | private void updateIndex(Collection events) { 75 | List rowsMutations = new ArrayList(); 76 | for (RowsMutationReplicationEvent event : events) { 77 | String qualifiedName = event.getSchema().toLowerCase() + "." + event.getTable().toLowerCase(); 78 | Collection indexingDirectives = 79 | synchronizationContext.getIndexingDirectives(qualifiedName); 80 | for (IndexingDirective indexingDirective : indexingDirectives) { 81 | rowsMutations.add(new RowsMutation(resolveAffectedRows(event), indexingDirective)); 82 | } 83 | } 84 | if (!rowsMutations.isEmpty()) { 85 | indexer.index(rowsMutations, synchronizationContext); 86 | } 87 | } 88 | 89 | private List resolveAffectedRows(RowsMutationReplicationEvent event) { 90 | if (event instanceof InsertRowsReplicationEvent) { 91 | return ((InsertRowsReplicationEvent) event).getRows(); 92 | } 93 | if (event instanceof UpdateRowsReplicationEvent) { 94 | List> rows = ((UpdateRowsReplicationEvent) event).getRows(); 95 | List result = new ArrayList(rows.size()); 96 | for (Map.Entry row : rows) { 97 | result.add(row.getKey()); 98 | } 99 | return result; 100 | } 101 | if (event instanceof DeleteRowsReplicationEvent) { 102 | return ((DeleteRowsReplicationEvent) event).getRows(); 103 | } 104 | throw new UnsupportedOperationException("Unexpected " + event.getClass()); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /rook-target-hibernate4-fulltextindex/src/main/java/com/github/shyiko/rook/target/hibernate4/fulltextindex/IndexingDirective.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate4.fulltextindex; 17 | 18 | import org.hibernate.search.indexes.interceptor.EntityIndexingInterceptor; 19 | 20 | import java.util.Collection; 21 | 22 | /** 23 | * @author Stanley Shyiko 24 | */ 25 | public class IndexingDirective { 26 | 27 | private final PrimaryKey primaryKey; 28 | private final boolean suppressSelfIndexing; 29 | private final EntityIndexingInterceptor entityIndexingInterceptor; 30 | private final Collection containerReferences; 31 | 32 | public IndexingDirective(PrimaryKey primaryKey, boolean suppressSelfIndexing, 33 | EntityIndexingInterceptor entityIndexingInterceptor, Collection containerReferences) { 34 | this.primaryKey = primaryKey; 35 | this.suppressSelfIndexing = suppressSelfIndexing; 36 | this.entityIndexingInterceptor = entityIndexingInterceptor; 37 | this.containerReferences = containerReferences; 38 | } 39 | 40 | public PrimaryKey getPrimaryKey() { 41 | return primaryKey; 42 | } 43 | 44 | public boolean isSuppressSelfIndexing() { 45 | return suppressSelfIndexing; 46 | } 47 | 48 | public EntityIndexingInterceptor getEntityIndexingInterceptor() { 49 | return entityIndexingInterceptor; 50 | } 51 | 52 | public Collection getContainerReferences() { 53 | return containerReferences; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /rook-target-hibernate4-fulltextindex/src/main/java/com/github/shyiko/rook/target/hibernate4/fulltextindex/PrimaryKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate4.fulltextindex; 17 | 18 | import org.hibernate.mapping.Column; 19 | import org.hibernate.mapping.Component; 20 | import org.hibernate.mapping.KeyValue; 21 | import org.hibernate.mapping.PersistentClass; 22 | import org.hibernate.mapping.Property; 23 | import org.hibernate.mapping.Table; 24 | import org.hibernate.property.Getter; 25 | 26 | import java.io.Serializable; 27 | import java.lang.reflect.Field; 28 | import java.util.HashMap; 29 | import java.util.Iterator; 30 | import java.util.Map; 31 | 32 | /** 33 | * @author Stanley Shyiko 34 | */ 35 | public class PrimaryKey { 36 | 37 | private Class entityClass; 38 | private Getter getter; 39 | private final KeyColumn[] positionWithinRow; 40 | 41 | public PrimaryKey(PersistentClass persistentClass) { 42 | this.entityClass = persistentClass.getMappedClass(); 43 | KeyValue keyValue = persistentClass.getKey(); 44 | Table table = persistentClass.getTable(); 45 | Map columnIndexByNameMap = getColumnIndexByNameMap(table); 46 | KeyColumn[] positionWithinRow = new KeyColumn[keyValue.getColumnSpan()]; 47 | int index = 0; 48 | if (keyValue instanceof Component) { 49 | Iterator propertyIterator = ((Component) keyValue).getPropertyIterator(); 50 | while (propertyIterator.hasNext()) { 51 | Property property = (Property) propertyIterator.next(); 52 | String columnName = ((Column) property.getColumnIterator().next()).getName(); 53 | positionWithinRow[index++] = new KeyColumn(property.getName(), columnIndexByNameMap.get(columnName)); 54 | } 55 | } else { 56 | Iterator columnIterator = keyValue.getColumnIterator(); 57 | while (columnIterator.hasNext()) { 58 | String columnName = ((Column) columnIterator.next()).getName(); 59 | positionWithinRow[index++] = new KeyColumn(columnName, columnIndexByNameMap.get(columnName)); 60 | } 61 | } 62 | if (positionWithinRow.length == 0) { 63 | throw new IllegalStateException("Unable to determine PK for " + table.getName()); 64 | } 65 | Property identifierProperty = persistentClass.getIdentifierProperty(); 66 | this.getter = identifierProperty.getGetter(this.entityClass); 67 | this.positionWithinRow = positionWithinRow; 68 | 69 | } 70 | 71 | public PrimaryKey(PrimaryKey primaryKey, Map columnIndexByNameMap) { 72 | this.entityClass = primaryKey.entityClass; 73 | KeyColumn[] positionWithinRow = new KeyColumn[columnIndexByNameMap.size()]; 74 | int index = 0; 75 | for (Map.Entry entry : columnIndexByNameMap.entrySet()) { 76 | positionWithinRow[index] = new KeyColumn(entry.getKey(), entry.getValue()); 77 | } 78 | this.getter = primaryKey.getter; 79 | this.positionWithinRow = positionWithinRow; 80 | } 81 | 82 | private Map getColumnIndexByNameMap(Table table) { 83 | Map columnIndexByName = new HashMap(); 84 | int index = 0; 85 | Iterator columnIterator = table.getColumnIterator(); 86 | while (columnIterator.hasNext()) { 87 | Column column = (Column) columnIterator.next(); 88 | columnIndexByName.put(column.getName(), index++); 89 | } 90 | return columnIndexByName; 91 | } 92 | 93 | public Class getEntityClass() { 94 | return entityClass; 95 | } 96 | 97 | public Serializable getIdentifier(Object entity) { 98 | return (Serializable) getter.get(entity); 99 | } 100 | 101 | public Serializable getIdentifier(Serializable[] row) { 102 | if (positionWithinRow.length == 1) { 103 | return row[positionWithinRow[0].index]; 104 | } 105 | try { 106 | Serializable identifier = (Serializable) entityClass.newInstance(); 107 | for (KeyColumn keyColumn : positionWithinRow) { 108 | Field field = entityClass.getDeclaredField(keyColumn.name); 109 | field.setAccessible(true); 110 | field.set(identifier, row[keyColumn.index]); 111 | } 112 | return identifier; 113 | } catch (Throwable e) { 114 | throw new IllegalStateException("Unable to instantiate entity key", e); 115 | } 116 | } 117 | 118 | /** 119 | * Class that contains single key column data. 120 | */ 121 | private static final class KeyColumn { 122 | 123 | private final String name; 124 | private final int index; 125 | 126 | private KeyColumn(String name, int index) { 127 | this.name = name; 128 | this.index = index; 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /rook-target-hibernate4-fulltextindex/src/main/java/com/github/shyiko/rook/target/hibernate4/fulltextindex/Reference.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate4.fulltextindex; 17 | 18 | import org.hibernate.property.Getter; 19 | 20 | import java.lang.reflect.Field; 21 | import java.lang.reflect.Member; 22 | import java.lang.reflect.ParameterizedType; 23 | import java.lang.reflect.Type; 24 | 25 | /** 26 | * @author Stanley Shyiko 27 | */ 28 | public class Reference { 29 | 30 | private final Class targetEntityClass; 31 | private final Getter getter; 32 | 33 | public Reference(Getter getter) { 34 | this.getter = getter; 35 | this.targetEntityClass = resolveTargetEntityClass(getter); 36 | } 37 | 38 | private Class resolveTargetEntityClass(Getter getter) { 39 | Class result = getter.getReturnType(); 40 | Member member = getter.getMember(); 41 | if (member instanceof Field) { 42 | Type genericType = ((Field) member).getGenericType(); 43 | if (genericType instanceof ParameterizedType) { 44 | Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments(); 45 | if (actualTypeArguments.length == 1) { 46 | result = (Class) actualTypeArguments[0]; 47 | } 48 | } 49 | // todo: else check *To*(targetEntity) 50 | } 51 | return result; 52 | } 53 | 54 | public Class getTargetEntityClass() { 55 | return targetEntityClass; 56 | } 57 | 58 | public Object navigateFrom(Object entity) { 59 | return getter.get(entity); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /rook-target-hibernate4-fulltextindex/src/main/java/com/github/shyiko/rook/target/hibernate4/fulltextindex/RowsMutation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate4.fulltextindex; 17 | 18 | import java.io.Serializable; 19 | import java.util.List; 20 | 21 | /** 22 | * @author Stanley Shyiko 23 | */ 24 | public class RowsMutation { 25 | 26 | private final List rows; 27 | private final IndexingDirective indexingDirective; 28 | 29 | public RowsMutation(List rows, IndexingDirective indexingDirective) { 30 | this.rows = rows; 31 | this.indexingDirective = indexingDirective; 32 | } 33 | 34 | public List getRows() { 35 | return rows; 36 | } 37 | 38 | public IndexingDirective getIndexingDirective() { 39 | return indexingDirective; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /rook-target-hibernate4-fulltextindex/src/main/java/com/github/shyiko/rook/target/hibernate4/fulltextindex/RowsMutationIndexer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate4.fulltextindex; 17 | 18 | import java.util.List; 19 | 20 | /** 21 | * @author Stanley Shyiko 22 | */ 23 | public interface RowsMutationIndexer { 24 | 25 | void index(List rowsMutations, SynchronizationContext synchronizationContext); 26 | } 27 | -------------------------------------------------------------------------------- /rook-target-hibernate4-fulltextindex/src/main/java/com/github/shyiko/rook/target/hibernate4/fulltextindex/SynchronizationContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.target.hibernate4.fulltextindex; 17 | 18 | import org.hibernate.SessionFactory; 19 | import org.hibernate.cfg.Configuration; 20 | import org.hibernate.engine.spi.SessionFactoryImplementor; 21 | import org.hibernate.mapping.Column; 22 | import org.hibernate.mapping.PersistentClass; 23 | import org.hibernate.mapping.Property; 24 | import org.hibernate.mapping.Table; 25 | import org.hibernate.mapping.ToOne; 26 | import org.hibernate.mapping.Value; 27 | import org.hibernate.property.Getter; 28 | import org.hibernate.search.annotations.ContainedIn; 29 | import org.hibernate.search.annotations.Indexed; 30 | import org.hibernate.search.indexes.interceptor.DefaultEntityInterceptor; 31 | import org.hibernate.search.indexes.interceptor.EntityIndexingInterceptor; 32 | 33 | import java.lang.annotation.Annotation; 34 | import java.lang.reflect.AccessibleObject; 35 | import java.lang.reflect.Member; 36 | import java.util.ArrayList; 37 | import java.util.Arrays; 38 | import java.util.Collection; 39 | import java.util.Collections; 40 | import java.util.HashMap; 41 | import java.util.HashSet; 42 | import java.util.Iterator; 43 | import java.util.Map; 44 | 45 | /** 46 | * @author Stanley Shyiko 47 | */ 48 | public class SynchronizationContext { 49 | 50 | private final String schema; 51 | private final SessionFactory sessionFactory; 52 | 53 | private final Map directivesByTable = new HashMap(); 54 | private final Map directivesByEntityClass = new HashMap(); 55 | 56 | public SynchronizationContext(Configuration configuration, SessionFactory sessionFactory) { 57 | this.schema = ((SessionFactoryImplementor) sessionFactory).getJdbcServices(). 58 | getExtractedMetaDataSupport().getConnectionCatalogName().toLowerCase(); 59 | this.sessionFactory = sessionFactory; 60 | loadIndexingDirectives(configuration); 61 | } 62 | 63 | public SessionFactoryImplementor getSessionFactory() { 64 | return (SessionFactoryImplementor) sessionFactory; 65 | } 66 | 67 | public Collection getIndexingDirectives(String table) { 68 | IndexingDirective indexingTarget = directivesByTable.get(table.toLowerCase()); 69 | return indexingTarget == null ? Collections.emptyList() : Arrays.asList(indexingTarget); 70 | } 71 | 72 | public IndexingDirective getIndexingDirective(Class entityClass) { 73 | return directivesByEntityClass.get(entityClass); 74 | } 75 | 76 | @SuppressWarnings("unchecked") 77 | private void loadIndexingDirectives(Configuration configuration) { 78 | Map directivesByEntityNameMap = new HashMap(); 79 | Collection allContainedInProperties = new ArrayList(); 80 | for (Iterator classIterator = configuration.getClassMappings(); classIterator.hasNext(); ) { 81 | PersistentClass persistentClass = classIterator.next(); 82 | boolean suppressSelfIndexing = true; 83 | Class mappedClass = persistentClass.getMappedClass(); 84 | Indexed indexed = (Indexed) mappedClass.getAnnotation(Indexed.class); 85 | EntityIndexingInterceptor indexingInterceptor = null; 86 | if (indexed != null) { 87 | suppressSelfIndexing = false; 88 | Class interceptorClass = indexed.interceptor(); 89 | if (interceptorClass != DefaultEntityInterceptor.class) { 90 | try { 91 | indexingInterceptor = interceptorClass.newInstance(); 92 | } catch (InstantiationException e) { 93 | throw new RuntimeException("Failed to instantiate " + interceptorClass, e); 94 | } catch (IllegalAccessException e) { 95 | throw new RuntimeException("Failed to instantiate " + interceptorClass, e); 96 | } 97 | } 98 | } 99 | Collection containedInProperties = extractAnnotatedProperties(persistentClass, ContainedIn.class); 100 | if (suppressSelfIndexing && containedInProperties.isEmpty()) { 101 | continue; 102 | } 103 | allContainedInProperties.addAll(containedInProperties); 104 | PrimaryKey primaryKey = new PrimaryKey(persistentClass); 105 | Collection containers = new ArrayList(); 106 | for (Property property : containedInProperties) { 107 | containers.add(new Reference(property.getGetter(mappedClass))); 108 | } 109 | IndexingDirective indexingDirective = new IndexingDirective(primaryKey, suppressSelfIndexing, 110 | indexingInterceptor, containers); 111 | Table table = persistentClass.getTable(); 112 | directivesByTable.put(schema + "." + table.getName().toLowerCase(), indexingDirective); 113 | directivesByEntityClass.put(mappedClass, indexingDirective); 114 | directivesByEntityNameMap.put(persistentClass.getEntityName(), indexingDirective); 115 | } 116 | loadIndexingDirectivesForJoinTables(allContainedInProperties, directivesByEntityNameMap); 117 | } 118 | 119 | @SuppressWarnings("unchecked") 120 | private Collection extractAnnotatedProperties(PersistentClass persistentClass, 121 | Class annotation) { 122 | Class mappedClass = persistentClass.getMappedClass(); 123 | Collection properties = new ArrayList(); 124 | for (Iterator propertyIterator = persistentClass.getPropertyIterator(); 125 | propertyIterator.hasNext(); ) { 126 | Property property = propertyIterator.next(); 127 | Getter getter = property.getGetter(mappedClass); 128 | if (getter == null) { 129 | continue; 130 | } 131 | Member mappedClassMember = getter.getMember(); 132 | boolean isRequestedAnnotationPresent = mappedClassMember instanceof AccessibleObject && 133 | ((AccessibleObject) mappedClassMember).isAnnotationPresent(annotation); 134 | if (isRequestedAnnotationPresent) { 135 | properties.add(property); 136 | } 137 | } 138 | return properties; 139 | } 140 | 141 | private void loadIndexingDirectivesForJoinTables(Collection allContainedInProperties, 142 | Map directivesByEntityNameMap) { 143 | for (Property property : allContainedInProperties) { 144 | Value value = property.getValue(); 145 | if (value instanceof org.hibernate.mapping.Collection) { 146 | org.hibernate.mapping.Collection collection = (org.hibernate.mapping.Collection) value; 147 | Table collectionTable = collection.getCollectionTable(); 148 | String tableName = schema + "." + collectionTable.getName().toLowerCase(); 149 | if (directivesByTable.containsKey(tableName)) { 150 | continue; 151 | } 152 | PrimaryKey primaryKey = resolveForeignPrimaryKey(collection, directivesByEntityNameMap); 153 | if (primaryKey == null) { 154 | continue; 155 | } 156 | IndexingDirective containerIndexingDirective = directivesByEntityClass.get(primaryKey.getEntityClass()); 157 | directivesByTable.put(tableName, 158 | new IndexingDirective(primaryKey, containerIndexingDirective.isSuppressSelfIndexing(), 159 | containerIndexingDirective.getEntityIndexingInterceptor(), 160 | containerIndexingDirective.getContainerReferences())); 161 | } 162 | } 163 | } 164 | 165 | @SuppressWarnings("unchecked") 166 | private PrimaryKey resolveForeignPrimaryKey(org.hibernate.mapping.Collection collection, 167 | Map directivesByEntityNameMap) { 168 | Table collectionTable = collection.getCollectionTable(); 169 | ToOne element = (ToOne) collection.getElement(); 170 | IndexingDirective indexingDirective = directivesByEntityNameMap.get(element.getReferencedEntityName()); 171 | if (indexingDirective == null) { 172 | return null; 173 | } 174 | Collection targetPrimaryKeyColumnNames = new HashSet(); 175 | for (Iterator columnIterator = element.getColumnIterator(); columnIterator.hasNext(); ) { 176 | Column column = columnIterator.next(); 177 | targetPrimaryKeyColumnNames.add(column.getName()); 178 | } 179 | Map columnIndexByNameMap = new HashMap(); 180 | int index = 0; 181 | for (Iterator columnIterator = collectionTable.getColumnIterator(); columnIterator.hasNext(); ) { 182 | Column column = columnIterator.next(); 183 | if (targetPrimaryKeyColumnNames.contains(column.getName())) { 184 | columnIndexByNameMap.put(column.getName(), index); 185 | } 186 | index++; 187 | } 188 | return new PrimaryKey(indexingDirective.getPrimaryKey(), columnIndexByNameMap); 189 | } 190 | 191 | } 192 | -------------------------------------------------------------------------------- /supplement/codequality/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /supplement/codequality/license.header: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright [yyyy] [name of copyright owner] 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ -------------------------------------------------------------------------------- /supplement/codequality/readme.md: -------------------------------------------------------------------------------- 1 | ### IDE integration 2 | 3 | 1. import checkstyle.xml configuration file 4 | 2. set "checkstyle.header.file" property to the absolute path of license.header file 5 | 3. add [checkstyle-nonstandard-0.1.0.jar](http://search.maven.org/remotecontent?filepath=com/github/shyiko/checkstyle-nonstandard/0.1.0/checkstyle-nonstandard-0.1.0.jar) to the list of third-party check providers -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.github.shyiko.rook 9 | rook 10 | ../../../pom.xml 11 | 0.1.4-SNAPSHOT 12 | 13 | 14 | com.github.shyiko.rook.examples 15 | hibernate4-cache-over-mysql 16 | 0.1.4-SNAPSHOT 17 | 18 | 19 | vagrant 20 | ${basedir}/../../vagrant/mysql-5.5.27-sandbox-prepackaged 21 | 22 | 23 | 24 | 25 | com.github.shyiko.rook 26 | rook-source-mysql 27 | 0.1.2-SNAPSHOT 28 | test 29 | 30 | 31 | com.github.shyiko.rook 32 | rook-target-hibernate4-cache 33 | 0.1.2-SNAPSHOT 34 | test 35 | 36 | 37 | mysql 38 | mysql-connector-java 39 | 5.1.21 40 | test 41 | 42 | 43 | org.slf4j 44 | slf4j-log4j12 45 | 1.6.1 46 | test 47 | 48 | 49 | log4j 50 | log4j 51 | 1.2.16 52 | test 53 | 54 | 55 | org.testng 56 | testng 57 | 6.1.1 58 | test 59 | 60 | 61 | 62 | 63 | 64 | 65 | org.apache.maven.plugins 66 | maven-surefire-plugin 67 | 2.10 68 | 69 | 70 | **/*IntegrationTest.java 71 | 72 | 73 | 74 | 75 | org.apache.maven.plugins 76 | maven-failsafe-plugin 77 | 2.15 78 | 79 | 80 | **/*IntegrationTest.java 81 | 82 | 83 | 84 | 85 | 86 | integration-test 87 | verify 88 | 89 | 90 | 91 | 92 | 96 | 97 | org.codehaus.mojo 98 | exec-maven-plugin 99 | 1.1.1 100 | 101 | 102 | start-vagrant-vm 103 | pre-integration-test 104 | 105 | exec 106 | 107 | 108 | ${vagrant.integration.box} 109 | ${vagrant.bin} 110 | 111 | up 112 | 113 | ${skipTests} 114 | 115 | 116 | 117 | destroy-vagrant-vm 118 | post-integration-test 119 | 120 | exec 121 | 122 | 123 | ${vagrant.integration.box} 124 | ${vagrant.bin} 125 | 126 | destroy 127 | --force 128 | 129 | ${skipTests} 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | coverage 140 | 141 | 142 | 143 | org.jacoco 144 | jacoco-maven-plugin 145 | 0.5.5.201112152213 146 | 147 | ${basedir}/target/coverage-reports/jacoco-unit.exec 148 | ${basedir}/target/coverage-reports/jacoco-unit.exec 149 | 150 | 151 | 152 | jacoco-initialize 153 | 154 | prepare-agent 155 | 156 | 157 | 158 | jacoco-site 159 | post-integration-test 160 | 161 | report 162 | 163 | 164 | 168 | true 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/readme.md: -------------------------------------------------------------------------------- 1 | # hibernate4-cache-over-mysql 2 | 3 | This module contains number of integration tests asserting Hibernate cache (2nd level & query) eviction in response to 4 | the replication events. Test environment (two MySQL nodes connected by means of row-based (master-slave) replication) 5 | is automatically provisioned by [vagrant](http://www.vagrantup.com/). 6 | 7 | In order to run tests hit: 8 | 9 | mvn clean verify 10 | 11 | > Make sure you have installed [vagrant](http://www.vagrantup.com/) (from [here](http://docs.vagrantup.com/v2/installation/index.html)) 12 | and [virtualbox](http://www.virtualbox.org/) (from [here](https://www.virtualbox.org/wiki/Downloads)) before running the line above. 13 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/src/test/java/com/github/shyiko/rook/it/h4com/CountDownReplicationListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.it.h4com; 17 | 18 | import com.github.shyiko.rook.api.ReplicationEventListener; 19 | import com.github.shyiko.rook.api.event.ReplicationEvent; 20 | import com.github.shyiko.rook.api.event.TXReplicationEvent; 21 | 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | import java.util.concurrent.TimeoutException; 25 | import java.util.concurrent.atomic.AtomicInteger; 26 | 27 | /** 28 | * @author Stanley Shyiko 29 | */ 30 | public class CountDownReplicationListener implements ReplicationEventListener { 31 | 32 | private final Map, AtomicInteger> countersByDataClass = 33 | new HashMap, AtomicInteger>(); 34 | private boolean expandTXReplicationEvent = true; 35 | 36 | public boolean isExpandTXReplicationEvent() { 37 | return expandTXReplicationEvent; 38 | } 39 | 40 | public void setExpandTXReplicationEvent(boolean expandTXReplicationEvent) { 41 | this.expandTXReplicationEvent = expandTXReplicationEvent; 42 | } 43 | 44 | @Override 45 | public void onEvent(ReplicationEvent event) { 46 | incrementCounter(getCounter(event.getClass())); 47 | if (event instanceof TXReplicationEvent) { 48 | for (ReplicationEvent nestedEvent : ((TXReplicationEvent) event).getEvents()) { 49 | incrementCounter(getCounter(nestedEvent.getClass())); 50 | } 51 | } 52 | } 53 | 54 | private AtomicInteger getCounter(Class key) { 55 | synchronized (countersByDataClass) { 56 | AtomicInteger counter = countersByDataClass.get(key); 57 | if (counter == null) { 58 | countersByDataClass.put(key, counter = new AtomicInteger()); 59 | } 60 | return counter; 61 | } 62 | } 63 | 64 | private void incrementCounter(AtomicInteger counter) { 65 | synchronized (counter) { 66 | if (counter.incrementAndGet() == 0) { 67 | counter.notify(); 68 | } 69 | } 70 | } 71 | 72 | public void waitFor(Class dataClass, int numberOfEvents, long timeoutInMilliseconds) 73 | throws TimeoutException, InterruptedException { 74 | waitForCounterToGetZero(dataClass.getSimpleName(), getCounter(dataClass), numberOfEvents, 75 | timeoutInMilliseconds); 76 | } 77 | 78 | private void waitForCounterToGetZero(String counterName, AtomicInteger counter, int numberOfExpectedEvents, 79 | long timeoutInMilliseconds) throws TimeoutException, InterruptedException { 80 | synchronized (counter) { 81 | counter.set(counter.get() - numberOfExpectedEvents); 82 | if (counter.get() != 0) { 83 | counter.wait(timeoutInMilliseconds); 84 | if (counter.get() != 0) { 85 | throw new TimeoutException("Received " + (numberOfExpectedEvents + counter.get()) + " " + 86 | counterName + " event(s) instead of expected " + numberOfExpectedEvents); 87 | } 88 | } 89 | } 90 | } 91 | 92 | public void reset() { 93 | synchronized (countersByDataClass) { 94 | countersByDataClass.clear(); 95 | } 96 | } 97 | 98 | @Override 99 | public String toString() { 100 | final StringBuilder sb = new StringBuilder(); 101 | sb.append("CountDownEventListener{"); 102 | sb.append("countersByDataClass=").append(countersByDataClass); 103 | sb.append('}'); 104 | return sb.toString(); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/src/test/java/com/github/shyiko/rook/it/h4com/model/CompositeKeyEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Igor Grunskiy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.it.h4com.model; 17 | 18 | import org.hibernate.annotations.Cache; 19 | import org.hibernate.annotations.CacheConcurrencyStrategy; 20 | 21 | import javax.persistence.Column; 22 | import javax.persistence.Id; 23 | import javax.persistence.IdClass; 24 | import java.io.Serializable; 25 | 26 | /** 27 | * @author Igor Grunskiy 28 | */ 29 | @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) 30 | @javax.persistence.Entity 31 | @IdClass(CompositeKeyEntity.CompositePK.class) 32 | public class CompositeKeyEntity implements Serializable { 33 | 34 | @Id 35 | @Column(name = "root_id", nullable = false) 36 | private Long rootId; 37 | 38 | @Id 39 | @Column(name = "ignored_id", nullable = false) 40 | private Long ignoredId; 41 | 42 | public CompositeKeyEntity() { 43 | } 44 | 45 | public CompositeKeyEntity(Long rootId, Long ignoredId) { 46 | this.ignoredId = ignoredId; 47 | this.rootId = rootId; 48 | } 49 | 50 | public Long getIgnoredId() { 51 | return ignoredId; 52 | } 53 | 54 | public void setIgnoredId(Long ignoredId) { 55 | this.ignoredId = ignoredId; 56 | } 57 | 58 | public Long getRootId() { 59 | return rootId; 60 | } 61 | 62 | public void setRootId(Long rootId) { 63 | this.rootId = rootId; 64 | } 65 | 66 | @Override 67 | public boolean equals(Object o) { 68 | if (this == o) return true; 69 | if (!(o instanceof CompositeKeyEntity)) return false; 70 | 71 | CompositeKeyEntity that = (CompositeKeyEntity) o; 72 | 73 | if (rootId != null ? !rootId.equals(that.rootId) : that.rootId != null) { 74 | return false; 75 | } 76 | return !(ignoredId != null ? !ignoredId.equals(that.ignoredId) : that.ignoredId != null); 77 | } 78 | 79 | @Override 80 | public int hashCode() { 81 | int result = rootId != null ? rootId.hashCode() : 0; 82 | result = 31 * result + (ignoredId != null ? ignoredId.hashCode() : 0); 83 | return result; 84 | } 85 | 86 | public static class CompositePK implements Serializable { 87 | Long rootId; 88 | Long ignoredId; 89 | 90 | @Override 91 | public boolean equals(Object o) { 92 | if (this == o) return true; 93 | if (!(o instanceof CompositePK)) return false; 94 | 95 | CompositePK that = (CompositePK) o; 96 | 97 | if (!rootId.equals(that.rootId)) return false; 98 | return ignoredId.equals(that.ignoredId); 99 | 100 | } 101 | 102 | @Override 103 | public int hashCode() { 104 | int result = (int) (rootId ^ (rootId >>> 32)); 105 | result = 31 * result + (int) (ignoredId ^ (ignoredId >>> 32)); 106 | return result; 107 | } 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/src/test/java/com/github/shyiko/rook/it/h4com/model/IgnoredEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Igor Grunskiy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.it.h4com.model; 17 | 18 | import org.hibernate.annotations.Cache; 19 | import org.hibernate.annotations.CacheConcurrencyStrategy; 20 | 21 | import javax.persistence.Column; 22 | import javax.persistence.GeneratedValue; 23 | import javax.persistence.Id; 24 | 25 | /** 26 | * @author Igor Grunskiy 27 | */ 28 | @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) 29 | @javax.persistence.Entity 30 | public class IgnoredEntity { 31 | @Id 32 | @GeneratedValue 33 | private long id; 34 | @Column 35 | private String name; 36 | 37 | public IgnoredEntity() { 38 | } 39 | 40 | public IgnoredEntity(String name) { 41 | this.name = name; 42 | } 43 | 44 | public long getId() { 45 | return id; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/src/test/java/com/github/shyiko/rook/it/h4com/model/JoinedOneToManyEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.it.h4com.model; 17 | 18 | import org.hibernate.annotations.Cache; 19 | import org.hibernate.annotations.CacheConcurrencyStrategy; 20 | import org.hibernate.annotations.LazyToOne; 21 | import org.hibernate.annotations.LazyToOneOption; 22 | 23 | import javax.persistence.Column; 24 | import javax.persistence.Entity; 25 | import javax.persistence.FetchType; 26 | import javax.persistence.GeneratedValue; 27 | import javax.persistence.Id; 28 | import javax.persistence.JoinColumn; 29 | import javax.persistence.ManyToOne; 30 | 31 | /** 32 | * @author Stanley Shyiko 33 | */ 34 | @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) 35 | @Entity 36 | public class JoinedOneToManyEntity { 37 | 38 | @Id 39 | @GeneratedValue 40 | private long id; 41 | @Column(nullable = false) 42 | private String name; 43 | @ManyToOne(fetch = FetchType.LAZY) 44 | @LazyToOne(LazyToOneOption.PROXY) 45 | @JoinColumn(name = "root_entity_id") 46 | private RootEntity rootEntity; 47 | 48 | public JoinedOneToManyEntity(String name) { 49 | this.name = name; 50 | } 51 | 52 | public RootEntity getRootEntity() { 53 | return rootEntity; 54 | } 55 | 56 | public void setRootEntity(RootEntity rootEntity) { 57 | this.rootEntity = rootEntity; 58 | } 59 | 60 | @Override 61 | public boolean equals(Object o) { 62 | if (this == o) { 63 | return true; 64 | } 65 | if (o == null || getClass() != o.getClass()) { 66 | return false; 67 | } 68 | JoinedOneToManyEntity that = (JoinedOneToManyEntity) o; 69 | return name.equals(that.name); 70 | } 71 | 72 | @Override 73 | public int hashCode() { 74 | return name.hashCode(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/src/test/java/com/github/shyiko/rook/it/h4com/model/OneToManyEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.it.h4com.model; 17 | 18 | import org.hibernate.annotations.Cache; 19 | import org.hibernate.annotations.CacheConcurrencyStrategy; 20 | 21 | import javax.persistence.Column; 22 | import javax.persistence.Entity; 23 | import javax.persistence.GeneratedValue; 24 | import javax.persistence.Id; 25 | 26 | /** 27 | * @author Stanley Shyiko 28 | */ 29 | @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) 30 | @Entity 31 | public class OneToManyEntity { 32 | 33 | @Id 34 | @GeneratedValue 35 | private long id; 36 | @Column(nullable = false) 37 | private String name; 38 | 39 | public OneToManyEntity() { 40 | } 41 | 42 | public OneToManyEntity(String name) { 43 | this.name = name; 44 | } 45 | 46 | public String getName() { 47 | return name; 48 | } 49 | 50 | public void setName(String name) { 51 | this.name = name; 52 | } 53 | 54 | @Override 55 | public boolean equals(Object o) { 56 | if (this == o) { 57 | return true; 58 | } 59 | if (o == null || getClass() != o.getClass()) { 60 | return false; 61 | } 62 | OneToManyEntity student = (OneToManyEntity) o; 63 | return name.equals(student.name); 64 | } 65 | 66 | @Override 67 | public int hashCode() { 68 | return name.hashCode(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/src/test/java/com/github/shyiko/rook/it/h4com/model/OneToOneEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.it.h4com.model; 17 | 18 | import org.hibernate.annotations.Cache; 19 | import org.hibernate.annotations.CacheConcurrencyStrategy; 20 | 21 | import javax.persistence.Column; 22 | import javax.persistence.Entity; 23 | import javax.persistence.GeneratedValue; 24 | import javax.persistence.Id; 25 | 26 | /** 27 | * @author Stanley Shyiko 28 | */ 29 | @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) 30 | @Entity 31 | public class OneToOneEntity { 32 | 33 | @Id 34 | @GeneratedValue 35 | private long id; 36 | @Column(nullable = false) 37 | private String name; 38 | 39 | public OneToOneEntity() { 40 | } 41 | 42 | public OneToOneEntity(String name) { 43 | this.name = name; 44 | } 45 | 46 | public String getName() { 47 | return name; 48 | } 49 | 50 | public void setName(String name) { 51 | this.name = name; 52 | } 53 | 54 | @Override 55 | public boolean equals(Object o) { 56 | if (this == o) { 57 | return true; 58 | } 59 | if (o == null || getClass() != o.getClass()) { 60 | return false; 61 | } 62 | OneToOneEntity teacher = (OneToOneEntity) o; 63 | return name.equals(teacher.name); 64 | 65 | } 66 | 67 | @Override 68 | public int hashCode() { 69 | return name.hashCode(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/src/test/java/com/github/shyiko/rook/it/h4com/model/RootEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.it.h4com.model; 17 | 18 | import org.hibernate.annotations.Cache; 19 | import org.hibernate.annotations.CacheConcurrencyStrategy; 20 | import org.hibernate.annotations.Cascade; 21 | import org.hibernate.annotations.LazyCollection; 22 | import org.hibernate.annotations.LazyCollectionOption; 23 | 24 | import javax.persistence.CascadeType; 25 | import javax.persistence.Column; 26 | import javax.persistence.FetchType; 27 | import javax.persistence.GeneratedValue; 28 | import javax.persistence.Id; 29 | import javax.persistence.OneToMany; 30 | import javax.persistence.OneToOne; 31 | import java.util.HashSet; 32 | import java.util.Set; 33 | 34 | /** 35 | * @author Stanley Shyiko 36 | */ 37 | @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) 38 | @javax.persistence.Entity 39 | public class RootEntity { 40 | 41 | @Id 42 | @GeneratedValue 43 | private long id; 44 | @Column 45 | private String name; 46 | @Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE) 47 | @OneToOne(cascade = { 48 | CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH 49 | }) 50 | private OneToOneEntity oneToOneEntity; 51 | @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) 52 | @LazyCollection(LazyCollectionOption.TRUE) 53 | @Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE) 54 | @OneToMany(cascade = { 55 | CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH 56 | }, fetch = FetchType.LAZY) 57 | private Set oneToManyEntities; 58 | @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) 59 | @LazyCollection(LazyCollectionOption.TRUE) 60 | @Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE) 61 | @OneToMany(cascade = { 62 | CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH 63 | }, fetch = FetchType.LAZY, mappedBy = "rootEntity") 64 | private Set joinedOneToManyEntities; 65 | @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) 66 | @OneToMany(mappedBy = "rootId", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) 67 | @LazyCollection(LazyCollectionOption.TRUE) 68 | private Set compositeRelations = new HashSet(); 69 | 70 | public RootEntity() { 71 | } 72 | 73 | public RootEntity(String name) { 74 | this.name = name; 75 | } 76 | 77 | public RootEntity(String name, OneToOneEntity oneToOneEntity, Set oneToManyEntities) { 78 | this.name = name; 79 | this.oneToOneEntity = oneToOneEntity; 80 | this.oneToManyEntities = oneToManyEntities; 81 | } 82 | 83 | public long getId() { 84 | return id; 85 | } 86 | 87 | public String getName() { 88 | return name; 89 | } 90 | 91 | public void setName(String name) { 92 | this.name = name; 93 | } 94 | 95 | public OneToOneEntity getOneToOneEntity() { 96 | return oneToOneEntity; 97 | } 98 | 99 | public void setOneToOneEntity(OneToOneEntity oneToOneEntity) { 100 | this.oneToOneEntity = oneToOneEntity; 101 | } 102 | 103 | public Set getOneToManyEntities() { 104 | return oneToManyEntities; 105 | } 106 | 107 | public void setOneToManyEntities(Set oneToManyEntities) { 108 | this.oneToManyEntities = oneToManyEntities; 109 | } 110 | 111 | public Set getJoinedOneToManyEntities() { 112 | return joinedOneToManyEntities; 113 | } 114 | 115 | public void setJoinedOneToManyEntities(Set joinedOneToManyEntities) { 116 | for (JoinedOneToManyEntity directedOneToManyEntity : joinedOneToManyEntities) { 117 | directedOneToManyEntity.setRootEntity(this); 118 | } 119 | this.joinedOneToManyEntities = joinedOneToManyEntities; 120 | } 121 | 122 | public Set getCompositeRelations() { 123 | return compositeRelations; 124 | } 125 | 126 | public void setCompositeRelations(Set compositeRelations) { 127 | this.compositeRelations = compositeRelations; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/src/test/resources/hibernate.cfg.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | com.mysql.jdbc.Driver 9 | org.hibernate.dialect.MySQL5InnoDBDialect 10 | org.hibernate.cache.ehcache.EhCacheRegionFactory 11 | true 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 2 | log4j.appender.stdout.Target=System.out 3 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 4 | log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1} - %m%n 5 | 6 | log4j.rootLogger=FATAL, stdout 7 | 8 | log4j.logger.com.github.shyiko.rook=TRACE 9 | 10 | #log4j.logger.org.hibernate.SQL=TRACE 11 | #log4j.logger.org.hibernate.type.descriptor.sql.BasicBinder=TRACE 12 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/src/test/resources/master-ehcache.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 9 | 11 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 31 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/src/test/resources/master-sdb-ehcache.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 9 | 11 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 31 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/src/test/resources/master-sdb.properties: -------------------------------------------------------------------------------- 1 | hibernate.connection.url=jdbc:mysql://localhost:33061/rook_h4cs_sdb_test 2 | hibernate.connection.username=msandbox 3 | hibernate.connection.password=msandbox 4 | hibernate.hbm2ddl.auto=create-drop 5 | net.sf.ehcache.configurationResourceName=master-sdb-ehcache.xml -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/src/test/resources/master.properties: -------------------------------------------------------------------------------- 1 | hibernate.connection.url=jdbc:mysql://localhost:33061/rook_h4cs_test 2 | hibernate.connection.username=msandbox 3 | hibernate.connection.password=msandbox 4 | hibernate.hbm2ddl.auto=create-drop 5 | net.sf.ehcache.configurationResourceName=master-ehcache.xml -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/src/test/resources/slave-ehcache.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 9 | 11 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 31 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/src/test/resources/slave-sdb-ehcache.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 9 | 11 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 31 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/src/test/resources/slave-sdb.properties: -------------------------------------------------------------------------------- 1 | hibernate.connection.url=jdbc:mysql://localhost:33062/rook_h4cs_sdb_test 2 | hibernate.connection.username=msandbox 3 | hibernate.connection.password=msandbox 4 | hibernate.hbm2ddl.auto=validate 5 | net.sf.ehcache.configurationResourceName=slave-sdb-ehcache.xml -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-cache-over-mysql/src/test/resources/slave.properties: -------------------------------------------------------------------------------- 1 | hibernate.connection.url=jdbc:mysql://localhost:33062/rook_h4cs_test 2 | hibernate.connection.username=msandbox 3 | hibernate.connection.password=msandbox 4 | hibernate.hbm2ddl.auto=validate 5 | net.sf.ehcache.configurationResourceName=slave-ehcache.xml -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-fulltextindex-over-mysql/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.github.shyiko.rook 9 | rook 10 | ../../../pom.xml 11 | 0.1.4-SNAPSHOT 12 | 13 | 14 | com.github.shyiko.rook.examples 15 | hibernate4-fulltextindex-over-mysql 16 | 0.1.4-SNAPSHOT 17 | 18 | 19 | vagrant 20 | ${basedir}/../../vagrant/mysql-5.5.27-sandbox-prepackaged 21 | 22 | 23 | 24 | 25 | com.github.shyiko.rook 26 | rook-source-mysql 27 | 0.1.2-SNAPSHOT 28 | test 29 | 30 | 31 | com.github.shyiko.rook 32 | rook-target-hibernate4-fulltextindex 33 | 0.1.2-SNAPSHOT 34 | test 35 | 36 | 37 | mysql 38 | mysql-connector-java 39 | 5.1.21 40 | test 41 | 42 | 43 | org.slf4j 44 | slf4j-log4j12 45 | 1.6.1 46 | test 47 | 48 | 49 | log4j 50 | log4j 51 | 1.2.16 52 | test 53 | 54 | 55 | org.testng 56 | testng 57 | 6.1.1 58 | test 59 | 60 | 61 | 62 | 63 | 64 | 65 | org.apache.maven.plugins 66 | maven-surefire-plugin 67 | 2.10 68 | 69 | 70 | **/*IntegrationTest.java 71 | 72 | 73 | 74 | 75 | org.apache.maven.plugins 76 | maven-failsafe-plugin 77 | 2.15 78 | 79 | 80 | **/*IntegrationTest.java 81 | 82 | 83 | 84 | 85 | 86 | integration-test 87 | verify 88 | 89 | 90 | 91 | 92 | 96 | 97 | org.codehaus.mojo 98 | exec-maven-plugin 99 | 1.1.1 100 | 101 | 102 | start-vagrant-vm 103 | pre-integration-test 104 | 105 | exec 106 | 107 | 108 | ${vagrant.integration.box} 109 | ${vagrant.bin} 110 | 111 | up 112 | 113 | ${skipTests} 114 | 115 | 116 | 117 | destroy-vagrant-vm 118 | post-integration-test 119 | 120 | exec 121 | 122 | 123 | ${vagrant.integration.box} 124 | ${vagrant.bin} 125 | 126 | destroy 127 | --force 128 | 129 | ${skipTests} 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | coverage 140 | 141 | 142 | 143 | org.jacoco 144 | jacoco-maven-plugin 145 | 0.5.5.201112152213 146 | 147 | ${basedir}/target/coverage-reports/jacoco-unit.exec 148 | ${basedir}/target/coverage-reports/jacoco-unit.exec 149 | 150 | 151 | 152 | jacoco-initialize 153 | 154 | prepare-agent 155 | 156 | 157 | 158 | jacoco-site 159 | post-integration-test 160 | 161 | report 162 | 163 | 164 | 168 | true 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-fulltextindex-over-mysql/readme.md: -------------------------------------------------------------------------------- 1 | # hibernate4-fulltextindex-over-mysql 2 | 3 | This module contains number of integration tests asserting Full Text index synchronization (with Hibernate 4 Search) 4 | using replication stream. Test environment (two MySQL nodes connected by means of row-based (master-slave) 5 | replication) is automatically provisioned by [vagrant](http://www.vagrantup.com/). 6 | 7 | In order to run tests hit: 8 | 9 | mvn clean verify 10 | 11 | > Make sure you have installed [vagrant](http://www.vagrantup.com/) (from [here](http://docs.vagrantup.com/v2/installation/index.html)) 12 | and [virtualbox](http://www.virtualbox.org/) (from [here](https://www.virtualbox.org/wiki/Downloads)) before running the line above. 13 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-fulltextindex-over-mysql/src/test/java/com/github/shyiko/rook/it/h4ftiom/CountDownReplicationListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.it.h4ftiom; 17 | 18 | import com.github.shyiko.rook.api.ReplicationEventListener; 19 | import com.github.shyiko.rook.api.event.ReplicationEvent; 20 | import com.github.shyiko.rook.api.event.TXReplicationEvent; 21 | 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | import java.util.concurrent.TimeoutException; 25 | import java.util.concurrent.atomic.AtomicInteger; 26 | 27 | /** 28 | * @author Stanley Shyiko 29 | */ 30 | public class CountDownReplicationListener implements ReplicationEventListener { 31 | 32 | private final Map, AtomicInteger> countersByDataClass = 33 | new HashMap, AtomicInteger>(); 34 | private boolean expandTXReplicationEvent = true; 35 | 36 | public boolean isExpandTXReplicationEvent() { 37 | return expandTXReplicationEvent; 38 | } 39 | 40 | public void setExpandTXReplicationEvent(boolean expandTXReplicationEvent) { 41 | this.expandTXReplicationEvent = expandTXReplicationEvent; 42 | } 43 | 44 | @Override 45 | public void onEvent(ReplicationEvent event) { 46 | incrementCounter(getCounter(event.getClass())); 47 | if (event instanceof TXReplicationEvent) { 48 | for (ReplicationEvent nestedEvent : ((TXReplicationEvent) event).getEvents()) { 49 | incrementCounter(getCounter(nestedEvent.getClass())); 50 | } 51 | } 52 | } 53 | 54 | private AtomicInteger getCounter(Class key) { 55 | synchronized (countersByDataClass) { 56 | AtomicInteger counter = countersByDataClass.get(key); 57 | if (counter == null) { 58 | countersByDataClass.put(key, counter = new AtomicInteger()); 59 | } 60 | return counter; 61 | } 62 | } 63 | 64 | private void incrementCounter(AtomicInteger counter) { 65 | synchronized (counter) { 66 | if (counter.incrementAndGet() == 0) { 67 | counter.notify(); 68 | } 69 | } 70 | } 71 | 72 | public void waitFor(Class dataClass, int numberOfEvents, long timeoutInMilliseconds) 73 | throws TimeoutException, InterruptedException { 74 | waitForCounterToGetZero(dataClass.getSimpleName(), getCounter(dataClass), numberOfEvents, 75 | timeoutInMilliseconds); 76 | } 77 | 78 | private void waitForCounterToGetZero(String counterName, AtomicInteger counter, int numberOfExpectedEvents, 79 | long timeoutInMilliseconds) throws TimeoutException, InterruptedException { 80 | synchronized (counter) { 81 | counter.set(counter.get() - numberOfExpectedEvents); 82 | if (counter.get() != 0) { 83 | counter.wait(timeoutInMilliseconds); 84 | if (counter.get() != 0) { 85 | throw new TimeoutException("Received " + (numberOfExpectedEvents + counter.get()) + " " + 86 | counterName + " event(s) instead of expected " + numberOfExpectedEvents); 87 | } 88 | } 89 | } 90 | } 91 | 92 | public void reset() { 93 | synchronized (countersByDataClass) { 94 | countersByDataClass.clear(); 95 | } 96 | } 97 | 98 | @Override 99 | public String toString() { 100 | final StringBuilder sb = new StringBuilder(); 101 | sb.append("CountDownEventListener{"); 102 | sb.append("countersByDataClass=").append(countersByDataClass); 103 | sb.append('}'); 104 | return sb.toString(); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-fulltextindex-over-mysql/src/test/java/com/github/shyiko/rook/it/h4ftiom/model/JoinedOneToManyEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.it.h4ftiom.model; 17 | 18 | import org.hibernate.annotations.LazyToOne; 19 | import org.hibernate.annotations.LazyToOneOption; 20 | import org.hibernate.search.annotations.ContainedIn; 21 | import org.hibernate.search.annotations.Field; 22 | 23 | import javax.persistence.Column; 24 | import javax.persistence.Entity; 25 | import javax.persistence.FetchType; 26 | import javax.persistence.GeneratedValue; 27 | import javax.persistence.Id; 28 | import javax.persistence.JoinColumn; 29 | import javax.persistence.ManyToOne; 30 | 31 | /** 32 | * @author Stanley Shyiko 33 | */ 34 | @Entity 35 | public class JoinedOneToManyEntity { 36 | 37 | @Id 38 | @GeneratedValue 39 | private long id; 40 | @Field 41 | @Column(nullable = false) 42 | private String name; 43 | @ContainedIn 44 | @ManyToOne(fetch = FetchType.LAZY) 45 | @LazyToOne(LazyToOneOption.PROXY) 46 | @JoinColumn(name = "root_entity_id") 47 | private RootEntity rootEntity; 48 | 49 | public JoinedOneToManyEntity(String name) { 50 | this.name = name; 51 | } 52 | 53 | public String getName() { 54 | return name; 55 | } 56 | 57 | public void setName(String name) { 58 | this.name = name; 59 | } 60 | 61 | public RootEntity getRootEntity() { 62 | return rootEntity; 63 | } 64 | 65 | public void setRootEntity(RootEntity rootEntity) { 66 | this.rootEntity = rootEntity; 67 | } 68 | 69 | @Override 70 | public boolean equals(Object o) { 71 | if (this == o) { 72 | return true; 73 | } 74 | if (o == null || getClass() != o.getClass()) { 75 | return false; 76 | } 77 | JoinedOneToManyEntity that = (JoinedOneToManyEntity) o; 78 | return name.equals(that.name); 79 | } 80 | 81 | @Override 82 | public int hashCode() { 83 | return name.hashCode(); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-fulltextindex-over-mysql/src/test/java/com/github/shyiko/rook/it/h4ftiom/model/ManyToManyEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.it.h4ftiom.model; 17 | 18 | import org.hibernate.annotations.LazyCollection; 19 | import org.hibernate.annotations.LazyCollectionOption; 20 | import org.hibernate.search.annotations.ContainedIn; 21 | import org.hibernate.search.annotations.Field; 22 | import org.hibernate.search.annotations.Indexed; 23 | 24 | import javax.persistence.Column; 25 | import javax.persistence.FetchType; 26 | import javax.persistence.GeneratedValue; 27 | import javax.persistence.Id; 28 | import javax.persistence.ManyToMany; 29 | import java.util.HashSet; 30 | import java.util.Set; 31 | 32 | /** 33 | * @author Stanley Shyiko 34 | */ 35 | @Indexed 36 | @javax.persistence.Entity 37 | public class ManyToManyEntity { 38 | 39 | @Id 40 | @GeneratedValue 41 | private long id; 42 | @Field 43 | @Column(nullable = false) 44 | private String name; 45 | @ContainedIn 46 | @LazyCollection(LazyCollectionOption.TRUE) 47 | @ManyToMany(fetch = FetchType.LAZY) 48 | private Set rootEntities = new HashSet(); 49 | 50 | public ManyToManyEntity() { 51 | } 52 | 53 | public ManyToManyEntity(String name) { 54 | this.name = name; 55 | } 56 | 57 | public String getName() { 58 | return name; 59 | } 60 | 61 | public void setName(String name) { 62 | this.name = name; 63 | } 64 | 65 | public Set getRootEntities() { 66 | return rootEntities; 67 | } 68 | 69 | public void addRootEntity(RootEntity rootEntity) { 70 | this.rootEntities.add(rootEntity); 71 | } 72 | 73 | public void removeRootEntity(RootEntity rootEntity) { 74 | this.rootEntities.remove(rootEntity); 75 | } 76 | 77 | @Override 78 | public boolean equals(Object o) { 79 | if (this == o) { 80 | return true; 81 | } 82 | if (o == null || getClass() != o.getClass()) { 83 | return false; 84 | } 85 | ManyToManyEntity student = (ManyToManyEntity) o; 86 | return name.equals(student.name); 87 | } 88 | 89 | @Override 90 | public int hashCode() { 91 | return name.hashCode(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-fulltextindex-over-mysql/src/test/java/com/github/shyiko/rook/it/h4ftiom/model/RootEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Stanley Shyiko 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.shyiko.rook.it.h4ftiom.model; 17 | 18 | import org.hibernate.annotations.Cascade; 19 | import org.hibernate.annotations.LazyCollection; 20 | import org.hibernate.annotations.LazyCollectionOption; 21 | import org.hibernate.search.annotations.Field; 22 | import org.hibernate.search.annotations.Indexed; 23 | import org.hibernate.search.annotations.IndexedEmbedded; 24 | 25 | import javax.persistence.CascadeType; 26 | import javax.persistence.Column; 27 | import javax.persistence.FetchType; 28 | import javax.persistence.GeneratedValue; 29 | import javax.persistence.Id; 30 | import javax.persistence.ManyToMany; 31 | import javax.persistence.OneToMany; 32 | import java.util.Collections; 33 | import java.util.HashSet; 34 | import java.util.Set; 35 | 36 | /** 37 | * @author Stanley Shyiko 38 | */ 39 | @Indexed 40 | @javax.persistence.Entity 41 | public class RootEntity { 42 | 43 | @Id 44 | @GeneratedValue 45 | private long id; 46 | @Field 47 | @Column 48 | private String name; 49 | @IndexedEmbedded 50 | @LazyCollection(LazyCollectionOption.TRUE) 51 | @Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE) 52 | @ManyToMany(cascade = { 53 | CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH 54 | }, fetch = FetchType.LAZY, mappedBy = "rootEntities") 55 | private Set manyToManyEntities = new HashSet(); 56 | @IndexedEmbedded 57 | @LazyCollection(LazyCollectionOption.TRUE) 58 | @Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE) 59 | @OneToMany(cascade = { 60 | CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH 61 | }, fetch = FetchType.LAZY, mappedBy = "rootEntity") 62 | private Set joinedOneToManyEntities = new HashSet(); 63 | 64 | public RootEntity() { 65 | } 66 | 67 | public RootEntity(String name) { 68 | this.name = name; 69 | } 70 | 71 | public RootEntity(String name, Set manyToManyEntities) { 72 | this(name, manyToManyEntities, Collections.emptySet()); 73 | } 74 | 75 | public RootEntity(String name, Set manyToManyEntities, 76 | Set joinedOneToManyEntities) { 77 | this.name = name; 78 | for (ManyToManyEntity manyToManyEntity : manyToManyEntities) { 79 | addManyToManyEntity(manyToManyEntity); 80 | } 81 | for (JoinedOneToManyEntity joinedOneToManyEntity : joinedOneToManyEntities) { 82 | addJoinedOneToManyEntity(joinedOneToManyEntity); 83 | } 84 | } 85 | 86 | public String getName() { 87 | return name; 88 | } 89 | 90 | public void setName(String name) { 91 | this.name = name; 92 | } 93 | 94 | public ManyToManyEntity getManyToManyEntityByName(String name) { 95 | for (ManyToManyEntity oneToManyEntity : this.manyToManyEntities) { 96 | if (name.equals(oneToManyEntity.getName())) { 97 | return oneToManyEntity; 98 | } 99 | } 100 | return null; 101 | } 102 | 103 | public void addManyToManyEntity(ManyToManyEntity manyToManyEntity) { 104 | this.manyToManyEntities.add(manyToManyEntity); 105 | manyToManyEntity.addRootEntity(this); 106 | } 107 | 108 | public void removeManyToManyEntity(ManyToManyEntity manyToManyEntity) { 109 | this.manyToManyEntities.remove(manyToManyEntity); 110 | manyToManyEntity.removeRootEntity(this); 111 | } 112 | 113 | public JoinedOneToManyEntity getJoinedOneToManyEntityByName(String name) { 114 | for (JoinedOneToManyEntity joinedOneToManyEntity : this.joinedOneToManyEntities) { 115 | if (name.equals(joinedOneToManyEntity.getName())) { 116 | return joinedOneToManyEntity; 117 | } 118 | } 119 | return null; 120 | } 121 | 122 | public void addJoinedOneToManyEntity(JoinedOneToManyEntity joinedOneToManyEntity) { 123 | this.joinedOneToManyEntities.add(joinedOneToManyEntity); 124 | joinedOneToManyEntity.setRootEntity(this); 125 | } 126 | 127 | public void removeJoinedOneToManyEntity(JoinedOneToManyEntity joinedOneToManyEntity) { 128 | this.joinedOneToManyEntities.remove(joinedOneToManyEntity); 129 | } 130 | 131 | @Override 132 | public boolean equals(Object o) { 133 | if (this == o) { 134 | return true; 135 | } 136 | if (o == null || getClass() != o.getClass()) { 137 | return false; 138 | } 139 | RootEntity student = (RootEntity) o; 140 | return name.equals(student.name); 141 | } 142 | 143 | @Override 144 | public int hashCode() { 145 | return name.hashCode(); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-fulltextindex-over-mysql/src/test/resources/hibernate.cfg.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | com.mysql.jdbc.Driver 9 | org.hibernate.dialect.MySQL5InnoDBDialect 10 | true 11 | ram 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-fulltextindex-over-mysql/src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 2 | log4j.appender.stdout.Target=System.out 3 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 4 | log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1} - %m%n 5 | 6 | log4j.rootLogger=FATAL, stdout 7 | 8 | log4j.logger.com.github.shyiko.rook=TRACE 9 | 10 | #log4j.logger.org.hibernate.SQL=TRACE 11 | #log4j.logger.org.hibernate.type.descriptor.sql.BasicBinder=TRACE 12 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-fulltextindex-over-mysql/src/test/resources/master.properties: -------------------------------------------------------------------------------- 1 | hibernate.connection.url=jdbc:mysql://localhost:33061/rook_h4ftiom_test 2 | hibernate.connection.username=msandbox 3 | hibernate.connection.password=msandbox 4 | hibernate.hbm2ddl.auto=create-drop 5 | -------------------------------------------------------------------------------- /supplement/integration-testing/hibernate4-fulltextindex-over-mysql/src/test/resources/slave.properties: -------------------------------------------------------------------------------- 1 | hibernate.connection.url=jdbc:mysql://localhost:33062/rook_h4ftiom_test 2 | hibernate.connection.username=msandbox 3 | hibernate.connection.password=msandbox 4 | hibernate.hbm2ddl.auto=validate 5 | -------------------------------------------------------------------------------- /supplement/vagrant/mysql-5.5.27-sandbox-prepackaged/vagrantfile: -------------------------------------------------------------------------------- 1 | # 2 | # unpacked version of box available at: 3 | # https://github.com/shyiko/mysql-binlog-connector-java/tree/master/supplement/vagrant/mysql-5.6.12-sandbox 4 | # 5 | Vagrant.configure("2") do |config| 6 | config.vm.box = 'mysql-5.5.27-sandbox' 7 | config.vm.box_url = 'https://copy.com/sixlvBAee4er' 8 | config.vm.network :forwarded_port, guest: 33061, host: 33061 9 | config.vm.network :forwarded_port, guest: 33062, host: 33062 10 | end 11 | --------------------------------------------------------------------------------