├── .editorconfig ├── .github ├── CODEOWNERS ├── codecov.yml ├── dependabot.yml ├── labeler.yml └── workflows │ ├── build.yml │ ├── dependabot.yml │ ├── docs.yml │ ├── labeler.yml │ ├── release-auto.yml │ ├── release.yml │ └── upgrade-gradle-wrapper.yml ├── .gitignore ├── DEVELOPMENT.md ├── LICENSE ├── README.md ├── api ├── common │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── coditory │ │ └── sherlock │ │ ├── DistributedLockBuilder.java │ │ ├── OwnerIdPolicy.java │ │ ├── SherlockException.java │ │ ├── connector │ │ ├── AcquireResult.java │ │ ├── AcquireResultWithValue.java │ │ ├── InitializationResult.java │ │ ├── LockResultLogger.java │ │ └── ReleaseResult.java │ │ └── migrator │ │ └── MigrationResult.java ├── coroutines-connector │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── coroutines │ │ │ ├── DelegatingDistributedLock.kt │ │ │ ├── SherlockWithConnector.kt │ │ │ ├── SherlockWithConnectorBuilder.kt │ │ │ └── SuspendingDistributedLockConnector.kt │ │ └── test │ │ └── groovy │ │ └── com │ │ └── coditory │ │ └── sherlock │ │ └── coroutines │ │ ├── InMemoryDistributedLockSpec.groovy │ │ └── base │ │ └── UsesKtSherlock.groovy ├── coroutines │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── coroutines │ │ │ ├── DistributedLock.kt │ │ │ ├── Sherlock.kt │ │ │ ├── migrator │ │ │ ├── SherlockMigrator.kt │ │ │ └── SherlockMigratorBuilder.kt │ │ │ └── test │ │ │ ├── DistributedLockMock.kt │ │ │ └── SherlockStub.kt │ │ └── test │ │ ├── groovy │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── coroutines │ │ │ └── MigratorSpec.groovy │ │ └── kotlin │ │ └── com │ │ └── coditory │ │ └── sherlock │ │ └── coroutines │ │ ├── DistributedLockMockSpec.kt │ │ ├── DistributedLockSpec.kt │ │ ├── SherlockStubSpec.kt │ │ ├── SuspendedMigratorSpec.kt │ │ └── base │ │ ├── Assertions.kt │ │ ├── BlockingKtMigrator.kt │ │ ├── DynamicTest.kt │ │ ├── TestTuple.kt │ │ └── Tuple.kt ├── reactor │ ├── build.gradle.kts │ └── src │ │ ├── integrationTest │ │ └── groovy │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── reactor │ │ │ ├── InMemoryDistributedLockSpec.groovy │ │ │ └── base │ │ │ └── UsesReactorSherlock.groovy │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── reactor │ │ │ ├── DelegatingDistributedLock.java │ │ │ ├── DistributedLock.java │ │ │ ├── DistributedLockConnector.java │ │ │ ├── Sherlock.java │ │ │ ├── SherlockWithConnector.java │ │ │ ├── SherlockWithConnectorBuilder.java │ │ │ ├── migrator │ │ │ ├── SherlockMigrator.java │ │ │ └── SherlockMigratorBuilder.java │ │ │ └── test │ │ │ ├── DistributedLockMock.java │ │ │ └── SherlockStub.java │ │ └── test │ │ └── groovy │ │ └── com │ │ └── coditory │ │ └── sherlock │ │ └── reactor │ │ ├── DistributedLockMockSpec.groovy │ │ ├── DistributedLockSpec.groovy │ │ ├── SherlockStubSpec.groovy │ │ └── migrator │ │ ├── BlockingReactorMigrator.groovy │ │ ├── MigratorReactorSpec.groovy │ │ └── MigratorSpec.groovy ├── rxjava │ ├── build.gradle.kts │ └── src │ │ ├── integrationTest │ │ └── groovy │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── rxjava │ │ │ ├── InMemoryDistributedLockSpec.groovy │ │ │ └── base │ │ │ └── UsesRxSherlock.groovy │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── rxjava │ │ │ ├── DelegatingDistributedLock.java │ │ │ ├── DistributedLock.java │ │ │ ├── DistributedLockConnector.java │ │ │ ├── Sherlock.java │ │ │ ├── SherlockWithConnector.java │ │ │ ├── SherlockWithConnectorBuilder.java │ │ │ ├── migrator │ │ │ ├── SherlockMigrator.java │ │ │ └── SherlockMigratorBuilder.java │ │ │ └── test │ │ │ ├── DistributedLockMock.java │ │ │ └── SherlockStub.java │ │ └── test │ │ └── groovy │ │ └── com │ │ └── coditory │ │ └── sherlock │ │ └── rxjava │ │ ├── DistributedLockMockSpec.groovy │ │ ├── DistributedLockSpec.groovy │ │ ├── SherlockStubSpec.groovy │ │ └── migrator │ │ ├── BlockingRxMigrator.groovy │ │ ├── MigratorRxJavaSpec.groovy │ │ └── MigratorSpec.groovy └── sync │ ├── build.gradle.kts │ └── src │ ├── main │ └── java │ │ └── com │ │ └── coditory │ │ └── sherlock │ │ ├── DelegatingDistributedLock.java │ │ ├── DistributedLock.java │ │ ├── DistributedLockConnector.java │ │ ├── Sherlock.java │ │ ├── SherlockWithConnector.java │ │ ├── SherlockWithConnectorBuilder.java │ │ ├── migrator │ │ ├── SherlockMigrator.java │ │ └── SherlockMigratorBuilder.java │ │ └── test │ │ ├── DistributedLockMock.java │ │ └── SherlockStub.java │ └── test │ └── groovy │ └── com │ └── coditory │ └── sherlock │ ├── DistributedLockMockSpec.groovy │ ├── DistributedLockSpec.groovy │ ├── SherlockStubSpec.groovy │ └── migrator │ ├── BlockingSyncMigrator.groovy │ └── SherlockMigratorSpec.groovy ├── build-logic ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ ├── build.coverage-root.gradle.kts │ ├── build.coverage.gradle.kts │ ├── build.java.gradle.kts │ ├── build.kotlin.gradle.kts │ ├── build.publish-root.gradle.kts │ ├── build.publish.gradle.kts │ ├── build.test.gradle.kts │ └── build.version.gradle.kts ├── build.gradle.kts ├── common ├── build.gradle.kts └── src │ └── main │ └── java │ └── com │ └── coditory │ └── sherlock │ ├── LockRequest.java │ ├── Ports.java │ ├── Preconditions.java │ ├── SherlockDefaults.java │ ├── Timer.java │ ├── UuidGenerator.java │ └── migrator │ ├── ChangeSet.java │ ├── ChangeSetMethodExtractor.java │ ├── MigrationChangeSet.java │ └── MigrationChangeSetMethod.java ├── connectors ├── inmem │ ├── common │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── inmem │ │ │ ├── InMemoryDistributedLock.java │ │ │ └── InMemoryDistributedLockStorage.java │ ├── coroutines │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── integrationTest │ │ │ └── groovy │ │ │ │ └── com │ │ │ │ └── coditory │ │ │ │ └── sherlock │ │ │ │ └── inmem │ │ │ │ └── coroutines │ │ │ │ ├── InMemoryDistributedLockSpec.groovy │ │ │ │ └── UsesKtInMemorySherlock.groovy │ │ │ └── main │ │ │ └── kotlin │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── inmem │ │ │ └── coroutines │ │ │ ├── InMemorySherlock.kt │ │ │ └── KtInMemoryDistributedLockConnector.kt │ ├── reactor │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── integrationTest │ │ │ └── groovy │ │ │ │ └── com │ │ │ │ └── coditory │ │ │ │ └── sherlock │ │ │ │ └── inmem │ │ │ │ └── reactor │ │ │ │ ├── InMemoryDistributedLockSpec.groovy │ │ │ │ └── UsesReactorInMemorySherlock.groovy │ │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── inmem │ │ │ └── reactor │ │ │ ├── InMemorySherlock.java │ │ │ └── ReactorInMemoryDistributedLockConnector.java │ ├── rxjava │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── integrationTest │ │ │ └── groovy │ │ │ │ └── com │ │ │ │ └── coditory │ │ │ │ └── sherlock │ │ │ │ └── inmem │ │ │ │ └── rxjava │ │ │ │ ├── InMemoryDistributedLockSpec.groovy │ │ │ │ └── UsesReactiveInMemorySherlock.groovy │ │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── inmem │ │ │ └── rxjava │ │ │ ├── InMemoryDistributedLockConnector.java │ │ │ └── InMemorySherlock.java │ └── sync │ │ ├── build.gradle.kts │ │ └── src │ │ ├── integrationTest │ │ └── groovy │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── inmem │ │ │ ├── InMemoryDistributedLockSpec.groovy │ │ │ └── UsesInMemorySherlock.groovy │ │ └── main │ │ └── java │ │ └── com │ │ └── coditory │ │ └── sherlock │ │ └── inmem │ │ ├── InMemoryDistributedLockConnector.java │ │ └── InMemorySherlock.java ├── mongo │ ├── common │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── mongo │ │ │ ├── MongoDistributedLock.java │ │ │ └── MongoDistributedLockQueries.java │ ├── coroutines │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── integrationTest │ │ │ └── groovy │ │ │ │ └── com │ │ │ │ └── coditory │ │ │ │ └── sherlock │ │ │ │ └── mongo │ │ │ │ └── coroutines │ │ │ │ ├── MongoClientHolder.groovy │ │ │ │ ├── MongoDbSpec.groovy │ │ │ │ ├── MongoDistributedLockSpec.groovy │ │ │ │ └── UsesKtMongoSherlock.groovy │ │ │ └── main │ │ │ └── kotlin │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── mongo │ │ │ └── coroutines │ │ │ ├── MongoCollectionInitializer.kt │ │ │ ├── MongoDistributedLockConnector.kt │ │ │ ├── MongoOperations.kt │ │ │ └── MongoSherlock.kt │ ├── reactor │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── integrationTest │ │ │ └── groovy │ │ │ │ └── com │ │ │ │ └── coditory │ │ │ │ └── sherlock │ │ │ │ └── mongo │ │ │ │ └── reactor │ │ │ │ ├── MongoClientHolder.groovy │ │ │ │ ├── MongoDbSpec.groovy │ │ │ │ ├── MongoDistributedLockSpec.groovy │ │ │ │ └── UsesReactorMongoSherlock.groovy │ │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── mongo │ │ │ └── reactor │ │ │ ├── MongoCollectionInitializer.java │ │ │ ├── MongoDistributedLockConnector.java │ │ │ └── MongoSherlock.java │ ├── rxjava │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── integrationTest │ │ │ └── groovy │ │ │ │ └── com │ │ │ │ └── coditory │ │ │ │ └── sherlock │ │ │ │ └── mongo │ │ │ │ └── rxjava │ │ │ │ ├── MongoClientHolder.groovy │ │ │ │ ├── MongoDbSpec.groovy │ │ │ │ ├── MongoDistributedLockSpec.groovy │ │ │ │ └── UsesRxMongoSherlock.groovy │ │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── mongo │ │ │ └── rxjava │ │ │ ├── MongoCollectionInitializer.java │ │ │ ├── MongoDistributedLockConnector.java │ │ │ └── MongoSherlock.java │ ├── sync │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── integrationTest │ │ │ └── groovy │ │ │ │ └── com │ │ │ │ └── coditory │ │ │ │ └── sherlock │ │ │ │ └── mongo │ │ │ │ ├── MongoDbSpec.groovy │ │ │ │ ├── MongoDistributedLockSpec.groovy │ │ │ │ └── UsesMongoSherlock.groovy │ │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── mongo │ │ │ ├── MongoCollectionInitializer.java │ │ │ ├── MongoDistributedLockConnector.java │ │ │ └── MongoSherlock.java │ └── tests │ │ ├── build.gradle.kts │ │ └── src │ │ └── main │ │ └── groovy │ │ └── com │ │ └── coditory │ │ └── sherlock │ │ └── mongo │ │ ├── MongoHolder.groovy │ │ ├── MongoIndexCreationSpec.groovy │ │ └── MongoLockStorageSpec.groovy └── sql │ ├── common-api │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── coditory │ │ └── sherlock │ │ └── sql │ │ ├── BindingMapper.java │ │ ├── BindingParameter.java │ │ └── BindingParameterMapping.java │ ├── common │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── coditory │ │ └── sherlock │ │ └── sql │ │ ├── PrecomputedBindingParameterMapper.java │ │ ├── SqlLockNamedQueriesTemplate.java │ │ └── SqlLockQueries.java │ ├── coroutines │ ├── build.gradle.kts │ └── src │ │ ├── integrationTest │ │ └── groovy │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── sql │ │ │ └── coroutines │ │ │ ├── MySqlConnectionPoolHolder.groovy │ │ │ ├── MySqlDbSpec.groovy │ │ │ ├── MySqlDistributedLockSpec.groovy │ │ │ ├── PostgresConnectionPoolHolder.groovy │ │ │ ├── PostgresDbSpec.groovy │ │ │ ├── PostgresDistributedLockSpec.groovy │ │ │ ├── SqlConnectionProvider.groovy │ │ │ └── UsesKtSqlSherlock.groovy │ │ └── main │ │ └── kotlin │ │ └── com │ │ └── coditory │ │ └── sherlock │ │ └── sql │ │ └── coroutines │ │ ├── ConnectionExt.kt │ │ ├── SqlDistributedLockConnector.kt │ │ ├── SqlSherlock.kt │ │ ├── SqlStatementBinder.kt │ │ └── SqlTableInitializer.kt │ ├── reactor │ ├── build.gradle.kts │ └── src │ │ ├── integrationTest │ │ └── groovy │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── sql │ │ │ └── reactor │ │ │ ├── MySqlConnectionPoolHolder.groovy │ │ │ ├── MySqlDbSpec.groovy │ │ │ ├── MySqlDistributedLockSpec.groovy │ │ │ ├── PostgresConnectionPoolHolder.groovy │ │ │ ├── PostgresDbSpec.groovy │ │ │ ├── PostgresDistributedLockSpec.groovy │ │ │ ├── SqlConnectionProvider.groovy │ │ │ └── UsesReactorSqlSherlock.groovy │ │ └── main │ │ └── java │ │ └── com │ │ └── coditory │ │ └── sherlock │ │ └── sql │ │ └── reactor │ │ ├── SqlDistributedLockConnector.java │ │ ├── SqlSherlock.java │ │ ├── SqlStatementBinder.java │ │ └── SqlTableInitializer.java │ ├── rxjava │ ├── build.gradle.kts │ └── src │ │ ├── integrationTest │ │ └── groovy │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── sql │ │ │ └── rxjava │ │ │ ├── MySqlConnectionPoolHolder.groovy │ │ │ ├── MySqlDbSpec.groovy │ │ │ ├── MySqlDistributedLockSpec.groovy │ │ │ ├── PostgresConnectionPoolHolder.groovy │ │ │ ├── PostgresDbSpec.groovy │ │ │ ├── PostgresDistributedLockSpec.groovy │ │ │ ├── SqlConnectioProvider.groovy │ │ │ └── UsesRxSqlSherlock.groovy │ │ └── main │ │ └── java │ │ └── com │ │ └── coditory │ │ └── sherlock │ │ └── sql │ │ └── rxjava │ │ ├── SqlDistributedLockConnector.java │ │ ├── SqlSherlock.java │ │ ├── SqlStatementBinder.java │ │ └── SqlTableInitializer.java │ ├── sync │ ├── build.gradle.kts │ └── src │ │ ├── integrationTest │ │ └── groovy │ │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── sql │ │ │ ├── MySqlDbSpec.groovy │ │ │ ├── MySqlDistributedLockSpec.groovy │ │ │ ├── PostgresDbSpec.groovy │ │ │ ├── PostgresDistributedLockSpec.groovy │ │ │ ├── SqlConnectioProvider.groovy │ │ │ └── UsesSqlSherlock.groovy │ │ └── main │ │ └── java │ │ └── com │ │ └── coditory │ │ └── sherlock │ │ └── sql │ │ ├── SqlDistributedLockConnector.java │ │ ├── SqlSherlock.java │ │ └── SqlTableInitializer.java │ └── tests │ ├── build.gradle.kts │ └── src │ └── main │ └── groovy │ └── com │ └── coditory │ └── sherlock │ └── sql │ ├── DataSourceConfigurer.groovy │ ├── MySqlHolder.groovy │ ├── PostgresHolder.groovy │ ├── SqlDistributedLocksCreator.groovy │ ├── SqlIndexCreationSpec.groovy │ ├── SqlLockCommitSpec.groovy │ ├── SqlLockStorageSpec.groovy │ └── SqlTableIndexes.groovy ├── docs ├── mkdocs.yml ├── requirements.txt ├── site │ ├── about.md │ ├── assets │ │ └── img │ │ │ ├── favicon.png │ │ │ ├── icon.png │ │ │ └── logo.png │ ├── connectors │ │ ├── index.md │ │ ├── inmem.md │ │ ├── mongo.md │ │ └── sql.md │ ├── index.md │ ├── locks.md │ ├── migrator.md │ └── testing.md └── theme │ └── main.html ├── examples ├── inmem-coroutines │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── kotlin │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── samples │ │ │ └── inmem │ │ │ └── coroutines │ │ │ ├── InMemKtAnnotatedMigrationSample.kt │ │ │ ├── InMemKtLockSample.kt │ │ │ └── InMemKtMigrationSample.kt │ │ └── resources │ │ └── logback.xml ├── inmem-reactor │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── samples │ │ │ └── inmem │ │ │ └── reactor │ │ │ ├── InMemReactorAnnotatedMigrationSample.java │ │ │ ├── InMemReactorLockSample.java │ │ │ └── InMemReactorMigrationSample.java │ │ └── resources │ │ └── logback.xml ├── inmem-rxjava │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── samples │ │ │ └── inmem │ │ │ └── rxjava │ │ │ ├── InMemRxAnnotatedMigrationSample.java │ │ │ ├── InMemRxLockSample.java │ │ │ └── InMemRxMigrationSample.java │ │ └── resources │ │ └── logback.xml ├── inmem-sync │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── samples │ │ │ └── inmem │ │ │ └── sync │ │ │ ├── InMemSyncAnnotatedMigrationSample.java │ │ │ ├── InMemSyncLockSample.java │ │ │ └── InMemSyncMigrationSample.java │ │ └── resources │ │ └── logback.xml ├── mongo-coroutines │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── kotlin │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── samples │ │ │ └── mongo │ │ │ └── coroutines │ │ │ ├── MongoKtAnnotatedMigrationSample.kt │ │ │ ├── MongoKtLockSample.kt │ │ │ └── MongoKtMigrationSample.kt │ │ └── resources │ │ └── logback.xml ├── mongo-reactor │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── samples │ │ │ └── mongo │ │ │ └── reactor │ │ │ ├── MongoReactorAnnotatedMigrationSample.java │ │ │ ├── MongoReactorLockSample.java │ │ │ └── MongoReactorMigrationSample.java │ │ └── resources │ │ └── logback.xml ├── mongo-rxjava │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── samples │ │ │ └── mongo │ │ │ └── rxjava │ │ │ ├── MongoRxAnnotatedMigrationSample.java │ │ │ ├── MongoRxLockSample.java │ │ │ └── MongoRxMigrationSample.java │ │ └── resources │ │ └── logback.xml ├── mongo-sync │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── samples │ │ │ └── mongo │ │ │ └── sync │ │ │ ├── MongoSyncAnnotatedMigrationSample.java │ │ │ ├── MongoSyncLockSample.java │ │ │ └── MongoSyncMigrationSample.java │ │ └── resources │ │ └── logback.xml ├── mysql-coroutines │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── kotlin │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── samples │ │ │ └── mysql │ │ │ └── coroutines │ │ │ ├── MySqlKtAnnotatedMigrationSample.kt │ │ │ ├── MySqlKtLockSample.kt │ │ │ └── MySqlKtMigrationSample.kt │ │ └── resources │ │ └── logback.xml ├── mysql-reactor │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── samples │ │ │ └── mysql │ │ │ └── reactor │ │ │ ├── MySqlReactorAnnotatedMigrationSample.java │ │ │ ├── MySqlReactorLockSample.java │ │ │ └── MySqlReactorMigrationSample.java │ │ └── resources │ │ └── logback.xml ├── mysql-rxjava │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── samples │ │ │ └── mysql │ │ │ └── rxjava │ │ │ ├── MySqlRxAnnotatedMigrationSample.java │ │ │ ├── MySqlRxLockSample.java │ │ │ └── MySqlRxMigrationSample.java │ │ └── resources │ │ └── logback.xml ├── mysql-sync │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── samples │ │ │ └── mysql │ │ │ └── sync │ │ │ ├── MySqlSyncAnnotatedMigrationSample.java │ │ │ ├── MySqlSyncLockSample.java │ │ │ └── MySqlSyncMigrationSample.java │ │ └── resources │ │ └── logback.xml ├── postgres-coroutines │ ├── build.gradle.kts │ ├── readme.md │ └── src │ │ └── main │ │ ├── kotlin │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── samples │ │ │ └── postgres │ │ │ └── coroutines │ │ │ ├── PostgresKtAnnotatedMigrationSample.kt │ │ │ ├── PostgresKtLockSample.kt │ │ │ └── PostgresKtMigrationSample.kt │ │ └── resources │ │ └── logback.xml ├── postgres-reactor │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── samples │ │ │ └── postgres │ │ │ └── reactor │ │ │ ├── PostgresReactorAnnotatedMigrationSample.java │ │ │ ├── PostgresReactorLockSample.java │ │ │ └── PostgresReactorMigrationSample.java │ │ └── resources │ │ └── logback.xml ├── postgres-rxjava │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── coditory │ │ │ └── sherlock │ │ │ └── samples │ │ │ └── postgres │ │ │ └── rxjava │ │ │ ├── PostgresRxAnnotatedMigrationSample.java │ │ │ ├── PostgresRxLockSample.java │ │ │ └── PostgresRxMigrationSample.java │ │ └── resources │ │ └── logback.xml └── postgres-sync │ ├── build.gradle.kts │ └── src │ └── main │ ├── java │ └── com │ │ └── coditory │ │ └── sherlock │ │ └── samples │ │ └── postgres │ │ └── sync │ │ ├── PostgresSyncAnnotatedMigrationSample.java │ │ ├── PostgresSyncLockSample.java │ │ └── PostgresSyncMigrationSample.java │ └── resources │ └── logback.xml ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── scripts ├── docker │ ├── README.md │ └── docker-compose.yml └── git │ └── pre-commit ├── settings.gradle.kts └── tests ├── build.gradle.kts └── src └── main ├── groovy └── com │ └── coditory │ └── sherlock │ ├── AcquireLockMultipleTimesSpec.groovy │ ├── AcquireLockSpec.groovy │ ├── BlockingReactorSherlockWrapper.groovy │ ├── BlockingRxSherlockWrapper.groovy │ ├── HandleDbFailureSpec.groovy │ ├── InfiniteAcquireLockSpec.groovy │ ├── LocksBaseSpec.groovy │ ├── PublisherSubscriber.groovy │ ├── ReleaseLockSpec.groovy │ ├── base │ ├── DatabaseManager.groovy │ ├── DistributedLocksCreator.groovy │ ├── JsonAssert.groovy │ ├── LockAssertions.groovy │ ├── LockTypes.groovy │ ├── SpecSimulatedException.groovy │ └── UpdatableFixedClock.groovy │ └── migrator │ ├── MigratorBaseSpec.groovy │ ├── MigratorChangeSetsSpec.groovy │ ├── MigratorSpec.groovy │ └── base │ ├── BlockingMigrator.groovy │ └── MigratorCreator.groovy ├── kotlin └── com │ └── coditory │ └── sherlock │ ├── BlockingKtDistributedLock.kt │ └── BlockingKtSherlockWrapper.kt └── resources └── logback.xml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.{java,groovy}] 11 | indent_size = 4 12 | 13 | [*.{kt,kts}] 14 | indent_size = 4 15 | ktlint_code_style = intellij_idea 16 | ktlint_function_signature_body_expression_wrapping = default 17 | ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = unset 18 | ktlint_ignore_back_ticked_identifier = true 19 | ktlint_standard_class-signature = disabled 20 | ktlint_standard_function-expression-body = disabled 21 | ktlint_standard_no-wildcard-imports = enabled -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @coditory/reviewers 2 | 3 | # Exclusions so changes made by dependabot and other bots could be auto reviewed by Coditory App. 4 | # Currently App cannot be a part of a team or CODEOWNERS file. 5 | # https://github.com/orgs/community/discussions/23064 6 | .github/workflows/* 7 | gradle/** 8 | **/build.gradle.kts 9 | **/settings.gradle.kts 10 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | patch: off -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | groups: 8 | # merged and released instantly 9 | sec-updates: 10 | applies-to: security-updates 11 | patterns: 12 | - "*" 13 | # merged automatically 14 | dev-dependencies: 15 | patterns: 16 | - "*" 17 | 18 | - package-ecosystem: "gradle" 19 | directory: "/" 20 | schedule: 21 | interval: "daily" 22 | groups: 23 | # merged and released instantly 24 | sec-updates: 25 | applies-to: security-updates 26 | patterns: 27 | - "*" 28 | # merged automatically 29 | dev-dependencies: 30 | patterns: 31 | # gradle plugins 32 | - "*ktlint-gradle" 33 | - "*jacoco" 34 | - "*publish-plugin" 35 | # test dependencies 36 | - "org.junit*" 37 | - "org.spockframework*" 38 | - "org.awaitility*" 39 | - "*-test" 40 | - "*assert" 41 | - "*hikaricp" 42 | - "org.testcontainers*" 43 | # merged and released automatically 44 | prod-dependencies: 45 | update-types: 46 | - "patch" 47 | - "minor" 48 | # requires human approval and has higher chance to fail build 49 | prod-dependencies-major: 50 | update-types: 51 | - "major" 52 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | backport: 2 | - head-branch: [ '^v[0-9]+\.x\.x' ] 3 | 4 | code: 5 | - changed-files: 6 | - any-glob-to-any-file: 7 | - "src/**" 8 | 9 | build: 10 | - changed-files: 11 | - any-glob-to-any-file: 12 | - "**/*.gradle*" 13 | 14 | ci: 15 | - changed-files: 16 | - any-glob-to-any-file: 17 | - ".github/**" 18 | 19 | documentation: 20 | - changed-files: 21 | - any-glob-to-any-file: 22 | - "**/*.md" 23 | - "docs/**" 24 | 25 | license: 26 | - changed-files: 27 | - any-glob-to-any-file: 28 | - "LICENSE" 29 | 30 | gradle: 31 | - changed-files: 32 | - any-glob-to-any-file: 33 | - "gradlew*" 34 | - ".gradle/**" 35 | - "gradle/**" 36 | - "setting.gradle*" 37 | 38 | gitignore: 39 | - changed-files: 40 | - any-glob-to-any-file: 41 | - ".gitignore" 42 | 43 | codestyle: 44 | - changed-files: 45 | - any-glob-to-any-file: 46 | - ".editorconfig" 47 | - ".idea/codeStyles/**" 48 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches-ignore: 8 | - 'gh-pages' 9 | 10 | jobs: 11 | build: 12 | uses: coditory/jvm-workflows/.github/workflows/build.yml@v1 13 | # Required to pass codecov token 14 | secrets: inherit 15 | with: 16 | java-version: 21 17 | build-command: ./gradlew build coverage 18 | -------------------------------------------------------------------------------- /.github/workflows/dependabot.yml: -------------------------------------------------------------------------------- 1 | name: "Dependabot" 2 | 3 | on: pull_request_target 4 | 5 | jobs: 6 | dependabot: 7 | uses: coditory/workflows/.github/workflows/dependabot.yml@v1 8 | secrets: inherit 9 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main] 7 | paths: 8 | - "docs/**" 9 | 10 | jobs: 11 | docs: 12 | uses: coditory/workflows/.github/workflows/docs.yml@v1 13 | secrets: inherit 14 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: "Labeler" 2 | 3 | on: pull_request_target 4 | 5 | jobs: 6 | labeler: 7 | uses: coditory/workflows/.github/workflows/labeler.yml@v1 8 | secrets: inherit 9 | -------------------------------------------------------------------------------- /.github/workflows/release-auto.yml: -------------------------------------------------------------------------------- 1 | name: Auto Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | security-updates-only: 7 | description: "Security updates only" 8 | type: boolean 9 | required: false 10 | default: false 11 | consider-snapshot: 12 | description: "Consider snapshot" 13 | type: boolean 14 | required: false 15 | default: false 16 | workflow_run: 17 | workflows: ["Build"] 18 | types: [completed] 19 | branches: 20 | - main 21 | - v*x.x 22 | schedule: 23 | # at 5:30 UTC every other month 24 | - cron: "30 5 1 */2 *" 25 | 26 | jobs: 27 | check: 28 | uses: coditory/workflows/.github/workflows/release-check.yml@v1 29 | secrets: inherit 30 | if: | 31 | github.event_name != 'workflow_run' 32 | || !contains(github.event.workflow_run.head_commit.message, '[ci-skip-build]') 33 | with: 34 | security-updates-only: ${{ inputs.security-updates-only || github.event_name == 'workflow_run' }} 35 | 36 | release: 37 | uses: ./.github/workflows/release.yml 38 | secrets: inherit 39 | needs: check 40 | if: needs.check.outputs.release == 'true' 41 | 42 | snapshot: 43 | uses: ./.github/workflows/release.yml 44 | secrets: inherit 45 | needs: check 46 | if: | 47 | (inputs.consider-snapshot || github.event_name == 'workflow_run') 48 | && needs.check.outputs.release != 'true' 49 | && needs.check.outputs.skip-code != 'no-changes' 50 | with: 51 | snapshot: true 52 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | branch: 7 | type: string 8 | description: Branch name to release 9 | required: true 10 | default: main 11 | section: 12 | type: choice 13 | description: Version section to increment 14 | options: 15 | - patch 16 | - minor 17 | - major 18 | required: true 19 | default: patch 20 | version: 21 | type: string 22 | description: ...or manually define version like 1.2.3, 1.2.3-suffix 23 | required: false 24 | snapshot: 25 | type: boolean 26 | description: Snapshot version 27 | required: true 28 | default: false 29 | # Called from release-auto 30 | workflow_call: 31 | inputs: 32 | branch: 33 | type: string 34 | required: false 35 | default: main 36 | section: 37 | type: string 38 | required: false 39 | default: patch 40 | version: 41 | type: string 42 | required: false 43 | snapshot: 44 | type: boolean 45 | required: false 46 | default: false 47 | 48 | jobs: 49 | release: 50 | uses: coditory/workflows/.github/workflows/release.yml@v1 51 | secrets: inherit 52 | with: 53 | branch: ${{ inputs.branch }} 54 | section: ${{ inputs.section }} 55 | version: ${{ inputs.version }} 56 | snapshot: ${{ inputs.snapshot }} 57 | java-version: 21 58 | release-command: ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository -Pversion=$NEXT_VERSION 59 | snapshot-command: ./gradlew publishToSonatype -Pversion=$NEXT_VERSION 60 | version-command: ./gradlew version --quiet --no-scan 61 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-gradle-wrapper.yml: -------------------------------------------------------------------------------- 1 | name: Upgrade Gradle Wrapper 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | # at 5:30 UTC every month 7 | - cron: "30 5 1 * *" 8 | 9 | jobs: 10 | upgrade-gradle-wrapper: 11 | uses: coditory/jvm-workflows/.github/workflows/upgrade-gradle-wrapper.yml@v1 12 | secrets: inherit 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle 2 | .log 3 | .gradle 4 | !gradle-wrapper.jar 5 | build 6 | .kotlin 7 | 8 | # IntelliJ 9 | *.iml 10 | .idea 11 | !.idea/codeStyles 12 | 13 | # Docker 14 | scripts/docker/**/data 15 | scripts/docker/**/data+bak 16 | -------------------------------------------------------------------------------- /api/common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.common) 9 | } 10 | -------------------------------------------------------------------------------- /api/common/src/main/java/com/coditory/sherlock/OwnerIdPolicy.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import static com.coditory.sherlock.Preconditions.expectNonEmpty; 6 | 7 | /** 8 | * Provides ownerId for locks. 9 | * It's executed once for every lock, during lock creation. 10 | *

