From 5639b3a6ec6477e8d2085f2fa7806e2a448d3a13 Mon Sep 17 00:00:00 2001
From: davidecerbo <Asdrubale89>
Date: Mon, 14 Oct 2019 00:10:15 +0200
Subject: [PATCH 1/4] first commit

---
 build.gradle                                  | 1479 +++++++++--------
 settings.gradle                               |    1 +
 .../statemachine/lock/LockInterceptor.java    |   53 +
 .../statemachine/lock/LockService.java        |   38 +
 .../lock/shedlock/ShedLockService.java        |   77 +
 .../cluster/LockInterceptorTest.java          |   61 +
 .../cluster/ShedLockServiceTest.java          |  161 ++
 7 files changed, 1145 insertions(+), 725 deletions(-)
 create mode 100755 spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockInterceptor.java
 create mode 100755 spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockService.java
 create mode 100755 spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/shedlock/ShedLockService.java
 create mode 100644 spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockInterceptorTest.java
 create mode 100644 spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/ShedLockServiceTest.java

diff --git a/build.gradle b/build.gradle
index a7da83177..30ffedb1a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,791 +1,820 @@
 buildscript {
-	ext {
-		log4jVersion = '1.2.17'
-		springBootVersion = '2.2.0.M6'
-		eclipsePersistenceVersion = '2.1.1'
-		kryoVersion = '4.0.2'
-		springCloudClusterVersion = '1.0.2.RELEASE'
-		springShellVersion = '1.1.0.RELEASE'
-		eclipseEmfXmiVersion = '2.11.1-v20150805-0538'
-		eclipseUml2CommonVersion = '2.0.0-v20140602-0749'
-		eclipseEmfCommonVersion = '2.11.0-v20150805-0538'
-		eclipseUml2TypesVersion = '2.0.0-v20140602-0749'
-		eclipseEmfEcoreVersion = '2.11.1-v20150805-0538'
-		eclipseUml2UmlVersion = '5.0.0-v20140602-0749'
-		curatorVersion = '2.11.1'
-		docResourcesVersion = '0.1.1.RELEASE'
-		awaitilityVersion = '3.1.6'
-		reactorBlockHoundVersion = '1.0.0.M3'
-	}
-	repositories {
-		maven { url 'https://repo.springsource.org/libs-release'}
-		maven { url 'https://repo.springsource.org/plugins-release' }
-		maven { url 'https://repo.springsource.org/plugins-snapshot' }
-	}
-	dependencies {
-		classpath('io.spring.gradle:propdeps-plugin:0.0.8')
-		classpath('org.asciidoctor:asciidoctor-gradle-plugin:1.5.6')
-		classpath 'org.asciidoctor:asciidoctorj-pdf:1.5.0-alpha.16'
-		classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
-	}
+    ext {
+        log4jVersion = '1.2.17'
+        springBootVersion = '2.2.0.M6'
+        eclipsePersistenceVersion = '2.1.1'
+        kryoVersion = '4.0.2'
+        springCloudClusterVersion = '1.0.2.RELEASE'
+        springShellVersion = '1.1.0.RELEASE'
+        eclipseEmfXmiVersion = '2.11.1-v20150805-0538'
+        eclipseUml2CommonVersion = '2.0.0-v20140602-0749'
+        eclipseEmfCommonVersion = '2.11.0-v20150805-0538'
+        eclipseUml2TypesVersion = '2.0.0-v20140602-0749'
+        eclipseEmfEcoreVersion = '2.11.1-v20150805-0538'
+        eclipseUml2UmlVersion = '5.0.0-v20140602-0749'
+        curatorVersion = '2.11.1'
+        docResourcesVersion = '0.1.1.RELEASE'
+        awaitilityVersion = '3.1.6'
+        reactorBlockHoundVersion = '1.0.0.M3'
+    }
+    repositories {
+        maven { url 'https://repo.springsource.org/libs-release' }
+        maven { url 'https://repo.springsource.org/plugins-release' }
+        maven { url 'https://repo.springsource.org/plugins-snapshot' }
+    }
+    dependencies {
+        classpath('io.spring.gradle:propdeps-plugin:0.0.8')
+        classpath('org.asciidoctor:asciidoctor-gradle-plugin:1.5.6')
+        classpath 'org.asciidoctor:asciidoctorj-pdf:1.5.0-alpha.16'
+        classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
+    }
 }
 
 def recipeProjects() {
-	subprojects.findAll { project ->
-		project.name.contains('spring-statemachine-recipes') && project.name != 'spring-statemachine-recipes-common'
-	}
+    subprojects.findAll { project ->
+        project.name.contains('spring-statemachine-recipes') && project.name != 'spring-statemachine-recipes-common'
+    }
 }
 
 def sampleProjects() {
-	subprojects.findAll { project ->
-		project.name.contains('spring-statemachine-samples') && project.name != 'spring-statemachine-samples-common'
-	}
+    subprojects.findAll { project ->
+        project.name.contains('spring-statemachine-samples') && project.name != 'spring-statemachine-samples-common'
+    }
 }
 
 def getResolvedVersionOf(dependency) {
-	// used for resolving version to docs
-	return configurations.compile.resolvedConfiguration.firstLevelModuleDependencies.findAll { it.moduleName == dependency }[0].moduleVersion
+    // used for resolving version to docs
+    return configurations.compile.resolvedConfiguration.firstLevelModuleDependencies.findAll {
+        it.moduleName == dependency
+    }[0].moduleVersion
 }
 
 configure(allprojects) {
-	apply plugin: 'java'
-	apply plugin: 'eclipse'
-	apply plugin: 'io.spring.dependency-management'
-	apply plugin: 'idea'
-	apply plugin: 'propdeps'
-
-	if (System.env.TRAVIS == 'true') {
-		tasks.withType(GroovyCompile) {
-			groovyOptions.fork = false
-		}
-		tasks.withType(Test) {
-			maxParallelForks = 1
-			minHeapSize = '256m'
-			maxHeapSize = '384m'
-		}
-	}
-
-	compileJava {
-		sourceCompatibility = 1.8
-		targetCompatibility = 1.8
-	}
-
-	compileTestJava {
-		sourceCompatibility = 1.8
-		targetCompatibility = 1.8
-	}
-
-	group = 'org.springframework.statemachine'
-
-	[compileJava, compileTestJava]*.options*.compilerArgs = ['-Xlint:none']
-
-	repositories {
-		mavenCentral()
-		maven { url 'https://repo.springsource.org/libs-snapshot' }
-		maven { url 'https://repo.springsource.org/libs-release' }
-		maven { url 'https://repo.springsource.org/libs-milestone' }
-	}
-
-	dependencyManagement {
-		imports {
-			mavenBom "org.springframework.boot:spring-boot-dependencies:$springBootVersion"
-		}
-		dependencies {
-			dependency "log4j:log4j:$log4jVersion"
-			dependency "org.eclipse.persistence:javax.persistence:$eclipsePersistenceVersion"
-			dependency "com.esotericsoftware:kryo-shaded:$kryoVersion"
-			dependency "org.springframework.shell:spring-shell:$springShellVersion"
-			dependency "org.eclipse.uml2:uml:$eclipseUml2UmlVersion"
-			dependency "org.eclipse.uml2:types:$eclipseUml2TypesVersion"
-			dependency "org.eclipse.uml2:common:$eclipseUml2CommonVersion"
-			dependency "org.eclipse.emf:org.eclipse.emf.ecore.xmi:$eclipseEmfXmiVersion"
-			dependency "org.eclipse.emf:org.eclipse.emf.ecore:$eclipseEmfEcoreVersion"
-			dependency "org.eclipse.emf:org.eclipse.emf.common:$eclipseEmfCommonVersion"
-			dependency "org.apache.curator:curator-recipes:$curatorVersion"
-			dependency "org.apache.curator:curator-test:$curatorVersion"
-			dependency "org.awaitility:awaitility:$awaitilityVersion"
-			dependency "io.projectreactor.tools:blockhound-junit-platform:$reactorBlockHoundVersion"
-		}
-	}
-
-	task integrationTest(type: Test) {
-		include '**/*IntegrationTests.*'
-	}
-
-	test {
-		useJUnitPlatform {
-			if (project.hasProperty('statemachineIncludeTags') && statemachineIncludeTags.size() > 0) {
-					includeTags = statemachineIncludeTags.split(',')
-			}
-			if (project.hasProperty('statemachineExcludeTags') && statemachineExcludeTags.size() > 0) {
-					excludeTags = statemachineExcludeTags.split(',')
-			}
-		}
-		exclude '**/*IntegrationTests.*'
-	}
+    apply plugin: 'java'
+    apply plugin: 'eclipse'
+    apply plugin: 'io.spring.dependency-management'
+    apply plugin: 'idea'
+    apply plugin: 'propdeps'
+
+    if (System.env.TRAVIS == 'true') {
+        tasks.withType(GroovyCompile) {
+            groovyOptions.fork = false
+        }
+        tasks.withType(Test) {
+            maxParallelForks = 1
+            minHeapSize = '256m'
+            maxHeapSize = '384m'
+        }
+    }
+
+    compileJava {
+        sourceCompatibility = 1.8
+        targetCompatibility = 1.8
+    }
+
+    compileTestJava {
+        sourceCompatibility = 1.8
+        targetCompatibility = 1.8
+    }
+
+    group = 'org.springframework.statemachine'
+
+    [compileJava, compileTestJava]*.options*.compilerArgs = ['-Xlint:none']
+
+    repositories {
+        mavenCentral()
+        maven { url 'https://repo.springsource.org/libs-snapshot' }
+        maven { url 'https://repo.springsource.org/libs-release' }
+        maven { url 'https://repo.springsource.org/libs-milestone' }
+    }
+
+    dependencyManagement {
+        imports {
+            mavenBom "org.springframework.boot:spring-boot-dependencies:$springBootVersion"
+        }
+        dependencies {
+            dependency "log4j:log4j:$log4jVersion"
+            dependency "org.eclipse.persistence:javax.persistence:$eclipsePersistenceVersion"
+            dependency "com.esotericsoftware:kryo-shaded:$kryoVersion"
+            dependency "org.springframework.shell:spring-shell:$springShellVersion"
+            dependency "org.eclipse.uml2:uml:$eclipseUml2UmlVersion"
+            dependency "org.eclipse.uml2:types:$eclipseUml2TypesVersion"
+            dependency "org.eclipse.uml2:common:$eclipseUml2CommonVersion"
+            dependency "org.eclipse.emf:org.eclipse.emf.ecore.xmi:$eclipseEmfXmiVersion"
+            dependency "org.eclipse.emf:org.eclipse.emf.ecore:$eclipseEmfEcoreVersion"
+            dependency "org.eclipse.emf:org.eclipse.emf.common:$eclipseEmfCommonVersion"
+            dependency "org.apache.curator:curator-recipes:$curatorVersion"
+            dependency "org.apache.curator:curator-test:$curatorVersion"
+            dependency "org.awaitility:awaitility:$awaitilityVersion"
+            dependency "io.projectreactor.tools:blockhound-junit-platform:$reactorBlockHoundVersion"
+        }
+    }
+
+    task integrationTest(type: Test) {
+        include '**/*IntegrationTests.*'
+    }
+
+    test {
+        useJUnitPlatform {
+            if (project.hasProperty('statemachineIncludeTags') && statemachineIncludeTags.size() > 0) {
+                includeTags = statemachineIncludeTags.split(',')
+            }
+            if (project.hasProperty('statemachineExcludeTags') && statemachineExcludeTags.size() > 0) {
+                excludeTags = statemachineExcludeTags.split(',')
+            }
+        }
+        exclude '**/*IntegrationTests.*'
+    }
 }
 
 configure(subprojects) { subproject ->
-	apply from: "${rootProject.projectDir}/publish-maven.gradle"
-
-	dependencies {
-		testCompile("org.junit.jupiter:junit-jupiter-api")
-		testRuntime("org.junit.jupiter:junit-jupiter-engine")
-		if (project.hasProperty('statemachineBlockHound') && statemachineBlockHound.toBoolean()) {
-			testRuntime("org.junit.platform:junit-platform-launcher")
-			testRuntime("io.projectreactor.tools:blockhound-junit-platform")
-		}
-	}
-
-	jar {
-		manifest.attributes['Implementation-Title'] = subproject.name
-		manifest.attributes['Implementation-Version'] = subproject.version
-
-		from("${rootProject.projectDir}/src/dist") {
-			include "license.txt"
-			include "notice.txt"
-			into "META-INF"
-			expand(copyright: new Date().format('yyyy'), version: project.version)
-		}
-	}
-
-	javadoc {
-		// /config/configuration/StateMachineConfiguration.html...
-		// java.lang.ClassCastException: com.sun.tools.javadoc.MethodDocImpl cannot be cast
-		// to com.sun.tools.javadoc.AnnotationTypeElementDocImpl
-		// @Bean(name = StateMachineSystemConstants.DEFAULT_ID_STATEMACHINEFACTORY)
-		// vs.
-		// @Bean
-
-		enabled = false
-		options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED
-		options.author = true
-		options.header = project.name
-		verbose = true
-	}
-
-	task sourcesJar(type: Jar, dependsOn:classes) {
-		classifier = 'sources'
-		from sourceSets.main.allJava
-	}
-
-	task javadocJar(type: Jar) {
-		classifier = 'javadoc'
-		from javadoc
-	}
-
-	artifacts {
-		archives sourcesJar
-		archives javadocJar
-	}
-
-	eclipse {
-		classpath {
-			plusConfigurations += [ configurations.optional ]
-		}
-	}
+    apply from: "${rootProject.projectDir}/publish-maven.gradle"
+
+    dependencies {
+        testCompile("org.junit.jupiter:junit-jupiter-api")
+        testRuntime("org.junit.jupiter:junit-jupiter-engine")
+        if (project.hasProperty('statemachineBlockHound') && statemachineBlockHound.toBoolean()) {
+            testRuntime("org.junit.platform:junit-platform-launcher")
+            testRuntime("io.projectreactor.tools:blockhound-junit-platform")
+        }
+    }
+
+    jar {
+        manifest.attributes['Implementation-Title'] = subproject.name
+        manifest.attributes['Implementation-Version'] = subproject.version
+
+        from("${rootProject.projectDir}/src/dist") {
+            include "license.txt"
+            include "notice.txt"
+            into "META-INF"
+            expand(copyright: new Date().format('yyyy'), version: project.version)
+        }
+    }
+
+    javadoc {
+        // /config/configuration/StateMachineConfiguration.html...
+        // java.lang.ClassCastException: com.sun.tools.javadoc.MethodDocImpl cannot be cast
+        // to com.sun.tools.javadoc.AnnotationTypeElementDocImpl
+        // @Bean(name = StateMachineSystemConstants.DEFAULT_ID_STATEMACHINEFACTORY)
+        // vs.
+        // @Bean
+
+        enabled = false
+        options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED
+        options.author = true
+        options.header = project.name
+        verbose = true
+    }
+
+    task sourcesJar(type: Jar, dependsOn: classes) {
+        classifier = 'sources'
+        from sourceSets.main.allJava
+    }
+
+    task javadocJar(type: Jar) {
+        classifier = 'javadoc'
+        from javadoc
+    }
+
+    artifacts {
+        archives sourcesJar
+        archives javadocJar
+    }
+
+    eclipse {
+        classpath {
+            plusConfigurations += [configurations.optional]
+        }
+    }
 }
 
 project('spring-statemachine-core') {
-	description = 'Spring State Machine Core'
-
-	configurations {
-		testArtifacts
-	}
-
-	dependencies {
-		compile 'org.springframework:spring-tx'
-		compile 'org.springframework:spring-messaging'
-		compile 'io.projectreactor:reactor-core'
-		optional 'org.springframework.security:spring-security-core'
-
-		testCompile 'org.springframework:spring-test'
-		testCompile 'org.springframework:spring-web'
-		testCompile 'org.springframework:spring-webmvc'
-		testCompile 'io.projectreactor:reactor-test'
-		testCompile 'org.apache.tomcat.embed:tomcat-embed-core'
-		testCompile 'org.hamcrest:hamcrest-core'
-		testCompile 'org.hamcrest:hamcrest-library'
-		testCompile('org.mockito:mockito-core') { dep ->
-			exclude group: 'org.hamcrest'
-		}
-		testCompile("org.junit.jupiter:junit-jupiter-api")
-		testCompile("org.junit.jupiter:junit-jupiter-engine")
-		testCompile 'org.assertj:assertj-core'
-		testCompile 'org.springframework.security:spring-security-config'
-		testCompile 'org.springframework.security:spring-security-test'
-		testCompile 'javax.servlet:javax.servlet-api'
-		testCompile 'org.awaitility:awaitility'
-		testRuntime 'org.apache.logging.log4j:log4j-core'
-	}
-
-	task testJar(type: Jar) {
-		classifier = 'tests'
-		from sourceSets.test.output
-	}
-
-	artifacts {
-		testArtifacts testJar
-	}
+    description = 'Spring State Machine Core'
+
+    configurations {
+        testArtifacts
+    }
+
+    dependencies {
+        compile 'org.springframework:spring-tx'
+        compile 'org.springframework:spring-messaging'
+        compile 'io.projectreactor:reactor-core'
+        optional 'org.springframework.security:spring-security-core'
+
+        testCompile 'org.springframework:spring-test'
+        testCompile 'org.springframework:spring-web'
+        testCompile 'org.springframework:spring-webmvc'
+        testCompile 'io.projectreactor:reactor-test'
+        testCompile 'org.apache.tomcat.embed:tomcat-embed-core'
+        testCompile 'org.hamcrest:hamcrest-core'
+        testCompile 'org.hamcrest:hamcrest-library'
+        testCompile('org.mockito:mockito-core') { dep ->
+            exclude group: 'org.hamcrest'
+        }
+        testCompile("org.junit.jupiter:junit-jupiter-api")
+        testCompile("org.junit.jupiter:junit-jupiter-engine")
+        testCompile 'org.assertj:assertj-core'
+        testCompile 'org.springframework.security:spring-security-config'
+        testCompile 'org.springframework.security:spring-security-test'
+        testCompile 'javax.servlet:javax.servlet-api'
+        testCompile 'org.awaitility:awaitility'
+        testRuntime 'org.apache.logging.log4j:log4j-core'
+    }
+
+    task testJar(type: Jar) {
+        classifier = 'tests'
+        from sourceSets.test.output
+    }
+
+    artifacts {
+        testArtifacts testJar
+    }
 }
 
 project('spring-statemachine-autoconfigure') {
-	description = 'Spring State Machine Boot Autoconfigure'
-
-	dependencies {
-		compile project(':spring-statemachine-core')
-		compile 'org.springframework.boot:spring-boot-autoconfigure'
-		compile 'org.springframework.boot:spring-boot-actuator-autoconfigure'
-		compile 'org.springframework.boot:spring-boot-actuator'
-		optional project(':spring-statemachine-data-common:spring-statemachine-data-jpa')
-		optional project(':spring-statemachine-data-common:spring-statemachine-data-redis')
-		optional project(':spring-statemachine-data-common:spring-statemachine-data-mongodb')
-		optional 'org.springframework.boot:spring-boot-autoconfigure-processor'
-		optional 'io.micrometer:micrometer-core'
-		optional 'org.eclipse.persistence:javax.persistence'
-		optional 'org.springframework.boot:spring-boot-starter-data-jpa'
-		optional 'org.springframework.boot:spring-boot-starter-data-redis'
-		optional 'org.springframework.boot:spring-boot-starter-data-mongodb'
-		testRuntime 'com.h2database:h2'
-		testCompile 'org.springframework.boot:spring-boot-test'
-		testCompile 'org.springframework:spring-test'
-		testCompile 'org.hamcrest:hamcrest-core'
-		testCompile 'org.hamcrest:hamcrest-library'
-		testCompile("org.junit.jupiter:junit-jupiter-api")
-		testCompile("org.junit.jupiter:junit-jupiter-engine")
-	}
+    description = 'Spring State Machine Boot Autoconfigure'
+
+    dependencies {
+        compile project(':spring-statemachine-core')
+        compile 'org.springframework.boot:spring-boot-autoconfigure'
+        compile 'org.springframework.boot:spring-boot-actuator-autoconfigure'
+        compile 'org.springframework.boot:spring-boot-actuator'
+        optional project(':spring-statemachine-data-common:spring-statemachine-data-jpa')
+        optional project(':spring-statemachine-data-common:spring-statemachine-data-redis')
+        optional project(':spring-statemachine-data-common:spring-statemachine-data-mongodb')
+        optional 'org.springframework.boot:spring-boot-autoconfigure-processor'
+        optional 'io.micrometer:micrometer-core'
+        optional 'org.eclipse.persistence:javax.persistence'
+        optional 'org.springframework.boot:spring-boot-starter-data-jpa'
+        optional 'org.springframework.boot:spring-boot-starter-data-redis'
+        optional 'org.springframework.boot:spring-boot-starter-data-mongodb'
+        testRuntime 'com.h2database:h2'
+        testCompile 'org.springframework.boot:spring-boot-test'
+        testCompile 'org.springframework:spring-test'
+        testCompile 'org.hamcrest:hamcrest-core'
+        testCompile 'org.hamcrest:hamcrest-library'
+        testCompile("org.junit.jupiter:junit-jupiter-api")
+        testCompile("org.junit.jupiter:junit-jupiter-engine")
+    }
 }
 
 project('spring-statemachine-test') {
-	description = "Spring State Machine Test"
-
-	dependencies {
-		compile 'org.springframework:spring-context'
-		compile project(':spring-statemachine-core')
-		compile 'org.springframework:spring-test'
-		compile 'org.hamcrest:hamcrest-core'
-		compile 'org.hamcrest:hamcrest-library'
-		compile 'junit:junit'
-		compile 'org.junit.jupiter:junit-jupiter-api'
-		compile 'org.junit.vintage:junit-vintage-engine'
-		compile 'org.assertj:assertj-core'
-		testCompile('org.mockito:mockito-core') { dep ->
-			exclude group: 'org.hamcrest'
-		}
-		testCompile project(path:':spring-statemachine-core', configuration:'testArtifacts')
-		testCompile 'io.projectreactor:reactor-test'
-	}
+    description = "Spring State Machine Test"
+
+    dependencies {
+        compile 'org.springframework:spring-context'
+        compile project(':spring-statemachine-core')
+        compile 'org.springframework:spring-test'
+        compile 'org.hamcrest:hamcrest-core'
+        compile 'org.hamcrest:hamcrest-library'
+        compile 'junit:junit'
+        compile 'org.junit.jupiter:junit-jupiter-api'
+        compile 'org.junit.vintage:junit-vintage-engine'
+        compile 'org.assertj:assertj-core'
+        testCompile('org.mockito:mockito-core') { dep ->
+            exclude group: 'org.hamcrest'
+        }
+        testCompile project(path: ':spring-statemachine-core', configuration: 'testArtifacts')
+        testCompile 'io.projectreactor:reactor-test'
+    }
 }
 
 project('spring-statemachine-kryo') {
-	description = 'Spring State Machine Kryo'
-
-	dependencies {
-		compile project(':spring-statemachine-core')
-		compile 'com.esotericsoftware:kryo-shaded'
-
-		testCompile (project(':spring-statemachine-test')) { dep ->
-			exclude group: 'junit', module: 'junit'
-			exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
-		}
-		testCompile 'org.springframework:spring-test'
-		testCompile 'org.hamcrest:hamcrest-core'
-		testCompile 'org.hamcrest:hamcrest-library'
-		testCompile("org.junit.jupiter:junit-jupiter-api")
-		testCompile("org.junit.jupiter:junit-jupiter-engine")
-		testRuntime 'org.apache.logging.log4j:log4j-core'
-	}
+    description = 'Spring State Machine Kryo'
+
+    dependencies {
+        compile project(':spring-statemachine-core')
+        compile 'com.esotericsoftware:kryo-shaded'
+
+        testCompile(project(':spring-statemachine-test')) { dep ->
+            exclude group: 'junit', module: 'junit'
+            exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
+        }
+        testCompile 'org.springframework:spring-test'
+        testCompile 'org.hamcrest:hamcrest-core'
+        testCompile 'org.hamcrest:hamcrest-library'
+        testCompile("org.junit.jupiter:junit-jupiter-api")
+        testCompile("org.junit.jupiter:junit-jupiter-engine")
+        testRuntime 'org.apache.logging.log4j:log4j-core'
+    }
 }
 
 project('spring-statemachine-zookeeper') {
-	description = 'Spring State Machine Zookeeper'
-
-	dependencies {
-		compile 'org.springframework:spring-context'
-		compile project(':spring-statemachine-core')
-		compile project(':spring-statemachine-kryo')
-		compile 'org.apache.curator:curator-recipes'
-		// github.com/spring-gradle-plugins/dependency-management-plugin/issues/136
-		runtime 'log4j:log4j'
-
-		testCompile (project(':spring-statemachine-test')) { dep ->
-			exclude group: 'junit', module: 'junit'
-			exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
-		}
-		testCompile 'org.apache.curator:curator-test'
-		testCompile 'org.springframework:spring-test'
-		testCompile 'org.hamcrest:hamcrest-core'
-		testCompile 'org.hamcrest:hamcrest-library'
-		testCompile("org.junit.jupiter:junit-jupiter-api")
-		testCompile("org.junit.jupiter:junit-jupiter-engine")
-		testRuntime 'org.apache.logging.log4j:log4j-core'
-	}
+    description = 'Spring State Machine Zookeeper'
+
+    dependencies {
+        compile 'org.springframework:spring-context'
+        compile project(':spring-statemachine-core')
+        compile project(':spring-statemachine-kryo')
+        compile 'org.apache.curator:curator-recipes'
+        // github.com/spring-gradle-plugins/dependency-management-plugin/issues/136
+        runtime 'log4j:log4j'
+
+        testCompile(project(':spring-statemachine-test')) { dep ->
+            exclude group: 'junit', module: 'junit'
+            exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
+        }
+        testCompile 'org.apache.curator:curator-test'
+        testCompile 'org.springframework:spring-test'
+        testCompile 'org.hamcrest:hamcrest-core'
+        testCompile 'org.hamcrest:hamcrest-library'
+        testCompile("org.junit.jupiter:junit-jupiter-api")
+        testCompile("org.junit.jupiter:junit-jupiter-engine")
+        testRuntime 'org.apache.logging.log4j:log4j-core'
+    }
 }
 
 project('spring-statemachine-data-common') {
-	configurations {
-		testArtifacts.extendsFrom testRuntime
-	}
-	dependencies {
-		compile project(':spring-statemachine-core')
-		compile project(':spring-statemachine-kryo')
-		compile 'org.springframework.data:spring-data-commons'
-		optional 'org.springframework.security:spring-security-core'
-		compile 'com.fasterxml.jackson.core:jackson-core'
-		compile 'com.fasterxml.jackson.core:jackson-databind'
-		testCompile (project(':spring-statemachine-test')) { dep ->
-			exclude group: 'junit', module: 'junit'
-			exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
-		}
-		testCompile project(path:':spring-statemachine-core', configuration:'testArtifacts')
-		testCompile 'io.projectreactor:reactor-test'
-		testCompile 'org.springframework.boot:spring-boot-starter-test'
-		testRuntime 'org.springframework.boot:spring-boot-starter-web'
-	}
-	task testJar(type: Jar) {
-		classifier = 'tests'
-		from sourceSets.test.output
-	}
-	artifacts {
-		testArtifacts testJar
-	}
+    configurations {
+        testArtifacts.extendsFrom testRuntime
+    }
+    dependencies {
+        compile project(':spring-statemachine-core')
+        compile project(':spring-statemachine-kryo')
+        compile 'org.springframework.data:spring-data-commons'
+        optional 'org.springframework.security:spring-security-core'
+        compile 'com.fasterxml.jackson.core:jackson-core'
+        compile 'com.fasterxml.jackson.core:jackson-databind'
+        testCompile(project(':spring-statemachine-test')) { dep ->
+            exclude group: 'junit', module: 'junit'
+            exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
+        }
+        testCompile project(path: ':spring-statemachine-core', configuration: 'testArtifacts')
+        testCompile 'io.projectreactor:reactor-test'
+        testCompile 'org.springframework.boot:spring-boot-starter-test'
+        testRuntime 'org.springframework.boot:spring-boot-starter-web'
+    }
+    task testJar(type: Jar) {
+        classifier = 'tests'
+        from sourceSets.test.output
+    }
+    artifacts {
+        testArtifacts testJar
+    }
 }
 
 project('spring-statemachine-cluster') {
-	description = 'Spring State Machine Cluster'
-
-	dependencies {
-		compile project(':spring-statemachine-zookeeper')
-		compile 'org.springframework.integration:spring-integration-zookeeper'
-
-		testCompile (project(':spring-statemachine-test')) { dep ->
-			exclude group: 'junit', module: 'junit'
-			exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
-		}
-		testCompile 'org.apache.curator:curator-test'
-		testCompile 'org.springframework:spring-test'
-		testCompile 'org.hamcrest:hamcrest-core'
-		testCompile 'org.hamcrest:hamcrest-library'
-		testCompile("org.junit.jupiter:junit-jupiter-api")
-		testCompile("org.junit.jupiter:junit-jupiter-engine")
-		testRuntime 'org.apache.logging.log4j:log4j-core'
-	}
+    description = 'Spring State Machine Cluster'
+
+    dependencies {
+        compile project(':spring-statemachine-zookeeper')
+        compile 'org.springframework.integration:spring-integration-zookeeper'
+
+        testCompile(project(':spring-statemachine-test')) { dep ->
+            exclude group: 'junit', module: 'junit'
+            exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
+        }
+        testCompile 'org.apache.curator:curator-test'
+        testCompile 'org.springframework:spring-test'
+        testCompile 'org.hamcrest:hamcrest-core'
+        testCompile 'org.hamcrest:hamcrest-library'
+        testCompile("org.junit.jupiter:junit-jupiter-api")
+        testCompile("org.junit.jupiter:junit-jupiter-engine")
+        testRuntime 'org.apache.logging.log4j:log4j-core'
+    }
 }
 
 project('spring-statemachine-uml') {
-	description = 'Spring State Machine Uml'
-
-	dependencies {
-		compile project(':spring-statemachine-core')
-		optional 'org.springframework.security:spring-security-core'
-
-		// these eclipse maven deps are simply broken
-		compile('org.eclipse.uml2:uml') { dep ->
-			exclude group: 'org.eclipse.core', module: 'runtime'
-			exclude group: 'org.eclipse.emf', module: 'ecore'
-			exclude group: 'org.eclipse.emf.ecore', module: 'xmi'
-			exclude group: 'org.eclipse.emf.mapping', module: 'ecore2xml'
-			exclude group: 'org.eclipse.uml2', module: 'common'
-			exclude group: 'org.eclipse.uml2', module: 'types'
-		}
-		compile('org.eclipse.uml2:types') { dep ->
-			exclude group: 'org.eclipse.core', module: 'runtime'
-			exclude group: 'org.eclipse.emf', module: 'ecore'
-			exclude group: 'org.eclipse.uml2', module: 'common'
-		}
-		compile('org.eclipse.uml2:common') { dep ->
-			exclude group: 'org.eclipse.core', module: 'runtime'
-			exclude group: 'org.eclipse.emf', module: 'ecore'
-		}
-		compile 'org.eclipse.emf:org.eclipse.emf.ecore.xmi'
-		compile 'org.eclipse.emf:org.eclipse.emf.ecore'
-		compile 'org.eclipse.emf:org.eclipse.emf.common'
-		testCompile project(path:':spring-statemachine-core', configuration:'testArtifacts')
-		testCompile 'io.projectreactor:reactor-test'
-		testCompile 'org.springframework:spring-test'
-		testCompile 'org.hamcrest:hamcrest-core'
-		testCompile 'org.hamcrest:hamcrest-library'
-		testCompile("org.junit.jupiter:junit-jupiter-api")
-		testCompile("org.junit.jupiter:junit-jupiter-engine")
-		testCompile 'org.awaitility:awaitility'
-		testRuntime 'org.apache.logging.log4j:log4j-core'
-	}
+    description = 'Spring State Machine Uml'
+
+    dependencies {
+        compile project(':spring-statemachine-core')
+        optional 'org.springframework.security:spring-security-core'
+
+        // these eclipse maven deps are simply broken
+        compile('org.eclipse.uml2:uml') { dep ->
+            exclude group: 'org.eclipse.core', module: 'runtime'
+            exclude group: 'org.eclipse.emf', module: 'ecore'
+            exclude group: 'org.eclipse.emf.ecore', module: 'xmi'
+            exclude group: 'org.eclipse.emf.mapping', module: 'ecore2xml'
+            exclude group: 'org.eclipse.uml2', module: 'common'
+            exclude group: 'org.eclipse.uml2', module: 'types'
+        }
+        compile('org.eclipse.uml2:types') { dep ->
+            exclude group: 'org.eclipse.core', module: 'runtime'
+            exclude group: 'org.eclipse.emf', module: 'ecore'
+            exclude group: 'org.eclipse.uml2', module: 'common'
+        }
+        compile('org.eclipse.uml2:common') { dep ->
+            exclude group: 'org.eclipse.core', module: 'runtime'
+            exclude group: 'org.eclipse.emf', module: 'ecore'
+        }
+        compile 'org.eclipse.emf:org.eclipse.emf.ecore.xmi'
+        compile 'org.eclipse.emf:org.eclipse.emf.ecore'
+        compile 'org.eclipse.emf:org.eclipse.emf.common'
+        testCompile project(path: ':spring-statemachine-core', configuration: 'testArtifacts')
+        testCompile 'io.projectreactor:reactor-test'
+        testCompile 'org.springframework:spring-test'
+        testCompile 'org.hamcrest:hamcrest-core'
+        testCompile 'org.hamcrest:hamcrest-library'
+        testCompile("org.junit.jupiter:junit-jupiter-api")
+        testCompile("org.junit.jupiter:junit-jupiter-engine")
+        testCompile 'org.awaitility:awaitility'
+        testRuntime 'org.apache.logging.log4j:log4j-core'
+    }
+}
+
+project('spring-statemachine-lock') {
+    description = 'Spring State Machine Lock'
+
+    dependencies {
+        compile project(':spring-statemachine-core')
+        compile group: 'net.javacrumbs.shedlock', name: 'shedlock-core', version: '3.0.0'
+        compile group: 'net.javacrumbs.shedlock', name: 'shedlock-provider-redis-spring', version: '3.0.0'
+        compile 'org.springframework:spring-messaging'
+
+        testCompile(project(':spring-statemachine-test')) { dep ->
+            exclude group: 'junit', module: 'junit'
+            exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
+        }
+        testCompile project(':spring-statemachine-data-common:spring-statemachine-data-redis')
+        testRuntime 'redis.clients:jedis'
+        testCompile('org.mockito:mockito-core') { dep ->
+            exclude group: 'org.hamcrest'
+        }
+        testCompile 'org.springframework:spring-test'
+        testCompile 'org.hamcrest:hamcrest-core'
+        testCompile 'org.hamcrest:hamcrest-library'
+        testCompile("org.junit.jupiter:junit-jupiter-api")
+        testCompile("org.junit.jupiter:junit-jupiter-engine")
+        testRuntime 'org.apache.logging.log4j:log4j-core'
+    }
 }
 
 project('spring-statemachine-build-tests') {
-	description = 'Spring State Machine Build Tests'
-	tasks.findByPath('artifactoryPublish')?.enabled = false
-
-	dependencies {
-		testCompile project(':spring-statemachine-uml')
-		testCompile project(':spring-statemachine-test')
-		testCompile project(':spring-statemachine-data-common:spring-statemachine-data-jpa')
-		testCompile project(':spring-statemachine-data-common:spring-statemachine-data-redis')
-		testCompile project(':spring-statemachine-data-common:spring-statemachine-data-mongodb')
-		testCompile project(path:':spring-statemachine-core', configuration:'testArtifacts')
-		testCompile 'io.projectreactor:reactor-test'
-		testCompile 'org.apache.commons:commons-pool2'
-		testRuntime 'org.springframework.boot:spring-boot-starter-data-mongodb'
-		testRuntime 'org.springframework.boot:spring-boot-starter-data-redis'
-		testRuntime 'redis.clients:jedis'
-		testCompile 'org.springframework.boot:spring-boot-starter-data-jpa'
-		testCompile 'com.h2database:h2'
-		testCompile 'org.springframework.boot:spring-boot-starter'
-		testCompile 'org.springframework:spring-test'
-		testCompile 'org.hamcrest:hamcrest-core'
-		testCompile 'org.hamcrest:hamcrest-library'
-	}
+    description = 'Spring State Machine Build Tests'
+    tasks.findByPath('artifactoryPublish')?.enabled = false
+
+    dependencies {
+        testCompile project(':spring-statemachine-uml')
+        testCompile project(':spring-statemachine-test')
+        testCompile project(':spring-statemachine-data-common:spring-statemachine-data-jpa')
+        testCompile project(':spring-statemachine-data-common:spring-statemachine-data-redis')
+        testCompile project(':spring-statemachine-data-common:spring-statemachine-data-mongodb')
+        testCompile project(path: ':spring-statemachine-core', configuration: 'testArtifacts')
+        testCompile 'io.projectreactor:reactor-test'
+        testCompile 'org.apache.commons:commons-pool2'
+        testRuntime 'org.springframework.boot:spring-boot-starter-data-mongodb'
+        testRuntime 'org.springframework.boot:spring-boot-starter-data-redis'
+        testRuntime 'redis.clients:jedis'
+        testCompile 'org.springframework.boot:spring-boot-starter-data-jpa'
+        testCompile 'com.h2database:h2'
+        testCompile 'org.springframework.boot:spring-boot-starter'
+        testCompile 'org.springframework:spring-test'
+        testCompile 'org.hamcrest:hamcrest-core'
+        testCompile 'org.hamcrest:hamcrest-library'
+    }
 }
 
 configure(recipeProjects()) {
-	dependencies {
-		compile project(':spring-statemachine-recipes-common')
-		testCompile project(path:':spring-statemachine-core', configuration:'testArtifacts')
-		testCompile 'io.projectreactor:reactor-test'
-		testCompile 'org.springframework:spring-test'
-		testCompile 'org.hamcrest:hamcrest-core'
-		testCompile 'org.hamcrest:hamcrest-library'
-		testCompile("org.junit.jupiter:junit-jupiter-api")
-		testCompile("org.junit.jupiter:junit-jupiter-engine")
-	}
+    dependencies {
+        compile project(':spring-statemachine-recipes-common')
+        testCompile project(path: ':spring-statemachine-core', configuration: 'testArtifacts')
+        testCompile 'io.projectreactor:reactor-test'
+        testCompile 'org.springframework:spring-test'
+        testCompile 'org.hamcrest:hamcrest-core'
+        testCompile 'org.hamcrest:hamcrest-library'
+        testCompile("org.junit.jupiter:junit-jupiter-api")
+        testCompile("org.junit.jupiter:junit-jupiter-engine")
+    }
 }
 
 project('spring-statemachine-recipes-common') {
-	dependencies {
-		compile 'org.springframework:spring-context'
-		compile project(':spring-statemachine-core')
-		testCompile project(path:':spring-statemachine-core', configuration:'testArtifacts')
-		testCompile 'io.projectreactor:reactor-test'
-		testCompile 'org.springframework:spring-test'
-		testCompile 'org.hamcrest:hamcrest-core'
-		testCompile 'org.hamcrest:hamcrest-library'
-		testCompile("org.junit.jupiter:junit-jupiter-api")
-		testCompile("org.junit.jupiter:junit-jupiter-engine")
-	}
+    dependencies {
+        compile 'org.springframework:spring-context'
+        compile project(':spring-statemachine-core')
+        testCompile project(path: ':spring-statemachine-core', configuration: 'testArtifacts')
+        testCompile 'io.projectreactor:reactor-test'
+        testCompile 'org.springframework:spring-test'
+        testCompile 'org.hamcrest:hamcrest-core'
+        testCompile 'org.hamcrest:hamcrest-library'
+        testCompile("org.junit.jupiter:junit-jupiter-api")
+        testCompile("org.junit.jupiter:junit-jupiter-engine")
+    }
 }
 
 project('spring-statemachine-bom') {
-	description = 'Spring Statemachine (Bill of Materials)'
-
-	dependencyManagement {
-		generatedPomCustomization {
-			enabled = false
-		}
-	}
-
-	configurations.archives.artifacts.clear()
-	artifacts {
-		// work around GRADLE-2406 by attaching text artifact
-		archives(file('spring-statemachine-bom.txt'))
-	}
-
-	install {
-		repositories.mavenInstaller {
-			pom.whenConfigured {
-				packaging = 'pom'
-				withXml {
-					asNode().children().last() + {
-						delegate.dependencyManagement {
-							delegate.dependencies {
-								parent.subprojects.sort { "$it.name" }.each { p ->
-									if (!p.name.contains('spring-statemachine-samples') &&
-											!p.name.contains('spring-statemachine-build-tests') &&
-											p != project) {
-										delegate.dependency {
-											delegate.groupId(p.group)
-											delegate.artifactId(p.name)
-											delegate.version(p.version)
-										}
-									}
-								}
-							}
-						}
-					}
-				}
-			}
-		}
-	}
+    description = 'Spring Statemachine (Bill of Materials)'
+
+    dependencyManagement {
+        generatedPomCustomization {
+            enabled = false
+        }
+    }
+
+    configurations.archives.artifacts.clear()
+    artifacts {
+        // work around GRADLE-2406 by attaching text artifact
+        archives(file('spring-statemachine-bom.txt'))
+    }
+
+    install {
+        repositories.mavenInstaller {
+            pom.whenConfigured {
+                packaging = 'pom'
+                withXml {
+                    asNode().children().last() + {
+                        delegate.dependencyManagement {
+                            delegate.dependencies {
+                                parent.subprojects.sort { "$it.name" }.each { p ->
+                                    if (!p.name.contains('spring-statemachine-samples') &&
+                                            !p.name.contains('spring-statemachine-build-tests') &&
+                                            p != project) {
+                                        delegate.dependency {
+                                            delegate.groupId(p.group)
+                                            delegate.artifactId(p.name)
+                                            delegate.version(p.version)
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
 
 }
 
 project('spring-statemachine-starter') {
-	description = 'Spring Statemachine Starter'
-	dependencies {
-		compile project(':spring-statemachine-autoconfigure')
-		compile 'org.springframework.boot:spring-boot-starter'
-	}
-
-	install {
-		repositories.mavenInstaller {
-			pom.whenConfigured {
-				withXml {
-					asNode().children().first() + {
-						delegate.parent {
-							delegate.groupId('org.springframework.boot')
-							delegate.artifactId('spring-boot-starter-parent')
-							delegate.version("$springBootVersion")
-						}
-					}
-				}
-			}
-		}
-	}
+    description = 'Spring Statemachine Starter'
+    dependencies {
+        compile project(':spring-statemachine-autoconfigure')
+        compile 'org.springframework.boot:spring-boot-starter'
+    }
+
+    install {
+        repositories.mavenInstaller {
+            pom.whenConfigured {
+                withXml {
+                    asNode().children().first() + {
+                        delegate.parent {
+                            delegate.groupId('org.springframework.boot')
+                            delegate.artifactId('spring-boot-starter-parent')
+                            delegate.version("$springBootVersion")
+                        }
+                    }
+                }
+            }
+        }
+    }
 }
 
 configure(sampleProjects()) {
-	apply plugin: 'org.springframework.boot'
-	tasks.findByPath('artifactoryPublish')?.enabled = false
-	// as samples are not published, we can use jdk8
-	compileJava {
-		sourceCompatibility = 1.8
-		targetCompatibility = 1.8
-	}
-	dependencies {
-		compile project(':spring-statemachine-core')
-		compile 'org.springframework:spring-context-support'
-		testCompile('org.mockito:mockito-core') { dep ->
-			exclude group: 'org.hamcrest'
-		}
-		testCompile (project(':spring-statemachine-test')) { dep ->
-			exclude group: 'junit', module: 'junit'
-			exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
-		}
-		testCompile 'org.springframework.boot:spring-boot-test'
-		testCompile 'org.springframework:spring-test'
-		testCompile 'org.hamcrest:hamcrest-core'
-		testCompile 'org.hamcrest:hamcrest-library'
-		testCompile("org.junit.jupiter:junit-jupiter-api")
-		testCompile("org.junit.jupiter:junit-jupiter-engine")
-	}
-	bootJar {
-		excludeDevtools = false
-	}
-	build.dependsOn bootJar
+    apply plugin: 'org.springframework.boot'
+    tasks.findByPath('artifactoryPublish')?.enabled = false
+    // as samples are not published, we can use jdk8
+    compileJava {
+        sourceCompatibility = 1.8
+        targetCompatibility = 1.8
+    }
+    dependencies {
+        compile project(':spring-statemachine-core')
+        compile 'org.springframework:spring-context-support'
+        testCompile('org.mockito:mockito-core') { dep ->
+            exclude group: 'org.hamcrest'
+        }
+        testCompile(project(':spring-statemachine-test')) { dep ->
+            exclude group: 'junit', module: 'junit'
+            exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
+        }
+        testCompile 'org.springframework.boot:spring-boot-test'
+        testCompile 'org.springframework:spring-test'
+        testCompile 'org.hamcrest:hamcrest-core'
+        testCompile 'org.hamcrest:hamcrest-library'
+        testCompile("org.junit.jupiter:junit-jupiter-api")
+        testCompile("org.junit.jupiter:junit-jupiter-engine")
+    }
+    bootJar {
+        excludeDevtools = false
+    }
+    build.dependsOn bootJar
 }
 
 project('spring-statemachine-samples-common') {
-	tasks.findByPath('artifactoryPublish')?.enabled = false
-	dependencies {
-		compile project(':spring-statemachine-core')
-		compile 'org.springframework.shell:spring-shell'
-		compile 'org.springframework.boot:spring-boot-starter'
-		testCompile project(path:':spring-statemachine-core', configuration:'testArtifacts')
-	}
+    tasks.findByPath('artifactoryPublish')?.enabled = false
+    dependencies {
+        compile project(':spring-statemachine-core')
+        compile 'org.springframework.shell:spring-shell'
+        compile 'org.springframework.boot:spring-boot-starter'
+        testCompile project(path: ':spring-statemachine-core', configuration: 'testArtifacts')
+    }
 }
 
 configure(rootProject) {
-	description = 'Spring State Machine'
-
-	apply plugin: 'org.asciidoctor.gradle.asciidoctor'
-
-	dependencies {
-		// just used to get version into docs
-		compile 'org.springframework:spring-core'
-		compile 'org.springframework.boot:spring-boot'
-	}
-
-	// don't publish the default jar for the root project
-	configurations.archives.artifacts.clear()
-
-	afterEvaluate {
-		tasks.findAll { it.name.startsWith('reference') }.each{ it.dependsOn.add('asciidoctor') }
-	}
-
-	asciidoctorj {
-		version = '1.5.8.1'
-	}
-
-	configurations {
-		docs
-	}
-
-	task prepareAsciidocBuild(type: Sync) {
-		dependsOn configurations.docs
-		// copy doc resources
-		from {
-			configurations.docs.collect { zipTree(it) }
-		}
-		// and doc sources
-		from 'docs/src/reference/asciidoc/'
-		// to a build directory of your choice
-		into "$buildDir/asciidoc/assemble"
-	}
-
-	task('reference', type: org.asciidoctor.gradle.AsciidoctorTask){
-		dependsOn 'prepareAsciidocBuild'
-		dependsOn 'copyDocsSamples'
-		backends 'pdf'
-		sourceDir "$buildDir/asciidoc/assemble"
-		sources {
-			include 'index.adoc'
-		}
-		options doctype: 'book', eruby: 'erubis'
-		logDocuments = true
-		attributes 'icons': 'font',
-			'sectanchors': '',
-			'toc': '',
-			'source-highlighter' : 'coderay'
-	}
-
-	asciidoctor {
-		dependsOn 'prepareAsciidocBuild'
-		dependsOn 'copyDocsSamples'
-		backends 'html5'
-		sourceDir "$buildDir/asciidoc/assemble"
-		sources {
-			include 'index.adoc'
-		}
-		resources {
-			from(sourceDir) {
-				include 'images/*', 'css/**', 'js/**', 'samples/**'
-			}
-		}
-		options doctype: 'book', eruby: 'erubis'
-		logDocuments = true
-		attributes 'docinfo': 'shared',
-			toc: 'left',
-			'toc-levels': '4',
-			// use provided stylesheet
-			stylesdir: "css/",
-			stylesheet: 'spring.css',
-			'linkcss': true,
-			'icons': 'font',
-			'sectanchors': '',
-			// use provided highlighter
-			'source-highlighter=highlight.js',
-			'highlightjsdir=js/highlight',
-			'highlightjs-theme=atom-one-dark-reasonable',
-			'idprefix': '',
-			'idseparator': '-',
-			'spring-statemachine-version' : project.version,
-			'spring-version' : getResolvedVersionOf("spring-core"),
-			'spring-boot-version' : getResolvedVersionOf("spring-boot"),
-			revnumber : project.version
-	}
-
-	dependencies { // for integration tests
-		docs "io.spring.docresources:spring-doc-resources:${docResourcesVersion}@zip"
-	}
-
-	task copyDocsSamples(type: Copy) {
-		from 'spring-statemachine-core/src/test/java/org/springframework/statemachine/docs'
-		from 'spring-statemachine-test/src/test/java/org/springframework/statemachine/test/docs'
-		from 'spring-statemachine-recipes/src/test/java/org/springframework/statemachine/recipes/docs'
-		from 'spring-statemachine-zookeeper/src/test/java/org/springframework/statemachine/zookeeper/docs'
-		from 'spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/docs'
-		from 'spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/docs'
-		from 'spring-statemachine-data/jpa/src/test/java/org/springframework/statemachine/data/jpa/docs'
-		from 'spring-statemachine-data/redis/src/test/java/org/springframework/statemachine/data/redis/docs'
-		from 'spring-statemachine-data/mongodb/src/test/java/org/springframework/statemachine/data/mongodb/docs'
-		from 'spring-statemachine-samples/src/main/java/'
-		from 'spring-statemachine-samples/washer/src/main/java/'
-		from 'spring-statemachine-samples/tasks/src/main/java/'
-		from 'spring-statemachine-samples/turnstile/src/main/java/'
-		from 'spring-statemachine-samples/turnstilereactive/src/main/java/'
-		from 'spring-statemachine-samples/showcase/src/main/java/'
-		from 'spring-statemachine-samples/cdplayer/src/main/java/'
-		from 'spring-statemachine-samples/persist/src/main/java/'
-		from 'spring-statemachine-samples/zookeeper/src/main/java/'
-		from 'spring-statemachine-samples/security/src/main/java/'
-		from 'spring-statemachine-samples/eventservice/src/main/java/'
-		from 'spring-statemachine-samples/datajpa/src/main/java/'
-		from 'spring-statemachine-samples/datajpa/src/main/resources/'
-		from 'spring-statemachine-samples/datajpamultipersist/src/main/java/'
-		from 'spring-statemachine-samples/datajpamultipersist/src/main/resources/'
-		from 'spring-statemachine-samples/datapersist/src/main/java/'
-		from 'spring-statemachine-samples/monitoring/src/main/java/'
-		include '**/*.java'
-		include '**/*.uml'
-		include '**/*.json'
-		into 'docs/src/reference/asciidoc/samples'
-	}
-
-	task api(type: Javadoc) {
-		group = 'Documentation'
-		description = 'Generates aggregated Javadoc API documentation.'
-		title = "${rootProject.description} ${version} API"
-		options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED
-		options.author = true
-		options.header = rootProject.description
-		options.links(
-			'https://docs.jboss.org/jbossas/javadoc/4.0.5/connector'
-		)
-
-		// disable javadocs for samples
-		source subprojects
-		.findAll { project ->
-			!project.name.contains('samples')
-		}
-		.collect { project ->
-			project.sourceSets.main.allJava
-		}
-
-		destinationDir = new File(buildDir, "api")
-		classpath = files(subprojects.collect { project ->
-			project.sourceSets.main.compileClasspath
-		})
-		maxMemory = '1024m'
-	}
-
-	task docsZip(type: Zip) {
-		group = 'Distribution'
-		classifier = 'docs'
-		description = "Builds -${classifier} archive containing api and reference for deployment."
-		from('src/dist') {
-			include 'changelog.txt'
-		}
-		from (api) {
-			into 'api'
-		}
-		from (reference) {
-			into 'reference'
-			include 'index.pdf'
-		}
-		from (asciidoctor) {
-			into 'reference'
-			include 'index.html'
-			include 'js/**'
-			include 'css/**'
-			include 'images/**'
-			include 'samples/**'
-		}
-	}
-
-	task distZip(type: Zip, dependsOn: [docsZip]) {
-		group = 'Distribution'
-		classifier = 'dist'
-		description = "Builds -${classifier} archive, containing all jars and docs, " +
-			"suitable for community download page."
-
-		ext.baseDir = "${project.name}-${project.version}";
-
-		from('src/dist') {
-			include 'readme.txt'
-			include 'license.txt'
-			include 'notice.txt'
-			into "${baseDir}"
-			expand(copyright: new Date().format('yyyy'), version: project.version)
-		}
-
-		from(zipTree(docsZip.archivePath)) {
-			into "${baseDir}/docs"
-		}
-
-		subprojects.each { subproject ->
-			into ("${baseDir}/libs") {
-				from subproject.jar
-				if (subproject.tasks.findByPath('sourcesJar')) {
-					from subproject.sourcesJar
-				}
-				if (subproject.tasks.findByPath('javadocJar')) {
-					from subproject.javadocJar
-				}
-			}
-		}
-	}
-
-	artifacts {
-		archives docsZip
-		archives distZip
-	}
+    description = 'Spring State Machine'
+
+    apply plugin: 'org.asciidoctor.gradle.asciidoctor'
+
+    dependencies {
+        // just used to get version into docs
+        compile 'org.springframework:spring-core'
+        compile 'org.springframework.boot:spring-boot'
+    }
+
+    // don't publish the default jar for the root project
+    configurations.archives.artifacts.clear()
+
+    afterEvaluate {
+        tasks.findAll { it.name.startsWith('reference') }.each { it.dependsOn.add('asciidoctor') }
+    }
+
+    asciidoctorj {
+        version = '1.5.8.1'
+    }
+
+    configurations {
+        docs
+    }
+
+    task prepareAsciidocBuild(type: Sync) {
+        dependsOn configurations.docs
+        // copy doc resources
+        from {
+            configurations.docs.collect { zipTree(it) }
+        }
+        // and doc sources
+        from 'docs/src/reference/asciidoc/'
+        // to a build directory of your choice
+        into "$buildDir/asciidoc/assemble"
+    }
+
+    task('reference', type: org.asciidoctor.gradle.AsciidoctorTask) {
+        dependsOn 'prepareAsciidocBuild'
+        dependsOn 'copyDocsSamples'
+        backends 'pdf'
+        sourceDir "$buildDir/asciidoc/assemble"
+        sources {
+            include 'index.adoc'
+        }
+        options doctype: 'book', eruby: 'erubis'
+        logDocuments = true
+        attributes 'icons': 'font',
+                'sectanchors': '',
+                'toc': '',
+                'source-highlighter': 'coderay'
+    }
+
+    asciidoctor {
+        dependsOn 'prepareAsciidocBuild'
+        dependsOn 'copyDocsSamples'
+        backends 'html5'
+        sourceDir "$buildDir/asciidoc/assemble"
+        sources {
+            include 'index.adoc'
+        }
+        resources {
+            from(sourceDir) {
+                include 'images/*', 'css/**', 'js/**', 'samples/**'
+            }
+        }
+        options doctype: 'book', eruby: 'erubis'
+        logDocuments = true
+        attributes 'docinfo': 'shared',
+                toc: 'left',
+                'toc-levels': '4',
+                // use provided stylesheet
+                stylesdir: "css/",
+                stylesheet: 'spring.css',
+                'linkcss': true,
+                'icons': 'font',
+                'sectanchors': '',
+                // use provided highlighter
+                'source-highlighter=highlight.js',
+                'highlightjsdir=js/highlight',
+                'highlightjs-theme=atom-one-dark-reasonable',
+                'idprefix': '',
+                'idseparator': '-',
+                'spring-statemachine-version': project.version,
+                'spring-version': getResolvedVersionOf("spring-core"),
+                'spring-boot-version': getResolvedVersionOf("spring-boot"),
+                revnumber: project.version
+    }
+
+    dependencies { // for integration tests
+        docs "io.spring.docresources:spring-doc-resources:${docResourcesVersion}@zip"
+    }
+
+    task copyDocsSamples(type: Copy) {
+        from 'spring-statemachine-core/src/test/java/org/springframework/statemachine/docs'
+        from 'spring-statemachine-test/src/test/java/org/springframework/statemachine/test/docs'
+        from 'spring-statemachine-recipes/src/test/java/org/springframework/statemachine/recipes/docs'
+        from 'spring-statemachine-zookeeper/src/test/java/org/springframework/statemachine/zookeeper/docs'
+        from 'spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/docs'
+        from 'spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/docs'
+        from 'spring-statemachine-data/jpa/src/test/java/org/springframework/statemachine/data/jpa/docs'
+        from 'spring-statemachine-data/redis/src/test/java/org/springframework/statemachine/data/redis/docs'
+        from 'spring-statemachine-data/mongodb/src/test/java/org/springframework/statemachine/data/mongodb/docs'
+        from 'spring-statemachine-samples/src/main/java/'
+        from 'spring-statemachine-samples/washer/src/main/java/'
+        from 'spring-statemachine-samples/tasks/src/main/java/'
+        from 'spring-statemachine-samples/turnstile/src/main/java/'
+        from 'spring-statemachine-samples/turnstilereactive/src/main/java/'
+        from 'spring-statemachine-samples/showcase/src/main/java/'
+        from 'spring-statemachine-samples/cdplayer/src/main/java/'
+        from 'spring-statemachine-samples/persist/src/main/java/'
+        from 'spring-statemachine-samples/zookeeper/src/main/java/'
+        from 'spring-statemachine-samples/security/src/main/java/'
+        from 'spring-statemachine-samples/eventservice/src/main/java/'
+        from 'spring-statemachine-samples/datajpa/src/main/java/'
+        from 'spring-statemachine-samples/datajpa/src/main/resources/'
+        from 'spring-statemachine-samples/datajpamultipersist/src/main/java/'
+        from 'spring-statemachine-samples/datajpamultipersist/src/main/resources/'
+        from 'spring-statemachine-samples/datapersist/src/main/java/'
+        from 'spring-statemachine-samples/monitoring/src/main/java/'
+        include '**/*.java'
+        include '**/*.uml'
+        include '**/*.json'
+        into 'docs/src/reference/asciidoc/samples'
+    }
+
+    task api(type: Javadoc) {
+        group = 'Documentation'
+        description = 'Generates aggregated Javadoc API documentation.'
+        title = "${rootProject.description} ${version} API"
+        options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED
+        options.author = true
+        options.header = rootProject.description
+        options.links(
+                'https://docs.jboss.org/jbossas/javadoc/4.0.5/connector'
+        )
+
+        // disable javadocs for samples
+        source subprojects
+                .findAll { project ->
+                    !project.name.contains('samples')
+                }
+                .collect { project ->
+                    project.sourceSets.main.allJava
+                }
+
+        destinationDir = new File(buildDir, "api")
+        classpath = files(subprojects.collect { project ->
+            project.sourceSets.main.compileClasspath
+        })
+        maxMemory = '1024m'
+    }
+
+    task docsZip(type: Zip) {
+        group = 'Distribution'
+        classifier = 'docs'
+        description = "Builds -${classifier} archive containing api and reference for deployment."
+        from('src/dist') {
+            include 'changelog.txt'
+        }
+        from(api) {
+            into 'api'
+        }
+        from(reference) {
+            into 'reference'
+            include 'index.pdf'
+        }
+        from(asciidoctor) {
+            into 'reference'
+            include 'index.html'
+            include 'js/**'
+            include 'css/**'
+            include 'images/**'
+            include 'samples/**'
+        }
+    }
+
+    task distZip(type: Zip, dependsOn: [docsZip]) {
+        group = 'Distribution'
+        classifier = 'dist'
+        description = "Builds -${classifier} archive, containing all jars and docs, " +
+                "suitable for community download page."
+
+        ext.baseDir = "${project.name}-${project.version}";
+
+        from('src/dist') {
+            include 'readme.txt'
+            include 'license.txt'
+            include 'notice.txt'
+            into "${baseDir}"
+            expand(copyright: new Date().format('yyyy'), version: project.version)
+        }
+
+        from(zipTree(docsZip.archivePath)) {
+            into "${baseDir}/docs"
+        }
+
+        subprojects.each { subproject ->
+            into("${baseDir}/libs") {
+                from subproject.jar
+                if (subproject.tasks.findByPath('sourcesJar')) {
+                    from subproject.sourcesJar
+                }
+                if (subproject.tasks.findByPath('javadocJar')) {
+                    from subproject.javadocJar
+                }
+            }
+        }
+    }
+
+    artifacts {
+        archives docsZip
+        archives distZip
+    }
 }
diff --git a/settings.gradle b/settings.gradle
index de7a9b251..a06d4cab5 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -5,6 +5,7 @@ include 'spring-statemachine-test'
 include 'spring-statemachine-kryo'
 include 'spring-statemachine-zookeeper'
 include 'spring-statemachine-cluster'
+include 'spring-statemachine-lock'
 include 'spring-statemachine-uml'
 include 'spring-statemachine-build-tests'
 include 'spring-statemachine-recipes'
diff --git a/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockInterceptor.java b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockInterceptor.java
new file mode 100755
index 000000000..21b2707ce
--- /dev/null
+++ b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockInterceptor.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.statemachine.lock;
+
+import org.springframework.messaging.Message;
+import org.springframework.statemachine.StateMachine;
+import org.springframework.statemachine.state.State;
+import org.springframework.statemachine.support.StateMachineInterceptorAdapter;
+import org.springframework.statemachine.transition.Transition;
+
+/**
+ * This aims to:
+ * - verify if there is a lock before executing the event, if there isn't the lock will be acquired and the event will be performed;
+ * - unlock the state machine when the state changes.
+ *
+ * @param <S> the type of state
+ * @param <E> the type of event
+ */
+public class LockInterceptor<S, E> extends StateMachineInterceptorAdapter<S, E> {
+
+    private LockService<S, E> lockService;
+    private int lockAtMostUntil;
+
+    public LockInterceptor(LockService<S, E> lockService, int lockAtMostUntil) {
+        this.lockService = lockService;
+        this.lockAtMostUntil = lockAtMostUntil;
+    }
+
+    @Override
+    public Message<E> preEvent(Message<E> message, StateMachine<S, E> stateMachine) {
+        boolean lockResult = this.lockService.lock(stateMachine, this.lockAtMostUntil);
+        return lockResult ? message : null;
+    }
+
+    @Override
+    public void postStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition, StateMachine<S, E> stateMachine) {
+        this.lockService.unLock(stateMachine);
+    }
+
+}
diff --git a/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockService.java b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockService.java
new file mode 100755
index 000000000..fa4507ed6
--- /dev/null
+++ b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockService.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.statemachine.lock;
+
+import org.springframework.statemachine.StateMachine;
+
+public interface LockService<S, E> {
+
+    /**
+     * Start the lock on a given state machine.
+     *
+     * @param stateMachine
+     * @param lockAtMostUntil the time in seconds that express when the lock expires. This is useful to limit deadlocks.
+     * @return
+     */
+    boolean lock(StateMachine<S, E> stateMachine, int lockAtMostUntil);
+
+    /**
+     * Unlock a target state machine.
+     *
+     * @param stateMachine
+     */
+    void unLock(StateMachine<S, E> stateMachine);
+
+}
diff --git a/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/shedlock/ShedLockService.java b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/shedlock/ShedLockService.java
new file mode 100755
index 000000000..6a5db7d4c
--- /dev/null
+++ b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/shedlock/ShedLockService.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.statemachine.lock.shedlock;
+
+import net.javacrumbs.shedlock.core.LockConfiguration;
+import net.javacrumbs.shedlock.core.LockProvider;
+import net.javacrumbs.shedlock.core.SimpleLock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.statemachine.StateMachine;
+import org.springframework.statemachine.lock.LockService;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ *
+ * This implementation uses ShedLock (https://github.com/jesty/ShedLock) in order to handle distributed locks on state machines.
+ * ShedLock offers many providers like Mongo, JDBC database, Redis, Hazelcast, ZooKeeper, CosmosDB or others.
+ *
+ * @param <S> the type of state
+ * @param <E> the type of event
+ */
+public class ShedLockService<S, E> implements LockService<S, E> {
+
+    private final static Logger log = LoggerFactory.getLogger(ShedLockService.class);
+
+    private final Map<String, SimpleLock> locks;
+    private final LockProvider lockProvider;
+
+    public ShedLockService(LockProvider lockProvider) {
+        this.lockProvider = lockProvider;
+        this.locks = new ConcurrentHashMap<>();
+    }
+
+    @Override
+    public boolean lock(StateMachine<S, E> stateMachine, int lockAtMostUntil) {
+        String id = stateMachine.getId();
+        LockConfiguration lockConfiguration = new LockConfiguration(id, Instant.now().plus(Duration.ofSeconds(lockAtMostUntil)));
+        Optional<SimpleLock> simpleLock = lockProvider.lock(lockConfiguration);
+        if (simpleLock.isPresent()) {
+            log.trace("Lock acquired for state machine with id {}, simpleLock: {}", id, simpleLock);
+            locks.put(id, simpleLock.get());
+            return true;
+        } else {
+            log.warn("Cannot acquire lock for state machine with id {}", id);
+            return false;
+        }
+    }
+
+    @Override
+    public void unLock(StateMachine<S, E> stateMachine) {
+        String id = stateMachine.getId();
+        SimpleLock simpleLock = locks.remove(id);
+        if (simpleLock != null) {
+            log.trace("Starting unlock on {}", id);
+            simpleLock.unlock();
+        }
+    }
+
+}
diff --git a/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockInterceptorTest.java b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockInterceptorTest.java
new file mode 100644
index 000000000..0ff34576d
--- /dev/null
+++ b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockInterceptorTest.java
@@ -0,0 +1,61 @@
+package org.springframework.statemachine.cluster;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.messaging.Message;
+import org.springframework.statemachine.StateMachine;
+import org.springframework.statemachine.lock.LockInterceptor;
+import org.springframework.statemachine.lock.LockService;
+import org.springframework.statemachine.state.State;
+import org.springframework.statemachine.transition.Transition;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+import static org.mockito.internal.verification.VerificationModeFactory.times;
+
+public class LockInterceptorTest {
+
+    @Test
+    public void postStateChangeTest(){
+        LockService<String, String> service = mock(LockService.class);
+        LockInterceptor<String, String> interceptor = new LockInterceptor<>(service, 120);
+        StateMachine<String, String> stateMachine = mock(StateMachine.class);
+        State<String, String> state = mock(State.class);
+        Message<String> message = mock(Message.class);
+        Transition<String, String> transition = mock(Transition.class);
+
+        interceptor.postStateChange(state, message, transition, stateMachine);
+
+        verify(service, times(1)).unLock(stateMachine);
+    }
+
+    @Test
+    public void preEventTestLockAcquired(){
+        LockService<String, String> service = mock(LockService.class);
+        LockInterceptor<String, String> interceptor = new LockInterceptor<>(service, 120);
+        Message<String> message = mock(Message.class);
+        StateMachine<String, String> stateMachine = mock(StateMachine.class);
+
+        when(service.lock(stateMachine, 120)).thenReturn(true);
+
+        Message<String> result = interceptor.preEvent(message, stateMachine);
+        assertThat(result).isEqualTo(message);
+
+        verify(service, times(1)).lock(stateMachine, 120);
+    }
+
+    @Test
+    public void preEventTestAlreadyLocked(){
+        LockService<String, String> service = mock(LockService.class);
+        LockInterceptor<String, String> interceptor = new LockInterceptor<>(service, 120);
+        Message<String> message = mock(Message.class);
+        StateMachine<String, String> stateMachine = mock(StateMachine.class);
+
+        when(service.lock(stateMachine, 120)).thenReturn(false);
+
+        Message<String> result = interceptor.preEvent(message, stateMachine);
+        assertThat(result).isNull();
+
+        verify(service, times(1)).lock(stateMachine, 120);
+    }
+
+}
diff --git a/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/ShedLockServiceTest.java b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/ShedLockServiceTest.java
new file mode 100644
index 000000000..06dc253cc
--- /dev/null
+++ b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/ShedLockServiceTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.statemachine.cluster;
+
+import net.javacrumbs.shedlock.core.LockProvider;
+import net.javacrumbs.shedlock.provider.redis.spring.RedisLockProvider;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.statemachine.StateMachine;
+import org.springframework.statemachine.StateMachineEventResult;
+import org.springframework.statemachine.config.EnableStateMachineFactory;
+import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
+import org.springframework.statemachine.config.StateMachineFactory;
+import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
+import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
+import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
+import org.springframework.statemachine.lock.LockInterceptor;
+import org.springframework.statemachine.lock.LockService;
+import org.springframework.statemachine.lock.shedlock.ShedLockService;
+import org.springframework.statemachine.support.StateMachineInterceptor;
+import reactor.core.publisher.Mono;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ShedLockServiceTest {
+
+    protected AnnotationConfigApplicationContext context;
+
+    @BeforeEach
+    public void setup() {
+        context = buildContext();
+        context.register(ShedLockConfig.class, Config1.class);
+        context.refresh();
+        context.getBean(RedisConnectionFactory.class).getConnection().flushAll();
+    }
+
+    @AfterEach
+    public void clean() {
+        if (context != null) {
+            context.close();
+        }
+    }
+
+    protected AnnotationConfigApplicationContext buildContext() {
+        return new AnnotationConfigApplicationContext();
+    }
+
+    @Test
+    public void testLockOnLockedMachine() {
+        LockService<String, String> lockService = context.getBean(LockService.class);
+        StateMachine<String, String> stateMachine = getStateMachineWithInterceptor();
+
+        boolean lock = lockService.lock(stateMachine, 120);
+        assertThat(lock).isTrue();
+
+        StateMachineEventResult<String, String> lockResult = stateMachine.sendEvent(Mono.just(buildE1Event())).blockLast();
+        assertThat(lockResult).isNull();
+        assertThat(stateMachine.getState().getId()).isEqualTo("S1");
+    }
+
+    @Test
+    public void testLock() {
+        StateMachine<String, String> stateMachine = getStateMachineWithInterceptor();
+        assertThat(stateMachine.getState().getId()).isEqualTo("S1");
+        StateMachineEventResult<String, String> lockResult = stateMachine.sendEvent(Mono.just(buildE1Event())).blockLast();
+        assertThat(lockResult).isNotNull();
+        assertThat(stateMachine.getState().getId()).isEqualTo("S2");
+    }
+
+    private StateMachine<String, String> getStateMachineWithInterceptor() {
+        StateMachineFactory<String, String> factory = context.getBean(StateMachineFactory.class);
+        StateMachine<String, String> stateMachine = factory.getStateMachine("testId");
+        LockService<String, String> lockService = context.getBean(LockService.class);
+        StateMachineInterceptor<String, String> lockInterceptor = new LockInterceptor<>(lockService, 120);
+        stateMachine.getStateMachineAccessor().doWithAllRegions(region -> region.addStateMachineInterceptor(lockInterceptor));
+        return stateMachine;
+    }
+
+    private Message<String> buildE1Event() {
+        return MessageBuilder
+                .withPayload("E1")
+                .build();
+    }
+
+    @Configuration
+    protected static class ShedLockConfig {
+
+        @Bean
+        public RedisConnectionFactory redisConnectionFactory() {
+            return new JedisConnectionFactory();
+        }
+
+        @Bean
+        public LockService lockService(RedisConnectionFactory connectionFactory) {
+            LockProvider lockProvider = new RedisLockProvider(connectionFactory, "test");
+            return new ShedLockService(lockProvider);
+        }
+
+    }
+
+    @Configuration
+    @EnableStateMachineFactory
+    static class Config1 extends StateMachineConfigurerAdapter<String, String> {
+
+        @Override
+        public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
+            config
+                    .withConfiguration()
+                    .autoStartup(true);
+        }
+
+        @Override
+        public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
+            states
+                    .withStates()
+                    .initial("S1")
+                    .state("S2")
+                    .state("S3");
+        }
+
+        @Override
+        public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
+            transitions
+                    .withExternal()
+                    .source("S1").target("S2")
+                    .event("E1")
+                    .action(stateContext -> System.out.println("Action on " + stateContext.getStateMachine().getId()))
+                    .and()
+                    .withExternal()
+                    .source("S2").target("S3")
+                    .event("E2")
+                    .and()
+                    .withExternal()
+                    .source("S3").target("S1")
+                    .event("E3");
+        }
+
+    }
+
+}

From f7179957f326087f25a91a845a4ea73e6fd1e956 Mon Sep 17 00:00:00 2001
From: Davide Cerbo <davide.cerbo@alliance-healthcare.net>
Date: Mon, 14 Oct 2019 16:45:34 +0200
Subject: [PATCH 2/4] use Guard and Listener to handle lock

---
 .../statemachine/lock/LockInterceptor.java    | 53 ----------------
 .../statemachine/lock/LockService.java        |  5 ++
 .../lock/LockStateMachineGuard.java           | 58 ++++++++++++++++++
 .../lock/LockStateMachineListener.java        | 50 +++++++++++++++
 .../cluster/LockInterceptorTest.java          | 61 -------------------
 .../cluster/LockStateMachineGuardTest.java    | 46 ++++++++++++++
 .../cluster/LockStateMachineListenerTest.java | 42 +++++++++++++
 .../cluster/ShedLockServiceTest.java          | 48 ++++++++++++---
 8 files changed, 239 insertions(+), 124 deletions(-)
 delete mode 100755 spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockInterceptor.java
 create mode 100644 spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockStateMachineGuard.java
 create mode 100644 spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockStateMachineListener.java
 delete mode 100644 spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockInterceptorTest.java
 create mode 100644 spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockStateMachineGuardTest.java
 create mode 100644 spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockStateMachineListenerTest.java

diff --git a/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockInterceptor.java b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockInterceptor.java
deleted file mode 100755
index 21b2707ce..000000000
--- a/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockInterceptor.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2015-2019 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.statemachine.lock;
-
-import org.springframework.messaging.Message;
-import org.springframework.statemachine.StateMachine;
-import org.springframework.statemachine.state.State;
-import org.springframework.statemachine.support.StateMachineInterceptorAdapter;
-import org.springframework.statemachine.transition.Transition;
-
-/**
- * This aims to:
- * - verify if there is a lock before executing the event, if there isn't the lock will be acquired and the event will be performed;
- * - unlock the state machine when the state changes.
- *
- * @param <S> the type of state
- * @param <E> the type of event
- */
-public class LockInterceptor<S, E> extends StateMachineInterceptorAdapter<S, E> {
-
-    private LockService<S, E> lockService;
-    private int lockAtMostUntil;
-
-    public LockInterceptor(LockService<S, E> lockService, int lockAtMostUntil) {
-        this.lockService = lockService;
-        this.lockAtMostUntil = lockAtMostUntil;
-    }
-
-    @Override
-    public Message<E> preEvent(Message<E> message, StateMachine<S, E> stateMachine) {
-        boolean lockResult = this.lockService.lock(stateMachine, this.lockAtMostUntil);
-        return lockResult ? message : null;
-    }
-
-    @Override
-    public void postStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition, StateMachine<S, E> stateMachine) {
-        this.lockService.unLock(stateMachine);
-    }
-
-}
diff --git a/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockService.java b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockService.java
index fa4507ed6..f9c1e6b07 100755
--- a/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockService.java
+++ b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockService.java
@@ -17,6 +17,11 @@
 
 import org.springframework.statemachine.StateMachine;
 
+/**
+ *
+ * @param <S> the type of state
+ * @param <E> the type of event
+ */
 public interface LockService<S, E> {
 
     /**
diff --git a/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockStateMachineGuard.java b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockStateMachineGuard.java
new file mode 100644
index 000000000..fcc4ed5fd
--- /dev/null
+++ b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockStateMachineGuard.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.statemachine.lock;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.statemachine.StateContext;
+import org.springframework.statemachine.guard.Guard;
+
+/**
+ * The guard will try to lock the state machine.
+ * @param <S> the type of state
+ * @param <E> the type of event
+ */
+public class LockStateMachineGuard<S, E> implements Guard<S, E> {
+
+    private final static Logger log = LoggerFactory.getLogger(LockStateMachineListener.class);
+
+    private final LockService<S, E> lockService;
+    private final int lockAtMostUntil;
+
+    /**
+     *
+     * @param lockService
+     * @param lockAtMostUntil defines when the lock expires
+     */
+    public LockStateMachineGuard(LockService<S, E> lockService, int lockAtMostUntil) {
+        this.lockService = lockService;
+        this.lockAtMostUntil = lockAtMostUntil;
+    }
+
+    /**
+     * @param stateContext
+     * @return true, if the lock has success, false otherwise.
+     */
+    @Override
+    public boolean evaluate(StateContext<S, E> stateContext) {
+        if (log.isDebugEnabled()) {
+			String id = stateContext.getStateMachine().getId();
+			log.debug("Starting lock on {} with event {} and status from {} to {}", id, stateContext.getEvent(), stateContext.getSource(), stateContext.getTarget());
+        }
+        return lockService.lock(stateContext.getStateMachine(), this.lockAtMostUntil);
+    }
+
+}
diff --git a/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockStateMachineListener.java b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockStateMachineListener.java
new file mode 100644
index 000000000..713f03f3d
--- /dev/null
+++ b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockStateMachineListener.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2015-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.statemachine.lock;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.statemachine.StateContext;
+import org.springframework.statemachine.StateContext.Stage;
+import org.springframework.statemachine.listener.StateMachineListenerAdapter;
+
+/**
+ * This listener is responsible of the unlock phase.
+ *
+ * @param <S> the type of state
+ * @param <E> the type of event
+ */
+public class LockStateMachineListener<S, E> extends StateMachineListenerAdapter<S, E> {
+
+    private final static Logger log = LoggerFactory.getLogger(LockStateMachineListener.class);
+
+    private final LockService<S, E> lockService;
+
+    public LockStateMachineListener(LockService<S, E> lockService) {
+        this.lockService = lockService;
+    }
+
+    @Override
+    public void stateContext(StateContext<S, E> stateContext) {
+        if (Stage.TRANSITION_END.equals(stateContext.getStage())) {
+            if (log.isDebugEnabled()) {
+                log.debug("Starting unlock for state machine with id: {}", stateContext.getStateMachine().getId());
+            }
+            lockService.unLock(stateContext.getStateMachine());
+        }
+    }
+
+}
diff --git a/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockInterceptorTest.java b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockInterceptorTest.java
deleted file mode 100644
index 0ff34576d..000000000
--- a/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockInterceptorTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package org.springframework.statemachine.cluster;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.messaging.Message;
-import org.springframework.statemachine.StateMachine;
-import org.springframework.statemachine.lock.LockInterceptor;
-import org.springframework.statemachine.lock.LockService;
-import org.springframework.statemachine.state.State;
-import org.springframework.statemachine.transition.Transition;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.*;
-import static org.mockito.internal.verification.VerificationModeFactory.times;
-
-public class LockInterceptorTest {
-
-    @Test
-    public void postStateChangeTest(){
-        LockService<String, String> service = mock(LockService.class);
-        LockInterceptor<String, String> interceptor = new LockInterceptor<>(service, 120);
-        StateMachine<String, String> stateMachine = mock(StateMachine.class);
-        State<String, String> state = mock(State.class);
-        Message<String> message = mock(Message.class);
-        Transition<String, String> transition = mock(Transition.class);
-
-        interceptor.postStateChange(state, message, transition, stateMachine);
-
-        verify(service, times(1)).unLock(stateMachine);
-    }
-
-    @Test
-    public void preEventTestLockAcquired(){
-        LockService<String, String> service = mock(LockService.class);
-        LockInterceptor<String, String> interceptor = new LockInterceptor<>(service, 120);
-        Message<String> message = mock(Message.class);
-        StateMachine<String, String> stateMachine = mock(StateMachine.class);
-
-        when(service.lock(stateMachine, 120)).thenReturn(true);
-
-        Message<String> result = interceptor.preEvent(message, stateMachine);
-        assertThat(result).isEqualTo(message);
-
-        verify(service, times(1)).lock(stateMachine, 120);
-    }
-
-    @Test
-    public void preEventTestAlreadyLocked(){
-        LockService<String, String> service = mock(LockService.class);
-        LockInterceptor<String, String> interceptor = new LockInterceptor<>(service, 120);
-        Message<String> message = mock(Message.class);
-        StateMachine<String, String> stateMachine = mock(StateMachine.class);
-
-        when(service.lock(stateMachine, 120)).thenReturn(false);
-
-        Message<String> result = interceptor.preEvent(message, stateMachine);
-        assertThat(result).isNull();
-
-        verify(service, times(1)).lock(stateMachine, 120);
-    }
-
-}
diff --git a/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockStateMachineGuardTest.java b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockStateMachineGuardTest.java
new file mode 100644
index 000000000..67c31330a
--- /dev/null
+++ b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockStateMachineGuardTest.java
@@ -0,0 +1,46 @@
+package org.springframework.statemachine.cluster;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.statemachine.StateContext;
+import org.springframework.statemachine.StateMachine;
+import org.springframework.statemachine.lock.LockService;
+import org.springframework.statemachine.lock.LockStateMachineGuard;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+
+public class LockStateMachineGuardTest {
+
+    @Test
+    public void testEvaluate(){
+        LockService service = mock(LockService.class);
+        StateContext stateContext = mock(StateContext.class);
+        LockStateMachineGuard lockStateMachineGuard = new LockStateMachineGuard(service, 120);
+        StateMachine stateMachine = mock(StateMachine.class);
+
+        when(stateContext.getStateMachine()).thenReturn(stateMachine);
+        when(service.lock(stateMachine, 120)).thenReturn(true);
+
+        boolean result = lockStateMachineGuard.evaluate(stateContext);
+        assertThat(result).isTrue();
+
+        verify(service, times(1)).lock(stateMachine, 120);
+    }
+
+    @Test
+    public void testEvaluateFails(){
+        LockService service = mock(LockService.class);
+        StateContext stateContext = mock(StateContext.class);
+        LockStateMachineGuard lockStateMachineGuard = new LockStateMachineGuard(service, 120);
+        StateMachine stateMachine = mock(StateMachine.class);
+
+        when(stateContext.getStateMachine()).thenReturn(stateMachine);
+        when(service.lock(stateMachine, 120)).thenReturn(true);
+
+        boolean result = lockStateMachineGuard.evaluate(stateContext);
+        assertThat(result).isTrue();
+
+        verify(service, times(1)).lock(stateMachine, 120);
+
+    }
+}
diff --git a/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockStateMachineListenerTest.java b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockStateMachineListenerTest.java
new file mode 100644
index 000000000..2e468cddc
--- /dev/null
+++ b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockStateMachineListenerTest.java
@@ -0,0 +1,42 @@
+package org.springframework.statemachine.cluster;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.statemachine.StateContext;
+import org.springframework.statemachine.StateMachine;
+import org.springframework.statemachine.lock.LockService;
+import org.springframework.statemachine.lock.LockStateMachineGuard;
+import org.springframework.statemachine.lock.LockStateMachineListener;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+
+public class LockStateMachineListenerTest {
+
+    @Test
+    public void stateContextCorrectStage(){
+        LockService service = mock(LockService.class);
+        LockStateMachineListener lockStateMachineListener = new LockStateMachineListener(service);
+        StateContext stateContext = mock(StateContext.class);
+        StateMachine stateMachine = mock(StateMachine.class);
+
+        when(stateContext.getStateMachine()).thenReturn(stateMachine);
+        when(stateContext.getStage()).thenReturn(StateContext.Stage.TRANSITION_END);
+        lockStateMachineListener.stateContext(stateContext);
+
+        verify(service, times(1)).unLock(stateMachine);
+    }
+
+    @Test
+    public void stateContextWrongState(){
+        LockService service = mock(LockService.class);
+        LockStateMachineListener lockStateMachineListener = new LockStateMachineListener(service);
+        StateContext stateContext = mock(StateContext.class);
+        StateMachine stateMachine = mock(StateMachine.class);
+
+        when(stateContext.getStateMachine()).thenReturn(stateMachine);
+        when(stateContext.getStage()).thenReturn(StateContext.Stage.TRANSITION);
+        lockStateMachineListener.stateContext(stateContext);
+
+        verify(service, never()).unLock(stateMachine);
+    }
+}
diff --git a/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/ShedLockServiceTest.java b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/ShedLockServiceTest.java
index 06dc253cc..9361c9374 100644
--- a/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/ShedLockServiceTest.java
+++ b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/ShedLockServiceTest.java
@@ -20,9 +20,11 @@
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnection;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
 import org.springframework.messaging.Message;
@@ -35,24 +37,26 @@
 import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
 import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
 import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
-import org.springframework.statemachine.lock.LockInterceptor;
 import org.springframework.statemachine.lock.LockService;
+import org.springframework.statemachine.lock.LockStateMachineGuard;
+import org.springframework.statemachine.lock.LockStateMachineListener;
 import org.springframework.statemachine.lock.shedlock.ShedLockService;
-import org.springframework.statemachine.support.StateMachineInterceptor;
 import reactor.core.publisher.Mono;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class ShedLockServiceTest {
 
-    protected AnnotationConfigApplicationContext context;
+    private AnnotationConfigApplicationContext context;
+    private RedisConnection connection;
 
     @BeforeEach
     public void setup() {
         context = buildContext();
         context.register(ShedLockConfig.class, Config1.class);
         context.refresh();
-        context.getBean(RedisConnectionFactory.class).getConnection().flushAll();
+        connection = context.getBean(RedisConnectionFactory.class).getConnection();
+        connection.flushAll();
     }
 
     @AfterEach
@@ -69,31 +73,50 @@ protected AnnotationConfigApplicationContext buildContext() {
     @Test
     public void testLockOnLockedMachine() {
         LockService<String, String> lockService = context.getBean(LockService.class);
-        StateMachine<String, String> stateMachine = getStateMachineWithInterceptor();
+        StateMachine<String, String> stateMachine = getStateMachine();
 
         boolean lock = lockService.lock(stateMachine, 120);
         assertThat(lock).isTrue();
 
         StateMachineEventResult<String, String> lockResult = stateMachine.sendEvent(Mono.just(buildE1Event())).blockLast();
-        assertThat(lockResult).isNull();
         assertThat(stateMachine.getState().getId()).isEqualTo("S1");
+        assertThat(connection.exists("job-lock:test:testId".getBytes())).isTrue();
     }
 
     @Test
     public void testLock() {
-        StateMachine<String, String> stateMachine = getStateMachineWithInterceptor();
+        StateMachine<String, String> stateMachine = getStateMachine();
         assertThat(stateMachine.getState().getId()).isEqualTo("S1");
         StateMachineEventResult<String, String> lockResult = stateMachine.sendEvent(Mono.just(buildE1Event())).blockLast();
         assertThat(lockResult).isNotNull();
+        assertThat(lockResult.getResultType()).isEqualTo(StateMachineEventResult.ResultType.ACCEPTED);
         assertThat(stateMachine.getState().getId()).isEqualTo("S2");
+        assertThat(connection.exists("job-lock:test:testId".getBytes())).isFalse();
     }
 
-    private StateMachine<String, String> getStateMachineWithInterceptor() {
+    @Test
+    public void testLockInSameState() {
+        StateMachine<String, String> stateMachine = getStateMachine();
+        assertThat(stateMachine.getState().getId()).isEqualTo("S1");
+        StateMachineEventResult<String, String> lockResult = stateMachine.sendEvent(Mono.just(buildE1Event())).blockLast();
+
+        assertThat(lockResult).isNotNull();
+        assertThat(stateMachine.getState().getId()).isEqualTo("S2");
+        assertThat(connection.exists("job-lock:test:testId".getBytes())).isFalse();
+
+        StateMachineEventResult<String, String> lockResultAfter = stateMachine.sendEvent(Mono.just(buildE1Event())).blockLast();
+        assertThat(lockResultAfter).isNotNull();
+        assertThat(stateMachine.getState().getId()).isEqualTo("S2");
+        assertThat(lockResult.getResultType()).isEqualTo(StateMachineEventResult.ResultType.ACCEPTED);
+        assertThat(connection.exists("job-lock:test:testId".getBytes())).isFalse();
+
+
+    }
+
+    private StateMachine<String, String> getStateMachine() {
         StateMachineFactory<String, String> factory = context.getBean(StateMachineFactory.class);
         StateMachine<String, String> stateMachine = factory.getStateMachine("testId");
         LockService<String, String> lockService = context.getBean(LockService.class);
-        StateMachineInterceptor<String, String> lockInterceptor = new LockInterceptor<>(lockService, 120);
-        stateMachine.getStateMachineAccessor().doWithAllRegions(region -> region.addStateMachineInterceptor(lockInterceptor));
         return stateMachine;
     }
 
@@ -123,10 +146,14 @@ public LockService lockService(RedisConnectionFactory connectionFactory) {
     @EnableStateMachineFactory
     static class Config1 extends StateMachineConfigurerAdapter<String, String> {
 
+        @Autowired
+        private LockService<String, String> lockService;
+
         @Override
         public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
             config
                     .withConfiguration()
+                    .listener(new LockStateMachineListener<>(lockService))
                     .autoStartup(true);
         }
 
@@ -143,6 +170,7 @@ public void configure(StateMachineStateConfigurer<String, String> states) throws
         public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
             transitions
                     .withExternal()
+                    .guard(new LockStateMachineGuard<>(lockService, 120))
                     .source("S1").target("S2")
                     .event("E1")
                     .action(stateContext -> System.out.println("Action on " + stateContext.getStateMachine().getId()))

From feebee5266aee8ab6edf39020087ca62fdbde75e Mon Sep 17 00:00:00 2001
From: Davide Cerbo <davide.cerbo@alliance-healthcare.net>
Date: Mon, 14 Oct 2019 18:03:04 +0200
Subject: [PATCH 3/4] added reddison implementation

---
 .../LeaderZookeeperStateMachineEnsemble.java  |   0
 .../shedlock}/AbstractZookeeperTests.java     |   0
 ...derZookeeperStateMachineEnsembleTests.java |   0
 spring-statemachine-lock/build.gradle         |  50 +++++
 .../lock/redisson/RedissonLockService.java    |  75 +++++++
 .../redisson/RedissonLockServiceTest.java     | 186 ++++++++++++++++++
 .../lock/shedlock/ShedLockService.java        |   0
 .../lock/shedlock}/ShedLockServiceTest.java   |   3 +-
 .../LockStateMachineGuardTest.java            |   2 +-
 .../LockStateMachineListenerTest.java         |   2 +-
 10 files changed, 314 insertions(+), 4 deletions(-)
 rename spring-statemachine-cluster/src/main/java/org/springframework/statemachine/{cluster => lock/shedlock}/LeaderZookeeperStateMachineEnsemble.java (100%)
 rename spring-statemachine-cluster/src/test/java/org/springframework/statemachine/{cluster => lock/shedlock}/AbstractZookeeperTests.java (100%)
 rename spring-statemachine-cluster/src/test/java/org/springframework/statemachine/{cluster => lock/shedlock}/LeaderZookeeperStateMachineEnsembleTests.java (100%)
 create mode 100644 spring-statemachine-lock/build.gradle
 create mode 100644 spring-statemachine-lock/redisson/src/main/java/org/springframework/statemachine/lock/redisson/RedissonLockService.java
 create mode 100644 spring-statemachine-lock/redisson/src/test/java/org/springframework/statemachine/lock/redisson/RedissonLockServiceTest.java
 rename spring-statemachine-lock/{ => shedlock}/src/main/java/org/springframework/statemachine/lock/shedlock/ShedLockService.java (100%)
 mode change 100755 => 100644
 rename spring-statemachine-lock/{src/test/java/org/springframework/statemachine/cluster => shedlock/src/test/java/org/springframework/statemachine/lock/shedlock}/ShedLockServiceTest.java (98%)
 rename spring-statemachine-lock/src/test/java/org/springframework/statemachine/{cluster => lock}/LockStateMachineGuardTest.java (97%)
 rename spring-statemachine-lock/src/test/java/org/springframework/statemachine/{cluster => lock}/LockStateMachineListenerTest.java (97%)

diff --git a/spring-statemachine-cluster/src/main/java/org/springframework/statemachine/cluster/LeaderZookeeperStateMachineEnsemble.java b/spring-statemachine-cluster/src/main/java/org/springframework/statemachine/lock/shedlock/LeaderZookeeperStateMachineEnsemble.java
similarity index 100%
rename from spring-statemachine-cluster/src/main/java/org/springframework/statemachine/cluster/LeaderZookeeperStateMachineEnsemble.java
rename to spring-statemachine-cluster/src/main/java/org/springframework/statemachine/lock/shedlock/LeaderZookeeperStateMachineEnsemble.java
diff --git a/spring-statemachine-cluster/src/test/java/org/springframework/statemachine/cluster/AbstractZookeeperTests.java b/spring-statemachine-cluster/src/test/java/org/springframework/statemachine/lock/shedlock/AbstractZookeeperTests.java
similarity index 100%
rename from spring-statemachine-cluster/src/test/java/org/springframework/statemachine/cluster/AbstractZookeeperTests.java
rename to spring-statemachine-cluster/src/test/java/org/springframework/statemachine/lock/shedlock/AbstractZookeeperTests.java
diff --git a/spring-statemachine-cluster/src/test/java/org/springframework/statemachine/cluster/LeaderZookeeperStateMachineEnsembleTests.java b/spring-statemachine-cluster/src/test/java/org/springframework/statemachine/lock/shedlock/LeaderZookeeperStateMachineEnsembleTests.java
similarity index 100%
rename from spring-statemachine-cluster/src/test/java/org/springframework/statemachine/cluster/LeaderZookeeperStateMachineEnsembleTests.java
rename to spring-statemachine-cluster/src/test/java/org/springframework/statemachine/lock/shedlock/LeaderZookeeperStateMachineEnsembleTests.java
diff --git a/spring-statemachine-lock/build.gradle b/spring-statemachine-lock/build.gradle
new file mode 100644
index 000000000..04918ec74
--- /dev/null
+++ b/spring-statemachine-lock/build.gradle
@@ -0,0 +1,50 @@
+description = 'Spring State Machine Lock Common'
+
+project('spring-statemachine-lock-shedlock') {
+	description = 'Spring State Machine Lock - ShedLock'
+	dependencies {
+		compile project(':spring-statemachine-lock-common')
+		compile group: 'net.javacrumbs.shedlock', name: 'shedlock-core', version: '3.0.0'
+		compile 'org.springframework:spring-messaging'
+
+		testCompile(project(':spring-statemachine-test')) { dep ->
+			exclude group: 'junit', module: 'junit'
+			exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
+		}
+		testRuntime 'redis.clients:jedis'
+		testCompile('org.mockito:mockito-core') { dep ->
+			exclude group: 'org.hamcrest'
+		}
+		testCompile group: 'net.javacrumbs.shedlock', name: 'shedlock-provider-redis-spring', version: '3.0.0'
+		testCompile 'org.springframework:spring-test'
+		testCompile 'org.hamcrest:hamcrest-core'
+		testCompile 'org.hamcrest:hamcrest-library'
+		testCompile("org.junit.jupiter:junit-jupiter-api")
+		testCompile("org.junit.jupiter:junit-jupiter-engine")
+		testRuntime 'org.apache.logging.log4j:log4j-core'
+	}
+}
+
+project('spring-statemachine-lock-redisson') {
+	description = 'Spring State Machine Lock - Redisson'
+	dependencies {
+		compile project(':spring-statemachine-lock-common')
+		compile 'org.redisson:redisson:3.11.4'
+		compile 'org.springframework:spring-messaging'
+
+		testCompile(project(':spring-statemachine-test')) { dep ->
+			exclude group: 'junit', module: 'junit'
+			exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
+		}
+		testRuntime 'redis.clients:jedis'
+		testCompile('org.mockito:mockito-core') { dep ->
+			exclude group: 'org.hamcrest'
+		}
+		testCompile 'org.springframework:spring-test'
+		testCompile 'org.hamcrest:hamcrest-core'
+		testCompile 'org.hamcrest:hamcrest-library'
+		testCompile("org.junit.jupiter:junit-jupiter-api")
+		testCompile("org.junit.jupiter:junit-jupiter-engine")
+		testRuntime 'org.apache.logging.log4j:log4j-core'
+	}
+}
\ No newline at end of file
diff --git a/spring-statemachine-lock/redisson/src/main/java/org/springframework/statemachine/lock/redisson/RedissonLockService.java b/spring-statemachine-lock/redisson/src/main/java/org/springframework/statemachine/lock/redisson/RedissonLockService.java
new file mode 100644
index 000000000..2ad0c9063
--- /dev/null
+++ b/spring-statemachine-lock/redisson/src/main/java/org/springframework/statemachine/lock/redisson/RedissonLockService.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2015-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.statemachine.lock.redisson;
+
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.statemachine.StateMachine;
+import org.springframework.statemachine.lock.LockService;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This implementation uses Redisson (https://github.com/redisson/redisson) in order to handle distributed locks on state machines.
+ * More info on https://carlosbecker.com/posts/distributed-locks-redis/ and https://github.com/redisson/redisson/wiki/8.-Distributed-locks-and-synchronizers
+ *
+ * @param <S> the type of state
+ * @param <E> the type of event
+ */
+public class RedissonLockService<S, E> implements LockService<S, E> {
+
+    private final static Logger log = LoggerFactory.getLogger(RedissonLockService.class);
+
+    private RedissonClient redissonClient;
+    private final Map<String, RLock> locks;
+
+
+    public RedissonLockService(RedissonClient redissonClient) {
+        this.redissonClient = redissonClient;
+        this.locks = new ConcurrentHashMap<>();
+    }
+
+    @Override
+    public boolean lock(StateMachine<S, E> stateMachine, int lockAtMostUntil) {
+        String id = stateMachine.getId();
+        RLock lock = this.redissonClient.getLock(id);
+        boolean result = false;
+        try {
+            log.trace("Lock acquired for state machine with id {}, Rlock: {}", id, lock);
+            lock.lock(lockAtMostUntil, TimeUnit.SECONDS);
+            this.locks.put(id, lock);
+            result = true;
+        } catch (IllegalStateException e) {
+            log.warn("Cannot acquire lock for state machine with id {}", id);
+        }
+        return result;
+    }
+
+    @Override
+    public void unLock(StateMachine<S, E> stateMachine) {
+        String id = stateMachine.getId();
+        RLock lock = locks.remove(id);
+        if (lock != null) {
+            log.trace("Starting unlock on {}", id);
+            lock.unlock();
+        }
+    }
+
+}
diff --git a/spring-statemachine-lock/redisson/src/test/java/org/springframework/statemachine/lock/redisson/RedissonLockServiceTest.java b/spring-statemachine-lock/redisson/src/test/java/org/springframework/statemachine/lock/redisson/RedissonLockServiceTest.java
new file mode 100644
index 000000000..242678b6a
--- /dev/null
+++ b/spring-statemachine-lock/redisson/src/test/java/org/springframework/statemachine/lock/redisson/RedissonLockServiceTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.statemachine.lock.redisson;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.Config;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.statemachine.StateMachine;
+import org.springframework.statemachine.StateMachineEventResult;
+import org.springframework.statemachine.config.EnableStateMachineFactory;
+import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
+import org.springframework.statemachine.config.StateMachineFactory;
+import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
+import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
+import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
+import org.springframework.statemachine.lock.LockService;
+import org.springframework.statemachine.lock.LockStateMachineGuard;
+import org.springframework.statemachine.lock.LockStateMachineListener;
+import reactor.core.publisher.Mono;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class RedissonLockServiceTest {
+
+    private AnnotationConfigApplicationContext context;
+    private RedissonClient connection;
+
+    @BeforeEach
+    public void setup() {
+        context = buildContext();
+        context.register(RedissonConfig.class, Config1.class);
+        context.refresh();
+        connection = context.getBean(RedissonClient.class);
+        connection.getKeys().flushall();
+    }
+
+    @AfterEach
+    public void clean() {
+        if (context != null) {
+            context.close();
+        }
+    }
+
+    protected AnnotationConfigApplicationContext buildContext() {
+        return new AnnotationConfigApplicationContext();
+    }
+
+    @Test
+    public void testLockOnLockedMachine() {
+        LockService<String, String> lockService = context.getBean(LockService.class);
+        StateMachine<String, String> stateMachine = getStateMachine();
+
+        boolean lock = lockService.lock(stateMachine, 120);
+        assertThat(lock).isTrue();
+
+        StateMachineEventResult<String, String> lockResult = stateMachine.sendEvent(Mono.just(buildE1Event())).blockLast();
+        assertThat(stateMachine.getState().getId()).isEqualTo("S1");
+        //assertThat(connection.getKeys().getkey("job-lock:test:testId".getBytes())).isTrue();
+    }
+
+    @Test
+    public void testLock() {
+        StateMachine<String, String> stateMachine = getStateMachine();
+        assertThat(stateMachine.getState().getId()).isEqualTo("S1");
+        StateMachineEventResult<String, String> lockResult = stateMachine.sendEvent(Mono.just(buildE1Event())).blockLast();
+        assertThat(lockResult).isNotNull();
+        assertThat(lockResult.getResultType()).isEqualTo(StateMachineEventResult.ResultType.ACCEPTED);
+        assertThat(stateMachine.getState().getId()).isEqualTo("S2");
+        //assertThat(connection.exists("job-lock:test:testId".getBytes())).isFalse();
+    }
+
+    @Test
+    public void testLockInSameState() {
+        StateMachine<String, String> stateMachine = getStateMachine();
+        assertThat(stateMachine.getState().getId()).isEqualTo("S1");
+        StateMachineEventResult<String, String> lockResult = stateMachine.sendEvent(Mono.just(buildE1Event())).blockLast();
+
+        assertThat(lockResult).isNotNull();
+        assertThat(stateMachine.getState().getId()).isEqualTo("S2");
+        //assertThat(connection.exists("job-lock:test:testId".getBytes())).isFalse();
+
+        StateMachineEventResult<String, String> lockResultAfter = stateMachine.sendEvent(Mono.just(buildE1Event())).blockLast();
+        assertThat(lockResultAfter).isNotNull();
+        assertThat(stateMachine.getState().getId()).isEqualTo("S2");
+        assertThat(lockResult.getResultType()).isEqualTo(StateMachineEventResult.ResultType.ACCEPTED);
+        //assertThat(connection.exists("job-lock:test:testId".getBytes())).isFalse();
+    }
+
+    private StateMachine<String, String> getStateMachine() {
+        StateMachineFactory<String, String> factory = context.getBean(StateMachineFactory.class);
+        StateMachine<String, String> stateMachine = factory.getStateMachine("testId");
+        LockService<String, String> lockService = context.getBean(LockService.class);
+        return stateMachine;
+    }
+
+    private Message<String> buildE1Event() {
+        return MessageBuilder
+                .withPayload("E1")
+                .build();
+    }
+
+    @Configuration
+    protected static class RedissonConfig {
+
+        @Bean
+        public RedissonClient redissonClient() {
+            Config config = new Config();
+            config.useSingleServer().setAddress("redis://localhost:6379");
+            return Redisson.create(config);
+        }
+
+        @Bean
+        public LockService lockService(RedissonClient redissonClient) {
+            return new RedissonLockService(redissonClient);
+        }
+
+    }
+
+
+    @Configuration
+    @EnableStateMachineFactory
+    static class Config1 extends StateMachineConfigurerAdapter<String, String> {
+
+        @Autowired
+        private LockService<String, String> lockService;
+
+        @Override
+        public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
+            config
+                    .withConfiguration()
+                    .listener(new LockStateMachineListener<>(lockService))
+                    .autoStartup(true);
+        }
+
+        @Override
+        public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
+            states
+                    .withStates()
+                    .initial("S1")
+                    .state("S2")
+                    .state("S3");
+        }
+
+        @Override
+        public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
+            transitions
+                    .withExternal()
+                    .guard(new LockStateMachineGuard<>(lockService, 120))
+                    .source("S1").target("S2")
+                    .event("E1")
+                    .action(stateContext -> System.out.println("Action on " + stateContext.getStateMachine().getId()))
+                    .and()
+                    .withExternal()
+                    .source("S2").target("S3")
+                    .event("E2")
+                    .and()
+                    .withExternal()
+                    .source("S3").target("S1")
+                    .event("E3");
+        }
+
+    }
+
+}
diff --git a/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/shedlock/ShedLockService.java b/spring-statemachine-lock/shedlock/src/main/java/org/springframework/statemachine/lock/shedlock/ShedLockService.java
old mode 100755
new mode 100644
similarity index 100%
rename from spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/shedlock/ShedLockService.java
rename to spring-statemachine-lock/shedlock/src/main/java/org/springframework/statemachine/lock/shedlock/ShedLockService.java
diff --git a/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/ShedLockServiceTest.java b/spring-statemachine-lock/shedlock/src/test/java/org/springframework/statemachine/lock/shedlock/ShedLockServiceTest.java
similarity index 98%
rename from spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/ShedLockServiceTest.java
rename to spring-statemachine-lock/shedlock/src/test/java/org/springframework/statemachine/lock/shedlock/ShedLockServiceTest.java
index 9361c9374..e4eee7092 100644
--- a/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/ShedLockServiceTest.java
+++ b/spring-statemachine-lock/shedlock/src/test/java/org/springframework/statemachine/lock/shedlock/ShedLockServiceTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springframework.statemachine.cluster;
+package org.springframework.statemachine.lock.shedlock;
 
 import net.javacrumbs.shedlock.core.LockProvider;
 import net.javacrumbs.shedlock.provider.redis.spring.RedisLockProvider;
@@ -40,7 +40,6 @@
 import org.springframework.statemachine.lock.LockService;
 import org.springframework.statemachine.lock.LockStateMachineGuard;
 import org.springframework.statemachine.lock.LockStateMachineListener;
-import org.springframework.statemachine.lock.shedlock.ShedLockService;
 import reactor.core.publisher.Mono;
 
 import static org.assertj.core.api.Assertions.assertThat;
diff --git a/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockStateMachineGuardTest.java b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/lock/LockStateMachineGuardTest.java
similarity index 97%
rename from spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockStateMachineGuardTest.java
rename to spring-statemachine-lock/src/test/java/org/springframework/statemachine/lock/LockStateMachineGuardTest.java
index 67c31330a..0146afc9a 100644
--- a/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockStateMachineGuardTest.java
+++ b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/lock/LockStateMachineGuardTest.java
@@ -1,4 +1,4 @@
-package org.springframework.statemachine.cluster;
+package org.springframework.statemachine.lock;
 
 import org.junit.jupiter.api.Test;
 import org.springframework.statemachine.StateContext;
diff --git a/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockStateMachineListenerTest.java b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/lock/LockStateMachineListenerTest.java
similarity index 97%
rename from spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockStateMachineListenerTest.java
rename to spring-statemachine-lock/src/test/java/org/springframework/statemachine/lock/LockStateMachineListenerTest.java
index 2e468cddc..d34927d13 100644
--- a/spring-statemachine-lock/src/test/java/org/springframework/statemachine/cluster/LockStateMachineListenerTest.java
+++ b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/lock/LockStateMachineListenerTest.java
@@ -1,4 +1,4 @@
-package org.springframework.statemachine.cluster;
+package org.springframework.statemachine.lock;
 
 import org.junit.jupiter.api.Test;
 import org.springframework.statemachine.StateContext;

From e2e81bd5248e81f695dc12265ba2dab8735d1772 Mon Sep 17 00:00:00 2001
From: davidecerbo <Asdrubale89>
Date: Mon, 14 Oct 2019 22:24:27 +0200
Subject: [PATCH 4/4] reviewed Redisson implementation

---
 build.gradle                                  |  4 +--
 settings.gradle                               | 11 +++++++-
 .../lock/redisson/RedissonLockService.java    | 16 +++++++-----
 .../redisson/RedissonLockServiceTest.java     | 25 ++++++++++---------
 .../lock/LockStateMachineGuard.java           | 12 ++++-----
 .../lock/LockStateMachineListener.java        |  8 +++---
 6 files changed, 44 insertions(+), 32 deletions(-)

diff --git a/build.gradle b/build.gradle
index 30ffedb1a..27816d608 100644
--- a/build.gradle
+++ b/build.gradle
@@ -414,13 +414,11 @@ project('spring-statemachine-uml') {
     }
 }
 
-project('spring-statemachine-lock') {
+project('spring-statemachine-lock-common') {
     description = 'Spring State Machine Lock'
 
     dependencies {
         compile project(':spring-statemachine-core')
-        compile group: 'net.javacrumbs.shedlock', name: 'shedlock-core', version: '3.0.0'
-        compile group: 'net.javacrumbs.shedlock', name: 'shedlock-provider-redis-spring', version: '3.0.0'
         compile 'org.springframework:spring-messaging'
 
         testCompile(project(':spring-statemachine-test')) { dep ->
diff --git a/settings.gradle b/settings.gradle
index a06d4cab5..77e40ea2e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -5,7 +5,6 @@ include 'spring-statemachine-test'
 include 'spring-statemachine-kryo'
 include 'spring-statemachine-zookeeper'
 include 'spring-statemachine-cluster'
-include 'spring-statemachine-lock'
 include 'spring-statemachine-uml'
 include 'spring-statemachine-build-tests'
 include 'spring-statemachine-recipes'
@@ -38,6 +37,10 @@ include 'spring-statemachine-data:jpa'
 include 'spring-statemachine-data:redis'
 include 'spring-statemachine-data:mongodb'
 
+include 'spring-statemachine-lock'
+include 'spring-statemachine-lock:shedlock'
+include 'spring-statemachine-lock:redisson'
+
 rootProject.children.find {
 	if (it.name == 'spring-statemachine-recipes') {
 		it.name = 'spring-statemachine-recipes-common'
@@ -54,5 +57,11 @@ rootProject.children.find {
 			it.name = 'spring-statemachine-data-' + it.name
 		}
 	}
+	if (it.name == 'spring-statemachine-lock') {
+		it.name = 'spring-statemachine-lock-common'
+		it.children.each {
+			it.name = 'spring-statemachine-lock-' + it.name
+		}
+	}
 }
 
diff --git a/spring-statemachine-lock/redisson/src/main/java/org/springframework/statemachine/lock/redisson/RedissonLockService.java b/spring-statemachine-lock/redisson/src/main/java/org/springframework/statemachine/lock/redisson/RedissonLockService.java
index 2ad0c9063..3bd0be7d9 100644
--- a/spring-statemachine-lock/redisson/src/main/java/org/springframework/statemachine/lock/redisson/RedissonLockService.java
+++ b/spring-statemachine-lock/redisson/src/main/java/org/springframework/statemachine/lock/redisson/RedissonLockService.java
@@ -37,10 +37,11 @@ public class RedissonLockService<S, E> implements LockService<S, E> {
 
     private final static Logger log = LoggerFactory.getLogger(RedissonLockService.class);
 
+    protected final static String LOCK_PREFIX = "lock:";
+
     private RedissonClient redissonClient;
     private final Map<String, RLock> locks;
 
-
     public RedissonLockService(RedissonClient redissonClient) {
         this.redissonClient = redissonClient;
         this.locks = new ConcurrentHashMap<>();
@@ -48,15 +49,14 @@ public RedissonLockService(RedissonClient redissonClient) {
 
     @Override
     public boolean lock(StateMachine<S, E> stateMachine, int lockAtMostUntil) {
-        String id = stateMachine.getId();
+        String id = buildLockId(stateMachine);
         RLock lock = this.redissonClient.getLock(id);
         boolean result = false;
         try {
             log.trace("Lock acquired for state machine with id {}, Rlock: {}", id, lock);
-            lock.lock(lockAtMostUntil, TimeUnit.SECONDS);
+            result = lock.tryLock(0, lockAtMostUntil, TimeUnit.SECONDS);
             this.locks.put(id, lock);
-            result = true;
-        } catch (IllegalStateException e) {
+        } catch (InterruptedException e) {
             log.warn("Cannot acquire lock for state machine with id {}", id);
         }
         return result;
@@ -64,7 +64,7 @@ public boolean lock(StateMachine<S, E> stateMachine, int lockAtMostUntil) {
 
     @Override
     public void unLock(StateMachine<S, E> stateMachine) {
-        String id = stateMachine.getId();
+        String id = buildLockId(stateMachine);
         RLock lock = locks.remove(id);
         if (lock != null) {
             log.trace("Starting unlock on {}", id);
@@ -72,4 +72,8 @@ public void unLock(StateMachine<S, E> stateMachine) {
         }
     }
 
+    private String buildLockId(StateMachine<S, E> stateMachine) {
+        return LOCK_PREFIX + stateMachine.getId();
+    }
+
 }
diff --git a/spring-statemachine-lock/redisson/src/test/java/org/springframework/statemachine/lock/redisson/RedissonLockServiceTest.java b/spring-statemachine-lock/redisson/src/test/java/org/springframework/statemachine/lock/redisson/RedissonLockServiceTest.java
index 242678b6a..2b939f8ed 100644
--- a/spring-statemachine-lock/redisson/src/test/java/org/springframework/statemachine/lock/redisson/RedissonLockServiceTest.java
+++ b/spring-statemachine-lock/redisson/src/test/java/org/springframework/statemachine/lock/redisson/RedissonLockServiceTest.java
@@ -20,7 +20,6 @@
 import org.junit.jupiter.api.Test;
 import org.redisson.Redisson;
 import org.redisson.api.RedissonClient;
-import org.redisson.config.Config;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 import org.springframework.context.annotation.Bean;
@@ -41,6 +40,7 @@
 import reactor.core.publisher.Mono;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.statemachine.lock.redisson.RedissonLockService.LOCK_PREFIX;
 
 public class RedissonLockServiceTest {
 
@@ -72,12 +72,15 @@ public void testLockOnLockedMachine() {
         LockService<String, String> lockService = context.getBean(LockService.class);
         StateMachine<String, String> stateMachine = getStateMachine();
 
-        boolean lock = lockService.lock(stateMachine, 120);
-        assertThat(lock).isTrue();
+        new Thread(() -> {
+            boolean lock = lockService.lock(stateMachine, 120);
+            assertThat(lock).isTrue();
+        }).start();
 
-        StateMachineEventResult<String, String> lockResult = stateMachine.sendEvent(Mono.just(buildE1Event())).blockLast();
+
+        stateMachine.sendEvent(Mono.just(buildE1Event())).blockLast();
         assertThat(stateMachine.getState().getId()).isEqualTo("S1");
-        //assertThat(connection.getKeys().getkey("job-lock:test:testId".getBytes())).isTrue();
+        assertThat(connection.getBucket(LOCK_PREFIX + stateMachine.getId()).isExists()).isTrue();
     }
 
     @Test
@@ -88,7 +91,7 @@ public void testLock() {
         assertThat(lockResult).isNotNull();
         assertThat(lockResult.getResultType()).isEqualTo(StateMachineEventResult.ResultType.ACCEPTED);
         assertThat(stateMachine.getState().getId()).isEqualTo("S2");
-        //assertThat(connection.exists("job-lock:test:testId".getBytes())).isFalse();
+        assertThat(connection.getBucket(LOCK_PREFIX + stateMachine.getId()).isExists()).isFalse();
     }
 
     @Test
@@ -99,19 +102,18 @@ public void testLockInSameState() {
 
         assertThat(lockResult).isNotNull();
         assertThat(stateMachine.getState().getId()).isEqualTo("S2");
-        //assertThat(connection.exists("job-lock:test:testId".getBytes())).isFalse();
+        assertThat(connection.getBucket(LOCK_PREFIX + stateMachine.getId()).isExists()).isFalse();
 
         StateMachineEventResult<String, String> lockResultAfter = stateMachine.sendEvent(Mono.just(buildE1Event())).blockLast();
         assertThat(lockResultAfter).isNotNull();
         assertThat(stateMachine.getState().getId()).isEqualTo("S2");
         assertThat(lockResult.getResultType()).isEqualTo(StateMachineEventResult.ResultType.ACCEPTED);
-        //assertThat(connection.exists("job-lock:test:testId".getBytes())).isFalse();
+        assertThat(connection.getBucket(LOCK_PREFIX + stateMachine.getId()).isExists()).isFalse();
     }
 
     private StateMachine<String, String> getStateMachine() {
         StateMachineFactory<String, String> factory = context.getBean(StateMachineFactory.class);
         StateMachine<String, String> stateMachine = factory.getStateMachine("testId");
-        LockService<String, String> lockService = context.getBean(LockService.class);
         return stateMachine;
     }
 
@@ -126,9 +128,8 @@ protected static class RedissonConfig {
 
         @Bean
         public RedissonClient redissonClient() {
-            Config config = new Config();
-            config.useSingleServer().setAddress("redis://localhost:6379");
-            return Redisson.create(config);
+            //default address is localhost:6379
+            return Redisson.create();
         }
 
         @Bean
diff --git a/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockStateMachineGuard.java b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockStateMachineGuard.java
index fcc4ed5fd..8dee34565 100644
--- a/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockStateMachineGuard.java
+++ b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockStateMachineGuard.java
@@ -15,25 +15,25 @@
  */
 package org.springframework.statemachine.lock;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.springframework.statemachine.StateContext;
 import org.springframework.statemachine.guard.Guard;
 
 /**
  * The guard will try to lock the state machine.
+ *
  * @param <S> the type of state
  * @param <E> the type of event
  */
 public class LockStateMachineGuard<S, E> implements Guard<S, E> {
 
-    private final static Logger log = LoggerFactory.getLogger(LockStateMachineListener.class);
+    private final static Log log = LogFactory.getLog(LockStateMachineListener.class);
 
     private final LockService<S, E> lockService;
     private final int lockAtMostUntil;
 
     /**
-     *
      * @param lockService
      * @param lockAtMostUntil defines when the lock expires
      */
@@ -49,8 +49,8 @@ public LockStateMachineGuard(LockService<S, E> lockService, int lockAtMostUntil)
     @Override
     public boolean evaluate(StateContext<S, E> stateContext) {
         if (log.isDebugEnabled()) {
-			String id = stateContext.getStateMachine().getId();
-			log.debug("Starting lock on {} with event {} and status from {} to {}", id, stateContext.getEvent(), stateContext.getSource(), stateContext.getTarget());
+            String id = stateContext.getStateMachine().getId();
+            log.debug("Starting lock on " + id + " with event " + stateContext.getEvent() + " and status from " + stateContext.getSource() + " to " + stateContext.getTarget());
         }
         return lockService.lock(stateContext.getStateMachine(), this.lockAtMostUntil);
     }
diff --git a/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockStateMachineListener.java b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockStateMachineListener.java
index 713f03f3d..9e65258a1 100644
--- a/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockStateMachineListener.java
+++ b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockStateMachineListener.java
@@ -15,8 +15,8 @@
  */
 package org.springframework.statemachine.lock;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.springframework.statemachine.StateContext;
 import org.springframework.statemachine.StateContext.Stage;
 import org.springframework.statemachine.listener.StateMachineListenerAdapter;
@@ -29,7 +29,7 @@
  */
 public class LockStateMachineListener<S, E> extends StateMachineListenerAdapter<S, E> {
 
-    private final static Logger log = LoggerFactory.getLogger(LockStateMachineListener.class);
+    private final static Log log = LogFactory.getLog(LockStateMachineListener.class);
 
     private final LockService<S, E> lockService;
 
@@ -41,7 +41,7 @@ public LockStateMachineListener(LockService<S, E> lockService) {
     public void stateContext(StateContext<S, E> stateContext) {
         if (Stage.TRANSITION_END.equals(stateContext.getStage())) {
             if (log.isDebugEnabled()) {
-                log.debug("Starting unlock for state machine with id: {}", stateContext.getStateMachine().getId());
+                log.debug("Starting unlock for state machine with id: " + stateContext.getStateMachine().getId());
             }
             lockService.unLock(stateContext.getStateMachine());
         }