From 5639b3a6ec6477e8d2085f2fa7806e2a448d3a13 Mon Sep 17 00:00:00 2001 From: davidecerbo 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 the type of state + * @param the type of event + */ +public class LockInterceptor extends StateMachineInterceptorAdapter { + + private LockService lockService; + private int lockAtMostUntil; + + public LockInterceptor(LockService lockService, int lockAtMostUntil) { + this.lockService = lockService; + this.lockAtMostUntil = lockAtMostUntil; + } + + @Override + public Message preEvent(Message message, StateMachine stateMachine) { + boolean lockResult = this.lockService.lock(stateMachine, this.lockAtMostUntil); + return lockResult ? message : null; + } + + @Override + public void postStateChange(State state, Message message, Transition transition, StateMachine 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 { + + /** + * 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 stateMachine, int lockAtMostUntil); + + /** + * Unlock a target state machine. + * + * @param stateMachine + */ + void unLock(StateMachine 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 the type of state + * @param the type of event + */ +public class ShedLockService implements LockService { + + private final static Logger log = LoggerFactory.getLogger(ShedLockService.class); + + private final Map locks; + private final LockProvider lockProvider; + + public ShedLockService(LockProvider lockProvider) { + this.lockProvider = lockProvider; + this.locks = new ConcurrentHashMap<>(); + } + + @Override + public boolean lock(StateMachine stateMachine, int lockAtMostUntil) { + String id = stateMachine.getId(); + LockConfiguration lockConfiguration = new LockConfiguration(id, Instant.now().plus(Duration.ofSeconds(lockAtMostUntil))); + Optional 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 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 service = mock(LockService.class); + LockInterceptor interceptor = new LockInterceptor<>(service, 120); + StateMachine stateMachine = mock(StateMachine.class); + State state = mock(State.class); + Message message = mock(Message.class); + Transition transition = mock(Transition.class); + + interceptor.postStateChange(state, message, transition, stateMachine); + + verify(service, times(1)).unLock(stateMachine); + } + + @Test + public void preEventTestLockAcquired(){ + LockService service = mock(LockService.class); + LockInterceptor interceptor = new LockInterceptor<>(service, 120); + Message message = mock(Message.class); + StateMachine stateMachine = mock(StateMachine.class); + + when(service.lock(stateMachine, 120)).thenReturn(true); + + Message result = interceptor.preEvent(message, stateMachine); + assertThat(result).isEqualTo(message); + + verify(service, times(1)).lock(stateMachine, 120); + } + + @Test + public void preEventTestAlreadyLocked(){ + LockService service = mock(LockService.class); + LockInterceptor interceptor = new LockInterceptor<>(service, 120); + Message message = mock(Message.class); + StateMachine stateMachine = mock(StateMachine.class); + + when(service.lock(stateMachine, 120)).thenReturn(false); + + Message 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 lockService = context.getBean(LockService.class); + StateMachine stateMachine = getStateMachineWithInterceptor(); + + boolean lock = lockService.lock(stateMachine, 120); + assertThat(lock).isTrue(); + + StateMachineEventResult lockResult = stateMachine.sendEvent(Mono.just(buildE1Event())).blockLast(); + assertThat(lockResult).isNull(); + assertThat(stateMachine.getState().getId()).isEqualTo("S1"); + } + + @Test + public void testLock() { + StateMachine stateMachine = getStateMachineWithInterceptor(); + assertThat(stateMachine.getState().getId()).isEqualTo("S1"); + StateMachineEventResult lockResult = stateMachine.sendEvent(Mono.just(buildE1Event())).blockLast(); + assertThat(lockResult).isNotNull(); + assertThat(stateMachine.getState().getId()).isEqualTo("S2"); + } + + private StateMachine getStateMachineWithInterceptor() { + StateMachineFactory factory = context.getBean(StateMachineFactory.class); + StateMachine stateMachine = factory.getStateMachine("testId"); + LockService lockService = context.getBean(LockService.class); + StateMachineInterceptor lockInterceptor = new LockInterceptor<>(lockService, 120); + stateMachine.getStateMachineAccessor().doWithAllRegions(region -> region.addStateMachineInterceptor(lockInterceptor)); + return stateMachine; + } + + private Message 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 { + + @Override + public void configure(StateMachineConfigurationConfigurer config) throws Exception { + config + .withConfiguration() + .autoStartup(true); + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial("S1") + .state("S2") + .state("S3"); + } + + @Override + public void configure(StateMachineTransitionConfigurer 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 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 the type of state - * @param the type of event - */ -public class LockInterceptor extends StateMachineInterceptorAdapter { - - private LockService lockService; - private int lockAtMostUntil; - - public LockInterceptor(LockService lockService, int lockAtMostUntil) { - this.lockService = lockService; - this.lockAtMostUntil = lockAtMostUntil; - } - - @Override - public Message preEvent(Message message, StateMachine stateMachine) { - boolean lockResult = this.lockService.lock(stateMachine, this.lockAtMostUntil); - return lockResult ? message : null; - } - - @Override - public void postStateChange(State state, Message message, Transition transition, StateMachine 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 the type of state + * @param the type of event + */ public interface LockService { /** 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 the type of state + * @param the type of event + */ +public class LockStateMachineGuard implements Guard { + + private final static Logger log = LoggerFactory.getLogger(LockStateMachineListener.class); + + private final LockService lockService; + private final int lockAtMostUntil; + + /** + * + * @param lockService + * @param lockAtMostUntil defines when the lock expires + */ + public LockStateMachineGuard(LockService 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 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 the type of state + * @param the type of event + */ +public class LockStateMachineListener extends StateMachineListenerAdapter { + + private final static Logger log = LoggerFactory.getLogger(LockStateMachineListener.class); + + private final LockService lockService; + + public LockStateMachineListener(LockService lockService) { + this.lockService = lockService; + } + + @Override + public void stateContext(StateContext 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 service = mock(LockService.class); - LockInterceptor interceptor = new LockInterceptor<>(service, 120); - StateMachine stateMachine = mock(StateMachine.class); - State state = mock(State.class); - Message message = mock(Message.class); - Transition transition = mock(Transition.class); - - interceptor.postStateChange(state, message, transition, stateMachine); - - verify(service, times(1)).unLock(stateMachine); - } - - @Test - public void preEventTestLockAcquired(){ - LockService service = mock(LockService.class); - LockInterceptor interceptor = new LockInterceptor<>(service, 120); - Message message = mock(Message.class); - StateMachine stateMachine = mock(StateMachine.class); - - when(service.lock(stateMachine, 120)).thenReturn(true); - - Message result = interceptor.preEvent(message, stateMachine); - assertThat(result).isEqualTo(message); - - verify(service, times(1)).lock(stateMachine, 120); - } - - @Test - public void preEventTestAlreadyLocked(){ - LockService service = mock(LockService.class); - LockInterceptor interceptor = new LockInterceptor<>(service, 120); - Message message = mock(Message.class); - StateMachine stateMachine = mock(StateMachine.class); - - when(service.lock(stateMachine, 120)).thenReturn(false); - - Message 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 lockService = context.getBean(LockService.class); - StateMachine stateMachine = getStateMachineWithInterceptor(); + StateMachine stateMachine = getStateMachine(); boolean lock = lockService.lock(stateMachine, 120); assertThat(lock).isTrue(); StateMachineEventResult 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 stateMachine = getStateMachineWithInterceptor(); + StateMachine stateMachine = getStateMachine(); assertThat(stateMachine.getState().getId()).isEqualTo("S1"); StateMachineEventResult 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 getStateMachineWithInterceptor() { + @Test + public void testLockInSameState() { + StateMachine stateMachine = getStateMachine(); + assertThat(stateMachine.getState().getId()).isEqualTo("S1"); + StateMachineEventResult 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 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 getStateMachine() { StateMachineFactory factory = context.getBean(StateMachineFactory.class); StateMachine stateMachine = factory.getStateMachine("testId"); LockService lockService = context.getBean(LockService.class); - StateMachineInterceptor 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 { + @Autowired + private LockService lockService; + @Override public void configure(StateMachineConfigurationConfigurer config) throws Exception { config .withConfiguration() + .listener(new LockStateMachineListener<>(lockService)) .autoStartup(true); } @@ -143,6 +170,7 @@ public void configure(StateMachineStateConfigurer states) throws public void configure(StateMachineTransitionConfigurer 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 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 the type of state + * @param the type of event + */ +public class RedissonLockService implements LockService { + + private final static Logger log = LoggerFactory.getLogger(RedissonLockService.class); + + private RedissonClient redissonClient; + private final Map locks; + + + public RedissonLockService(RedissonClient redissonClient) { + this.redissonClient = redissonClient; + this.locks = new ConcurrentHashMap<>(); + } + + @Override + public boolean lock(StateMachine 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 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 lockService = context.getBean(LockService.class); + StateMachine stateMachine = getStateMachine(); + + boolean lock = lockService.lock(stateMachine, 120); + assertThat(lock).isTrue(); + + StateMachineEventResult 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 stateMachine = getStateMachine(); + assertThat(stateMachine.getState().getId()).isEqualTo("S1"); + StateMachineEventResult 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 stateMachine = getStateMachine(); + assertThat(stateMachine.getState().getId()).isEqualTo("S1"); + StateMachineEventResult 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 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 getStateMachine() { + StateMachineFactory factory = context.getBean(StateMachineFactory.class); + StateMachine stateMachine = factory.getStateMachine("testId"); + LockService lockService = context.getBean(LockService.class); + return stateMachine; + } + + private Message 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 { + + @Autowired + private LockService lockService; + + @Override + public void configure(StateMachineConfigurationConfigurer config) throws Exception { + config + .withConfiguration() + .listener(new LockStateMachineListener<>(lockService)) + .autoStartup(true); + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial("S1") + .state("S2") + .state("S3"); + } + + @Override + public void configure(StateMachineTransitionConfigurer 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 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 implements LockService { private final static Logger log = LoggerFactory.getLogger(RedissonLockService.class); + protected final static String LOCK_PREFIX = "lock:"; + private RedissonClient redissonClient; private final Map 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 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 stateMachine, int lockAtMostUntil) { @Override public void unLock(StateMachine 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 stateMachine) { } } + private String buildLockId(StateMachine 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 lockService = context.getBean(LockService.class); StateMachine stateMachine = getStateMachine(); - boolean lock = lockService.lock(stateMachine, 120); - assertThat(lock).isTrue(); + new Thread(() -> { + boolean lock = lockService.lock(stateMachine, 120); + assertThat(lock).isTrue(); + }).start(); - StateMachineEventResult 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 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 getStateMachine() { StateMachineFactory factory = context.getBean(StateMachineFactory.class); StateMachine stateMachine = factory.getStateMachine("testId"); - LockService 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 the type of state * @param the type of event */ public class LockStateMachineGuard implements Guard { - private final static Logger log = LoggerFactory.getLogger(LockStateMachineListener.class); + private final static Log log = LogFactory.getLog(LockStateMachineListener.class); private final LockService lockService; private final int lockAtMostUntil; /** - * * @param lockService * @param lockAtMostUntil defines when the lock expires */ @@ -49,8 +49,8 @@ public LockStateMachineGuard(LockService lockService, int lockAtMostUntil) @Override public boolean evaluate(StateContext 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 extends StateMachineListenerAdapter { - private final static Logger log = LoggerFactory.getLogger(LockStateMachineListener.class); + private final static Log log = LogFactory.getLog(LockStateMachineListener.class); private final LockService lockService; @@ -41,7 +41,7 @@ public LockStateMachineListener(LockService lockService) { public void stateContext(StateContext 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()); }