├── .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 [](https://travis-ci.org/shyiko/rook) [](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 extends ReplicationEventListener> 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 extends ReplicationEventListener> 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 extends EntityIndexingInterceptor> 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 extends Annotation> 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 extends ReplicationEvent> 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 extends ReplicationEvent> 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 extends ReplicationEvent> 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 extends ReplicationEvent> 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 |
--------------------------------------------------------------------------------