diff --git a/build.gradle b/build.gradle index 9c08022a1..4ebdbd94b 100644 --- a/build.gradle +++ b/build.gradle @@ -31,761 +31,788 @@ buildscript { } 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' + 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' + } +} - 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-common') { + description = 'Spring State Machine Lock' + + dependencies { + compile project(':spring-statemachine-core') + 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..77e40ea2e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -37,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' @@ -53,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-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..3bd0be7d9 --- /dev/null +++ b/spring-statemachine-lock/redisson/src/main/java/org/springframework/statemachine/lock/redisson/RedissonLockService.java @@ -0,0 +1,79 @@ +/* + * 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); + + 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<>(); + } + + @Override + public boolean lock(StateMachine stateMachine, int lockAtMostUntil) { + 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); + result = lock.tryLock(0, lockAtMostUntil, TimeUnit.SECONDS); + this.locks.put(id, lock); + } catch (InterruptedException e) { + log.warn("Cannot acquire lock for state machine with id {}", id); + } + return result; + } + + @Override + public void unLock(StateMachine stateMachine) { + String id = buildLockId(stateMachine); + RLock lock = locks.remove(id); + if (lock != null) { + log.trace("Starting unlock on {}", id); + lock.unlock(); + } + } + + 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 new file mode 100644 index 000000000..2b939f8ed --- /dev/null +++ b/spring-statemachine-lock/redisson/src/test/java/org/springframework/statemachine/lock/redisson/RedissonLockServiceTest.java @@ -0,0 +1,187 @@ +/* + * 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.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; +import static org.springframework.statemachine.lock.redisson.RedissonLockService.LOCK_PREFIX; + +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(); + + new Thread(() -> { + boolean lock = lockService.lock(stateMachine, 120); + assertThat(lock).isTrue(); + }).start(); + + + stateMachine.sendEvent(Mono.just(buildE1Event())).blockLast(); + assertThat(stateMachine.getState().getId()).isEqualTo("S1"); + assertThat(connection.getBucket(LOCK_PREFIX + stateMachine.getId()).isExists()).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.getBucket(LOCK_PREFIX + stateMachine.getId()).isExists()).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.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.getBucket(LOCK_PREFIX + stateMachine.getId()).isExists()).isFalse(); + } + + private StateMachine getStateMachine() { + StateMachineFactory factory = context.getBean(StateMachineFactory.class); + StateMachine stateMachine = factory.getStateMachine("testId"); + return stateMachine; + } + + private Message buildE1Event() { + return MessageBuilder + .withPayload("E1") + .build(); + } + + @Configuration + protected static class RedissonConfig { + + @Bean + public RedissonClient redissonClient() { + //default address is localhost:6379 + return Redisson.create(); + } + + @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/shedlock/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 new file mode 100644 index 000000000..6a5db7d4c --- /dev/null +++ b/spring-statemachine-lock/shedlock/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/shedlock/src/test/java/org/springframework/statemachine/lock/shedlock/ShedLockServiceTest.java b/spring-statemachine-lock/shedlock/src/test/java/org/springframework/statemachine/lock/shedlock/ShedLockServiceTest.java new file mode 100644 index 000000000..e4eee7092 --- /dev/null +++ b/spring-statemachine-lock/shedlock/src/test/java/org/springframework/statemachine/lock/shedlock/ShedLockServiceTest.java @@ -0,0 +1,188 @@ +/* + * 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.shedlock; + +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.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; +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 ShedLockServiceTest { + + private AnnotationConfigApplicationContext context; + private RedisConnection connection; + + @BeforeEach + public void setup() { + context = buildContext(); + context.register(ShedLockConfig.class, Config1.class); + context.refresh(); + connection = context.getBean(RedisConnectionFactory.class).getConnection(); + connection.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.exists("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 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 { + + @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/LockService.java b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockService.java new file mode 100755 index 000000000..f9c1e6b07 --- /dev/null +++ b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockService.java @@ -0,0 +1,43 @@ +/* + * 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; + +/** + * + * @param the type of state + * @param the type of event + */ +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/LockStateMachineGuard.java b/spring-statemachine-lock/src/main/java/org/springframework/statemachine/lock/LockStateMachineGuard.java new file mode 100644 index 000000000..8dee34565 --- /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.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 Log log = LogFactory.getLog(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 " + 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 new file mode 100644 index 000000000..9e65258a1 --- /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.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; + +/** + * 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 Log log = LogFactory.getLog(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/lock/LockStateMachineGuardTest.java b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/lock/LockStateMachineGuardTest.java new file mode 100644 index 000000000..0146afc9a --- /dev/null +++ b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/lock/LockStateMachineGuardTest.java @@ -0,0 +1,46 @@ +package org.springframework.statemachine.lock; + +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/lock/LockStateMachineListenerTest.java b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/lock/LockStateMachineListenerTest.java new file mode 100644 index 000000000..d34927d13 --- /dev/null +++ b/spring-statemachine-lock/src/test/java/org/springframework/statemachine/lock/LockStateMachineListenerTest.java @@ -0,0 +1,42 @@ +package org.springframework.statemachine.lock; + +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); + } +}