11 | * If two JVM processes use the same ownerId, they are treated by Sherlock as the same owner. 12 | */ 13 | public interface OwnerIdPolicy { 14 | @NotNull 15 | static OwnerIdPolicy defaultOwnerIdPolicy() { 16 | return OwnerIdPolicy.uniqueOwnerId(); 17 | } 18 | 19 | @NotNull 20 | static OwnerIdPolicy staticOwnerId(@NotNull String ownerId) { 21 | expectNonEmpty(ownerId, "ownerId"); 22 | return new StaticOwnerIdPolicy(ownerId); 23 | } 24 | 25 | @NotNull 26 | static OwnerIdPolicy uniqueOwnerId() { 27 | return StaticOwnerIdPolicy.RANDOM_OWNER_ID_POLICY; 28 | } 29 | 30 | @NotNull 31 | static OwnerIdPolicy staticUniqueOwnerId() { 32 | return StaticOwnerIdPolicy.RANDOM_STATIC_OWNER_ID_POLICY; 33 | } 34 | 35 | @NotNull 36 | String getOwnerId(); 37 | } 38 | 39 | class StaticOwnerIdPolicy implements OwnerIdPolicy { 40 | static final OwnerIdPolicy RANDOM_OWNER_ID_POLICY = UuidGenerator::uuid; 41 | static final OwnerIdPolicy RANDOM_STATIC_OWNER_ID_POLICY = new StaticOwnerIdPolicy(UuidGenerator.uuid()); 42 | 43 | private final String ownerId; 44 | 45 | StaticOwnerIdPolicy(@NotNull String ownerId) { 46 | expectNonEmpty(ownerId, "ownerId"); 47 | this.ownerId = ownerId; 48 | } 49 | 50 | @Override 51 | @NotNull 52 | public String getOwnerId() { 53 | return ownerId; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /api/common/src/main/java/com/coditory/sherlock/SherlockException.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock; 2 | 3 | public class SherlockException extends RuntimeException { 4 | public SherlockException(String message) { 5 | super(message); 6 | } 7 | 8 | public SherlockException(String message, Throwable cause) { 9 | super(message, cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /api/common/src/main/java/com/coditory/sherlock/connector/InitializationResult.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.connector; 2 | 3 | import java.util.Objects; 4 | 5 | public final class InitializationResult { 6 | public static InitializationResult of(boolean initialized) { 7 | return initialized ? INITIALIZED : SKIPPED; 8 | } 9 | 10 | public static InitializationResult initializedResult() { 11 | return INITIALIZED; 12 | } 13 | 14 | public static InitializationResult skippedResult() { 15 | return SKIPPED; 16 | } 17 | 18 | private static final InitializationResult INITIALIZED = new InitializationResult(true); 19 | private static final InitializationResult SKIPPED = new InitializationResult(false); 20 | 21 | private final boolean initialized; 22 | 23 | private InitializationResult(boolean initialized) { 24 | this.initialized = initialized; 25 | } 26 | 27 | public boolean initialized() { 28 | return initialized; 29 | } 30 | 31 | public InitializationResult onSkipped(Runnable action) { 32 | if (!initialized) { 33 | action.run(); 34 | } 35 | return this; 36 | } 37 | 38 | public InitializationResult onInitialized(Runnable action) { 39 | if (initialized) { 40 | action.run(); 41 | } 42 | return this; 43 | } 44 | 45 | @Override 46 | public boolean equals(Object o) { 47 | if (this == o) return true; 48 | if (o == null || getClass() != o.getClass()) return false; 49 | InitializationResult that = (InitializationResult) o; 50 | return initialized == that.initialized; 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | return Objects.hash(initialized); 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "InitializationResult{" + 61 | "initialized=" + initialized + 62 | '}'; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /api/common/src/main/java/com/coditory/sherlock/connector/LockResultLogger.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.connector; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import static com.coditory.sherlock.Preconditions.expectNonNull; 8 | 9 | public final class LockResultLogger { 10 | private final String lockId; 11 | private final Logger logger; 12 | 13 | public LockResultLogger(@NotNull String lockId, @NotNull Class lockType) { 14 | expectNonNull(lockId, "lockId"); 15 | expectNonNull(lockType, "lockType"); 16 | this.lockId = lockId; 17 | this.logger = LoggerFactory.getLogger(lockType); 18 | } 19 | 20 | public void logResult(@NotNull AcquireResult result) { 21 | expectNonNull(result, "result"); 22 | if (result.acquired()) { 23 | logger.debug("Lock acquired: {}", lockId); 24 | } else { 25 | logger.debug("Lock not acquired: {}", lockId); 26 | } 27 | } 28 | 29 | public void logResult(@NotNull ReleaseResult result) { 30 | expectNonNull(result, "result"); 31 | if (result.released()) { 32 | logger.debug("Lock released: {}", lockId); 33 | } else { 34 | logger.debug("Lock not released: {}", lockId); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /api/common/src/main/java/com/coditory/sherlock/connector/ReleaseResult.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.connector; 2 | 3 | import java.util.Objects; 4 | 5 | public final class ReleaseResult { 6 | public static ReleaseResult of(boolean released) { 7 | return released ? RELEASED : SKIPPED; 8 | } 9 | 10 | public static ReleaseResult releasedResult() { 11 | return RELEASED; 12 | } 13 | 14 | public static ReleaseResult skippedResult() { 15 | return SKIPPED; 16 | } 17 | 18 | private static final ReleaseResult RELEASED = new ReleaseResult(true); 19 | private static final ReleaseResult SKIPPED = new ReleaseResult(false); 20 | 21 | private final boolean released; 22 | 23 | private ReleaseResult(boolean released) { 24 | this.released = released; 25 | } 26 | 27 | public boolean released() { 28 | return released; 29 | } 30 | 31 | public ReleaseResult onSkipped(Runnable action) { 32 | if (!released) { 33 | action.run(); 34 | } 35 | return this; 36 | } 37 | 38 | public ReleaseResult onReleased(Runnable action) { 39 | if (released) { 40 | action.run(); 41 | } 42 | return this; 43 | } 44 | 45 | @Override 46 | public boolean equals(Object o) { 47 | if (this == o) return true; 48 | if (o == null || getClass() != o.getClass()) return false; 49 | ReleaseResult that = (ReleaseResult) o; 50 | return released == that.released; 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | return Objects.hash(released); 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "ReleaseResult{" + 61 | "released=" + released + 62 | '}'; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /api/coroutines-connector/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.kotlin") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.api.apiCommon) 9 | api(projects.api.apiCoroutines) 10 | implementation(projects.common) 11 | implementation(libs.kotlinx.coroutines.core) 12 | implementation(libs.kotlinx.coroutines.reactive) 13 | testImplementation(projects.connectors.inmem.inmemCoroutines) 14 | testImplementation(projects.tests) 15 | } 16 | -------------------------------------------------------------------------------- /api/coroutines-connector/src/main/kotlin/com/coditory/sherlock/coroutines/SuspendingDistributedLockConnector.kt: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.coroutines 2 | 3 | import com.coditory.sherlock.LockRequest 4 | 5 | interface SuspendingDistributedLockConnector { 6 | /** 7 | * Initializes underlying infrastructure for locks. 8 | * Most frequently triggers database table creation and index creation. 9 | * 10 | * 11 | * If it is not executed explicitly, connector may execute it during first acquire acquisition or 12 | * release. 13 | */ 14 | suspend fun initialize() 15 | 16 | /** 17 | * Acquire a lock. 18 | */ 19 | suspend fun acquire(lockRequest: LockRequest): Boolean 20 | 21 | /** 22 | * Acquire a lock or prolong it if it was acquired by the same instance. 23 | */ 24 | suspend fun acquireOrProlong(lockRequest: LockRequest): Boolean 25 | 26 | /** 27 | * Acquire a lock even if it was already acquired by someone else 28 | */ 29 | suspend fun forceAcquire(lockRequest: LockRequest): Boolean 30 | 31 | /** 32 | * Unlock a lock if wat acquired by the same instance. 33 | */ 34 | suspend fun release(lockId: String, ownerId: String): Boolean 35 | 36 | /** 37 | * Release a lock without checking its owner or release date. 38 | */ 39 | suspend fun forceRelease(lockId: String): Boolean 40 | 41 | /** 42 | * Release all locks without checking their owners or release dates. 43 | */ 44 | suspend fun forceReleaseAll(): Boolean 45 | } 46 | -------------------------------------------------------------------------------- /api/coroutines-connector/src/test/groovy/com/coditory/sherlock/coroutines/InMemoryDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.coroutines 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.InfiniteAcquireLockSpec 6 | import com.coditory.sherlock.ReleaseLockSpec 7 | import com.coditory.sherlock.coroutines.base.UsesKtSherlock 8 | 9 | class CoroutinesInMemoryReleaseLockSpec extends ReleaseLockSpec 10 | implements UsesKtSherlock {} 11 | 12 | class CoroutinesInMemoryAcquireLockSpec extends AcquireLockSpec 13 | implements UsesKtSherlock {} 14 | 15 | class CoroutinesInMemoryAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 16 | implements UsesKtSherlock {} 17 | 18 | class CoroutinesInMemoryInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 19 | implements UsesKtSherlock {} 20 | 21 | -------------------------------------------------------------------------------- /api/coroutines-connector/src/test/groovy/com/coditory/sherlock/coroutines/base/UsesKtSherlock.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.coroutines.base 2 | 3 | import com.coditory.sherlock.BlockingKtSherlockWrapper 4 | import com.coditory.sherlock.base.DistributedLocksCreator 5 | import com.coditory.sherlock.coroutines.Sherlock 6 | import com.coditory.sherlock.inmem.coroutines.InMemorySherlock 7 | 8 | import java.time.Clock 9 | import java.time.Duration 10 | 11 | trait UsesKtSherlock implements DistributedLocksCreator { 12 | @Override 13 | com.coditory.sherlock.Sherlock createSherlock(String ownerId, Duration duration, Clock clock, String collectionName) { 14 | Sherlock reactorSherlock = InMemorySherlock.builder() 15 | .withOwnerId(ownerId) 16 | .withLockDuration(duration) 17 | .withClock(clock) 18 | .build() 19 | return new BlockingKtSherlockWrapper(reactorSherlock) 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /api/coroutines/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.kotlin") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.api.apiCommon) 9 | api(libs.kotlinx.coroutines.core) 10 | implementation(projects.common) 11 | implementation(libs.kotlinx.coroutines.reactive) 12 | testImplementation(libs.kotlinx.coroutines.test) 13 | testImplementation(projects.tests) 14 | testImplementation(projects.connectors.inmem.inmemCoroutines) 15 | } 16 | 17 | tasks.named("compileTestGroovy") { 18 | // make groovy test see kotlin test 19 | classpath += files(tasks.compileTestKotlin) 20 | } 21 | -------------------------------------------------------------------------------- /api/coroutines/src/test/groovy/com/coditory/sherlock/coroutines/MigratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.coroutines 2 | 3 | import com.coditory.sherlock.BlockingKtSherlockWrapper 4 | import com.coditory.sherlock.Sherlock 5 | import com.coditory.sherlock.coroutines.base.BlockingKtMigratorBuilder 6 | import com.coditory.sherlock.inmem.coroutines.InMemorySherlock 7 | import com.coditory.sherlock.migrator.MigratorChangeSetsSpec 8 | import com.coditory.sherlock.migrator.MigratorSpec 9 | import com.coditory.sherlock.migrator.base.BlockingMigratorBuilder 10 | import com.coditory.sherlock.migrator.base.MigratorCreator 11 | 12 | import java.time.Clock 13 | import java.time.Duration 14 | 15 | class CoroutinesMigratorSpec extends MigratorSpec implements UsesKtInMemorySherlock {} 16 | 17 | class CoroutinesMigratorChangeSetSpec extends MigratorChangeSetsSpec implements UsesKtInMemorySherlock {} 18 | 19 | trait UsesKtInMemorySherlock implements MigratorCreator { 20 | @Override 21 | Sherlock createSherlock(String ownerId, Duration duration, Clock clock, String collectionName) { 22 | com.coditory.sherlock.coroutines.Sherlock sherlock = InMemorySherlock.builder() 23 | .withOwnerId(ownerId) 24 | .withLockDuration(duration) 25 | .withClock(clock) 26 | .build() 27 | return new BlockingKtSherlockWrapper(sherlock) 28 | } 29 | 30 | @Override 31 | BlockingMigratorBuilder createMigratorBuilder(Sherlock sherlock) { 32 | if (sherlock instanceof BlockingKtSherlockWrapper) { 33 | return new BlockingKtMigratorBuilder(sherlock.unwrap()) 34 | } 35 | throw new IllegalArgumentException("Expected ${BlockingKtSherlockWrapper.simpleName}, got: " + sherlock.class.simpleName) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /api/coroutines/src/test/kotlin/com/coditory/sherlock/coroutines/base/Assertions.kt: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.coroutines.base 2 | 3 | import org.junit.jupiter.api.Assertions.fail 4 | import kotlin.reflect.KClass 5 | 6 | @Suppress("UNCHECKED_CAST") 7 | suspend fun assertThrows( 8 | action: suspend () -> Unit, 9 | type: KClass, 10 | ): T { 11 | return try { 12 | action() 13 | fail("Expected thrown exception") 14 | } catch (e: Throwable) { 15 | if (!type.isInstance(e)) { 16 | throw e 17 | } 18 | e as T 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /api/coroutines/src/test/kotlin/com/coditory/sherlock/coroutines/base/BlockingKtMigrator.kt: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.coroutines.base 2 | 3 | import com.coditory.sherlock.coroutines.Sherlock 4 | import com.coditory.sherlock.coroutines.migrator.SherlockMigrator 5 | import com.coditory.sherlock.coroutines.migrator.SherlockMigratorBuilder 6 | import com.coditory.sherlock.migrator.MigrationResult 7 | import com.coditory.sherlock.migrator.base.BlockingMigrator 8 | import com.coditory.sherlock.migrator.base.BlockingMigratorBuilder 9 | import kotlinx.coroutines.runBlocking 10 | 11 | class BlockingKtMigrator( 12 | private val migrator: SherlockMigrator, 13 | ) : BlockingMigrator { 14 | override fun migrate(): MigrationResult { 15 | return runBlocking { migrator.migrate() } 16 | } 17 | } 18 | 19 | class BlockingKtMigratorBuilder(sherlock: Sherlock) : BlockingMigratorBuilder { 20 | private val builder = SherlockMigratorBuilder(sherlock) 21 | 22 | override fun setMigrationId(migrationId: String): BlockingMigratorBuilder { 23 | builder.setMigrationId(migrationId) 24 | return this 25 | } 26 | 27 | override fun addChangeSet( 28 | changeSetId: String, 29 | changeSet: Runnable, 30 | ): BlockingMigratorBuilder { 31 | builder.addChangeSet(changeSetId, changeSet) 32 | return this 33 | } 34 | 35 | override fun addAnnotatedChangeSets(annotated: Any): BlockingMigratorBuilder { 36 | builder.addAnnotatedChangeSets(annotated) 37 | return this 38 | } 39 | 40 | override fun build(): BlockingMigrator { 41 | val migrator = builder.build() 42 | return BlockingKtMigrator(migrator) 43 | } 44 | 45 | override fun migrate(): MigrationResult { 46 | return build().migrate() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /api/coroutines/src/test/kotlin/com/coditory/sherlock/coroutines/base/DynamicTest.kt: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.coroutines.base 2 | 3 | import kotlinx.coroutines.test.TestScope 4 | import kotlinx.coroutines.test.runTest 5 | import org.junit.jupiter.api.DynamicTest 6 | 7 | fun runDynamicTest( 8 | name: String, 9 | testBody: suspend TestScope.() -> Unit, 10 | ): DynamicTest { 11 | return DynamicTest.dynamicTest(name) { 12 | runTest(testBody = testBody) 13 | } 14 | } 15 | 16 | fun List.runDynamicTest(testBody: suspend TestScope.(T) -> Unit): List { 17 | return this.map { 18 | DynamicTest.dynamicTest(it.name) { 19 | runTest { 20 | testBody(it) 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /api/coroutines/src/test/kotlin/com/coditory/sherlock/coroutines/base/Tuple.kt: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.coroutines.base 2 | 3 | fun tuple(p1: T1) = Tuple(p1) 4 | 5 | fun tuple( 6 | p1: T1, 7 | p2: T2, 8 | ) = Tuple2(p1, p2) 9 | 10 | fun tuple( 11 | p1: T1, 12 | p2: T2, 13 | p3: T3, 14 | ) = Tuple3(p1, p2, p3) 15 | 16 | fun tuple( 17 | p1: T1, 18 | p2: T2, 19 | p3: T3, 20 | p4: T4, 21 | ) = Tuple4(p1, p2, p3, p4) 22 | 23 | fun tuple( 24 | p1: T1, 25 | p2: T2, 26 | p3: T3, 27 | p4: T4, 28 | p5: T5, 29 | ) = Tuple5(p1, p2, p3, p4, p5) 30 | 31 | fun tuple( 32 | p1: T1, 33 | p2: T2, 34 | p3: T3, 35 | p4: T4, 36 | p5: T5, 37 | p6: T6, 38 | ): Tuple6 { 39 | return Tuple6(p1, p2, p3, p4, p5, p6) 40 | } 41 | 42 | data class Tuple(val p1: T1) 43 | 44 | data class Tuple2(val p1: T1, val p2: T2) 45 | 46 | data class Tuple3(val p1: T1, val p2: T2, val p3: T3) 47 | 48 | data class Tuple4(val p1: T1, val p2: T2, val p3: T3, val p4: T4) 49 | 50 | data class Tuple5(val p1: T1, val p2: T2, val p3: T3, val p4: T4, val p5: T5) 51 | 52 | data class Tuple6(val p1: T1, val p2: T2, val p3: T3, val p4: T4, val p5: T5, val p6: T6) 53 | -------------------------------------------------------------------------------- /api/reactor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.api.apiCommon) 9 | api(libs.reactor.core) 10 | implementation(projects.common) 11 | testImplementation(projects.tests) 12 | testImplementation(projects.connectors.inmem.inmemReactor) 13 | } 14 | -------------------------------------------------------------------------------- /api/reactor/src/integrationTest/groovy/com/coditory/sherlock/reactor/InMemoryDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.reactor 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.InfiniteAcquireLockSpec 6 | import com.coditory.sherlock.ReleaseLockSpec 7 | import com.coditory.sherlock.reactor.base.UsesReactorSherlock 8 | 9 | class ReactorInMemoryReleaseLockSpec extends ReleaseLockSpec 10 | implements UsesReactorSherlock {} 11 | 12 | class ReactorInMemoryAcquireLockSpec extends AcquireLockSpec 13 | implements UsesReactorSherlock {} 14 | 15 | class ReactorInMemoryAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 16 | implements UsesReactorSherlock {} 17 | 18 | class ReactorInMemoryInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 19 | implements UsesReactorSherlock {} 20 | -------------------------------------------------------------------------------- /api/reactor/src/integrationTest/groovy/com/coditory/sherlock/reactor/base/UsesReactorSherlock.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.reactor.base 2 | 3 | import com.coditory.sherlock.BlockingReactorSherlockWrapper 4 | import com.coditory.sherlock.Sherlock 5 | import com.coditory.sherlock.base.DistributedLocksCreator 6 | import com.coditory.sherlock.inmem.reactor.InMemorySherlock 7 | 8 | import java.time.Clock 9 | import java.time.Duration 10 | 11 | trait UsesReactorSherlock implements DistributedLocksCreator { 12 | @Override 13 | Sherlock createSherlock(String ownerId, Duration duration, Clock clock, String collectionName) { 14 | com.coditory.sherlock.reactor.Sherlock reactorSherlock = InMemorySherlock.builder() 15 | .withOwnerId(ownerId) 16 | .withLockDuration(duration) 17 | .withClock(clock) 18 | .build() 19 | return new BlockingReactorSherlockWrapper(reactorSherlock) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /api/reactor/src/main/java/com/coditory/sherlock/reactor/DistributedLockConnector.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.reactor; 2 | 3 | import com.coditory.sherlock.LockRequest; 4 | import com.coditory.sherlock.connector.AcquireResult; 5 | import com.coditory.sherlock.connector.InitializationResult; 6 | import com.coditory.sherlock.connector.ReleaseResult; 7 | import org.jetbrains.annotations.NotNull; 8 | import reactor.core.publisher.Mono; 9 | 10 | public interface DistributedLockConnector { 11 | /** 12 | * Initializes underlying infrastructure for locks. 13 | * Most frequently triggers database table creation and index creation. 14 | *

15 | * If it is not executed explicitly, connector may execute it during first acquire acquisition or 16 | * release. 17 | */ 18 | @NotNull 19 | Mono initialize(); 20 | 21 | /** 22 | * Acquires a lock. 23 | */ 24 | @NotNull 25 | Mono acquire(@NotNull LockRequest lockRequest); 26 | 27 | /** 28 | * Acquires a lock or prolongs it if it was acquired by the same instance. 29 | */ 30 | @NotNull 31 | Mono acquireOrProlong(LockRequest lockRequest); 32 | 33 | /** 34 | * Acquires a lock even if it was already acquired by someone else. 35 | */ 36 | @NotNull 37 | Mono forceAcquire(@NotNull LockRequest lockRequest); 38 | 39 | /** 40 | * Unlocks a lock if wat acquired by the same instance. 41 | */ 42 | @NotNull 43 | Mono release(@NotNull String lockId, @NotNull String ownerId); 44 | 45 | /** 46 | * Releases a lock without checking its owner or release date. 47 | */ 48 | @NotNull 49 | Mono forceRelease(@NotNull String lockId); 50 | 51 | /** 52 | * Releases all locks without checking their owners or release dates. 53 | */ 54 | @NotNull 55 | Mono forceReleaseAll(); 56 | } 57 | -------------------------------------------------------------------------------- /api/reactor/src/test/groovy/com/coditory/sherlock/reactor/migrator/BlockingReactorMigrator.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.reactor.migrator 2 | 3 | import com.coditory.sherlock.migrator.MigrationResult 4 | import com.coditory.sherlock.migrator.base.BlockingMigrator 5 | import com.coditory.sherlock.migrator.base.BlockingMigratorBuilder 6 | import com.coditory.sherlock.reactor.Sherlock 7 | 8 | import static java.util.Objects.requireNonNull 9 | 10 | class BlockingReactorMigrator implements BlockingMigrator { 11 | private final SherlockMigrator migrator 12 | 13 | BlockingReactorMigrator(SherlockMigrator migrator) { 14 | this.migrator = requireNonNull(migrator) 15 | } 16 | 17 | @Override 18 | MigrationResult migrate() { 19 | return migrator.migrate().block() 20 | } 21 | } 22 | 23 | class BlockingReactorMigratorBuilder implements BlockingMigratorBuilder { 24 | private final SherlockMigratorBuilder builder 25 | 26 | BlockingReactorMigratorBuilder(Sherlock sherlock) { 27 | requireNonNull(sherlock) 28 | builder = new SherlockMigratorBuilder(sherlock) 29 | } 30 | 31 | @Override 32 | BlockingMigratorBuilder setMigrationId(String migrationId) { 33 | builder.setMigrationId(migrationId) 34 | return this 35 | } 36 | 37 | @Override 38 | BlockingMigratorBuilder addChangeSet(String changeSetId, Runnable changeSet) { 39 | builder.addChangeSet(changeSetId, changeSet) 40 | return this 41 | } 42 | 43 | @Override 44 | BlockingMigratorBuilder addAnnotatedChangeSets(Object object) { 45 | builder.addAnnotatedChangeSets(object) 46 | return this 47 | } 48 | 49 | @Override 50 | BlockingMigrator build() { 51 | SherlockMigrator migrator = builder.build() 52 | return new BlockingReactorMigrator(migrator) 53 | } 54 | 55 | @Override 56 | MigrationResult migrate() { 57 | return build().migrate() 58 | } 59 | } -------------------------------------------------------------------------------- /api/reactor/src/test/groovy/com/coditory/sherlock/reactor/migrator/MigratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.reactor.migrator 2 | 3 | import com.coditory.sherlock.BlockingReactorSherlockWrapper 4 | import com.coditory.sherlock.Sherlock 5 | import com.coditory.sherlock.migrator.MigratorChangeSetsSpec 6 | import com.coditory.sherlock.migrator.MigratorSpec 7 | import com.coditory.sherlock.migrator.base.BlockingMigratorBuilder 8 | import com.coditory.sherlock.migrator.base.MigratorCreator 9 | 10 | import java.time.Clock 11 | import java.time.Duration 12 | 13 | import static com.coditory.sherlock.inmem.reactor.InMemorySherlock.builder 14 | 15 | class ReactorMigratorSpec extends MigratorSpec implements UsesReactorInMemorySherlock {} 16 | 17 | class ReactorMigratorChangeSetSpec extends MigratorChangeSetsSpec implements UsesReactorInMemorySherlock {} 18 | 19 | trait UsesReactorInMemorySherlock implements MigratorCreator { 20 | @Override 21 | Sherlock createSherlock(String ownerId, Duration duration, Clock clock, String collectionName) { 22 | com.coditory.sherlock.reactor.Sherlock reactorLocks = builder() 23 | .withOwnerId(ownerId) 24 | .withLockDuration(duration) 25 | .withClock(clock) 26 | .build() 27 | return new BlockingReactorSherlockWrapper(reactorLocks) 28 | } 29 | 30 | @Override 31 | BlockingMigratorBuilder createMigratorBuilder(Sherlock sherlock) { 32 | if (sherlock instanceof BlockingReactorSherlockWrapper) { 33 | return new BlockingReactorMigratorBuilder(sherlock.unwrap()) 34 | } 35 | throw new IllegalArgumentException("Expected ${BlockingReactorMigratorBuilder.simpleName}, got: " + sherlock.class.simpleName) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /api/rxjava/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.api.apiCommon) 9 | api(libs.rxjava) 10 | implementation(projects.common) 11 | testImplementation(projects.tests) 12 | testImplementation(projects.connectors.inmem.inmemRxjava) 13 | } 14 | -------------------------------------------------------------------------------- /api/rxjava/src/integrationTest/groovy/com/coditory/sherlock/rxjava/InMemoryDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.rxjava 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.InfiniteAcquireLockSpec 6 | import com.coditory.sherlock.ReleaseLockSpec 7 | import com.coditory.sherlock.rxjava.base.UsesRxSherlock 8 | 9 | class RxInMemoryReleaseLockSpec extends ReleaseLockSpec 10 | implements UsesRxSherlock {} 11 | 12 | class RxInMemoryAcquireLockSpec extends AcquireLockSpec 13 | implements UsesRxSherlock {} 14 | 15 | class RxInMemoryAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 16 | implements UsesRxSherlock {} 17 | 18 | class RxInMemoryInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 19 | implements UsesRxSherlock {} 20 | -------------------------------------------------------------------------------- /api/rxjava/src/integrationTest/groovy/com/coditory/sherlock/rxjava/base/UsesRxSherlock.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.rxjava.base 2 | 3 | import com.coditory.sherlock.BlockingRxSherlockWrapper 4 | import com.coditory.sherlock.Sherlock 5 | import com.coditory.sherlock.base.DistributedLocksCreator 6 | import com.coditory.sherlock.inmem.rxjava.InMemorySherlock 7 | 8 | import java.time.Clock 9 | import java.time.Duration 10 | 11 | trait UsesRxSherlock implements DistributedLocksCreator { 12 | @Override 13 | Sherlock createSherlock(String ownerId, Duration duration, Clock clock, String collectionName) { 14 | com.coditory.sherlock.rxjava.Sherlock rxSherlock = InMemorySherlock.builder() 15 | .withOwnerId(ownerId) 16 | .withLockDuration(duration) 17 | .withClock(clock) 18 | .build() 19 | return new BlockingRxSherlockWrapper(rxSherlock) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /api/rxjava/src/main/java/com/coditory/sherlock/rxjava/DistributedLockConnector.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.rxjava; 2 | 3 | import com.coditory.sherlock.LockRequest; 4 | import com.coditory.sherlock.connector.AcquireResult; 5 | import com.coditory.sherlock.connector.InitializationResult; 6 | import com.coditory.sherlock.connector.ReleaseResult; 7 | import io.reactivex.rxjava3.core.Single; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public interface DistributedLockConnector { 11 | /** 12 | * Initializes underlying infrastructure for locks. 13 | * Most frequently triggers database table creation and index creation. 14 | *

15 | * If it is not executed explicitly, connector may execute it during first acquire acquisition or 16 | * release. 17 | */ 18 | @NotNull 19 | Single initialize(); 20 | 21 | /** 22 | * Acquire a lock. 23 | */ 24 | @NotNull 25 | Single acquire(@NotNull LockRequest lockRequest); 26 | 27 | /** 28 | * Acquire a lock or prolong it if it was acquired by the same instance. 29 | */ 30 | @NotNull 31 | Single acquireOrProlong(LockRequest lockRequest); 32 | 33 | /** 34 | * Acquire a lock even if it was already acquired by someone else 35 | */ 36 | @NotNull 37 | Single forceAcquire(@NotNull LockRequest lockRequest); 38 | 39 | /** 40 | * Unlock a lock if wat acquired by the same instance. 41 | */ 42 | @NotNull 43 | Single release(@NotNull String lockId, @NotNull String ownerId); 44 | 45 | /** 46 | * Release a lock without checking its owner or release date. 47 | */ 48 | @NotNull 49 | Single forceRelease(@NotNull String lockId); 50 | 51 | /** 52 | * Release all locks without checking their owners or release dates. 53 | */ 54 | @NotNull 55 | Single forceReleaseAll(); 56 | } 57 | -------------------------------------------------------------------------------- /api/rxjava/src/test/groovy/com/coditory/sherlock/rxjava/migrator/BlockingRxMigrator.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.rxjava.migrator 2 | 3 | import com.coditory.sherlock.migrator.MigrationResult 4 | import com.coditory.sherlock.migrator.base.BlockingMigrator 5 | import com.coditory.sherlock.migrator.base.BlockingMigratorBuilder 6 | import com.coditory.sherlock.rxjava.Sherlock 7 | 8 | import static java.util.Objects.requireNonNull 9 | 10 | class BlockingRxMigrator implements BlockingMigrator { 11 | private final SherlockMigrator migrator 12 | 13 | BlockingRxMigrator(SherlockMigrator migrator) { 14 | this.migrator = requireNonNull(migrator) 15 | } 16 | 17 | @Override 18 | MigrationResult migrate() { 19 | return migrator.migrate().blockingGet() 20 | } 21 | } 22 | 23 | class BlockingRxMigratorBuilder implements BlockingMigratorBuilder { 24 | private final SherlockMigratorBuilder builder 25 | 26 | BlockingRxMigratorBuilder(Sherlock sherlock) { 27 | requireNonNull(sherlock) 28 | builder = new SherlockMigratorBuilder(sherlock) 29 | } 30 | 31 | @Override 32 | BlockingMigratorBuilder setMigrationId(String migrationId) { 33 | builder.setMigrationId(migrationId) 34 | return this 35 | } 36 | 37 | @Override 38 | BlockingMigratorBuilder addChangeSet(String changeSetId, Runnable changeSet) { 39 | builder.addChangeSet(changeSetId, changeSet) 40 | return this 41 | } 42 | 43 | @Override 44 | BlockingMigratorBuilder addAnnotatedChangeSets(Object object) { 45 | builder.addAnnotatedChangeSets(object) 46 | return this 47 | } 48 | 49 | @Override 50 | BlockingMigrator build() { 51 | SherlockMigrator migrator = builder.build() 52 | return new BlockingRxMigrator(migrator) 53 | } 54 | 55 | @Override 56 | MigrationResult migrate() { 57 | return build().migrate() 58 | } 59 | } -------------------------------------------------------------------------------- /api/rxjava/src/test/groovy/com/coditory/sherlock/rxjava/migrator/MigratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.rxjava.migrator 2 | 3 | import com.coditory.sherlock.BlockingRxSherlockWrapper 4 | import com.coditory.sherlock.Sherlock 5 | import com.coditory.sherlock.inmem.rxjava.InMemorySherlock 6 | import com.coditory.sherlock.migrator.MigratorChangeSetsSpec 7 | import com.coditory.sherlock.migrator.MigratorSpec 8 | import com.coditory.sherlock.migrator.base.BlockingMigratorBuilder 9 | import com.coditory.sherlock.migrator.base.MigratorCreator 10 | 11 | import java.time.Clock 12 | import java.time.Duration 13 | 14 | class RxMigratorSpec extends MigratorSpec implements UsesRxInMemorySherlock {} 15 | 16 | class RxMigratorChangeSetSpec extends MigratorChangeSetsSpec implements UsesRxInMemorySherlock {} 17 | 18 | trait UsesRxInMemorySherlock implements MigratorCreator { 19 | @Override 20 | Sherlock createSherlock(String ownerId, Duration duration, Clock clock, String collectionName) { 21 | com.coditory.sherlock.rxjava.Sherlock rxLocks = InMemorySherlock.builder() 22 | .withOwnerId(ownerId) 23 | .withLockDuration(duration) 24 | .withClock(clock) 25 | .build() 26 | return new BlockingRxSherlockWrapper(rxLocks) 27 | } 28 | 29 | @Override 30 | BlockingMigratorBuilder createMigratorBuilder(Sherlock sherlock) { 31 | if (sherlock instanceof BlockingRxSherlockWrapper) { 32 | return new BlockingRxMigratorBuilder(sherlock.unwrap()) 33 | } 34 | throw new IllegalArgumentException("Expected ${BlockingRxSherlockWrapper.simpleName}, got: " + sherlock.class.simpleName) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /api/sync/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.api.apiCommon) 9 | implementation(projects.common) 10 | testImplementation(projects.tests) 11 | testImplementation(projects.connectors.inmem.inmemSync) 12 | } 13 | -------------------------------------------------------------------------------- /api/sync/src/main/java/com/coditory/sherlock/DistributedLockConnector.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | public interface DistributedLockConnector { 6 | /** 7 | * Initializes underlying infrastructure for locks. 8 | * Most frequently triggers database table creation and index creation. 9 | *

10 | * If it is not executed explicitly, connector may execute it during first acquire acquisition or 11 | * release. 12 | */ 13 | void initialize(); 14 | 15 | /** 16 | * Acquire a lock. 17 | * 18 | * @return boolean - true if acquire was acquired by this call 19 | */ 20 | boolean acquire(@NotNull LockRequest lockRequest); 21 | 22 | /** 23 | * Acquire a lock or prolong it if it was acquired by the same instance. 24 | * 25 | * @return boolean - true if acquire was acquired by this call 26 | */ 27 | boolean acquireOrProlong(@NotNull LockRequest lockRequest); 28 | 29 | /** 30 | * Acquire a lock even if it was already acquired by someone else 31 | * 32 | * @return boolean - true if acquire was acquired by this call 33 | */ 34 | boolean forceAcquire(@NotNull LockRequest lockRequest); 35 | 36 | /** 37 | * Unlock a lock if wat acquired by the same instance. 38 | * 39 | * @return boolean - true if acquire was released by this call 40 | */ 41 | boolean release(@NotNull String lockId, @NotNull String ownerId); 42 | 43 | /** 44 | * Release a lock without checking its owner or release date. 45 | * 46 | * @return boolean - true if acquire was released by this call 47 | */ 48 | boolean forceRelease(@NotNull String lockId); 49 | 50 | /** 51 | * Release all locks without checking their owners or release dates. 52 | * 53 | * @return boolean - true if at least one lock was released 54 | */ 55 | boolean forceReleaseAll(); 56 | } 57 | -------------------------------------------------------------------------------- /api/sync/src/test/groovy/com/coditory/sherlock/migrator/BlockingSyncMigrator.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.migrator 2 | 3 | import com.coditory.sherlock.Sherlock 4 | import com.coditory.sherlock.migrator.base.BlockingMigrator 5 | import com.coditory.sherlock.migrator.base.BlockingMigratorBuilder 6 | 7 | import static java.util.Objects.requireNonNull 8 | 9 | class BlockingSyncMigrator implements BlockingMigrator { 10 | private final SherlockMigrator migrator 11 | 12 | BlockingSyncMigrator(SherlockMigrator migrator) { 13 | this.migrator = requireNonNull(migrator) 14 | } 15 | 16 | @Override 17 | MigrationResult migrate() { 18 | return migrator.migrate() 19 | } 20 | } 21 | 22 | class BlockingSyncMigratorBuilder implements BlockingMigratorBuilder { 23 | private final SherlockMigratorBuilder builder 24 | 25 | BlockingSyncMigratorBuilder(Sherlock sherlock) { 26 | requireNonNull(sherlock) 27 | builder = new SherlockMigratorBuilder(sherlock) 28 | } 29 | 30 | @Override 31 | BlockingMigratorBuilder setMigrationId(String migrationId) { 32 | builder.setMigrationId(migrationId) 33 | return this 34 | } 35 | 36 | @Override 37 | BlockingMigratorBuilder addChangeSet(String changeSetId, Runnable changeSet) { 38 | builder.addChangeSet(changeSetId, changeSet) 39 | return this 40 | } 41 | 42 | @Override 43 | BlockingMigratorBuilder addAnnotatedChangeSets(Object object) { 44 | builder.addAnnotatedChangeSets(object) 45 | return this 46 | } 47 | 48 | @Override 49 | BlockingMigrator build() { 50 | SherlockMigrator migrator = builder.build() 51 | return new BlockingSyncMigrator(migrator) 52 | } 53 | 54 | @Override 55 | MigrationResult migrate() { 56 | return build().migrate() 57 | } 58 | } -------------------------------------------------------------------------------- /api/sync/src/test/groovy/com/coditory/sherlock/migrator/SherlockMigratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.migrator 2 | 3 | import com.coditory.sherlock.Sherlock 4 | import com.coditory.sherlock.inmem.InMemorySherlock 5 | import com.coditory.sherlock.migrator.base.BlockingMigratorBuilder 6 | import com.coditory.sherlock.migrator.base.MigratorCreator 7 | 8 | import java.time.Clock 9 | import java.time.Duration 10 | 11 | class SyncMigratorSpec extends MigratorSpec implements UsesInMemorySherlock {} 12 | 13 | class SyncMigratorChangeSetSpec extends MigratorChangeSetsSpec implements UsesInMemorySherlock { 14 | } 15 | 16 | trait UsesInMemorySherlock implements MigratorCreator { 17 | @Override 18 | BlockingMigratorBuilder createMigratorBuilder(Sherlock sherlock) { 19 | return new BlockingSyncMigratorBuilder(sherlock) 20 | } 21 | 22 | @Override 23 | Sherlock createSherlock(String ownerId, Duration duration, Clock clock, String collectionName) { 24 | return InMemorySherlock.builder() 25 | .withOwnerId(ownerId) 26 | .withLockDuration(duration) 27 | .withClock(clock) 28 | .build() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /build-logic/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | kotlin("jvm") version embeddedKotlinVersion 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | gradlePluginPortal() 9 | } 10 | 11 | dependencies { 12 | implementation(libs.gradle.kotlin) 13 | implementation(libs.gradle.ktlint) 14 | implementation(libs.gradle.nexusPublish) 15 | compileOnly(files(libs::class.java.protectionDomain.codeSource.location)) 16 | } 17 | -------------------------------------------------------------------------------- /build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "build-logic" 2 | 3 | dependencyResolutionManagement { 4 | versionCatalogs { 5 | create("libs") { 6 | from(files("../gradle/libs.versions.toml")) 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/build.coverage-root.gradle.kts: -------------------------------------------------------------------------------- 1 | import gradle.kotlin.dsl.accessors._1d662f89dd4d27eb95f0093e004edd00.main 2 | import gradle.kotlin.dsl.accessors._1d662f89dd4d27eb95f0093e004edd00.sourceSets 3 | 4 | plugins { 5 | id("jacoco") 6 | } 7 | 8 | tasks.register("coverage") { 9 | description = "Creates combined coverage report" 10 | 11 | val coveredProjects = 12 | subprojects 13 | .filter { it.plugins.hasPlugin("jacoco") } 14 | 15 | coveredProjects 16 | .forEach { subproject -> 17 | executionData(fileTree(subproject.layout.buildDirectory).include("jacoco/*.exec")) 18 | sourceSets(subproject.sourceSets.main.get()) 19 | } 20 | 21 | dependsOn( 22 | coveredProjects.map { it.tasks.findByName("test") } + 23 | coveredProjects.map { it.tasks.findByName("integrationTest") }, 24 | ) 25 | 26 | reports { 27 | xml.required.set(true) 28 | html.required.set(true) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/build.coverage.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | id("groovy") 4 | id("jacoco") 5 | } 6 | 7 | val libs = extensions.getByType(org.gradle.accessors.dm.LibrariesForLibs::class) 8 | 9 | // coverage introduces time overhead 10 | // enable coverage on demand only 11 | // ./gradlew ... -Pcoverage 12 | // ...or with a task 13 | // ./gradlew ... coverage 14 | val coverageEnabled = 15 | (project.hasProperty("coverage") && project.properties["coverage"] != "false") || 16 | project.gradle.startParameter.taskNames.contains("coverage") 17 | 18 | jacoco { 19 | toolVersion = libs.versions.jacoco.get() 20 | } 21 | 22 | tasks.withType().configureEach { 23 | configure { 24 | isEnabled = coverageEnabled 25 | } 26 | } 27 | 28 | // coverage report combining all types of tests (unit + integration) 29 | tasks.jacocoTestReport.configure { 30 | executionData(fileTree(project.layout.buildDirectory).include("jacoco/*.exec")) 31 | reports { 32 | xml.required.set(true) 33 | html.required.set(true) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/build.java.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | id("build.test") 4 | } 5 | 6 | val libs = extensions.getByType(org.gradle.accessors.dm.LibrariesForLibs::class) 7 | 8 | java { 9 | toolchain { 10 | languageVersion.set(JavaLanguageVersion.of(libs.versions.java.get())) 11 | } 12 | withJavadocJar() 13 | withSourcesJar() 14 | } 15 | 16 | tasks.withType().configureEach { 17 | options.encoding = "UTF-8" 18 | options.compilerArgs.addAll(listOf("-Werror", "-Xlint", "-Xlint:-serial")) 19 | } 20 | 21 | // make javadoc less strict to limit noisy logs 22 | tasks.withType().configureEach { 23 | val sourceSet = project.extensions.getByType().sourceSets.getByName("main") 24 | source = sourceSet.allJava 25 | classpath = sourceSet.compileClasspath 26 | isFailOnError = false 27 | options { 28 | this as StandardJavadocDocletOptions 29 | addBooleanOption("Xdoclint:none", true) 30 | addStringOption("Xmaxwarns", "1") 31 | memberLevel = JavadocMemberLevel.PUBLIC 32 | outputLevel = JavadocOutputLevel.QUIET 33 | encoding = "UTF-8" 34 | addBooleanOption("html5", true) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/build.kotlin.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | id("build.java") 5 | kotlin("jvm") 6 | id("org.jlleitschuh.gradle.ktlint") 7 | } 8 | 9 | val libs = extensions.getByType(org.gradle.accessors.dm.LibrariesForLibs::class) 10 | 11 | ktlint { 12 | version.set(libs.versions.ktlint.get()) 13 | } 14 | 15 | kotlin { 16 | target.compilations { 17 | getByName("integrationTest") 18 | .associateWith(getByName("test")) 19 | } 20 | jvmToolchain { 21 | languageVersion.set(JavaLanguageVersion.of(libs.versions.java.get())) 22 | } 23 | } 24 | 25 | tasks.withType().configureEach { 26 | compilerOptions { 27 | allWarningsAsErrors.set(true) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/build.publish-root.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.time.Duration 2 | 3 | plugins { 4 | id("io.github.gradle-nexus.publish-plugin") 5 | } 6 | 7 | nexusPublishing { 8 | connectTimeout.set(Duration.ofMinutes(5)) 9 | clientTimeout.set(Duration.ofMinutes(5)) 10 | repositories { 11 | sonatype { 12 | System.getenv("OSSRH_STAGING_PROFILE_ID")?.let { stagingProfileId = it } 13 | System.getenv("OSSRH_USERNAME")?.let { username.set(it) } 14 | System.getenv("OSSRH_PASSWORD")?.let { password.set(it) } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/build.version.gradle.kts: -------------------------------------------------------------------------------- 1 | // Prints project version. 2 | // Usage: ./gradlew version --quiet 3 | tasks.register("version") { 4 | val version = project.version 5 | doLast { println(version) } 6 | } 7 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.version") 3 | id("build.coverage-root") 4 | id("build.publish-root") 5 | } 6 | -------------------------------------------------------------------------------- /common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | // This module is NOT a public API - imported as implementation 2 | 3 | plugins { 4 | id("build.java") 5 | id("build.coverage") 6 | id("build.publish") 7 | } 8 | 9 | dependencies { 10 | api(libs.slf4j.api) 11 | api(libs.jetbrains.annotations) 12 | } 13 | -------------------------------------------------------------------------------- /common/src/main/java/com/coditory/sherlock/LockRequest.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.time.Duration; 7 | 8 | import static com.coditory.sherlock.Preconditions.expectNonEmpty; 9 | import static com.coditory.sherlock.Preconditions.expectTruncatedToMillis; 10 | 11 | public record LockRequest( 12 | @NotNull String lockId, 13 | @NotNull String ownerId, 14 | @Nullable Duration duration 15 | ) { 16 | public LockRequest { 17 | expectNonEmpty(lockId, "lockId"); 18 | expectNonEmpty(ownerId, "ownerId"); 19 | if (duration != null) { 20 | expectTruncatedToMillis(duration, "duration"); 21 | } 22 | } 23 | 24 | public LockRequest( 25 | @NotNull String lockId, 26 | @NotNull String ownerId 27 | ) { 28 | this(lockId, ownerId, null); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /common/src/main/java/com/coditory/sherlock/SherlockDefaults.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock; 2 | 3 | import java.time.Clock; 4 | import java.time.Duration; 5 | 6 | public final class SherlockDefaults { 7 | public static final Duration DEFAULT_LOCK_DURATION = Duration.ofMinutes(5); 8 | public static final Clock DEFAULT_CLOCK = Clock.systemUTC(); 9 | public static final String DEFAULT_MIGRATOR_LOCK_ID = "migrator"; 10 | 11 | private SherlockDefaults() { 12 | throw new UnsupportedOperationException("Do not instantiate utility class"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /common/src/main/java/com/coditory/sherlock/Timer.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | public class Timer { 6 | public static Timer start() { 7 | return new Timer(); 8 | } 9 | 10 | private final long started = System.currentTimeMillis(); 11 | 12 | public long elapsedMs() { 13 | return System.currentTimeMillis() - started; 14 | } 15 | 16 | @NotNull 17 | public String elapsed() { 18 | return elapsedMs() + "ms"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /common/src/main/java/com/coditory/sherlock/UuidGenerator.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.UUID; 6 | 7 | public final class UuidGenerator { 8 | private UuidGenerator() { 9 | throw new UnsupportedOperationException("Do not instantiate utility class"); 10 | } 11 | 12 | @NotNull 13 | public static String uuid() { 14 | return UUID.randomUUID() 15 | .toString(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /common/src/main/java/com/coditory/sherlock/migrator/ChangeSet.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.migrator; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ChangeSet { 11 | int order(); 12 | 13 | String id(); 14 | 15 | String description() default ""; 16 | 17 | String author() default ""; 18 | } 19 | -------------------------------------------------------------------------------- /common/src/main/java/com/coditory/sherlock/migrator/MigrationChangeSet.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.migrator; 2 | 3 | import java.util.function.Supplier; 4 | 5 | public record MigrationChangeSet(String id, Supplier action) { 6 | static MigrationChangeSet from(MigrationChangeSetMethod changeSetMethod) { 7 | return new MigrationChangeSet<>( 8 | changeSetMethod.id(), 9 | ChangeSetMethodExtractor.invokeMethod(changeSetMethod.method(), changeSetMethod.object(), changeSetMethod.returnType()) 10 | ); 11 | } 12 | 13 | public R execute() { 14 | return action.get(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /common/src/main/java/com/coditory/sherlock/migrator/MigrationChangeSetMethod.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.migrator; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | public record MigrationChangeSetMethod(String id, Object object, Method method, Class returnType) { 6 | } 7 | -------------------------------------------------------------------------------- /connectors/inmem/common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.common) 9 | } 10 | -------------------------------------------------------------------------------- /connectors/inmem/coroutines/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.kotlin") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.api.apiCoroutines) 9 | api(projects.api.apiCoroutinesConnector) 10 | implementation(projects.connectors.inmem.inmemCommon) 11 | integrationTestImplementation(projects.tests) 12 | } 13 | -------------------------------------------------------------------------------- /connectors/inmem/coroutines/src/integrationTest/groovy/com/coditory/sherlock/inmem/coroutines/InMemoryDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.inmem.coroutines 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.InfiniteAcquireLockSpec 6 | import com.coditory.sherlock.ReleaseLockSpec 7 | 8 | class KtInMemoryReleaseLockSpec extends ReleaseLockSpec 9 | implements UsesKtInMemorySherlock {} 10 | 11 | class KtInMemoryAcquireLockSpec extends AcquireLockSpec 12 | implements UsesKtInMemorySherlock {} 13 | 14 | class KtInMemoryAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 15 | implements UsesKtInMemorySherlock {} 16 | 17 | class KtInMemoryInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 18 | implements UsesKtInMemorySherlock {} 19 | 20 | -------------------------------------------------------------------------------- /connectors/inmem/coroutines/src/integrationTest/groovy/com/coditory/sherlock/inmem/coroutines/UsesKtInMemorySherlock.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.inmem.coroutines 2 | 3 | import com.coditory.sherlock.BlockingKtSherlockWrapper 4 | import com.coditory.sherlock.Sherlock 5 | import com.coditory.sherlock.base.DistributedLocksCreator 6 | 7 | import java.time.Clock 8 | import java.time.Duration 9 | 10 | trait UsesKtInMemorySherlock implements DistributedLocksCreator { 11 | @Override 12 | Sherlock createSherlock(String instanceId, Duration duration, Clock clock, String collectionName) { 13 | com.coditory.sherlock.coroutines.Sherlock reactiveLocks = InMemorySherlock.builder() 14 | .withOwnerId(instanceId) 15 | .withLockDuration(duration) 16 | .withClock(clock) 17 | .build() 18 | return new BlockingKtSherlockWrapper(reactiveLocks) 19 | } 20 | } 21 | 22 | 23 | -------------------------------------------------------------------------------- /connectors/inmem/coroutines/src/main/kotlin/com/coditory/sherlock/inmem/coroutines/KtInMemoryDistributedLockConnector.kt: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.inmem.coroutines 2 | 3 | import com.coditory.sherlock.LockRequest 4 | import com.coditory.sherlock.coroutines.SuspendingDistributedLockConnector 5 | import com.coditory.sherlock.inmem.InMemoryDistributedLockStorage 6 | import java.time.Clock 7 | import java.time.Instant 8 | 9 | internal class KtInMemoryDistributedLockConnector( 10 | private val clock: Clock, 11 | private val storage: InMemoryDistributedLockStorage, 12 | ) : SuspendingDistributedLockConnector { 13 | override suspend fun initialize() { 14 | // deliberately empty 15 | } 16 | 17 | override suspend fun acquire(lockRequest: LockRequest): Boolean { 18 | return storage.acquire(lockRequest, now()) 19 | } 20 | 21 | override suspend fun acquireOrProlong(lockRequest: LockRequest): Boolean { 22 | return storage.acquireOrProlong(lockRequest, now()) 23 | } 24 | 25 | override suspend fun forceAcquire(lockRequest: LockRequest): Boolean { 26 | return storage.forceAcquire(lockRequest, now()) 27 | } 28 | 29 | override suspend fun release(lockId: String, ownerId: String): Boolean { 30 | return storage.release(lockId, now(), ownerId) 31 | } 32 | 33 | override suspend fun forceRelease(lockId: String): Boolean { 34 | return storage.forceRelease(lockId, now()) 35 | } 36 | 37 | override suspend fun forceReleaseAll(): Boolean { 38 | return storage.forceReleaseAll(now()) 39 | } 40 | 41 | private fun now(): Instant { 42 | return clock.instant() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /connectors/inmem/reactor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.api.apiReactor) 9 | api(projects.connectors.inmem.inmemSync) 10 | implementation(projects.connectors.inmem.inmemCommon) 11 | integrationTestImplementation(projects.tests) 12 | } 13 | -------------------------------------------------------------------------------- /connectors/inmem/reactor/src/integrationTest/groovy/com/coditory/sherlock/inmem/reactor/InMemoryDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.inmem.reactor 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.InfiniteAcquireLockSpec 6 | import com.coditory.sherlock.ReleaseLockSpec 7 | 8 | class ReactorInMemoryReleaseLockSpec extends ReleaseLockSpec 9 | implements UsesReactorInMemorySherlock {} 10 | 11 | class ReactorInMemoryAcquireLockSpec extends AcquireLockSpec 12 | implements UsesReactorInMemorySherlock {} 13 | 14 | class ReactorInMemoryAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 15 | implements UsesReactorInMemorySherlock {} 16 | 17 | class ReactorInMemoryInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 18 | implements UsesReactorInMemorySherlock {} 19 | 20 | -------------------------------------------------------------------------------- /connectors/inmem/reactor/src/integrationTest/groovy/com/coditory/sherlock/inmem/reactor/UsesReactorInMemorySherlock.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.inmem.reactor 2 | 3 | import com.coditory.sherlock.BlockingReactorSherlockWrapper 4 | import com.coditory.sherlock.Sherlock 5 | import com.coditory.sherlock.base.DistributedLocksCreator 6 | 7 | import java.time.Clock 8 | import java.time.Duration 9 | 10 | trait UsesReactorInMemorySherlock implements DistributedLocksCreator { 11 | @Override 12 | Sherlock createSherlock(String instanceId, Duration duration, Clock clock, String collectionName) { 13 | com.coditory.sherlock.reactor.Sherlock reactorLocks = InMemorySherlock.builder() 14 | .withOwnerId(instanceId) 15 | .withLockDuration(duration) 16 | .withClock(clock) 17 | .build() 18 | return new BlockingReactorSherlockWrapper(reactorLocks) 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /connectors/inmem/rxjava/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.api.apiRxjava) 9 | api(projects.connectors.inmem.inmemSync) 10 | implementation(projects.connectors.inmem.inmemCommon) 11 | integrationTestImplementation(projects.tests) 12 | } 13 | -------------------------------------------------------------------------------- /connectors/inmem/rxjava/src/integrationTest/groovy/com/coditory/sherlock/inmem/rxjava/InMemoryDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.inmem.rxjava 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.InfiniteAcquireLockSpec 6 | import com.coditory.sherlock.ReleaseLockSpec 7 | 8 | class ReactiveInMemoryReleaseLockSpec extends ReleaseLockSpec 9 | implements UsesReactiveInMemorySherlock {} 10 | 11 | class ReactiveInMemoryAcquireLockSpec extends AcquireLockSpec 12 | implements UsesReactiveInMemorySherlock {} 13 | 14 | class ReactiveInMemoryAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 15 | implements UsesReactiveInMemorySherlock {} 16 | 17 | class ReactiveInMemoryInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 18 | implements UsesReactiveInMemorySherlock {} 19 | 20 | -------------------------------------------------------------------------------- /connectors/inmem/rxjava/src/integrationTest/groovy/com/coditory/sherlock/inmem/rxjava/UsesReactiveInMemorySherlock.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.inmem.rxjava 2 | 3 | import com.coditory.sherlock.BlockingRxSherlockWrapper 4 | import com.coditory.sherlock.Sherlock 5 | import com.coditory.sherlock.base.DistributedLocksCreator 6 | 7 | import java.time.Clock 8 | import java.time.Duration 9 | 10 | trait UsesReactiveInMemorySherlock implements DistributedLocksCreator { 11 | @Override 12 | Sherlock createSherlock(String instanceId, Duration duration, Clock clock, String collectionName) { 13 | com.coditory.sherlock.rxjava.Sherlock reactiveLocks = InMemorySherlock.builder() 14 | .withOwnerId(instanceId) 15 | .withLockDuration(duration) 16 | .withClock(clock) 17 | .build() 18 | return new BlockingRxSherlockWrapper(reactiveLocks) 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /connectors/inmem/sync/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.api.apiSync) 9 | implementation(projects.connectors.inmem.inmemCommon) 10 | integrationTestImplementation(projects.tests) 11 | } 12 | -------------------------------------------------------------------------------- /connectors/inmem/sync/src/integrationTest/groovy/com/coditory/sherlock/inmem/InMemoryDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.inmem 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.InfiniteAcquireLockSpec 6 | import com.coditory.sherlock.ReleaseLockSpec 7 | 8 | class InMemoryReleaseLockSpec extends ReleaseLockSpec 9 | implements UsesInMemorySherlock {} 10 | 11 | class InMemoryAcquireLockSpec extends AcquireLockSpec 12 | implements UsesInMemorySherlock {} 13 | 14 | class InMemoryAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 15 | implements UsesInMemorySherlock {} 16 | 17 | class InMemoryInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 18 | implements UsesInMemorySherlock {} 19 | -------------------------------------------------------------------------------- /connectors/inmem/sync/src/integrationTest/groovy/com/coditory/sherlock/inmem/UsesInMemorySherlock.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.inmem 2 | 3 | import com.coditory.sherlock.Sherlock 4 | import com.coditory.sherlock.base.DistributedLocksCreator 5 | 6 | import java.time.Clock 7 | import java.time.Duration 8 | 9 | import static InMemorySherlock.builder 10 | 11 | trait UsesInMemorySherlock implements DistributedLocksCreator { 12 | @Override 13 | Sherlock createSherlock(String instanceId, Duration duration, Clock clock, String collectionName) { 14 | return builder() 15 | .withOwnerId(instanceId) 16 | .withLockDuration(duration) 17 | .withClock(clock) 18 | .build() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /connectors/mongo/common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | // This module is NOT a public API - imported as implementation 2 | 3 | plugins { 4 | id("build.java") 5 | id("build.publish") 6 | id("build.coverage") 7 | } 8 | 9 | dependencies { 10 | api(projects.common) 11 | api(libs.mongodb.core) 12 | } 13 | -------------------------------------------------------------------------------- /connectors/mongo/coroutines/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.kotlin") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.api.apiCoroutines) 9 | api(projects.api.apiCoroutinesConnector) 10 | api(libs.mongodb.coroutine) 11 | implementation(projects.connectors.mongo.mongoCommon) 12 | integrationTestImplementation(projects.connectors.mongo.mongoTests) 13 | } 14 | -------------------------------------------------------------------------------- /connectors/mongo/coroutines/src/integrationTest/groovy/com/coditory/sherlock/mongo/coroutines/MongoClientHolder.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo.coroutines 2 | 3 | import com.coditory.sherlock.mongo.MongoHolder 4 | import com.mongodb.kotlin.client.coroutine.MongoClient 5 | import groovy.transform.CompileStatic 6 | 7 | @CompileStatic 8 | class MongoClientHolder { 9 | private static MongoClient mongoClient 10 | 11 | synchronized static MongoClient getClient() { 12 | if (mongoClient != null) return mongoClient 13 | MongoHolder.startDb() 14 | String url = MongoHolder.getConnectionString() 15 | mongoClient = MongoClient.@Factory.create(url) 16 | return mongoClient 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /connectors/mongo/coroutines/src/integrationTest/groovy/com/coditory/sherlock/mongo/coroutines/MongoDbSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo.coroutines 2 | 3 | import com.coditory.sherlock.mongo.MongoIndexCreationSpec 4 | import com.coditory.sherlock.mongo.MongoLockStorageSpec 5 | 6 | class KtMongoIndexCreationSpec extends MongoIndexCreationSpec 7 | implements UsesKtMongoSherlock {} 8 | 9 | class KtMongoLockStorageSpec extends MongoLockStorageSpec 10 | implements UsesKtMongoSherlock {} 11 | -------------------------------------------------------------------------------- /connectors/mongo/coroutines/src/integrationTest/groovy/com/coditory/sherlock/mongo/coroutines/MongoDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo.coroutines 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.HandleDbFailureSpec 6 | import com.coditory.sherlock.InfiniteAcquireLockSpec 7 | import com.coditory.sherlock.ReleaseLockSpec 8 | 9 | class KtMongoReleaseLockSpec extends ReleaseLockSpec 10 | implements UsesKtMongoSherlock {} 11 | 12 | class KtMongoAcquireLockSpec extends AcquireLockSpec 13 | implements UsesKtMongoSherlock {} 14 | 15 | class KtMongoAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 16 | implements UsesKtMongoSherlock {} 17 | 18 | class KtMongoInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 19 | implements UsesKtMongoSherlock {} 20 | 21 | class KtMongoHandleDbFailureSpec extends HandleDbFailureSpec 22 | implements UsesKtMongoSherlock {} 23 | -------------------------------------------------------------------------------- /connectors/mongo/coroutines/src/integrationTest/groovy/com/coditory/sherlock/mongo/coroutines/UsesKtMongoSherlock.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo.coroutines 2 | 3 | import com.coditory.sherlock.BlockingKtSherlockWrapper 4 | import com.coditory.sherlock.Sherlock 5 | import com.coditory.sherlock.base.DatabaseManager 6 | import com.coditory.sherlock.base.DistributedLocksCreator 7 | import com.coditory.sherlock.mongo.MongoHolder 8 | import com.mongodb.kotlin.client.coroutine.MongoCollection 9 | import org.bson.Document 10 | 11 | import java.time.Clock 12 | import java.time.Duration 13 | 14 | trait UsesKtMongoSherlock implements DistributedLocksCreator, DatabaseManager { 15 | @Override 16 | Sherlock createSherlock(String instanceId, Duration duration, Clock clock, String collectionName) { 17 | MongoCollection collection = MongoOperations.INSTANCE.getLocksCollection( 18 | MongoClientHolder.getClient(), MongoHolder.databaseName, collectionName) 19 | com.coditory.sherlock.coroutines.Sherlock coroutinesLocks = MongoSherlock.builder() 20 | .withLocksCollection(collection) 21 | .withOwnerId(instanceId) 22 | .withLockDuration(duration) 23 | .withClock(clock) 24 | .build() 25 | return new BlockingKtSherlockWrapper(coroutinesLocks) 26 | } 27 | 28 | @Override 29 | void stopDatabase() { 30 | MongoHolder.stopDb() 31 | } 32 | 33 | @Override 34 | void startDatabase() { 35 | MongoHolder.startDb() 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /connectors/mongo/coroutines/src/main/kotlin/com/coditory/sherlock/mongo/coroutines/MongoCollectionInitializer.kt: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo.coroutines 2 | 3 | import com.coditory.sherlock.mongo.MongoDistributedLock.INDEX 4 | import com.coditory.sherlock.mongo.MongoDistributedLock.INDEX_OPTIONS 5 | import com.mongodb.kotlin.client.coroutine.MongoCollection 6 | import org.bson.Document 7 | import java.util.concurrent.atomic.AtomicBoolean 8 | 9 | internal class MongoCollectionInitializer( 10 | private val collection: MongoCollection, 11 | ) { 12 | private val indexesCreated = AtomicBoolean(false) 13 | 14 | suspend fun getInitializedCollection(): MongoCollection { 15 | val shouldCreateIndexes = indexesCreated.compareAndSet(false, true) 16 | if (!shouldCreateIndexes) { 17 | return collection 18 | } 19 | collection.createIndex(INDEX, INDEX_OPTIONS) 20 | return collection 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /connectors/mongo/coroutines/src/main/kotlin/com/coditory/sherlock/mongo/coroutines/MongoOperations.kt: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo.coroutines 2 | 3 | import com.mongodb.kotlin.client.coroutine.MongoClient 4 | import com.mongodb.kotlin.client.coroutine.MongoCollection 5 | import org.bson.Document 6 | 7 | // Used for tests, as some kotlin specific constructs don't work with groovy. 8 | internal object MongoOperations { 9 | @JvmStatic 10 | fun getLocksCollection( 11 | mongoClient: MongoClient, 12 | databaseName: String, 13 | collectionName: String, 14 | ): MongoCollection { 15 | return mongoClient 16 | .getDatabase(databaseName) 17 | .getCollection(collectionName) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /connectors/mongo/reactor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.api.apiReactor) 9 | api(libs.mongodb.reactivestreams) 10 | implementation(projects.connectors.mongo.mongoCommon) 11 | integrationTestImplementation(projects.connectors.mongo.mongoTests) 12 | } 13 | -------------------------------------------------------------------------------- /connectors/mongo/reactor/src/integrationTest/groovy/com/coditory/sherlock/mongo/reactor/MongoClientHolder.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo.reactor 2 | 3 | import com.coditory.sherlock.mongo.MongoHolder 4 | import com.mongodb.reactivestreams.client.MongoClient 5 | import com.mongodb.reactivestreams.client.MongoClients 6 | import groovy.transform.CompileStatic 7 | 8 | @CompileStatic 9 | class MongoClientHolder { 10 | private static MongoClient mongoClient 11 | 12 | synchronized static MongoClient getClient() { 13 | if (mongoClient != null) return mongoClient 14 | MongoHolder.startDb() 15 | String url = MongoHolder.getConnectionString() 16 | mongoClient = MongoClients.create(url) 17 | return mongoClient 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /connectors/mongo/reactor/src/integrationTest/groovy/com/coditory/sherlock/mongo/reactor/MongoDbSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo.reactor 2 | 3 | import com.coditory.sherlock.mongo.MongoIndexCreationSpec 4 | import com.coditory.sherlock.mongo.MongoLockStorageSpec 5 | 6 | class ReactorMongoIndexCreationSpec extends MongoIndexCreationSpec 7 | implements UsesReactorMongoSherlock {} 8 | 9 | class ReactorMongoLockStorageSpec extends MongoLockStorageSpec 10 | implements UsesReactorMongoSherlock {} 11 | -------------------------------------------------------------------------------- /connectors/mongo/reactor/src/integrationTest/groovy/com/coditory/sherlock/mongo/reactor/MongoDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo.reactor 2 | 3 | 4 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 5 | import com.coditory.sherlock.AcquireLockSpec 6 | import com.coditory.sherlock.HandleDbFailureSpec 7 | import com.coditory.sherlock.InfiniteAcquireLockSpec 8 | import com.coditory.sherlock.ReleaseLockSpec 9 | 10 | class ReactorMongoReleaseLockSpec extends ReleaseLockSpec 11 | implements UsesReactorMongoSherlock {} 12 | 13 | class ReactorMongoAcquireLockSpec extends AcquireLockSpec 14 | implements UsesReactorMongoSherlock {} 15 | 16 | class ReactorMongoAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 17 | implements UsesReactorMongoSherlock {} 18 | 19 | class ReactorMongoInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 20 | implements UsesReactorMongoSherlock {} 21 | 22 | class ReactorMongoHandleDbFailureSpec extends HandleDbFailureSpec 23 | implements UsesReactorMongoSherlock {} 24 | -------------------------------------------------------------------------------- /connectors/mongo/reactor/src/integrationTest/groovy/com/coditory/sherlock/mongo/reactor/UsesReactorMongoSherlock.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo.reactor 2 | 3 | import com.coditory.sherlock.BlockingReactorSherlockWrapper 4 | import com.coditory.sherlock.Sherlock 5 | import com.coditory.sherlock.base.DatabaseManager 6 | import com.coditory.sherlock.base.DistributedLocksCreator 7 | import com.coditory.sherlock.mongo.MongoHolder 8 | import com.mongodb.reactivestreams.client.MongoCollection 9 | import org.bson.Document 10 | 11 | import java.time.Clock 12 | import java.time.Duration 13 | 14 | import static com.coditory.sherlock.mongo.MongoHolder.databaseName 15 | 16 | trait UsesReactorMongoSherlock implements DistributedLocksCreator, DatabaseManager { 17 | @Override 18 | Sherlock createSherlock(String ownerId, Duration duration, Clock clock, String collectionName) { 19 | com.coditory.sherlock.reactor.Sherlock reactorLocks = MongoSherlock.builder() 20 | .withLocksCollection(getLocksCollection(collectionName)) 21 | .withOwnerId(ownerId) 22 | .withLockDuration(duration) 23 | .withClock(clock) 24 | .build() 25 | return new BlockingReactorSherlockWrapper(reactorLocks) 26 | } 27 | 28 | MongoCollection getLocksCollection(String collectionName) { 29 | return MongoClientHolder.getClient() 30 | .getDatabase(databaseName) 31 | .getCollection(collectionName) 32 | } 33 | 34 | @Override 35 | void stopDatabase() { 36 | MongoHolder.stopDb() 37 | } 38 | 39 | @Override 40 | void startDatabase() { 41 | MongoHolder.startDb() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /connectors/mongo/reactor/src/main/java/com/coditory/sherlock/mongo/reactor/MongoCollectionInitializer.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo.reactor; 2 | 3 | import com.mongodb.reactivestreams.client.MongoCollection; 4 | import org.bson.Document; 5 | import reactor.core.publisher.Mono; 6 | 7 | import java.util.concurrent.atomic.AtomicBoolean; 8 | 9 | import static com.coditory.sherlock.Preconditions.expectNonNull; 10 | import static com.coditory.sherlock.mongo.MongoDistributedLock.INDEX; 11 | import static com.coditory.sherlock.mongo.MongoDistributedLock.INDEX_OPTIONS; 12 | 13 | class MongoCollectionInitializer { 14 | private final MongoCollection collection; 15 | private final AtomicBoolean indexesCreated = new AtomicBoolean(false); 16 | 17 | MongoCollectionInitializer(MongoCollection collection) { 18 | expectNonNull(collection, "collection"); 19 | validateConnection(collection); 20 | this.collection = collection; 21 | } 22 | 23 | Mono> getInitializedCollection() { 24 | boolean shouldCreateIndexes = indexesCreated.compareAndSet(false, true); 25 | if (!shouldCreateIndexes) { 26 | return Mono.just(collection); 27 | } 28 | return Mono.from(collection.createIndex(INDEX, INDEX_OPTIONS)) 29 | .map(result -> collection); 30 | } 31 | 32 | private void validateConnection(MongoCollection collection) { 33 | String readPreference = collection.getReadPreference().getName(); 34 | if (!"primary".equalsIgnoreCase(readPreference)) { 35 | throw new IllegalArgumentException("Expected Mongo connection with readPreference=primary"); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /connectors/mongo/rxjava/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.api.apiRxjava) 9 | api(libs.mongodb.reactivestreams) 10 | implementation(projects.connectors.mongo.mongoCommon) 11 | integrationTestImplementation(projects.connectors.mongo.mongoTests) 12 | } 13 | -------------------------------------------------------------------------------- /connectors/mongo/rxjava/src/integrationTest/groovy/com/coditory/sherlock/mongo/rxjava/MongoClientHolder.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo.rxjava 2 | 3 | import com.coditory.sherlock.mongo.MongoHolder 4 | import com.mongodb.reactivestreams.client.MongoClient 5 | import com.mongodb.reactivestreams.client.MongoClients 6 | import groovy.transform.CompileStatic 7 | 8 | @CompileStatic 9 | class MongoClientHolder { 10 | private static MongoClient mongoClient 11 | 12 | synchronized static MongoClient getClient() { 13 | if (mongoClient != null) return mongoClient 14 | MongoHolder.startDb() 15 | String url = MongoHolder.getConnectionString() 16 | mongoClient = MongoClients.create(url) 17 | return mongoClient 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /connectors/mongo/rxjava/src/integrationTest/groovy/com/coditory/sherlock/mongo/rxjava/MongoDbSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo.rxjava 2 | 3 | import com.coditory.sherlock.mongo.MongoIndexCreationSpec 4 | import com.coditory.sherlock.mongo.MongoLockStorageSpec 5 | 6 | class RxMongoIndexCreationSpec extends MongoIndexCreationSpec 7 | implements UsesRxMongoSherlock {} 8 | 9 | class RxMongoLockStorageSpec extends MongoLockStorageSpec 10 | implements UsesRxMongoSherlock {} 11 | -------------------------------------------------------------------------------- /connectors/mongo/rxjava/src/integrationTest/groovy/com/coditory/sherlock/mongo/rxjava/MongoDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo.rxjava 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.HandleDbFailureSpec 6 | import com.coditory.sherlock.InfiniteAcquireLockSpec 7 | import com.coditory.sherlock.ReleaseLockSpec 8 | 9 | class RxMongoReleaseLockSpec extends ReleaseLockSpec 10 | implements UsesRxMongoSherlock {} 11 | 12 | class RxMongoAcquireLockSpec extends AcquireLockSpec 13 | implements UsesRxMongoSherlock {} 14 | 15 | class RxMongoAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 16 | implements UsesRxMongoSherlock {} 17 | 18 | class RxMongoInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 19 | implements UsesRxMongoSherlock {} 20 | 21 | class RxMongoHandleDbFailureSpec extends HandleDbFailureSpec 22 | implements UsesRxMongoSherlock {} 23 | -------------------------------------------------------------------------------- /connectors/mongo/rxjava/src/integrationTest/groovy/com/coditory/sherlock/mongo/rxjava/UsesRxMongoSherlock.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo.rxjava 2 | 3 | import com.coditory.sherlock.BlockingRxSherlockWrapper 4 | import com.coditory.sherlock.Sherlock 5 | import com.coditory.sherlock.base.DatabaseManager 6 | import com.coditory.sherlock.base.DistributedLocksCreator 7 | import com.coditory.sherlock.mongo.MongoHolder 8 | import com.mongodb.reactivestreams.client.MongoCollection 9 | import org.bson.Document 10 | 11 | import java.time.Clock 12 | import java.time.Duration 13 | 14 | trait UsesRxMongoSherlock implements DistributedLocksCreator, DatabaseManager { 15 | @Override 16 | Sherlock createSherlock(String ownerId, Duration duration, Clock clock, String collectionName) { 17 | com.coditory.sherlock.rxjava.Sherlock reactiveLocks = MongoSherlock.builder() 18 | .withLocksCollection(getLocksCollection(collectionName)) 19 | .withOwnerId(ownerId) 20 | .withLockDuration(duration) 21 | .withClock(clock) 22 | .build() 23 | return new BlockingRxSherlockWrapper(reactiveLocks) 24 | } 25 | 26 | MongoCollection getLocksCollection(String collectionName) { 27 | return MongoClientHolder.getClient() 28 | .getDatabase(MongoHolder.databaseName) 29 | .getCollection(collectionName) 30 | } 31 | 32 | @Override 33 | void stopDatabase() { 34 | MongoHolder.stopDb() 35 | } 36 | 37 | @Override 38 | void startDatabase() { 39 | MongoHolder.startDb() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /connectors/mongo/rxjava/src/main/java/com/coditory/sherlock/mongo/rxjava/MongoCollectionInitializer.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo.rxjava; 2 | 3 | import com.mongodb.reactivestreams.client.MongoCollection; 4 | import io.reactivex.rxjava3.core.Single; 5 | import org.bson.Document; 6 | 7 | import java.util.concurrent.atomic.AtomicBoolean; 8 | 9 | import static com.coditory.sherlock.Preconditions.expectNonNull; 10 | import static com.coditory.sherlock.mongo.MongoDistributedLock.INDEX; 11 | import static com.coditory.sherlock.mongo.MongoDistributedLock.INDEX_OPTIONS; 12 | 13 | class MongoCollectionInitializer { 14 | private final MongoCollection collection; 15 | private final AtomicBoolean indexesCreated = new AtomicBoolean(false); 16 | 17 | MongoCollectionInitializer(MongoCollection collection) { 18 | expectNonNull(collection, "collection"); 19 | validateConnection(collection); 20 | this.collection = collection; 21 | } 22 | 23 | Single> getInitializedCollection() { 24 | boolean shouldCreateIndexes = indexesCreated.compareAndSet(false, true); 25 | if (!shouldCreateIndexes) { 26 | return Single.just(collection); 27 | } 28 | return Single.fromPublisher(collection.createIndex(INDEX, INDEX_OPTIONS)) 29 | .map(result -> collection); 30 | } 31 | 32 | private void validateConnection(MongoCollection collection) { 33 | String readPreference = collection.getReadPreference().getName(); 34 | if (!"primary".equalsIgnoreCase(readPreference)) { 35 | throw new IllegalArgumentException("Expected Mongo connection with readPreference=primary"); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /connectors/mongo/sync/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.api.apiSync) 9 | api(libs.mongodb.sync) 10 | implementation(projects.connectors.mongo.mongoCommon) 11 | integrationTestImplementation(projects.connectors.mongo.mongoTests) 12 | } 13 | -------------------------------------------------------------------------------- /connectors/mongo/sync/src/integrationTest/groovy/com/coditory/sherlock/mongo/MongoDbSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo 2 | 3 | class RxMongoIndexCreationSpec extends MongoIndexCreationSpec 4 | implements UsesMongoSherlock {} 5 | 6 | class RxMongoLockStorageSpec extends MongoLockStorageSpec 7 | implements UsesMongoSherlock {} 8 | -------------------------------------------------------------------------------- /connectors/mongo/sync/src/integrationTest/groovy/com/coditory/sherlock/mongo/MongoDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.HandleDbFailureSpec 6 | import com.coditory.sherlock.InfiniteAcquireLockSpec 7 | import com.coditory.sherlock.ReleaseLockSpec 8 | 9 | class MongoReleaseLockSpec extends ReleaseLockSpec 10 | implements UsesMongoSherlock {} 11 | 12 | class MongoAcquireLockSpec extends AcquireLockSpec 13 | implements UsesMongoSherlock {} 14 | 15 | class MongoAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 16 | implements UsesMongoSherlock {} 17 | 18 | class MongoInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 19 | implements UsesMongoSherlock {} 20 | 21 | class MongoHandleDbFailureSpec extends HandleDbFailureSpec 22 | implements UsesMongoSherlock {} 23 | -------------------------------------------------------------------------------- /connectors/mongo/sync/src/integrationTest/groovy/com/coditory/sherlock/mongo/UsesMongoSherlock.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo 2 | 3 | 4 | import com.coditory.sherlock.Sherlock 5 | import com.coditory.sherlock.base.DatabaseManager 6 | import com.coditory.sherlock.base.DistributedLocksCreator 7 | import com.mongodb.client.MongoCollection 8 | import org.bson.Document 9 | 10 | import java.time.Clock 11 | import java.time.Duration 12 | 13 | trait UsesMongoSherlock implements DistributedLocksCreator, DatabaseManager { 14 | @Override 15 | Sherlock createSherlock(String instanceId, Duration duration, Clock clock, String collectionName) { 16 | return MongoSherlock.builder() 17 | .withLocksCollection(getLocksCollection(collectionName)) 18 | .withOwnerId(instanceId) 19 | .withLockDuration(duration) 20 | .withClock(clock) 21 | .build() 22 | } 23 | 24 | MongoCollection getLocksCollection(String collectionName) { 25 | return MongoHolder.getClient() 26 | .getDatabase(MongoHolder.databaseName) 27 | .getCollection(collectionName) 28 | } 29 | 30 | @Override 31 | void stopDatabase() { 32 | MongoHolder.stopDb() 33 | } 34 | 35 | @Override 36 | void startDatabase() { 37 | MongoHolder.startDb() 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /connectors/mongo/sync/src/main/java/com/coditory/sherlock/mongo/MongoCollectionInitializer.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.mongo; 2 | 3 | import com.mongodb.client.MongoCollection; 4 | import org.bson.Document; 5 | 6 | import java.util.concurrent.atomic.AtomicBoolean; 7 | 8 | import static com.coditory.sherlock.Preconditions.expectNonNull; 9 | import static com.coditory.sherlock.mongo.MongoDistributedLock.INDEX; 10 | import static com.coditory.sherlock.mongo.MongoDistributedLock.INDEX_OPTIONS; 11 | 12 | class MongoCollectionInitializer { 13 | private final MongoCollection collection; 14 | private final AtomicBoolean indexesCreated = new AtomicBoolean(false); 15 | 16 | MongoCollectionInitializer(MongoCollection collection) { 17 | expectNonNull(collection, "collection"); 18 | this.collection = collection; 19 | } 20 | 21 | MongoCollection getInitializedCollection() { 22 | boolean shouldCreateIndexes = indexesCreated.compareAndSet(false, true); 23 | if (!shouldCreateIndexes) { 24 | return collection; 25 | } 26 | collection.createIndex(INDEX, INDEX_OPTIONS); 27 | return collection; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /connectors/mongo/tests/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | } 4 | 5 | dependencies { 6 | api(projects.common) 7 | api(projects.tests) 8 | api(projects.connectors.mongo.mongoCommon) 9 | api(libs.mongodb.sync) 10 | api(libs.spock.core) 11 | api(libs.jsonassert) 12 | implementation(libs.testcontainers.mongodb) 13 | } 14 | -------------------------------------------------------------------------------- /connectors/sql/common-api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | implementation(projects.common) 9 | } 10 | -------------------------------------------------------------------------------- /connectors/sql/common-api/src/main/java/com/coditory/sherlock/sql/BindingMapper.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import static com.coditory.sherlock.Preconditions.expect; 6 | import static com.coditory.sherlock.Preconditions.expectNonNull; 7 | 8 | public interface BindingMapper { 9 | BindingMapper ORDERED_QUESTION_MARK = (param) -> new BindingParameterMapping("?", param.getIndex()); 10 | BindingMapper INDEXED_QUESTION_MARK = (param) -> { 11 | int oneBasedIndex = param.getIndex() + 1; 12 | String value = "$" + oneBasedIndex; 13 | return new BindingParameterMapping(value, value); 14 | }; 15 | BindingMapper AT_NAME_MARK = (param) -> { 16 | String queryKey = "@" + param.getName(); 17 | return new BindingParameterMapping(queryKey, param.getName()); 18 | }; 19 | BindingMapper MYSQL_MAPPER = ORDERED_QUESTION_MARK; 20 | BindingMapper POSTGRES_MAPPER = INDEXED_QUESTION_MARK; 21 | BindingMapper H2_MAPPER = INDEXED_QUESTION_MARK; 22 | BindingMapper MSSQL_MAPPER = AT_NAME_MARK; 23 | BindingMapper SPANNER_MAPPER = AT_NAME_MARK; 24 | BindingMapper JDBC_MAPPER = ORDERED_QUESTION_MARK; 25 | 26 | @NotNull 27 | default BindingParameterMapping mapBinding(int index, @NotNull String name) { 28 | expect(index >= 0, "Expected index >= 0. Got: %d", index); 29 | expectNonNull(name, "name"); 30 | return mapBinding(new BindingParameter(index, name)); 31 | } 32 | 33 | @NotNull 34 | BindingParameterMapping mapBinding(@NotNull BindingParameter bindingParameter); 35 | } 36 | -------------------------------------------------------------------------------- /connectors/sql/common-api/src/main/java/com/coditory/sherlock/sql/BindingParameter.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.Objects; 6 | 7 | import static com.coditory.sherlock.Preconditions.expect; 8 | import static com.coditory.sherlock.Preconditions.expectNonNull; 9 | 10 | public final class BindingParameter { 11 | private final int index; 12 | private final String name; 13 | 14 | public BindingParameter(int index, @NotNull String name) { 15 | expect(index >= 0, "Expected index >= 0. Got: %d", index); 16 | expectNonNull(name, "name"); 17 | this.index = index; 18 | this.name = name; 19 | } 20 | 21 | public int getIndex() { 22 | return index; 23 | } 24 | 25 | @NotNull 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | @Override 31 | public boolean equals(Object o) { 32 | if (this == o) return true; 33 | if (o == null || getClass() != o.getClass()) return false; 34 | BindingParameter that = (BindingParameter) o; 35 | return index == that.index && Objects.equals(name, that.name); 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | return Objects.hash(index, name); 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return "BindingParameter{" + 46 | "index=" + index + 47 | ", name='" + name + '\'' + 48 | '}'; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /connectors/sql/common-api/src/main/java/com/coditory/sherlock/sql/BindingParameterMapping.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.Objects; 6 | 7 | import static com.coditory.sherlock.Preconditions.expectNonEmpty; 8 | import static com.coditory.sherlock.Preconditions.expectNonNull; 9 | 10 | public final class BindingParameterMapping { 11 | private final String queryMarker; 12 | private final Object bindingKey; 13 | 14 | public BindingParameterMapping(@NotNull String queryMarker, @NotNull Object bindingKey) { 15 | expectNonEmpty(queryMarker, "queryMarker"); 16 | expectNonNull(bindingKey, "bindingKey"); 17 | this.queryMarker = queryMarker; 18 | this.bindingKey = bindingKey; 19 | } 20 | 21 | @NotNull 22 | public String getQueryMarker() { 23 | return queryMarker; 24 | } 25 | 26 | @NotNull 27 | public Object getBindingKey() { 28 | return bindingKey; 29 | } 30 | 31 | @Override 32 | public boolean equals(Object o) { 33 | if (this == o) return true; 34 | if (o == null || getClass() != o.getClass()) return false; 35 | BindingParameterMapping that = (BindingParameterMapping) o; 36 | return Objects.equals(queryMarker, that.queryMarker) && Objects.equals(bindingKey, that.bindingKey); 37 | } 38 | 39 | @Override 40 | public int hashCode() { 41 | return Objects.hash(queryMarker, bindingKey); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "BindingParameter{" + 47 | "queryMarker='" + queryMarker + '\'' + 48 | ", bindingKey=" + bindingKey + 49 | '}'; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /connectors/sql/common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | // This module is NOT a public API - imported as implementation 2 | 3 | plugins { 4 | id("build.java") 5 | id("build.publish") 6 | id("build.coverage") 7 | } 8 | 9 | dependencies { 10 | api(projects.common) 11 | api(projects.connectors.sql.sqlCommonApi) 12 | } 13 | -------------------------------------------------------------------------------- /connectors/sql/common/src/main/java/com/coditory/sherlock/sql/PrecomputedBindingParameterMapper.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import static com.coditory.sherlock.Preconditions.expectNonNull; 9 | 10 | public final class PrecomputedBindingParameterMapper implements BindingMapper { 11 | @NotNull 12 | public static PrecomputedBindingParameterMapper from(@NotNull BindingMapper mapper) { 13 | expectNonNull(mapper, "mapper"); 14 | Map result = new HashMap<>(); 15 | for (String param : SqlLockNamedQueriesTemplate.ParameterNames.ALL_PARAMS) { 16 | for (int i = 0; i < 10; ++i) { 17 | BindingParameter bindingParameter = new BindingParameter(i, param); 18 | BindingParameterMapping mapping = mapper.mapBinding(bindingParameter); 19 | result.put(bindingParameter, mapping); 20 | } 21 | } 22 | return new PrecomputedBindingParameterMapper(result); 23 | } 24 | 25 | private final Map mapping; 26 | 27 | private PrecomputedBindingParameterMapper(Map mapping) { 28 | this.mapping = mapping; 29 | } 30 | 31 | @Override 32 | @NotNull 33 | public BindingParameterMapping mapBinding(@NotNull BindingParameter bindingParameter) { 34 | BindingParameterMapping result = mapping.get(bindingParameter); 35 | if (result == null) { 36 | throw new IllegalArgumentException("Could not find precomputed binding parameter for: " + bindingParameter); 37 | } 38 | return result; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /connectors/sql/coroutines/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.kotlin") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.api.apiCoroutines) 9 | api(libs.r2dbc.spi) 10 | api(projects.connectors.sql.sqlCommonApi) 11 | api(projects.api.apiCoroutinesConnector) 12 | implementation(projects.connectors.sql.sqlCommon) 13 | implementation(libs.kotlinx.coroutines.core) 14 | implementation(libs.kotlinx.coroutines.reactive) 15 | integrationTestImplementation(projects.connectors.sql.sqlTests) 16 | integrationTestImplementation(libs.r2dbc.pool) 17 | // integration: postgres 18 | integrationTestImplementation(libs.r2dbc.postgresql) 19 | integrationTestImplementation(libs.testcontainers.postgresql) 20 | // integration: mysql 21 | integrationTestImplementation(libs.r2dbc.mysql) 22 | integrationTestImplementation(libs.testcontainers.mysql) 23 | } 24 | -------------------------------------------------------------------------------- /connectors/sql/coroutines/src/integrationTest/groovy/com/coditory/sherlock/sql/coroutines/MySqlConnectionPoolHolder.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.coroutines 2 | 3 | import com.coditory.sherlock.sql.MySqlHolder 4 | import groovy.transform.CompileStatic 5 | import io.r2dbc.spi.ConnectionFactories 6 | import io.r2dbc.spi.ConnectionFactory 7 | import io.r2dbc.spi.ConnectionFactoryOptions 8 | import io.r2dbc.spi.Option 9 | 10 | @CompileStatic 11 | class MySqlConnectionPoolHolder { 12 | private static ConnectionFactory connectionFactory = null 13 | 14 | synchronized static ConnectionFactory getConnectionFactory() { 15 | MySqlHolder.startDb() 16 | if (connectionFactory != null) { 17 | return connectionFactory 18 | } 19 | ConnectionFactoryOptions options = ConnectionFactoryOptions 20 | .parse(MySqlHolder.getJdbcUrl().replace("jdbc:", "r2dbc:")) 21 | .mutate() 22 | .option(Option.valueOf("connectionTimeZone"), "UTC") 23 | .option(ConnectionFactoryOptions.USER, MySqlHolder.getUsername()) 24 | .option(ConnectionFactoryOptions.PASSWORD, MySqlHolder.getPassword()) 25 | .option(ConnectionFactoryOptions.DATABASE, MySqlHolder.getDatabaseName()) 26 | .build() 27 | connectionFactory = ConnectionFactories.get(options) 28 | return connectionFactory 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /connectors/sql/coroutines/src/integrationTest/groovy/com/coditory/sherlock/sql/coroutines/MySqlDbSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.coroutines 2 | 3 | import com.coditory.sherlock.sql.SqlIndexCreationSpec 4 | import com.coditory.sherlock.sql.SqlLockStorageSpec 5 | 6 | class KtMySqlLockStorageSpec extends SqlLockStorageSpec 7 | implements UsesKtSqlSherlock, MySqlConnectionProvider {} 8 | 9 | class KtMySqlIndexCreationSpec extends SqlIndexCreationSpec 10 | implements UsesKtSqlSherlock, MySqlConnectionProvider { 11 | List expectedIndexNames = ["PRIMARY", tableName + "_IDX"].sort() 12 | } 13 | -------------------------------------------------------------------------------- /connectors/sql/coroutines/src/integrationTest/groovy/com/coditory/sherlock/sql/coroutines/MySqlDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.coroutines 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.HandleDbFailureSpec 6 | import com.coditory.sherlock.InfiniteAcquireLockSpec 7 | import com.coditory.sherlock.ReleaseLockSpec 8 | 9 | class MySqlReleaseLockSpec extends ReleaseLockSpec 10 | implements UsesKtSqlSherlock, MySqlConnectionProvider {} 11 | 12 | class MySqlAcquireLockSpec extends AcquireLockSpec 13 | implements UsesKtSqlSherlock, MySqlConnectionProvider {} 14 | 15 | class MySqlAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 16 | implements UsesKtSqlSherlock, MySqlConnectionProvider {} 17 | 18 | class MySqlInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 19 | implements UsesKtSqlSherlock, MySqlConnectionProvider {} 20 | 21 | class MySqlHandleDbFailureSpec extends HandleDbFailureSpec 22 | implements UsesKtSqlSherlock, MySqlConnectionProvider {} 23 | -------------------------------------------------------------------------------- /connectors/sql/coroutines/src/integrationTest/groovy/com/coditory/sherlock/sql/coroutines/PostgresDbSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.coroutines 2 | 3 | import com.coditory.sherlock.sql.SqlIndexCreationSpec 4 | import com.coditory.sherlock.sql.SqlLockStorageSpec 5 | 6 | class KtPostgresLockStorageSpec extends SqlLockStorageSpec 7 | implements UsesKtSqlSherlock, PostgresConnectionProvider {} 8 | 9 | class KtPostgresIndexCreationSpec extends SqlIndexCreationSpec 10 | implements UsesKtSqlSherlock, PostgresConnectionProvider { 11 | List expectedIndexNames = [tableName + "_pkey", tableName + "_idx"].sort() 12 | } 13 | -------------------------------------------------------------------------------- /connectors/sql/coroutines/src/integrationTest/groovy/com/coditory/sherlock/sql/coroutines/PostgresDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.coroutines 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.HandleDbFailureSpec 6 | import com.coditory.sherlock.InfiniteAcquireLockSpec 7 | import com.coditory.sherlock.ReleaseLockSpec 8 | 9 | class PostgresReleaseLockSpec extends ReleaseLockSpec 10 | implements UsesKtSqlSherlock, PostgresConnectionProvider {} 11 | 12 | class PostgresAcquireLockSpec extends AcquireLockSpec 13 | implements UsesKtSqlSherlock, PostgresConnectionProvider {} 14 | 15 | class PostgresAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 16 | implements UsesKtSqlSherlock, PostgresConnectionProvider {} 17 | 18 | class PostgresInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 19 | implements UsesKtSqlSherlock, PostgresConnectionProvider {} 20 | 21 | class PostgresHandleDbFailureSpec extends HandleDbFailureSpec 22 | implements UsesKtSqlSherlock, PostgresConnectionProvider {} 23 | -------------------------------------------------------------------------------- /connectors/sql/coroutines/src/integrationTest/groovy/com/coditory/sherlock/sql/coroutines/UsesKtSqlSherlock.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.coroutines 2 | 3 | import com.coditory.sherlock.BlockingKtSherlockWrapper 4 | import com.coditory.sherlock.Sherlock 5 | import com.coditory.sherlock.base.DistributedLocksCreator 6 | import com.coditory.sherlock.sql.BindingMapper 7 | import io.r2dbc.spi.ConnectionFactory 8 | 9 | import java.sql.Connection 10 | import java.time.Clock 11 | import java.time.Duration 12 | 13 | trait UsesKtSqlSherlock implements DistributedLocksCreator { 14 | abstract ConnectionFactory getConnectionFactory() 15 | 16 | abstract Connection getBlockingConnection() 17 | 18 | abstract BindingMapper getBindingMapper() 19 | 20 | @Override 21 | Sherlock createSherlock(String instanceId, Duration duration, Clock clock, String tableName) { 22 | com.coditory.sherlock.coroutines.Sherlock coroutineSqlSherlock = SqlSherlock.builder() 23 | .withConnectionFactory(connectionFactory) 24 | .withBindingMapper(bindingMapper) 25 | .withLocksTable(tableName) 26 | .withOwnerId(instanceId) 27 | .withLockDuration(duration) 28 | .withClock(clock) 29 | .build() 30 | return new BlockingKtSherlockWrapper(coroutineSqlSherlock) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /connectors/sql/coroutines/src/main/kotlin/com/coditory/sherlock/sql/coroutines/ConnectionExt.kt: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.coroutines 2 | 3 | import io.r2dbc.spi.Connection 4 | import kotlinx.coroutines.reactive.awaitFirstOrNull 5 | 6 | suspend inline fun Connection.use(block: (Connection) -> R): R { 7 | var exception: Throwable? = null 8 | try { 9 | return block(this) 10 | } catch (e: Throwable) { 11 | exception = e 12 | throw e 13 | } finally { 14 | when (exception) { 15 | null -> close().awaitFirstOrNull() 16 | 17 | else -> 18 | try { 19 | close().awaitFirstOrNull() 20 | } catch (closeException: Throwable) { 21 | // cause.addSuppressed(closeException) // ignored here 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /connectors/sql/reactor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.api.apiReactor) 9 | api(libs.r2dbc.spi) 10 | api(projects.connectors.sql.sqlCommonApi) 11 | implementation(projects.connectors.sql.sqlCommon) 12 | integrationTestImplementation(projects.connectors.sql.sqlTests) 13 | integrationTestImplementation(libs.r2dbc.pool) 14 | // integration: postgres 15 | integrationTestImplementation(libs.r2dbc.postgresql) 16 | integrationTestImplementation(libs.testcontainers.postgresql) 17 | // integration: mysql 18 | integrationTestImplementation(libs.r2dbc.mysql) 19 | integrationTestImplementation(libs.testcontainers.mysql) 20 | } 21 | -------------------------------------------------------------------------------- /connectors/sql/reactor/src/integrationTest/groovy/com/coditory/sherlock/sql/reactor/MySqlConnectionPoolHolder.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.reactor 2 | 3 | import com.coditory.sherlock.sql.MySqlHolder 4 | import groovy.transform.CompileStatic 5 | import io.r2dbc.spi.ConnectionFactories 6 | import io.r2dbc.spi.ConnectionFactory 7 | import io.r2dbc.spi.ConnectionFactoryOptions 8 | import io.r2dbc.spi.Option 9 | 10 | @CompileStatic 11 | class MySqlConnectionPoolHolder { 12 | private static ConnectionFactory connectionFactory = null 13 | 14 | synchronized static ConnectionFactory getConnectionFactory() { 15 | MySqlHolder.startDb() 16 | if (connectionFactory != null) { 17 | return connectionFactory 18 | } 19 | ConnectionFactoryOptions options = ConnectionFactoryOptions 20 | .parse(MySqlHolder.getJdbcUrl().replace("jdbc:", "r2dbc:")) 21 | .mutate() 22 | .option(Option.valueOf("connectionTimeZone"), "UTC") 23 | .option(ConnectionFactoryOptions.USER, MySqlHolder.getUsername()) 24 | .option(ConnectionFactoryOptions.PASSWORD, MySqlHolder.getPassword()) 25 | .option(ConnectionFactoryOptions.DATABASE, MySqlHolder.getDatabaseName()) 26 | .build() 27 | connectionFactory = ConnectionFactories.get(options) 28 | return connectionFactory 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /connectors/sql/reactor/src/integrationTest/groovy/com/coditory/sherlock/sql/reactor/MySqlDbSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.reactor 2 | 3 | import com.coditory.sherlock.sql.SqlIndexCreationSpec 4 | import com.coditory.sherlock.sql.SqlLockStorageSpec 5 | 6 | class ReactorMySqlLockStorageSpec extends SqlLockStorageSpec 7 | implements UsesReactorSqlSherlock, MySqlConnectionProvider {} 8 | 9 | class ReactorMySqlIndexCreationSpec extends SqlIndexCreationSpec 10 | implements UsesReactorSqlSherlock, MySqlConnectionProvider { 11 | List expectedIndexNames = ["PRIMARY", tableName + "_IDX"].sort() 12 | } 13 | -------------------------------------------------------------------------------- /connectors/sql/reactor/src/integrationTest/groovy/com/coditory/sherlock/sql/reactor/MySqlDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.reactor 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.HandleDbFailureSpec 6 | import com.coditory.sherlock.InfiniteAcquireLockSpec 7 | import com.coditory.sherlock.ReleaseLockSpec 8 | 9 | class ReactorMySqlReleaseLockSpec extends ReleaseLockSpec 10 | implements UsesReactorSqlSherlock, MySqlConnectionProvider {} 11 | 12 | class ReactorMySqlAcquireLockSpec extends AcquireLockSpec 13 | implements UsesReactorSqlSherlock, MySqlConnectionProvider {} 14 | 15 | class ReactorMySqlAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 16 | implements UsesReactorSqlSherlock, MySqlConnectionProvider {} 17 | 18 | class ReactorMySqlInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 19 | implements UsesReactorSqlSherlock, MySqlConnectionProvider {} 20 | 21 | class ReactorMySqlHandleDbFailureSpec extends HandleDbFailureSpec 22 | implements UsesReactorSqlSherlock, MySqlConnectionProvider {} 23 | -------------------------------------------------------------------------------- /connectors/sql/reactor/src/integrationTest/groovy/com/coditory/sherlock/sql/reactor/PostgresDbSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.reactor 2 | 3 | import com.coditory.sherlock.sql.SqlIndexCreationSpec 4 | import com.coditory.sherlock.sql.SqlLockStorageSpec 5 | 6 | class ReactorPostgresLockStorageSpec extends SqlLockStorageSpec 7 | implements UsesReactorSqlSherlock, PostgresConnectionProvider {} 8 | 9 | class ReactorPostgresIndexCreationSpec extends SqlIndexCreationSpec 10 | implements UsesReactorSqlSherlock, PostgresConnectionProvider { 11 | List expectedIndexNames = [tableName + "_pkey", tableName + "_idx"].sort() 12 | } 13 | -------------------------------------------------------------------------------- /connectors/sql/reactor/src/integrationTest/groovy/com/coditory/sherlock/sql/reactor/PostgresDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.reactor 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.HandleDbFailureSpec 6 | import com.coditory.sherlock.InfiniteAcquireLockSpec 7 | import com.coditory.sherlock.ReleaseLockSpec 8 | 9 | class ReactorPostgresReleaseLockSpec extends ReleaseLockSpec 10 | implements UsesReactorSqlSherlock, PostgresConnectionProvider {} 11 | 12 | class ReactorPostgresAcquireLockSpec extends AcquireLockSpec 13 | implements UsesReactorSqlSherlock, PostgresConnectionProvider {} 14 | 15 | class ReactorPostgresAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 16 | implements UsesReactorSqlSherlock, PostgresConnectionProvider {} 17 | 18 | class ReactorPostgresInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 19 | implements UsesReactorSqlSherlock, PostgresConnectionProvider {} 20 | 21 | class ReactorPostgresHandleDbFailureSpec extends HandleDbFailureSpec 22 | implements UsesReactorSqlSherlock, PostgresConnectionProvider {} 23 | -------------------------------------------------------------------------------- /connectors/sql/reactor/src/integrationTest/groovy/com/coditory/sherlock/sql/reactor/UsesReactorSqlSherlock.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.reactor 2 | 3 | import com.coditory.sherlock.BlockingReactorSherlockWrapper 4 | import com.coditory.sherlock.Sherlock 5 | import com.coditory.sherlock.base.DistributedLocksCreator 6 | import com.coditory.sherlock.sql.BindingMapper 7 | import io.r2dbc.spi.ConnectionFactory 8 | 9 | import java.sql.Connection 10 | import java.time.Clock 11 | import java.time.Duration 12 | 13 | trait UsesReactorSqlSherlock implements DistributedLocksCreator { 14 | abstract ConnectionFactory getConnectionFactory() 15 | 16 | abstract Connection getBlockingConnection() 17 | 18 | abstract BindingMapper getBindingMapper() 19 | 20 | @Override 21 | Sherlock createSherlock(String instanceId, Duration duration, Clock clock, String tableName) { 22 | com.coditory.sherlock.reactor.Sherlock reactorLocks = SqlSherlock.builder() 23 | .withConnectionFactory(connectionFactory) 24 | .withLocksTable(tableName) 25 | .withBindingMapper(bindingMapper) 26 | .withOwnerId(instanceId) 27 | .withLockDuration(duration) 28 | .withClock(clock) 29 | .build() 30 | return new BlockingReactorSherlockWrapper(reactorLocks) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /connectors/sql/rxjava/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.api.apiRxjava) 9 | api(libs.r2dbc.spi) 10 | api(projects.connectors.sql.sqlCommonApi) 11 | implementation(projects.connectors.sql.sqlCommon) 12 | integrationTestImplementation(projects.connectors.sql.sqlTests) 13 | integrationTestImplementation(libs.r2dbc.pool) 14 | // integration: postgres 15 | integrationTestImplementation(libs.r2dbc.postgresql) 16 | integrationTestImplementation(libs.testcontainers.postgresql) 17 | // integration: mysql 18 | integrationTestImplementation(libs.r2dbc.mysql) 19 | integrationTestImplementation(libs.testcontainers.mysql) 20 | } 21 | -------------------------------------------------------------------------------- /connectors/sql/rxjava/src/integrationTest/groovy/com/coditory/sherlock/sql/rxjava/MySqlConnectionPoolHolder.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.rxjava 2 | 3 | import com.coditory.sherlock.sql.MySqlHolder 4 | import groovy.transform.CompileStatic 5 | import io.r2dbc.spi.ConnectionFactories 6 | import io.r2dbc.spi.ConnectionFactory 7 | import io.r2dbc.spi.ConnectionFactoryOptions 8 | import io.r2dbc.spi.Option 9 | 10 | @CompileStatic 11 | class MySqlConnectionPoolHolder { 12 | private static ConnectionFactory connectionFactory = null 13 | 14 | synchronized static ConnectionFactory getConnectionFactory() { 15 | MySqlHolder.startDb() 16 | if (connectionFactory != null) { 17 | return connectionFactory 18 | } 19 | ConnectionFactoryOptions options = ConnectionFactoryOptions 20 | .parse(MySqlHolder.getJdbcUrl().replace("jdbc:", "r2dbc:")) 21 | .mutate() 22 | .option(Option.valueOf("connectionTimeZone"), "UTC") 23 | .option(ConnectionFactoryOptions.USER, MySqlHolder.getUsername()) 24 | .option(ConnectionFactoryOptions.PASSWORD, MySqlHolder.getPassword()) 25 | .option(ConnectionFactoryOptions.DATABASE, MySqlHolder.getDatabaseName()) 26 | .build() 27 | connectionFactory = ConnectionFactories.get(options) 28 | return connectionFactory 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /connectors/sql/rxjava/src/integrationTest/groovy/com/coditory/sherlock/sql/rxjava/MySqlDbSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.rxjava 2 | 3 | import com.coditory.sherlock.sql.SqlIndexCreationSpec 4 | import com.coditory.sherlock.sql.SqlLockStorageSpec 5 | 6 | class RxMySqlLockStorageSpec extends SqlLockStorageSpec 7 | implements UsesRxSqlSherlock, MySqlConnectionProvider {} 8 | 9 | class RxMySqlIndexCreationSpec extends SqlIndexCreationSpec 10 | implements UsesRxSqlSherlock, MySqlConnectionProvider { 11 | List expectedIndexNames = ["PRIMARY", tableName + "_IDX"].sort() 12 | } 13 | -------------------------------------------------------------------------------- /connectors/sql/rxjava/src/integrationTest/groovy/com/coditory/sherlock/sql/rxjava/MySqlDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.rxjava 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.HandleDbFailureSpec 6 | import com.coditory.sherlock.InfiniteAcquireLockSpec 7 | import com.coditory.sherlock.ReleaseLockSpec 8 | 9 | class RxMySqlReleaseLockSpec extends ReleaseLockSpec 10 | implements UsesRxSqlSherlock, MySqlConnectionProvider {} 11 | 12 | class RxMySqlAcquireLockSpec extends AcquireLockSpec 13 | implements UsesRxSqlSherlock, MySqlConnectionProvider {} 14 | 15 | class RxMySqlAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 16 | implements UsesRxSqlSherlock, MySqlConnectionProvider {} 17 | 18 | class RxMySqlInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 19 | implements UsesRxSqlSherlock, MySqlConnectionProvider {} 20 | 21 | class RxMySqlHandleDbFailureSpec extends HandleDbFailureSpec 22 | implements UsesRxSqlSherlock, MySqlConnectionProvider {} 23 | -------------------------------------------------------------------------------- /connectors/sql/rxjava/src/integrationTest/groovy/com/coditory/sherlock/sql/rxjava/PostgresDbSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.rxjava 2 | 3 | import com.coditory.sherlock.sql.SqlIndexCreationSpec 4 | import com.coditory.sherlock.sql.SqlLockStorageSpec 5 | 6 | class RxPostgresLockStorageSpec extends SqlLockStorageSpec 7 | implements UsesRxSqlSherlock, PostgresConnectionProvider {} 8 | 9 | class RxPostgresIndexCreationSpec extends SqlIndexCreationSpec 10 | implements UsesRxSqlSherlock, PostgresConnectionProvider { 11 | List expectedIndexNames = [tableName + "_pkey", tableName + "_idx"].sort() 12 | } 13 | -------------------------------------------------------------------------------- /connectors/sql/rxjava/src/integrationTest/groovy/com/coditory/sherlock/sql/rxjava/PostgresDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.rxjava 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.HandleDbFailureSpec 6 | import com.coditory.sherlock.InfiniteAcquireLockSpec 7 | import com.coditory.sherlock.ReleaseLockSpec 8 | 9 | class RxPostgresReleaseLockSpec extends ReleaseLockSpec 10 | implements UsesRxSqlSherlock, PostgresConnectionProvider {} 11 | 12 | class RxPostgresAcquireLockSpec extends AcquireLockSpec 13 | implements UsesRxSqlSherlock, PostgresConnectionProvider {} 14 | 15 | class RxPostgresAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 16 | implements UsesRxSqlSherlock, PostgresConnectionProvider {} 17 | 18 | class RxPostgresInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 19 | implements UsesRxSqlSherlock, PostgresConnectionProvider {} 20 | 21 | class RxPostgresHandleDbFailureSpec extends HandleDbFailureSpec 22 | implements UsesRxSqlSherlock, PostgresConnectionProvider {} 23 | -------------------------------------------------------------------------------- /connectors/sql/rxjava/src/integrationTest/groovy/com/coditory/sherlock/sql/rxjava/UsesRxSqlSherlock.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql.rxjava 2 | 3 | import com.coditory.sherlock.BlockingRxSherlockWrapper 4 | import com.coditory.sherlock.Sherlock 5 | import com.coditory.sherlock.base.DistributedLocksCreator 6 | import com.coditory.sherlock.sql.BindingMapper 7 | import io.r2dbc.spi.ConnectionFactory 8 | 9 | import java.sql.Connection 10 | import java.time.Clock 11 | import java.time.Duration 12 | 13 | trait UsesRxSqlSherlock implements DistributedLocksCreator { 14 | abstract ConnectionFactory getConnectionFactory() 15 | 16 | abstract Connection getBlockingConnection() 17 | 18 | abstract BindingMapper getBindingMapper() 19 | 20 | @Override 21 | Sherlock createSherlock(String instanceId, Duration duration, Clock clock, String tableName) { 22 | com.coditory.sherlock.rxjava.Sherlock reactorLocks = SqlSherlock.builder() 23 | .withConnectionFactory(connectionFactory) 24 | .withLocksTable(tableName) 25 | .withBindingMapper(bindingMapper) 26 | .withOwnerId(instanceId) 27 | .withLockDuration(duration) 28 | .withClock(clock) 29 | .build() 30 | return new BlockingRxSherlockWrapper(reactorLocks) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /connectors/sql/sync/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | id("build.publish") 4 | id("build.coverage") 5 | } 6 | 7 | dependencies { 8 | api(projects.api.apiSync) 9 | api(projects.connectors.sql.sqlCommonApi) 10 | implementation(projects.connectors.sql.sqlCommon) 11 | integrationTestImplementation(projects.connectors.sql.sqlTests) 12 | } 13 | -------------------------------------------------------------------------------- /connectors/sql/sync/src/integrationTest/groovy/com/coditory/sherlock/sql/MySqlDbSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql 2 | 3 | class MySqlLockStorageSpec extends SqlLockStorageSpec 4 | implements UsesSqlSherlock, MySqlConnectionProvider {} 5 | 6 | class MySqlIndexCreationSpec extends SqlIndexCreationSpec 7 | implements UsesSqlSherlock, MySqlConnectionProvider { 8 | List expectedIndexNames = ["PRIMARY", SqlIndexCreationSpec.tableName + "_IDX"].sort() 9 | } 10 | -------------------------------------------------------------------------------- /connectors/sql/sync/src/integrationTest/groovy/com/coditory/sherlock/sql/MySqlDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.HandleDbFailureSpec 6 | import com.coditory.sherlock.InfiniteAcquireLockSpec 7 | import com.coditory.sherlock.ReleaseLockSpec 8 | 9 | class MySqlReleaseLockSpec extends ReleaseLockSpec 10 | implements UsesSqlSherlock, MySqlConnectionProvider {} 11 | 12 | class MySqlAcquireLockSpec extends AcquireLockSpec 13 | implements UsesSqlSherlock, MySqlConnectionProvider {} 14 | 15 | class MySqlAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 16 | implements UsesSqlSherlock, MySqlConnectionProvider {} 17 | 18 | class MySqlInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 19 | implements UsesSqlSherlock, MySqlConnectionProvider {} 20 | 21 | class MySqlHandleDbFailureSpec extends HandleDbFailureSpec 22 | implements UsesSqlSherlock, MySqlConnectionProvider {} 23 | 24 | class MySqlLockCommitSpec extends SqlLockCommitSpec 25 | implements UsesSqlSherlock, MySqlConnectionProvider {} 26 | -------------------------------------------------------------------------------- /connectors/sql/sync/src/integrationTest/groovy/com/coditory/sherlock/sql/PostgresDbSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql 2 | 3 | class PostgresLockStorageSpec extends SqlLockStorageSpec 4 | implements UsesSqlSherlock, PostgresConnectionProvider {} 5 | 6 | class PostgresIndexCreationSpec extends SqlIndexCreationSpec 7 | implements UsesSqlSherlock, PostgresConnectionProvider { 8 | List expectedIndexNames = [SqlIndexCreationSpec.tableName + "_pkey", SqlIndexCreationSpec.tableName + "_idx"].sort() 9 | } 10 | -------------------------------------------------------------------------------- /connectors/sql/sync/src/integrationTest/groovy/com/coditory/sherlock/sql/PostgresDistributedLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql 2 | 3 | import com.coditory.sherlock.AcquireLockMultipleTimesSpec 4 | import com.coditory.sherlock.AcquireLockSpec 5 | import com.coditory.sherlock.HandleDbFailureSpec 6 | import com.coditory.sherlock.InfiniteAcquireLockSpec 7 | import com.coditory.sherlock.ReleaseLockSpec 8 | 9 | class PostgresReleaseLockSpec extends ReleaseLockSpec 10 | implements UsesSqlSherlock, PostgresConnectionProvider {} 11 | 12 | class PostgresAcquireLockSpec extends AcquireLockSpec 13 | implements UsesSqlSherlock, PostgresConnectionProvider {} 14 | 15 | class PostgresAcquireLockMultipleTimesSpec extends AcquireLockMultipleTimesSpec 16 | implements UsesSqlSherlock, PostgresConnectionProvider {} 17 | 18 | class PostgresInfiniteAcquireLockSpec extends InfiniteAcquireLockSpec 19 | implements UsesSqlSherlock, PostgresConnectionProvider {} 20 | 21 | class PostgresHandleDbFailureSpec extends HandleDbFailureSpec 22 | implements UsesSqlSherlock, PostgresConnectionProvider {} 23 | 24 | class PostgresSqlLockCommitSpec extends SqlLockCommitSpec 25 | implements UsesSqlSherlock, PostgresConnectionProvider {} 26 | -------------------------------------------------------------------------------- /connectors/sql/sync/src/integrationTest/groovy/com/coditory/sherlock/sql/SqlConnectioProvider.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql 2 | 3 | 4 | import com.coditory.sherlock.base.DatabaseManager 5 | import groovy.transform.CompileStatic 6 | 7 | import javax.sql.DataSource 8 | 9 | @CompileStatic 10 | interface SqlConnectionProvider { 11 | DataSource getDataSource(); 12 | 13 | DataSource getDataSource(DataSourceConfigurer configurer); 14 | } 15 | 16 | @CompileStatic 17 | trait PostgresConnectionProvider implements SqlConnectionProvider, DatabaseManager { 18 | @Override 19 | DataSource getDataSource() { 20 | return PostgresHolder.getDataSource() 21 | } 22 | 23 | @Override 24 | DataSource getDataSource(DataSourceConfigurer configurer) { 25 | return PostgresHolder.getDataSource(configurer) 26 | } 27 | 28 | @Override 29 | void stopDatabase() { 30 | PostgresHolder.stopDb() 31 | } 32 | 33 | @Override 34 | void startDatabase() { 35 | PostgresHolder.startDb() 36 | } 37 | } 38 | 39 | @CompileStatic 40 | trait MySqlConnectionProvider implements SqlConnectionProvider, DatabaseManager { 41 | @Override 42 | DataSource getDataSource() { 43 | return MySqlHolder.getDataSource() 44 | } 45 | 46 | @Override 47 | DataSource getDataSource(DataSourceConfigurer configurer) { 48 | return MySqlHolder.getDataSource(configurer) 49 | } 50 | 51 | @Override 52 | void stopDatabase() { 53 | MySqlHolder.stopDb() 54 | } 55 | 56 | @Override 57 | void startDatabase() { 58 | MySqlHolder.startDb() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /connectors/sql/sync/src/integrationTest/groovy/com/coditory/sherlock/sql/UsesSqlSherlock.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql 2 | 3 | 4 | import com.coditory.sherlock.Sherlock 5 | 6 | import javax.sql.DataSource 7 | import java.time.Clock 8 | import java.time.Duration 9 | 10 | trait UsesSqlSherlock implements SqlDistributedLocksCreator { 11 | abstract DataSource getDataSource() 12 | 13 | abstract DataSource getDataSource(DataSourceConfigurer configurer) 14 | 15 | @Override 16 | Sherlock createSherlock(String instanceId, Duration duration, Clock clock, String tableName) { 17 | return SqlSherlock.builder() 18 | .withDataSource(dataSource) 19 | .withLocksTable(tableName) 20 | .withOwnerId(instanceId) 21 | .withLockDuration(duration) 22 | .withClock(clock) 23 | .build() 24 | } 25 | 26 | @Override 27 | Sherlock createSherlock(DataSourceConfigurer configurer) { 28 | return SqlSherlock.builder() 29 | .withDataSource(getDataSource(configurer)) 30 | .build() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /connectors/sql/tests/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | } 4 | 5 | dependencies { 6 | api(projects.common) 7 | api(projects.tests) 8 | api(projects.connectors.sql.sqlCommon) 9 | api(libs.spock.core) 10 | api(libs.hikaricp) 11 | // integration: postgres 12 | api(libs.postgresql) 13 | implementation(libs.testcontainers.postgresql) 14 | // integration: mysql 15 | api(libs.mysql) 16 | implementation(libs.testcontainers.mysql) 17 | } 18 | -------------------------------------------------------------------------------- /connectors/sql/tests/src/main/groovy/com/coditory/sherlock/sql/DataSourceConfigurer.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql 2 | 3 | import com.zaxxer.hikari.HikariConfig 4 | 5 | interface DataSourceConfigurer { 6 | void configure(HikariConfig config) 7 | } 8 | -------------------------------------------------------------------------------- /connectors/sql/tests/src/main/groovy/com/coditory/sherlock/sql/SqlDistributedLocksCreator.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql 2 | 3 | import com.coditory.sherlock.Sherlock 4 | import com.coditory.sherlock.base.DistributedLocksCreator 5 | import groovy.transform.CompileStatic 6 | 7 | @CompileStatic 8 | interface SqlDistributedLocksCreator extends DistributedLocksCreator { 9 | Sherlock createSherlock(DataSourceConfigurer configurer) 10 | } 11 | -------------------------------------------------------------------------------- /connectors/sql/tests/src/main/groovy/com/coditory/sherlock/sql/SqlLockCommitSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql 2 | 3 | import com.coditory.sherlock.DistributedLock 4 | import com.coditory.sherlock.LocksBaseSpec 5 | import com.coditory.sherlock.Sherlock 6 | import spock.lang.Unroll 7 | 8 | abstract class SqlLockCommitSpec extends LocksBaseSpec implements SqlDistributedLocksCreator { 9 | @Unroll 10 | def "should preserve commit lock with dataSource auto-commit=#autoCommit"() { 11 | given: 12 | Sherlock sherlock = createSherlock({ 13 | it.setAutoCommit(autoCommit) 14 | it.setMaximumPoolSize(2) 15 | }) 16 | DistributedLock lock = sherlock.createLock("lock") 17 | when: 18 | boolean firstResult = lock.acquire() 19 | boolean secondResult = lock.acquire() 20 | then: 21 | firstResult == true 22 | secondResult == false 23 | where: 24 | autoCommit << [true, false] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /connectors/sql/tests/src/main/groovy/com/coditory/sherlock/sql/SqlTableIndexes.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.sql 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | import java.sql.Connection 6 | import java.sql.DatabaseMetaData 7 | import java.sql.ResultSet 8 | 9 | @CompileStatic 10 | class SqlTableIndexes { 11 | static List listTableIndexes(Connection connection, String tableName) { 12 | Set indexes = new HashSet<>() 13 | DatabaseMetaData metaData = connection.getMetaData() 14 | List schemaList = getSchemaList(metaData) 15 | for (int i = 0; i < schemaList.size(); i++) { 16 | ResultSet indexValues 17 | try { 18 | indexValues = metaData.getIndexInfo(null, schemaList.get(i), tableName, false, false) 19 | while (indexValues.next()) { 20 | String dbIndexName = indexValues.getString("INDEX_NAME") 21 | if (dbIndexName != null) { 22 | indexes.add(dbIndexName) 23 | } 24 | } 25 | } finally { 26 | if (indexValues != null) indexValues.close() 27 | } 28 | } 29 | return indexes.toList().sort() 30 | } 31 | 32 | static private List getSchemaList(DatabaseMetaData metaData) { 33 | List schemaList = new ArrayList<>() 34 | ResultSet rs 35 | try { 36 | rs = metaData.getSchemas() 37 | while (rs.next()) { 38 | String tableSchema = rs.getString(1) 39 | if (tableSchema != null) { 40 | schemaList.add(tableSchema) 41 | } 42 | } 43 | if (schemaList.isEmpty()) { 44 | schemaList.add(null) 45 | } 46 | } finally { 47 | if (rs) rs.close() 48 | } 49 | return schemaList 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs>=1 2 | mkdocs-minify-plugin>=0.3 3 | mkdocs-material>=9 4 | mkdocs-markdownextradata-plugin>=0.2.5 5 | Pygments>=2.17 6 | pymdown-extensions>=10 -------------------------------------------------------------------------------- /docs/site/about.md: -------------------------------------------------------------------------------- 1 | - [GitHub Repository](https://github.com/coditory/sherlock-distributed-lock/) 2 | - [License](https://github.com/coditory/sherlock-distributed-lock/blob/master/LICENSE) 3 | - [Changelog](https://github.com/coditory/sherlock-distributed-lock/releases) -------------------------------------------------------------------------------- /docs/site/assets/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coditory/sherlock-distributed-lock/01e3ff4628833dd34db146661236f6905a615944/docs/site/assets/img/favicon.png -------------------------------------------------------------------------------- /docs/site/assets/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coditory/sherlock-distributed-lock/01e3ff4628833dd34db146661236f6905a615944/docs/site/assets/img/icon.png -------------------------------------------------------------------------------- /docs/site/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coditory/sherlock-distributed-lock/01e3ff4628833dd34db146661236f6905a615944/docs/site/assets/img/logo.png -------------------------------------------------------------------------------- /docs/site/connectors/index.md: -------------------------------------------------------------------------------- 1 | # Sherlock Connectors 2 | 3 | Sherlock multiple database connectors to fit in your infrastructure. Provided connectors: 4 | 5 | - [Mongo Connector](mongo.md) - Uses [MongoDB](https://www.mongodb.com/) and its JVM drivers to store locks 6 | - [SQL Connector](sql.md) - Uses SQL database (tested on [Postgres](https://www.postgresql.org/) and [MySQL](https://www.mysql.com/)) and JDBC drivers to store locks 7 | - [In-Memory Connector](inmem.md) - Stores locks in memory. Created for local development and testing. 8 | -------------------------------------------------------------------------------- /docs/site/index.md: -------------------------------------------------------------------------------- 1 | # Sherlock Distributed Lock 2 | 3 | [![Build](https://github.com/coditory/sherlock-distributed-lock/actions/workflows/build.yml/badge.svg)](https://github.com/coditory/sherlock-distributed-lock/actions/workflows/build.yml) 4 | [![Coverage](https://codecov.io/gh/coditory/sherlock-distributed-lock/branch/main/graph/badge.svg)](https://codecov.io/gh/coditory/sherlock-distributed-lock) 5 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.coditory.sherlock/sherlock-api/badge.svg)](https://search.maven.org/search?q=com.coditory.sherlock) 6 | [![JavaDoc](https://www.javadoc.io/badge/com.coditory.sherlock/sherlock-api.svg)](http://www.javadoc.io/doc/com.coditory.sherlock/sherlock-api) 7 | 8 |

9 | Sherlock Distributed Lock Logo 10 |
11 | 12 | [Sherlock](https://github.com/coditory/sherlock-distributed-lock) is a distributed locking library for JVM projects. 13 | It exposes both synchronous and reactive APIs (Reactor, RxJava, Kotlin Coroutines) 14 | and uses database [connectors](connectors/index.md) to store locks. 15 | It was created as a simple solution to manage distributed locks among microservices. 16 | 17 | 18 | ## How it works? 19 | 20 | Locks are acquired for a [specific duration](locks.md#lock-duration). 21 | When lock owning instance unexpectedly goes down, 22 | lock is automatically released after expiration. 23 | 24 | ## Quick start 25 | 26 | - [MongoDB](connectors/mongo.md) - using sherlock with MongoDB 27 | - [SQL](connectors/sql.md) - using sherlock with SQL databases 28 | - [In-Memory](connectors/inmem.md) - using in-memory Sherlock for local development or testing 29 | - [Testing](testing.md) - stubbing and mocking Sherlock in unit tests 30 | - [Migrator](migrator.md) - using Sherlock as for database migration 31 | 32 | -------------------------------------------------------------------------------- /docs/site/testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | Sherlock provides stubs and mocks for testing purposes. Try it out: 4 | `SherlockStub`, `ReactorSherlockStub`, `DistributedLockMock` and `ReactorDistributedLockMock`. 5 | 6 | !!! info "Creating own stub and mocks" 7 | Sherlock API consists mostly of interfaces, so it's easy to create stubs and mocks for your own purposes. 8 | 9 | Sample usage in spock tests: 10 | 11 | ```groovy 12 | def "should release a lock after operation"() { 13 | given: "there is a released lock" 14 | DistributedLockMock lock = DistributedLockMock.alwaysReleasedLock() 15 | when: "single instance action is executed" 16 | boolean taskPerformed = singleInstanceAction(lock) 17 | then: "the task was performed" 18 | taskPerformed == true 19 | and: "lock was acquired and released" 20 | lock.wasAcquiredAndReleased == true 21 | } 22 | 23 | def "should not perform single instance action when lock is locked"() { 24 | given: "there is a lock acquired by other instance" 25 | DistributedLockMock lock = DistributedLockMock.alwaysAcquiredLock() 26 | when: "single instance action is executed" 27 | boolean taskPerformed = singleInstanceAction(lock) 28 | then: "action did not perform the task" 29 | taskPerformed == false 30 | and: "action failed acquiring the lock" 31 | lock.wasAcquireRejected == true 32 | and: "action did not release the lock" 33 | lock.wasReleaseInvoked == false 34 | } 35 | ``` 36 | 37 | !!! info "In Memory Connector" 38 | The easiest way to setup Sherlock in tests is to use [In-Memory Connector](connectors/inmem.md). 39 | Use stubs when you need more control over the locking mechanism. 40 | -------------------------------------------------------------------------------- /docs/theme/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block extrahead %} 4 | 5 | 6 | 11 | 12 | {% endblock %} 13 | 14 | {% block header %} 15 | 16 | 20 | 21 | {% include "partials/header.html" %} 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /examples/inmem-coroutines/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.kotlin") 3 | } 4 | 5 | dependencies { 6 | implementation(projects.connectors.inmem.inmemCoroutines) 7 | implementation(libs.logback.classic) 8 | implementation(libs.kotlinx.coroutines.core) 9 | } 10 | -------------------------------------------------------------------------------- /examples/inmem-coroutines/src/main/kotlin/com/coditory/sherlock/samples/inmem/coroutines/InMemKtLockSample.kt: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.inmem.coroutines 2 | 3 | import com.coditory.sherlock.inmem.coroutines.InMemorySherlock 4 | import kotlinx.coroutines.runBlocking 5 | import org.slf4j.Logger 6 | import org.slf4j.LoggerFactory 7 | 8 | object InMemKtLockSample { 9 | private val logger: Logger = LoggerFactory.getLogger(this.javaClass) 10 | 11 | private suspend fun sample() { 12 | val sherlock = InMemorySherlock.create() 13 | val lock = sherlock.createLock("sample-lock") 14 | lock.runLocked { 15 | logger.info("Lock acquired!") 16 | } 17 | } 18 | 19 | @JvmStatic 20 | fun main(args: Array) { 21 | runBlocking { sample() } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/inmem-coroutines/src/main/kotlin/com/coditory/sherlock/samples/inmem/coroutines/InMemKtMigrationSample.kt: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.inmem.coroutines 2 | 3 | import com.coditory.sherlock.coroutines.migrator.SherlockMigrator 4 | import com.coditory.sherlock.inmem.coroutines.InMemorySherlock 5 | import kotlinx.coroutines.runBlocking 6 | import org.slf4j.Logger 7 | import org.slf4j.LoggerFactory 8 | 9 | object InMemKtMigrationSample { 10 | private val logger: Logger = LoggerFactory.getLogger(this.javaClass) 11 | 12 | private suspend fun sample() { 13 | val sherlock = InMemorySherlock.create() 14 | // first commit - all migrations are executed 15 | SherlockMigrator.builder(sherlock) 16 | .addChangeSet("change-set-1") { logger.info("Change-set 1") } 17 | .addChangeSet("change-set-2") { logger.info("Change-set 2") } 18 | .migrate() 19 | // second commit - only new change-set is executed 20 | SherlockMigrator.builder(sherlock) 21 | .addChangeSet("change-set-1") { logger.info("Change-set 1") } 22 | .addChangeSet("change-set-2") { logger.info("Change-set 2") } 23 | .addChangeSet("change-set-3") { logger.info("Change-set 3") } 24 | .migrate() 25 | } 26 | 27 | @JvmStatic 28 | fun main(args: Array) { 29 | runBlocking { sample() } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/inmem-coroutines/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/inmem-reactor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | } 4 | 5 | dependencies { 6 | implementation(projects.connectors.inmem.inmemReactor) 7 | implementation(libs.logback.classic) 8 | } 9 | -------------------------------------------------------------------------------- /examples/inmem-reactor/src/main/java/com/coditory/sherlock/samples/inmem/reactor/InMemReactorLockSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.inmem.reactor; 2 | 3 | import com.coditory.sherlock.inmem.reactor.InMemorySherlock; 4 | import com.coditory.sherlock.reactor.DistributedLock; 5 | import com.coditory.sherlock.reactor.Sherlock; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import reactor.core.publisher.Mono; 9 | 10 | public class InMemReactorLockSample { 11 | private static final Logger logger = LoggerFactory.getLogger(InMemReactorLockSample.class); 12 | 13 | public static void main(String[] args) { 14 | Sherlock sherlock = InMemorySherlock.create(); 15 | DistributedLock lock = sherlock.createLock("sample-lock"); 16 | lock.runLocked(Mono.fromCallable(() -> { 17 | logger.info("Lock acquired!"); 18 | return true; 19 | })).block(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/inmem-reactor/src/main/java/com/coditory/sherlock/samples/inmem/reactor/InMemReactorMigrationSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.inmem.reactor; 2 | 3 | import com.coditory.sherlock.inmem.reactor.InMemorySherlock; 4 | import com.coditory.sherlock.reactor.Sherlock; 5 | import com.coditory.sherlock.reactor.migrator.SherlockMigrator; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import reactor.core.publisher.Mono; 9 | 10 | public class InMemReactorMigrationSample { 11 | private static final Logger logger = LoggerFactory.getLogger(InMemReactorMigrationSample.class); 12 | 13 | public static void main(String[] args) { 14 | Sherlock sherlock = InMemorySherlock.create(); 15 | // first commit - all migrations are executed 16 | // acceptable changesets types: () -> {}, Mono, () -> Mono 17 | SherlockMigrator.builder(sherlock) 18 | .addChangeSet("change-set-1", Mono.fromRunnable(() -> logger.info("Change-set 1"))) 19 | .addChangeSet("change-set-2", () -> Mono.fromRunnable(() -> logger.info("Change-set 2"))) 20 | .migrate() 21 | .block(); 22 | // second commit - only new change-set is executed 23 | SherlockMigrator.builder(sherlock) 24 | .addChangeSet("change-set-1", () -> logger.info("Change-set 1")) 25 | .addChangeSet("change-set-2", () -> logger.info("Change-set 2")) 26 | .addChangeSet("change-set-3", () -> logger.info("Change-set 3")) 27 | .migrate() 28 | .block(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/inmem-reactor/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/inmem-rxjava/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | } 4 | 5 | dependencies { 6 | implementation(projects.connectors.inmem.inmemRxjava) 7 | implementation(libs.logback.classic) 8 | } 9 | -------------------------------------------------------------------------------- /examples/inmem-rxjava/src/main/java/com/coditory/sherlock/samples/inmem/rxjava/InMemRxLockSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.inmem.rxjava; 2 | 3 | import com.coditory.sherlock.inmem.rxjava.InMemorySherlock; 4 | import com.coditory.sherlock.rxjava.DistributedLock; 5 | import com.coditory.sherlock.rxjava.Sherlock; 6 | import io.reactivex.rxjava3.core.Single; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | public class InMemRxLockSample { 11 | private static final Logger logger = LoggerFactory.getLogger(InMemRxLockSample.class); 12 | 13 | public static void main(String[] args) { 14 | Sherlock sherlock = InMemorySherlock.create(); 15 | DistributedLock lock = sherlock.createLock("sample-lock"); 16 | lock.runLocked(Single.fromCallable(() -> { 17 | logger.info("Lock acquired!"); 18 | return true; 19 | })).blockingGet(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/inmem-rxjava/src/main/java/com/coditory/sherlock/samples/inmem/rxjava/InMemRxMigrationSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.inmem.rxjava; 2 | 3 | import com.coditory.sherlock.inmem.rxjava.InMemorySherlock; 4 | import com.coditory.sherlock.rxjava.Sherlock; 5 | import com.coditory.sherlock.rxjava.migrator.SherlockMigrator; 6 | import io.reactivex.rxjava3.core.Completable; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | public class InMemRxMigrationSample { 11 | private static final Logger logger = LoggerFactory.getLogger(InMemRxMigrationSample.class); 12 | 13 | public static void main(String[] args) { 14 | Sherlock sherlock = InMemorySherlock.create(); 15 | // first commit - all migrations are executed 16 | // acceptable changesets types: () -> {}, Completable, () -> Completable 17 | SherlockMigrator.builder(sherlock) 18 | .addChangeSet("change-set-1", Completable.fromRunnable(() -> logger.info("Change-set 1"))) 19 | .addChangeSet("change-set-2", () -> Completable.fromRunnable(() -> logger.info("Change-set 2"))) 20 | .migrate() 21 | .blockingGet(); 22 | // second commit - only new change-set is executed 23 | SherlockMigrator.builder(sherlock) 24 | .addChangeSet("change-set-1", () -> logger.info("Change-set 1")) 25 | .addChangeSet("change-set-2", () -> logger.info("Change-set 2")) 26 | .addChangeSet("change-set-3", () -> logger.info("Change-set 3")) 27 | .migrate() 28 | .blockingGet(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/inmem-rxjava/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/inmem-sync/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | id("build.kotlin") 4 | } 5 | 6 | dependencies { 7 | implementation(projects.connectors.inmem.inmemSync) 8 | implementation(libs.logback.classic) 9 | } 10 | -------------------------------------------------------------------------------- /examples/inmem-sync/src/main/java/com/coditory/sherlock/samples/inmem/sync/InMemSyncLockSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.inmem.sync; 2 | 3 | import com.coditory.sherlock.DistributedLock; 4 | import com.coditory.sherlock.Sherlock; 5 | import com.coditory.sherlock.inmem.InMemorySherlock; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | public class InMemSyncLockSample { 10 | private static final Logger logger = LoggerFactory.getLogger(InMemSyncLockSample.class); 11 | 12 | public static void main(String[] args) { 13 | Sherlock sherlock = InMemorySherlock.create(); 14 | DistributedLock lock = sherlock.createLock("sample-lock"); 15 | lock.runLocked(() -> logger.info("Lock acquired!")); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/inmem-sync/src/main/java/com/coditory/sherlock/samples/inmem/sync/InMemSyncMigrationSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.inmem.sync; 2 | 3 | import com.coditory.sherlock.Sherlock; 4 | import com.coditory.sherlock.inmem.InMemorySherlock; 5 | import com.coditory.sherlock.migrator.SherlockMigrator; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | public class InMemSyncMigrationSample { 10 | private static final Logger logger = LoggerFactory.getLogger(InMemSyncMigrationSample.class); 11 | 12 | public static void main(String[] args) { 13 | Sherlock sherlock = InMemorySherlock.create(); 14 | // first commit - all migrations are executed 15 | SherlockMigrator.builder(sherlock) 16 | .addChangeSet("change-set-1", () -> logger.info("Change-set 1")) 17 | .addChangeSet("change-set-2", () -> logger.info("Change-set 2")) 18 | .migrate(); 19 | // second commit - only new change-set is executed 20 | SherlockMigrator.builder(sherlock) 21 | .addChangeSet("change-set-1", () -> logger.info("Change-set 1")) 22 | .addChangeSet("change-set-2", () -> logger.info("Change-set 2")) 23 | .addChangeSet("change-set-3", () -> logger.info("Change-set 3")) 24 | .migrate(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/inmem-sync/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/mongo-coroutines/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.kotlin") 3 | } 4 | 5 | dependencies { 6 | implementation(projects.connectors.mongo.mongoCoroutines) 7 | implementation(libs.logback.classic) 8 | } 9 | -------------------------------------------------------------------------------- /examples/mongo-coroutines/src/main/kotlin/com/coditory/sherlock/samples/mongo/coroutines/MongoKtLockSample.kt: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.mongo.coroutines 2 | 3 | import com.coditory.sherlock.mongo.coroutines.MongoSherlock 4 | import com.mongodb.kotlin.client.coroutine.MongoClient 5 | import com.mongodb.kotlin.client.coroutine.MongoCollection 6 | import kotlinx.coroutines.runBlocking 7 | import org.bson.Document 8 | import org.slf4j.Logger 9 | import org.slf4j.LoggerFactory 10 | 11 | object MongoKtLockSample { 12 | private val logger: Logger = LoggerFactory.getLogger(this.javaClass) 13 | 14 | private fun getCollection(): MongoCollection { 15 | val database = "sherlock" 16 | val mongoClient = MongoClient.create("mongodb://localhost:27017/$database") 17 | return mongoClient 18 | .getDatabase(database) 19 | .getCollection("locks") 20 | } 21 | 22 | private suspend fun sample() { 23 | val sherlock = MongoSherlock.create(getCollection()) 24 | val lock = sherlock.createLock("sample-lock") 25 | lock.runLocked { logger.info("Lock acquired!") } 26 | } 27 | 28 | @JvmStatic 29 | fun main(args: Array) { 30 | runBlocking { sample() } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/mongo-coroutines/src/main/kotlin/com/coditory/sherlock/samples/mongo/coroutines/MongoKtMigrationSample.kt: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.mongo.coroutines 2 | 3 | import com.coditory.sherlock.coroutines.migrator.SherlockMigrator 4 | import com.coditory.sherlock.mongo.coroutines.MongoSherlock 5 | import com.mongodb.kotlin.client.coroutine.MongoClient 6 | import com.mongodb.kotlin.client.coroutine.MongoCollection 7 | import kotlinx.coroutines.runBlocking 8 | import org.bson.Document 9 | import org.slf4j.Logger 10 | import org.slf4j.LoggerFactory 11 | 12 | object MongoKtMigrationSample { 13 | private val logger: Logger = LoggerFactory.getLogger(this.javaClass) 14 | 15 | private fun locksCollection(): MongoCollection { 16 | val database = "sherlock" 17 | val mongoClient = MongoClient.create("mongodb://localhost:27017/$database") 18 | return mongoClient 19 | .getDatabase(database) 20 | .getCollection("locks") 21 | } 22 | 23 | private suspend fun sample() { 24 | val sherlock = MongoSherlock.create(locksCollection()) 25 | // first commit - all migrations are executed 26 | SherlockMigrator.builder(sherlock) 27 | .addChangeSet("change-set-1") { logger.info("Change-set 1") } 28 | .addChangeSet("change-set-2") { logger.info("Change-set 2") } 29 | .migrate() 30 | // second commit - only new change-set is executed 31 | SherlockMigrator.builder(sherlock) 32 | .addChangeSet("change-set-1") { logger.info("Change-set 1") } 33 | .addChangeSet("change-set-2") { logger.info("Change-set 2") } 34 | .addChangeSet("change-set-3") { logger.info("Change-set 3") } 35 | .migrate() 36 | } 37 | 38 | @JvmStatic 39 | fun main(args: Array) { 40 | runBlocking { sample() } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/mongo-coroutines/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/mongo-reactor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | } 4 | 5 | dependencies { 6 | implementation(projects.connectors.mongo.mongoReactor) 7 | implementation(libs.logback.classic) 8 | } 9 | -------------------------------------------------------------------------------- /examples/mongo-reactor/src/main/java/com/coditory/sherlock/samples/mongo/reactor/MongoReactorLockSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.mongo.reactor; 2 | 3 | import com.coditory.sherlock.mongo.reactor.MongoSherlock; 4 | import com.coditory.sherlock.reactor.DistributedLock; 5 | import com.coditory.sherlock.reactor.Sherlock; 6 | import com.mongodb.reactivestreams.client.MongoClient; 7 | import com.mongodb.reactivestreams.client.MongoClients; 8 | import com.mongodb.reactivestreams.client.MongoCollection; 9 | import org.bson.Document; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | public class MongoReactorLockSample { 14 | private static final Logger logger = LoggerFactory.getLogger(MongoReactorLockSample.class); 15 | 16 | private static MongoCollection getCollection() { 17 | String database = "sherlock"; 18 | MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017/" + database); 19 | return mongoClient 20 | .getDatabase("sherlock") 21 | .getCollection("locks"); 22 | } 23 | 24 | public static void main(String[] args) { 25 | Sherlock sherlock = MongoSherlock.create(getCollection()); 26 | DistributedLock lock = sherlock.createLock("sample-lock2"); 27 | lock.runLocked(() -> logger.info("Lock acquired!")) 28 | .block(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/mongo-reactor/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/mongo-rxjava/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | } 4 | 5 | dependencies { 6 | implementation(projects.connectors.mongo.mongoRxjava) 7 | implementation(libs.logback.classic) 8 | } 9 | -------------------------------------------------------------------------------- /examples/mongo-rxjava/src/main/java/com/coditory/sherlock/samples/mongo/rxjava/MongoRxLockSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.mongo.rxjava; 2 | 3 | import com.coditory.sherlock.mongo.rxjava.MongoSherlock; 4 | import com.coditory.sherlock.rxjava.DistributedLock; 5 | import com.coditory.sherlock.rxjava.Sherlock; 6 | import com.mongodb.reactivestreams.client.MongoClient; 7 | import com.mongodb.reactivestreams.client.MongoClients; 8 | import com.mongodb.reactivestreams.client.MongoCollection; 9 | import org.bson.Document; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | public class MongoRxLockSample { 14 | private static final Logger logger = LoggerFactory.getLogger(MongoRxLockSample.class); 15 | 16 | private static MongoCollection getCollection() { 17 | String database = "sherlock"; 18 | MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017/" + database); 19 | return mongoClient 20 | .getDatabase("sherlock") 21 | .getCollection("locks"); 22 | } 23 | 24 | public static void main(String[] args) { 25 | Sherlock sherlock = MongoSherlock.create(getCollection()); 26 | DistributedLock lock = sherlock.createLock("sample-lock"); 27 | lock.runLocked(() -> logger.info("Lock acquired!")) 28 | .blockingGet(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/mongo-rxjava/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/mongo-sync/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | } 4 | 5 | dependencies { 6 | implementation(projects.connectors.mongo.mongoSync) 7 | implementation(libs.logback.classic) 8 | } 9 | -------------------------------------------------------------------------------- /examples/mongo-sync/src/main/java/com/coditory/sherlock/samples/mongo/sync/MongoSyncLockSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.mongo.sync; 2 | 3 | import com.coditory.sherlock.DistributedLock; 4 | import com.coditory.sherlock.Sherlock; 5 | import com.coditory.sherlock.mongo.MongoSherlock; 6 | import com.mongodb.client.MongoClient; 7 | import com.mongodb.client.MongoClients; 8 | import com.mongodb.client.MongoCollection; 9 | import org.bson.Document; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | public class MongoSyncLockSample { 14 | private static final Logger logger = LoggerFactory.getLogger(MongoSyncLockSample.class); 15 | 16 | private static MongoCollection getCollection() { 17 | String database = "sherlock"; 18 | String connectionString = "mongodb://localhost:27017/" + database; 19 | MongoClient mongoClient = MongoClients.create(connectionString); 20 | return mongoClient 21 | .getDatabase("sherlock") 22 | .getCollection("locks"); 23 | } 24 | 25 | public static void main(String[] args) { 26 | Sherlock sherlock = MongoSherlock.create(getCollection()); 27 | DistributedLock lock = sherlock.createLock("sample-lock"); 28 | lock.runLocked(() -> logger.info("Lock acquired!")); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/mongo-sync/src/main/java/com/coditory/sherlock/samples/mongo/sync/MongoSyncMigrationSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.mongo.sync; 2 | 3 | import com.coditory.sherlock.Sherlock; 4 | import com.coditory.sherlock.migrator.SherlockMigrator; 5 | import com.coditory.sherlock.mongo.MongoSherlock; 6 | import com.mongodb.client.MongoClient; 7 | import com.mongodb.client.MongoClients; 8 | import com.mongodb.client.MongoCollection; 9 | import org.bson.Document; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | public class MongoSyncMigrationSample { 14 | private static final Logger logger = LoggerFactory.getLogger(MongoSyncMigrationSample.class); 15 | 16 | private static MongoCollection locksCollection() { 17 | String database = "sherlock"; 18 | MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017/" + database); 19 | return mongoClient 20 | .getDatabase("sherlock") 21 | .getCollection("locks"); 22 | } 23 | 24 | public static void main(String[] args) { 25 | Sherlock sherlock = MongoSherlock.create(locksCollection()); 26 | // first commit - all migrations are executed 27 | SherlockMigrator.builder(sherlock) 28 | .addChangeSet("change-set-1", () -> logger.info("Change-set 1")) 29 | .addChangeSet("change-set-2", () -> logger.info("Change-set 2")) 30 | .migrate(); 31 | // second commit - only new change-set is executed 32 | SherlockMigrator.builder(sherlock) 33 | .addChangeSet("change-set-1", () -> logger.info("Change-set 1")) 34 | .addChangeSet("change-set-2", () -> logger.info("Change-set 2")) 35 | .addChangeSet("change-set-3", () -> logger.info("Change-set 3")) 36 | .migrate(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/mongo-sync/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/mysql-coroutines/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.kotlin") 3 | } 4 | 5 | dependencies { 6 | implementation(projects.connectors.sql.sqlCoroutines) 7 | implementation(libs.r2dbc.mysql) 8 | implementation(libs.hikaricp) 9 | implementation(libs.logback.classic) 10 | } 11 | -------------------------------------------------------------------------------- /examples/mysql-coroutines/src/main/kotlin/com/coditory/sherlock/samples/mysql/coroutines/MySqlKtLockSample.kt: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.mysql.coroutines 2 | 3 | import com.coditory.sherlock.sql.BindingMapper.MYSQL_MAPPER 4 | import com.coditory.sherlock.sql.coroutines.SqlSherlock 5 | import io.r2dbc.spi.ConnectionFactories 6 | import io.r2dbc.spi.ConnectionFactory 7 | import io.r2dbc.spi.ConnectionFactoryOptions 8 | import kotlinx.coroutines.runBlocking 9 | import org.slf4j.Logger 10 | import org.slf4j.LoggerFactory 11 | 12 | object MySqlKtLockSample { 13 | private val logger: Logger = LoggerFactory.getLogger(this.javaClass) 14 | 15 | private fun getConnectionFactory(): ConnectionFactory { 16 | val database = "test" 17 | val options = 18 | ConnectionFactoryOptions 19 | .parse("r2dbc:mysql://localhost:3306/$database") 20 | .mutate() 21 | .option(ConnectionFactoryOptions.USER, "mysql") 22 | .option(ConnectionFactoryOptions.PASSWORD, "mysql") 23 | .option(ConnectionFactoryOptions.DATABASE, database) 24 | .build() 25 | return ConnectionFactories.get(options) 26 | } 27 | 28 | private suspend fun sample() { 29 | val sherlock = SqlSherlock.create(getConnectionFactory(), MYSQL_MAPPER) 30 | val lock = sherlock.createLock("sample-lock") 31 | lock 32 | .runLocked { logger.info("Lock acquired!") } 33 | } 34 | 35 | @JvmStatic 36 | fun main(args: Array) { 37 | runBlocking { sample() } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/mysql-coroutines/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/mysql-reactor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | } 4 | 5 | dependencies { 6 | implementation(projects.connectors.sql.sqlReactor) 7 | implementation(libs.r2dbc.mysql) 8 | implementation(libs.mysql) 9 | implementation(libs.logback.classic) 10 | implementation(libs.hikaricp) 11 | } 12 | -------------------------------------------------------------------------------- /examples/mysql-reactor/src/main/java/com/coditory/sherlock/samples/mysql/reactor/MySqlReactorLockSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.mysql.reactor; 2 | 3 | import com.coditory.sherlock.reactor.DistributedLock; 4 | import com.coditory.sherlock.reactor.Sherlock; 5 | import com.coditory.sherlock.sql.BindingMapper; 6 | import com.coditory.sherlock.sql.reactor.SqlSherlock; 7 | import io.r2dbc.spi.ConnectionFactories; 8 | import io.r2dbc.spi.ConnectionFactory; 9 | import io.r2dbc.spi.ConnectionFactoryOptions; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | public class MySqlReactorLockSample { 14 | private static final Logger logger = LoggerFactory.getLogger(MySqlReactorLockSample.class); 15 | 16 | private static ConnectionFactory getConnectionFactory() { 17 | String database = "test"; 18 | ConnectionFactoryOptions options = ConnectionFactoryOptions 19 | .parse("r2dbc:mysql://localhost:3306/" + database) 20 | .mutate() 21 | .option(ConnectionFactoryOptions.USER, "mysql") 22 | .option(ConnectionFactoryOptions.PASSWORD, "mysql") 23 | .option(ConnectionFactoryOptions.DATABASE, database) 24 | .build(); 25 | return ConnectionFactories.get(options); 26 | } 27 | 28 | public static void main(String[] args) { 29 | Sherlock sherlock = SqlSherlock.create(getConnectionFactory(), BindingMapper.MYSQL_MAPPER); 30 | DistributedLock lock = sherlock.createLock("sample-lock"); 31 | lock 32 | .runLocked(() -> logger.info("Lock acquired!")) 33 | .block(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/mysql-reactor/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/mysql-rxjava/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | } 4 | 5 | dependencies { 6 | implementation(projects.connectors.sql.sqlRxjava) 7 | implementation(libs.r2dbc.mysql) 8 | implementation(libs.mysql) 9 | implementation(libs.logback.classic) 10 | implementation(libs.hikaricp) 11 | } 12 | -------------------------------------------------------------------------------- /examples/mysql-rxjava/src/main/java/com/coditory/sherlock/samples/mysql/rxjava/MySqlRxLockSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.mysql.rxjava; 2 | 3 | import com.coditory.sherlock.rxjava.DistributedLock; 4 | import com.coditory.sherlock.rxjava.Sherlock; 5 | import com.coditory.sherlock.sql.BindingMapper; 6 | import com.coditory.sherlock.sql.rxjava.SqlSherlock; 7 | import io.r2dbc.spi.ConnectionFactories; 8 | import io.r2dbc.spi.ConnectionFactory; 9 | import io.r2dbc.spi.ConnectionFactoryOptions; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | public class MySqlRxLockSample { 14 | private static final Logger logger = LoggerFactory.getLogger(MySqlRxLockSample.class); 15 | 16 | private static ConnectionFactory getConnectionFactory() { 17 | String database = "test"; 18 | ConnectionFactoryOptions options = ConnectionFactoryOptions 19 | .parse("r2dbc:mysql://localhost:3306/" + database) 20 | .mutate() 21 | .option(ConnectionFactoryOptions.USER, "mysql") 22 | .option(ConnectionFactoryOptions.PASSWORD, "mysql") 23 | .option(ConnectionFactoryOptions.DATABASE, database) 24 | .build(); 25 | return ConnectionFactories.get(options); 26 | } 27 | 28 | public static void main(String[] args) { 29 | Sherlock sherlock = SqlSherlock.create(getConnectionFactory(), BindingMapper.MYSQL_MAPPER); 30 | DistributedLock lock = sherlock.createLock("sample-lock"); 31 | lock 32 | .runLocked(() -> logger.info("Lock acquired!")) 33 | .blockingGet(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/mysql-rxjava/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/mysql-sync/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | } 4 | 5 | dependencies { 6 | implementation(projects.connectors.sql.sqlSync) 7 | implementation(libs.mysql) 8 | implementation(libs.logback.classic) 9 | implementation(libs.hikaricp) 10 | } 11 | -------------------------------------------------------------------------------- /examples/mysql-sync/src/main/java/com/coditory/sherlock/samples/mysql/sync/MySqlSyncLockSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.mysql.sync; 2 | 3 | import com.coditory.sherlock.DistributedLock; 4 | import com.coditory.sherlock.Sherlock; 5 | import com.coditory.sherlock.sql.SqlSherlock; 6 | import com.zaxxer.hikari.HikariConfig; 7 | import com.zaxxer.hikari.HikariDataSource; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import javax.sql.DataSource; 12 | 13 | public class MySqlSyncLockSample { 14 | private static final Logger logger = LoggerFactory.getLogger(MySqlSyncLockSample.class); 15 | 16 | private static DataSource dataSource() { 17 | HikariConfig config = new HikariConfig(); 18 | config.setJdbcUrl("jdbc:mysql://localhost:3306/test"); 19 | config.setUsername("mysql"); 20 | config.setPassword("mysql"); 21 | return new HikariDataSource(config); 22 | } 23 | 24 | public static void main(String[] args) { 25 | Sherlock sherlock = SqlSherlock.create(dataSource()); 26 | DistributedLock lock = sherlock.createLock("sample-lock"); 27 | lock.runLocked(() -> logger.info("Lock acquired!")); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/mysql-sync/src/main/java/com/coditory/sherlock/samples/mysql/sync/MySqlSyncMigrationSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.mysql.sync; 2 | 3 | import com.coditory.sherlock.Sherlock; 4 | import com.coditory.sherlock.migrator.SherlockMigrator; 5 | import com.coditory.sherlock.sql.SqlSherlock; 6 | import com.zaxxer.hikari.HikariConfig; 7 | import com.zaxxer.hikari.HikariDataSource; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import javax.sql.DataSource; 12 | 13 | public class MySqlSyncMigrationSample { 14 | private static final Logger logger = LoggerFactory.getLogger(MySqlSyncMigrationSample.class); 15 | 16 | private static DataSource dataSource() { 17 | HikariConfig config = new HikariConfig(); 18 | config.setJdbcUrl("jdbc:mysql://localhost:3306/test"); 19 | config.setUsername("mysql"); 20 | config.setPassword("mysql"); 21 | return new HikariDataSource(config); 22 | } 23 | 24 | public static void main(String[] args) { 25 | Sherlock sherlock = SqlSherlock.create(dataSource()); 26 | // first commit - all migrations are executed 27 | SherlockMigrator.builder(sherlock) 28 | .addChangeSet("change-set-1", () -> logger.info("Change-set 1")) 29 | .addChangeSet("change-set-2", () -> logger.info("Change-set 2")) 30 | .migrate(); 31 | // second commit - only new change-set is executed 32 | SherlockMigrator.builder(sherlock) 33 | .addChangeSet("change-set-1", () -> logger.info("Change-set 1")) 34 | .addChangeSet("change-set-2", () -> logger.info("Change-set 2")) 35 | .addChangeSet("change-set-3", () -> logger.info("Change-set 3")) 36 | .migrate(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/mysql-sync/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/postgres-coroutines/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.kotlin") 3 | } 4 | 5 | dependencies { 6 | implementation(projects.connectors.sql.sqlCoroutines) 7 | implementation(libs.r2dbc.postgresql) 8 | implementation(libs.hikaricp) 9 | implementation(libs.logback.classic) 10 | } 11 | -------------------------------------------------------------------------------- /examples/postgres-coroutines/readme.md: -------------------------------------------------------------------------------- 1 | # Sample client code for distributed-lock 2 | 3 | Used only for sandbox testing 4 | 5 | - [In-memory Storage + Reactor API](src/main/java/com/coditory/sherlock/samples/inmem/InMemReactorSample.java) 6 | - [In-memory Storage + RxJava API](src/main/java/com/coditory/sherlock/samples/inmem/InMemRxJavaSample.java) 7 | - [In-memory Storage + Sync API](src/main/java/com/coditory/sherlock/samples/inmem/InMemSyncSample.java) 8 | - [Mongo Storage + Reactor API](src/main/java/com/coditory/sherlock/samples/mongo/MongoReactorSample.java) 9 | - [Mongo Storage + RxJava API](src/main/java/com/coditory/sherlock/samples/mongo/MongoRxJavaSample.java) 10 | - [Mongo Storage + Sync API](src/main/java/com/coditory/sherlock/samples/mongo/MongoSyncSample.java) 11 | - [Postgres Storage + Sync API](src/main/java/com/coditory/sherlock/samples/postgres/PostgresSyncSample.java) 12 | - [MySql Storage + Sync API](src/main/java/com/coditory/sherlock/samples/mysql/MySqlSyncSample.java) -------------------------------------------------------------------------------- /examples/postgres-coroutines/src/main/kotlin/com/coditory/sherlock/samples/postgres/coroutines/PostgresKtLockSample.kt: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.postgres.coroutines 2 | 3 | import com.coditory.sherlock.sql.BindingMapper.POSTGRES_MAPPER 4 | import com.coditory.sherlock.sql.coroutines.SqlSherlock 5 | import io.r2dbc.spi.ConnectionFactories 6 | import io.r2dbc.spi.ConnectionFactory 7 | import io.r2dbc.spi.ConnectionFactoryOptions 8 | import kotlinx.coroutines.runBlocking 9 | import org.slf4j.Logger 10 | import org.slf4j.LoggerFactory 11 | 12 | object PostgresKtLockSample { 13 | private val logger: Logger = LoggerFactory.getLogger(this.javaClass) 14 | 15 | private fun getConnectionFactory(): ConnectionFactory { 16 | val database = "test" 17 | val options = 18 | ConnectionFactoryOptions 19 | .parse("r2dbc:postgresql://localhost:5432/$database") 20 | .mutate() 21 | .option(ConnectionFactoryOptions.USER, "postgres") 22 | .option(ConnectionFactoryOptions.PASSWORD, "postgres") 23 | .option(ConnectionFactoryOptions.DATABASE, database) 24 | .build() 25 | return ConnectionFactories.get(options) 26 | } 27 | 28 | private suspend fun sample() { 29 | val sherlock = SqlSherlock.create(getConnectionFactory(), POSTGRES_MAPPER) 30 | val lock = sherlock.createLock("sample-lock") 31 | lock 32 | .runLocked { logger.info("Lock acquired!") } 33 | } 34 | 35 | @JvmStatic 36 | fun main(args: Array) { 37 | runBlocking { sample() } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/postgres-coroutines/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/postgres-reactor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | } 4 | 5 | dependencies { 6 | implementation(projects.connectors.sql.sqlReactor) 7 | implementation(libs.r2dbc.postgresql) 8 | implementation(libs.postgresql) 9 | implementation(libs.logback.classic) 10 | implementation(libs.hikaricp) 11 | } 12 | -------------------------------------------------------------------------------- /examples/postgres-reactor/src/main/java/com/coditory/sherlock/samples/postgres/reactor/PostgresReactorLockSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.postgres.reactor; 2 | 3 | import com.coditory.sherlock.reactor.DistributedLock; 4 | import com.coditory.sherlock.reactor.Sherlock; 5 | import com.coditory.sherlock.sql.reactor.SqlSherlock; 6 | import io.r2dbc.spi.ConnectionFactories; 7 | import io.r2dbc.spi.ConnectionFactory; 8 | import io.r2dbc.spi.ConnectionFactoryOptions; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import static com.coditory.sherlock.sql.BindingMapper.POSTGRES_MAPPER; 13 | 14 | public class PostgresReactorLockSample { 15 | private static final Logger logger = LoggerFactory.getLogger(PostgresReactorLockSample.class); 16 | 17 | private static ConnectionFactory getConnectionFactory() { 18 | String database = "test"; 19 | ConnectionFactoryOptions options = ConnectionFactoryOptions 20 | .parse("r2dbc:postgresql://localhost:5432/" + database) 21 | .mutate() 22 | .option(ConnectionFactoryOptions.USER, "postgres") 23 | .option(ConnectionFactoryOptions.PASSWORD, "postgres") 24 | .option(ConnectionFactoryOptions.DATABASE, database) 25 | .build(); 26 | return ConnectionFactories.get(options); 27 | } 28 | 29 | public static void main(String[] args) { 30 | Sherlock sherlock = SqlSherlock.create(getConnectionFactory(), POSTGRES_MAPPER); 31 | DistributedLock lock = sherlock.createLock("sample-lock"); 32 | lock.runLocked(() -> logger.info("Lock acquired!")) 33 | .block(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/postgres-reactor/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/postgres-rxjava/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | } 4 | 5 | dependencies { 6 | implementation(projects.connectors.sql.sqlRxjava) 7 | implementation(libs.r2dbc.postgresql) 8 | implementation(libs.postgresql) 9 | implementation(libs.logback.classic) 10 | implementation(libs.hikaricp) 11 | } 12 | -------------------------------------------------------------------------------- /examples/postgres-rxjava/src/main/java/com/coditory/sherlock/samples/postgres/rxjava/PostgresRxLockSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.postgres.rxjava; 2 | 3 | import com.coditory.sherlock.rxjava.DistributedLock; 4 | import com.coditory.sherlock.rxjava.Sherlock; 5 | import com.coditory.sherlock.sql.rxjava.SqlSherlock; 6 | import io.r2dbc.spi.ConnectionFactories; 7 | import io.r2dbc.spi.ConnectionFactory; 8 | import io.r2dbc.spi.ConnectionFactoryOptions; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import static com.coditory.sherlock.sql.BindingMapper.POSTGRES_MAPPER; 13 | 14 | public class PostgresRxLockSample { 15 | private static final Logger logger = LoggerFactory.getLogger(PostgresRxLockSample.class); 16 | 17 | private static ConnectionFactory getConnectionFactory() { 18 | String database = "test"; 19 | ConnectionFactoryOptions options = ConnectionFactoryOptions 20 | .parse("r2dbc:postgresql://localhost:5432/" + database) 21 | .mutate() 22 | .option(ConnectionFactoryOptions.USER, "postgres") 23 | .option(ConnectionFactoryOptions.PASSWORD, "postgres") 24 | .option(ConnectionFactoryOptions.DATABASE, database) 25 | .build(); 26 | return ConnectionFactories.get(options); 27 | } 28 | 29 | public static void main(String[] args) { 30 | Sherlock sherlock = SqlSherlock.create(getConnectionFactory(), POSTGRES_MAPPER); 31 | DistributedLock lock = sherlock.createLock("sample-lock"); 32 | lock.runLocked(() -> logger.info("Lock acquired!")) 33 | .blockingGet(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/postgres-rxjava/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/postgres-sync/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | } 4 | 5 | dependencies { 6 | implementation(projects.connectors.sql.sqlSync) 7 | implementation(libs.postgresql) 8 | implementation(libs.logback.classic) 9 | implementation(libs.hikaricp) 10 | } 11 | -------------------------------------------------------------------------------- /examples/postgres-sync/src/main/java/com/coditory/sherlock/samples/postgres/sync/PostgresSyncLockSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.postgres.sync; 2 | 3 | import com.coditory.sherlock.DistributedLock; 4 | import com.coditory.sherlock.Sherlock; 5 | import com.coditory.sherlock.sql.SqlSherlock; 6 | import com.zaxxer.hikari.HikariConfig; 7 | import com.zaxxer.hikari.HikariDataSource; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import javax.sql.DataSource; 12 | 13 | public class PostgresSyncLockSample { 14 | private static final Logger logger = LoggerFactory.getLogger(PostgresSyncLockSample.class); 15 | 16 | private static DataSource dataSource() { 17 | HikariConfig config = new HikariConfig(); 18 | config.setJdbcUrl("jdbc:postgresql://localhost:5432/test"); 19 | config.setUsername("postgres"); 20 | config.setPassword("postgres"); 21 | return new HikariDataSource(config); 22 | } 23 | 24 | public static void main(String[] args) { 25 | Sherlock sherlock = SqlSherlock.create(dataSource()); 26 | DistributedLock lock = sherlock.createLock("sample-lock"); 27 | lock.runLocked(() -> logger.info("Lock acquired!")); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/postgres-sync/src/main/java/com/coditory/sherlock/samples/postgres/sync/PostgresSyncMigrationSample.java: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.samples.postgres.sync; 2 | 3 | import com.coditory.sherlock.Sherlock; 4 | import com.coditory.sherlock.migrator.SherlockMigrator; 5 | import com.coditory.sherlock.sql.SqlSherlock; 6 | import com.zaxxer.hikari.HikariConfig; 7 | import com.zaxxer.hikari.HikariDataSource; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import javax.sql.DataSource; 12 | 13 | public class PostgresSyncMigrationSample { 14 | private static final Logger logger = LoggerFactory.getLogger(PostgresSyncMigrationSample.class); 15 | 16 | private static DataSource dataSource() { 17 | HikariConfig config = new HikariConfig(); 18 | config.setJdbcUrl("jdbc:postgresql://localhost:5432/test"); 19 | config.setUsername("postgres"); 20 | config.setPassword("postgres"); 21 | return new HikariDataSource(config); 22 | } 23 | 24 | public static void main(String[] args) { 25 | Sherlock sherlock = SqlSherlock.create(dataSource()); 26 | // first commit - all migrations are executed 27 | SherlockMigrator.builder(sherlock) 28 | .addChangeSet("change-set-1", () -> logger.info("Change-set 1")) 29 | .addChangeSet("change-set-2", () -> logger.info("Change-set 2")) 30 | .migrate(); 31 | // second commit - only new change-set is executed 32 | SherlockMigrator.builder(sherlock) 33 | .addChangeSet("change-set-1", () -> logger.info("Change-set 1")) 34 | .addChangeSet("change-set-2", () -> logger.info("Change-set 2")) 35 | .addChangeSet("change-set-3", () -> logger.info("Change-set 3")) 36 | .migrate(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/postgres-sync/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=1.0.3 2 | org.gradle.jvmargs=-Xmx3g 3 | org.gradle.configuration-cache=true 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coditory/sherlock-distributed-lock/01e3ff4628833dd34db146661236f6905a615944/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /scripts/docker/README.md: -------------------------------------------------------------------------------- 1 | # Databases 2 | 3 | A [Docker](https://www.docker.com) composition with databases used by sherlock. 4 | 5 | ## Sample usage 6 | 7 | ``` 8 | docker-compose up 9 | ``` 10 | 11 | ## Important values 12 | 13 | ### Mongo 14 | - Connection string: `mongodb://localhost:27017` 15 | 16 | ### MySql 17 | - database: test 18 | - username: mysql 19 | - password: mysql 20 | - Jdbc connection: `jdbc:mysql://localhost:3306/test` 21 | 22 | ### PostgreSQL 23 | - database: test 24 | - username: postgres 25 | - password: postgres 26 | - Jdbc connection: `jdbc:postgresql://localhost:5432/test` 27 | -------------------------------------------------------------------------------- /scripts/docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | name: sherlock_dbs 3 | services: 4 | mongo: 5 | image: mongo:7 6 | container_name: sherlock_dbs-mongo 7 | restart: on-failure 8 | volumes: 9 | - ./data/mongo:/data/db 10 | ports: 11 | - 27017:27017 12 | mysql: 13 | image: mysql:8 14 | container_name: sherlock_dbs-mysql 15 | command: --default-authentication-plugin=mysql_native_password 16 | environment: 17 | MYSQL_DATABASE: "test" 18 | MYSQL_USER: "mysql" 19 | MYSQL_PASSWORD: "mysql" 20 | # root username is 'root' 21 | MYSQL_ROOT_PASSWORD: "root" 22 | volumes: 23 | - ./data/mysql:/var/lib/mysql 24 | ports: 25 | - 3306:3306 26 | postgres: 27 | image: postgres:11 28 | container_name: sherlock_dbs-postgres 29 | environment: 30 | POSTGRES_DB: "test" 31 | POSTGRES_USER: "postgres" 32 | POSTGRES_PASSWORD: "postgres" 33 | volumes: 34 | - ./data/postgres:/var/lib/postgresql/data 35 | ports: 36 | - 5432:5432 37 | -------------------------------------------------------------------------------- /scripts/git/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euf -o pipefail 3 | 4 | function run_ktlint { 5 | if ! command -v ktlint &>/dev/null; then 6 | echo -e "${RED}Please install Ktlint${ENDCOLOR} (https://pinterest.github.io/ktlint/latest/install/cli/#download-and-verification)" 7 | exit 1 8 | fi 9 | # https://pinterest.github.io/ktlint/0.49.0/install/cli/#git-hooks 10 | KT_FILES=$(git diff --name-only --cached --relative --diff-filter=ACMR -- '*.kt' '*.kts') 11 | if [ -n "$KT_FILES" ]; then 12 | echo -e "${BLUE}Ktlint: linting $(echo "$KT_FILES" | wc -l) files${ENDCOLOR}" 13 | start="$(date +%s)" 14 | echo -n "$KT_FILES" | tr '\n' ',' | ktlint --relative --format --patterns-from-stdin=',' 15 | echo -e "${GREEN}Ktlint: finished in $(($(date +%s) - start))s${ENDCOLOR}" 16 | echo "$KT_FILES" | xargs git add 17 | fi 18 | } 19 | 20 | if [ "${NO_COLOR:-}" != "false" ]; then 21 | RED="\e[31m" 22 | GREEN="\e[32m" 23 | BLUE="\e[34m" 24 | ENDCOLOR="\e[0m" 25 | else 26 | RED="" 27 | GREEN="" 28 | BLUE="" 29 | ENDCOLOR="" 30 | fi 31 | 32 | prestart="$(date +%s)" 33 | echo -e "${BLUE}Pre-commit: Starting${ENDCOLOR}" 34 | 35 | if [ ./scripts/git/pre-commit -nt .git/hooks/pre-commit ]; then 36 | cp -f ./scripts/git/pre-commit .git/hooks/pre-commit 37 | echo -e "${RED}Updated git pre-commit hook. Please re-run commit.${ENDCOLOR}" 38 | exit 1 39 | fi 40 | 41 | run_ktlint 42 | 43 | echo -e "${GREEN}Pre-commit: finished in $(($(date +%s) - prestart))s${ENDCOLOR}" 44 | -------------------------------------------------------------------------------- /tests/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("build.java") 3 | id("build.kotlin") 4 | } 5 | 6 | dependencies { 7 | api(projects.api.apiCommon) 8 | api(projects.api.apiSync) 9 | api(projects.api.apiReactor) 10 | api(projects.api.apiRxjava) 11 | api(projects.api.apiCoroutines) 12 | api(projects.common) 13 | api(libs.spock.core) 14 | api(libs.jsonassert) 15 | api(libs.awaitility) 16 | api(libs.logback.core) 17 | api(libs.logback.classic) 18 | api(libs.kotlinx.coroutines.core) 19 | } 20 | -------------------------------------------------------------------------------- /tests/src/main/groovy/com/coditory/sherlock/HandleDbFailureSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock 2 | 3 | import com.coditory.sherlock.base.DatabaseManager 4 | import com.coditory.sherlock.base.LockAssertions 5 | import com.coditory.sherlock.base.LockTypes 6 | 7 | abstract class HandleDbFailureSpec extends LocksBaseSpec implements LockAssertions, DatabaseManager { 8 | String lockId = "acquire-id" 9 | String instanceId = "instance-id" 10 | 11 | def "should reconnect after DB failure"() { 12 | given: 13 | sherlock.initialize() 14 | DistributedLock lock = createLock(LockTypes.SINGLE_ENTRANT, lockId, instanceId) 15 | when: 16 | stopDatabase() 17 | lock.acquire() 18 | then: 19 | SherlockException e = thrown(SherlockException) 20 | e.getMessage().startsWith("Could not acquire lock") 21 | 22 | when: 23 | startDatabase() 24 | boolean result = lock.acquire() 25 | then: 26 | result == true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/src/main/groovy/com/coditory/sherlock/InfiniteAcquireLockSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock 2 | 3 | import com.coditory.sherlock.base.LockAssertions 4 | import spock.lang.Unroll 5 | 6 | import static com.coditory.sherlock.base.LockTypes.allLockTypes 7 | 8 | abstract class InfiniteAcquireLockSpec extends LocksBaseSpec implements LockAssertions { 9 | @Unroll 10 | def "infinite lock should stay acquired even when default lock duration passes - #type"() { 11 | given: 12 | DistributedLock lock = createLock(type, sampleLockId, sampleOwnerId) 13 | and: 14 | lock.acquireForever() 15 | 16 | when: 17 | fixedClock.tick(defaultLockDuration) 18 | then: 19 | assertAcquired(lock.id) 20 | 21 | where: 22 | type << allLockTypes() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/src/main/groovy/com/coditory/sherlock/base/DatabaseManager.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.base 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | interface DatabaseManager { 7 | void stopDatabase() 8 | 9 | void startDatabase() 10 | } 11 | -------------------------------------------------------------------------------- /tests/src/main/groovy/com/coditory/sherlock/base/DistributedLocksCreator.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.base 2 | 3 | import com.coditory.sherlock.Sherlock 4 | import groovy.transform.CompileStatic 5 | 6 | import java.time.Clock 7 | import java.time.Duration 8 | 9 | @CompileStatic 10 | interface DistributedLocksCreator { 11 | Sherlock createSherlock(String ownerId, Duration duration, Clock clock, String collectionName); 12 | } 13 | -------------------------------------------------------------------------------- /tests/src/main/groovy/com/coditory/sherlock/base/JsonAssert.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.base 2 | 3 | import org.skyscreamer.jsonassert.JSONCompare 4 | import org.skyscreamer.jsonassert.JSONCompareMode 5 | import org.skyscreamer.jsonassert.JSONCompareResult 6 | 7 | class JsonAssert { 8 | static boolean assertJsonEqual(String actual, String expected) { 9 | assertJsonEqualWithMode(actual, expected, JSONCompareMode.STRICT) 10 | return true 11 | } 12 | 13 | static boolean assertJsonLenientEqual(String actual, String expected) { 14 | assertJsonEqualWithMode(actual, expected, JSONCompareMode.LENIENT) 15 | return true 16 | } 17 | 18 | private static void assertJsonEqualWithMode(String actual, String expected, JSONCompareMode mode) { 19 | JSONCompareResult result = JSONCompare.compareJSON(expected, actual, mode) 20 | if (result.failed()) { 21 | String message = """ 22 | |Error: 23 | |${result.getMessage()} 24 | | 25 | |Actual: 26 | |${actual} 27 | | 28 | |Expected: 29 | |${expected} 30 | """.stripMargin() 31 | assert false: message 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/src/main/groovy/com/coditory/sherlock/base/LockAssertions.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.base 2 | 3 | import com.coditory.sherlock.DistributedLock 4 | import com.coditory.sherlock.LocksBaseSpec 5 | import groovy.transform.CompileStatic 6 | import groovy.transform.SelfType 7 | 8 | import java.time.Duration 9 | import java.time.Instant 10 | 11 | import static LockTypes.SINGLE_ENTRANT 12 | 13 | @CompileStatic 14 | @SelfType(LocksBaseSpec) 15 | trait LockAssertions { 16 | boolean assertAcquired(String lockId) { 17 | DistributedLock otherLock = createLockWithRandomOwner(lockId) 18 | assert otherLock.acquire() == false 19 | return true 20 | } 21 | 22 | boolean assertAcquired(String lockId, Duration duration) { 23 | DistributedLock otherLock = createLockWithRandomOwner(lockId) 24 | assert otherLock.acquire() == false 25 | Instant backup = fixedClock.instant() 26 | fixedClock.tick(duration - Duration.ofMillis(1)) 27 | assert otherLock.acquire() == false 28 | fixedClock.tick(Duration.ofMillis(1)) 29 | assert otherLock.acquire() == true 30 | otherLock.release() 31 | fixedClock.setup(backup) 32 | return true 33 | } 34 | 35 | boolean assertAcquiredForever(String lockId) { 36 | DistributedLock otherLock = createLockWithRandomOwner(lockId) 37 | assert otherLock.acquire() == false 38 | Instant backup = fixedClock.instant() 39 | fixedClock.tick(Duration.ofDays(100)) 40 | assert otherLock.acquire() == false 41 | fixedClock.setup(backup) 42 | return true 43 | } 44 | 45 | boolean assertReleased(String lockId) { 46 | DistributedLock otherLock = createLockWithRandomOwner(lockId) 47 | assert otherLock.acquire() == true 48 | otherLock.release() 49 | return true 50 | } 51 | 52 | private DistributedLock createLockWithRandomOwner(String lockId) { 53 | return createLock(SINGLE_ENTRANT, lockId, UUID.randomUUID().toString()) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/src/main/groovy/com/coditory/sherlock/base/LockTypes.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.base 2 | 3 | import com.coditory.sherlock.DistributedLock 4 | import com.coditory.sherlock.DistributedLockBuilder 5 | import com.coditory.sherlock.Sherlock 6 | import groovy.transform.CompileStatic 7 | 8 | @CompileStatic 9 | enum LockTypes { 10 | SINGLE_ENTRANT{ 11 | @Override 12 | DistributedLockBuilder createLock(Sherlock sherlock) { 13 | return sherlock.createLock() 14 | } 15 | }, 16 | REENTRANT{ 17 | @Override 18 | DistributedLockBuilder createLock(Sherlock sherlock) { 19 | return sherlock.createReentrantLock() 20 | } 21 | }, 22 | OVERRIDING{ 23 | @Override 24 | DistributedLockBuilder createLock(Sherlock sherlock) { 25 | return sherlock.createOverridingLock() 26 | } 27 | }; 28 | 29 | abstract DistributedLockBuilder createLock(Sherlock sherlock); 30 | 31 | static List allLockTypes() { 32 | return values().toList() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/src/main/groovy/com/coditory/sherlock/base/SpecSimulatedException.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.base 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | class SpecSimulatedException extends RuntimeException { 7 | static throwSpecSimulatedException() { 8 | throw new SpecSimulatedException() 9 | } 10 | 11 | SpecSimulatedException() { 12 | this("Simulated exception for test") 13 | } 14 | 15 | SpecSimulatedException(String message) { 16 | super(message) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/src/main/groovy/com/coditory/sherlock/base/UpdatableFixedClock.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.base 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | import java.time.Clock 6 | import java.time.Duration 7 | import java.time.Instant 8 | import java.time.ZoneId 9 | 10 | import static java.time.Duration.ofDays 11 | import static java.time.Duration.ofNanos 12 | import static java.util.Objects.requireNonNull 13 | 14 | @CompileStatic 15 | class UpdatableFixedClock extends Clock { 16 | // Always use instant with nanos for testing. Some databases (like mongo) trim nanos - you should test for that! 17 | public static final Instant DEFAULT_FIXED_TIME = Instant.parse('2015-12-03T10:15:30.123456Z') 18 | public static final ZoneId DEFAULT_ZONE_ID = ZoneId.of('Europe/Warsaw') 19 | 20 | static final UpdatableFixedClock defaultUpdatableFixedClock() { 21 | return new UpdatableFixedClock(DEFAULT_FIXED_TIME, DEFAULT_ZONE_ID) 22 | } 23 | 24 | private final ZoneId zoneId 25 | private Instant fixedTime 26 | 27 | private UpdatableFixedClock(Instant fixedTime, ZoneId zoneId) { 28 | this.fixedTime = requireNonNull(fixedTime) 29 | this.zoneId = requireNonNull(zoneId) 30 | } 31 | 32 | @Override 33 | ZoneId getZone() { 34 | return zoneId 35 | } 36 | 37 | @Override 38 | UpdatableFixedClock withZone(ZoneId zone) { 39 | return new UpdatableFixedClock(this.fixedTime, zone) 40 | } 41 | 42 | @Override 43 | Instant instant() { 44 | return fixedTime 45 | } 46 | 47 | Instant futureInstant(Duration duration = ofDays(1)) { 48 | return this.fixedTime + duration 49 | } 50 | 51 | Instant pastInstant(Duration duration = ofDays(1)) { 52 | return this.fixedTime - duration 53 | } 54 | 55 | void reset() { 56 | this.fixedTime = DEFAULT_FIXED_TIME 57 | } 58 | 59 | void tick(Duration duration = ofNanos(1)) { 60 | this.fixedTime = this.fixedTime + duration 61 | } 62 | 63 | void setup(Instant instant) { 64 | this.fixedTime = instant 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/src/main/groovy/com/coditory/sherlock/migrator/MigratorBaseSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.migrator 2 | 3 | import com.coditory.sherlock.LocksBaseSpec 4 | import com.coditory.sherlock.migrator.base.MigratorCreator 5 | 6 | abstract class MigratorBaseSpec extends LocksBaseSpec implements MigratorCreator { 7 | 8 | } 9 | 10 | -------------------------------------------------------------------------------- /tests/src/main/groovy/com/coditory/sherlock/migrator/base/BlockingMigrator.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.migrator.base 2 | 3 | import com.coditory.sherlock.migrator.MigrationResult 4 | 5 | interface BlockingMigrator { 6 | MigrationResult migrate() 7 | } 8 | 9 | interface BlockingMigratorBuilder { 10 | 11 | BlockingMigratorBuilder setMigrationId(String migrationId) 12 | 13 | BlockingMigratorBuilder addChangeSet(String changeSetId, Runnable changeSet) 14 | 15 | BlockingMigratorBuilder addAnnotatedChangeSets(Object object) 16 | 17 | BlockingMigrator build() 18 | 19 | MigrationResult migrate() 20 | } -------------------------------------------------------------------------------- /tests/src/main/groovy/com/coditory/sherlock/migrator/base/MigratorCreator.groovy: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock.migrator.base 2 | 3 | import com.coditory.sherlock.Sherlock 4 | import com.coditory.sherlock.base.DistributedLocksCreator 5 | import groovy.transform.CompileStatic 6 | 7 | @CompileStatic 8 | interface MigratorCreator extends DistributedLocksCreator { 9 | BlockingMigratorBuilder createMigratorBuilder(Sherlock sherlock) 10 | } -------------------------------------------------------------------------------- /tests/src/main/kotlin/com/coditory/sherlock/BlockingKtDistributedLock.kt: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock 2 | 3 | import kotlinx.coroutines.runBlocking 4 | import java.time.Duration 5 | 6 | class BlockingKtDistributedLock( 7 | private val lock: com.coditory.sherlock.coroutines.DistributedLock, 8 | ) : DistributedLock { 9 | override fun getId(): String { 10 | return lock.id 11 | } 12 | 13 | override fun acquire(): Boolean = runBlocking { 14 | lock.acquire() 15 | } 16 | 17 | override fun acquire(duration: Duration): Boolean = runBlocking { 18 | lock.acquire(duration) 19 | } 20 | 21 | override fun acquireForever(): Boolean = runBlocking { 22 | lock.acquireForever() 23 | } 24 | 25 | override fun release(): Boolean = runBlocking { 26 | lock.release() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/src/main/kotlin/com/coditory/sherlock/BlockingKtSherlockWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.coditory.sherlock 2 | 3 | import kotlinx.coroutines.runBlocking 4 | 5 | class BlockingKtSherlockWrapper( 6 | private val sherlock: com.coditory.sherlock.coroutines.Sherlock, 7 | ) : Sherlock { 8 | fun unwrap(): com.coditory.sherlock.coroutines.Sherlock = sherlock 9 | 10 | override fun initialize() = runBlocking { 11 | sherlock.initialize() 12 | } 13 | 14 | override fun createLock(): DistributedLockBuilder { 15 | return blockingLockBuilder(sherlock.createLock()) 16 | } 17 | 18 | override fun createReentrantLock(): DistributedLockBuilder { 19 | return blockingLockBuilder(sherlock.createReentrantLock()) 20 | } 21 | 22 | override fun createOverridingLock(): DistributedLockBuilder { 23 | return blockingLockBuilder(sherlock.createOverridingLock()) 24 | } 25 | 26 | override fun forceReleaseAllLocks(): Boolean = runBlocking { 27 | sherlock.forceReleaseAllLocks() 28 | } 29 | 30 | override fun forceReleaseLock(lockId: String): Boolean { 31 | return createOverridingLock(lockId) 32 | .release() 33 | } 34 | 35 | private fun blockingLockBuilder( 36 | builder: DistributedLockBuilder, 37 | ): DistributedLockBuilder { 38 | return builder.withMappedLock { lock -> BlockingKtDistributedLock(lock) } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | --------------------------------------------------------------------------------